第十一阶段 stage 1:污点代替没收、模块不设上限、出块 ×10

- 预测市场内幕单改为确定性记污点:结算或卖出时照常兑付,内幕份额对应的兑付记收款钱包污点;删除 PredictInsiderShare、PredictInsiderCatch
- 操纵检出被查后照常兑付,预测兑付记钱包污点、永续盈利记 Poinbase 账户污点;删除 ManipFreezeDays
- Wallet.AddTaintCapped:污点不超过钱包当时的价值,超出部分按 PredictFraudShare / PredictFraudCap 记诈骗罪证(TaintCrime.Commit)
- 模块效果不设上限,只留算力、耗电、损耗乘数的下限(ModuleHashFloor / ModuleKjFloor / ModuleWearFloor)
- BlockReward 100 → 1000,NpcHash0 360 → 3600
- 修正内幕卖出日志"只退本金"的错误说法;8 种语言文字与手册更新

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Opus 5.5 committed 2026-11-08 14:17:41 +08:00
1 parent 45a6ec2f77
commit bc23ae056b
18 files changed
+248 -205

No files matched your search

+76 -81
View File
@@ -210,16 +210,19 @@ internal static class Program
Check(RigSlots.Shape(2) == "000000" + "000000" + new string('1', 18) && RigSlots.Shape(5) == new string('0', 30) && RigSlots.Shape(3).Length == 30, "形状:宽 3、共 10 行,前 2 × 槽数行可放");
});
Test("模块:折算系数 4,算力 +20%、省电 25%、损耗 −30% 封顶;性能加成提高损耗与暴毙", () =>
Test("模块:折算系数 4,不设上限,只有防出错的下限;性能加成提高损耗与暴毙", () =>
{
var perf = ModuleBonus.From(16, 0, 0);
Check(Near(perf.Hash, 1.04, 1e-9) && Near(perf.Wear, 1.04, 1e-9) && Near(perf.Death, 1.04, 1e-9), "一个性能模块(+16):算力 +4%,损耗和暴毙 +4%");
var cores = ModuleBonus.From(200 * 3, -100 * 3, 100 * 3);
Check(Near(cores.Hash, 1.20, 1e-9), $"塞满神经核心,算力也只 +20%:{cores.Hash:0.00}");
Check(Near(cores.Hash, 2.5, 1e-9) && Near(cores.Death, 2.5, 1e-9), $"三个无限制神经核心:算力 ×2.5,暴毙率同样 ×2.5:{cores.Hash:0.00}");
Check(Near(cores.Kj, 1.75, 1e-9), $"神经核心的负效率照常增加耗电:×{cores.Kj:0.00}");
Check(Near(cores.Wear, 0.70 * 1.20, 1e-9), "品质封顶 −30%,再乘性能带来的 +20%");
var eco = ModuleBonus.From(-16, 32 * 5, 0);
Check(Near(eco.Kj, 0.75, 1e-9) && Near(eco.Hash, 0.96, 1e-9), "省电封顶 25%;节能模块的负性能照常降低算力");
Check(Near(cores.Wear, 0.25 * 2.5, 1e-9), "品质 +300 → 损耗 −75%,再乘性能带来的发热");
var nine = ModuleBonus.From(200 * 9, -100 * 9, 100 * 9);
Check(Near(nine.Hash, 5.5, 1e-9) && Near(nine.Kj, 3.25, 1e-9) && nine.Wear == Tuning.ModuleWearFloor, "九个核心:算力 ×5.5、耗电 ×3.25,损耗降到下限");
var eco = ModuleBonus.From(-16 * 9, 32 * 9, 0);
Check(Near(eco.Kj, 1 - 32 * 9 / 400.0, 1e-9) && Near(eco.Hash, 1 - 16 * 9 / 400.0, 1e-9), "九个节能模块:省电 72%,负性能照常降低算力");
Check(ModuleBonus.From(0, 400 * 2, 0).Kj == Tuning.ModuleKjFloor && ModuleBonus.From(-400 * 2, 0, 0).Hash == Tuning.ModuleHashFloor, "耗电、算力不低于下限");
var none = ModuleBonus.From(0, 0, 0);
Check(none.Hash == 1 && none.Kj == 1 && none.Wear == 1 && none.Death == 1, "没有模块时不变");
});
@@ -1429,16 +1432,16 @@ internal static class Program
Check(bk.Buy(acct, m, false, 10, 5, pr, false, out _) == PredictResult.Opposite, "不能同盘反方向");
// 卖出:价差为 0,只付手续费
double before = acct.Get(Assets.CRC);
Check(bk.SellAll(acct, m, 5, out double got) == PredictResult.Ok && Math.Abs(got - 100 * (1 - Tuning.PredictFee)) < 1e-6, $"原价卖回只扣手续费:{got:0.00}");
Check(bk.SellAll(acct, m, 5, pr, out double got) == PredictResult.Ok && Math.Abs(got - 100 * (1 - Tuning.PredictFee)) < 1e-6, $"原价卖回只扣手续费:{got:0.00}");
Check(Math.Abs(acct.Get(Assets.CRC) - before - got) < 1e-6 && bk.PosOf(m.Id, acct.Address) == null, "回到账户");
Check(acct.Taint < 400 && acct.Taint > 359.2, "卖回时污点按退回的比例回来");
// 持仓上限
var big = new Wallet { Address = "x" }; big.Add(Assets.CRC, 1e7);
Check(bk.Buy(big, m, true, 1e6, 5, pr, false, out _) == PredictResult.Cap, "超过份额上限被拒");
Check(bk.SellAll(acct, m, 5, out _) == PredictResult.NoPosition, "没持仓不能卖");
Check(bk.SellAll(acct, m, 5, pr, out _) == PredictResult.NoPosition, "没持仓不能卖");
});
Test("预测市场:结算、作废、内幕被查、观测", () =>
Test("预测市场:结算、作废、内幕记污点、观测", () =>
{
Func<string, double> pr = a => 1;
var rng = new Rng(5);
@@ -1446,7 +1449,7 @@ internal static class Program
acct.Add(Assets.CRC, 100000);
var bk = new PredictBook();
// 观测:第一晚没有 Prev,不算 Started
bk.Night(4, new[] { "oxymoreWave" }, m => null, _ => acct, rng);
bk.Night(4, new[] { "oxymoreWave" }, m => null, _ => acct, pr, rng);
Check(!bk.Started.ContainsKey(4), "第一晚没有对照,不记新出现的事件");
var win = PredictMarket.Create(++bk.Seq, PredictKind.Event, "powerShortage", "停电", 0, 4, 5, 6, 0.2);
var lose = PredictMarket.Create(++bk.Seq, PredictKind.Event, "blackout", "x", 0, 4, 5, 6, 0.2);
@@ -1457,9 +1460,9 @@ internal static class Program
bk.Buy(acct, lose, true, stake, 4, pr, false, out _);
bk.Buy(acct, voided, true, stake, 4, pr, false, out _);
double cash = acct.Get(Assets.CRC);
bk.Night(5, new[] { "oxymoreWave", "powerShortage" }, bk.Happened, _ => acct, rng);
bk.Night(5, new[] { "oxymoreWave", "powerShortage" }, bk.Happened, _ => acct, pr, rng);
Check(win.Outcome < 0 && lose.Outcome < 0, "窗口没结束不结算");
var evs = bk.Night(6, new[] { "oxymoreWave", "powerShortage" }, bk.Happened, _ => acct, rng);
var evs = bk.Night(6, new[] { "oxymoreWave", "powerShortage" }, bk.Happened, _ => acct, pr, rng);
Check(win.Outcome == -1 || bk.Markets.All(x => x.Id != win.Id), "到期的盘口移出列表");
Check(evs.Any(e => e.Kind == "win" && e.Title == "停电" && Math.Abs(e.Amount - sw) < 1e-6), "押中兑付份数");
Check(evs.Any(e => e.Kind == "lose" && e.Title == "x"), "押错没收");
@@ -1467,44 +1470,34 @@ internal static class Program
Check(bk.Markets.Count == 1 && bk.Markets[0].Id == voided.Id, "后面的窗口还没到");
// 缺观测:8 号之前 7、8 的记录缺,到期作废退本金
double c2 = acct.Get(Assets.CRC);
var ev2 = bk.Night(9, new string[0], bk.Happened, _ => acct, rng);
var ev2 = bk.Night(9, new string[0], bk.Happened, _ => acct, pr, rng);
Check(ev2.Any(e => e.Kind == "void") && Math.Abs(acct.Get(Assets.CRC) - c2 - 50) < 1e-6, $"缺数据作废并退本金:{acct.Get(Assets.CRC) - c2:0.00}");
// 内幕被查:持仓达到上限的 40% 就有概率,按查处概率被查
int caught = 0, wins = 0;
for (int i = 0; i < 400; i++)
{
var b2 = new PredictBook(); var a2 = new Wallet { Address = "p" }; a2.Add(Assets.CRC, 1e6);
var mk = PredictMarket.Create(++b2.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); b2.Markets.Add(mk);
double target = Tuning.PredictInsiderShare * mk.ShareCap * 1.05, spend = 0;
spend = (mk.BuyCost(true, target)) * (1 + Tuning.PredictFee);
b2.Buy(a2, mk, true, spend, 4, pr, true, out _);
var e = b2.Night(5, new string[0], mm => true, _ => a2, new Rng((ulong)(i + 1)));
if (e[0].Kind == "caught") { caught++; Check(e[0].Fraud > 0 && e[0].Fraud <= Tuning.PredictFraudCap, "罪证有上限"); }
else wins++;
}
Check(Math.Abs(caught / 400.0 - Tuning.PredictInsiderCatch) < 0.08, $"内幕大单按查处概率被查:{caught}/400");
// 小额内幕不会被查
// 内幕单:照常兑付全部份数,只有内幕份额对应的兑付记污点
var b2 = new PredictBook(); var a2 = new Wallet { Address = "p" }; a2.Add(Assets.CRC, 1e6);
var mk = PredictMarket.Create(++b2.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); b2.Markets.Add(mk);
b2.Buy(a2, mk, true, mk.BuyCost(true, 0.2 * mk.ShareCap) * 1.02, 4, pr, true, out double si);
b2.Buy(a2, mk, true, 50, 4, pr, false, out double sn);
double c2b = a2.Get(Assets.CRC), t2b = a2.Taint;
var e2 = b2.Night(5, new string[0], mm => true, _ => a2, pr, new Rng(1));
Check(e2[0].Kind == "insider" && Near(e2[0].Amount, si + sn, 1e-6), $"内幕单照常结算:{e2[0].Kind}");
Check(Near(a2.Get(Assets.CRC) - c2b, si + sn, 1e-6), "全部份数照常兑付");
Check(Near(a2.Taint - t2b, si, 1e-6) && Near(e2[0].Taint, si, 1e-6) && e2[0].Over == 0 && e2[0].Fraud == 0, $"只有内幕份额的兑付记污点:+{a2.Taint - t2b:0.00} / {si:0.00}");
// 不是内幕单:普通结算,不加污点
var b3 = new PredictBook(); var a3 = new Wallet { Address = "p" }; a3.Add(Assets.CRC, 1e6);
var m3 = PredictMarket.Create(++b3.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); b3.Markets.Add(m3);
b3.Buy(a3, m3, true, m3.BuyCost(true, 0.2 * m3.ShareCap) * 1.02, 4, pr, true, out _);
Check(b3.Night(5, new string[0], mm => true, _ => a3, new Rng(1))[0].Kind == "win", "小额内幕安全");
// 被查:只退本金
var b4 = new PredictBook(); var a4 = new Wallet { Address = "p" }; a4.Add(Assets.CRC, 1e6);
b3.Buy(a3, m3, true, 200, 4, pr, false, out _);
Check(b3.Night(5, new string[0], mm => true, _ => a3, pr, new Rng(1))[0].Kind == "win" && a3.Taint == 0, "非内幕单不记污点");
// 污点上限:钱包原有污点已超过价值(币价跌过),新加的全部超出,折成罪证
var b4 = new PredictBook(); var a4 = new Wallet { Address = "p" }; a4.Add(Assets.CRC, 1e5);
var m4 = PredictMarket.Create(++b4.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); b4.Markets.Add(m4);
double s4 = m4.BuyCost(true, 0.5 * m4.ShareCap) * 1.02;
b4.Buy(a4, m4, true, s4, 4, pr, true, out _);
double c4 = a4.Get(Assets.CRC);
for (ulong sd = 1; sd < 50; sd++)
{
var bb = new PredictBook(); bb.Markets.Add(m4); bb.Positions.AddRange(b4.Positions.Select(x => new PredictPos { Market = x.Market, Wallet = x.Wallet, Yes = x.Yes, Shares = x.Shares, Cost = x.Cost, Insider = x.Insider }));
var aa = new Wallet { Address = "p" };
m4.Outcome = -1;
var ee = bb.Night(5, new string[0], mm => true, _ => aa, new Rng(sd));
if (ee[0].Kind == "caught") { Check(aa.Get(Assets.CRC) < 1e-6, "被查本金和收益全部没收"); break; }
}
b4.Buy(a4, m4, true, m4.BuyCost(true, 0.1 * m4.ShareCap) * 1.02, 4, pr, true, out double s4);
a4.Taint = a4.Value(pr) + 1000;
var e4 = b4.Night(5, new string[0], mm => true, _ => a4, pr, new Rng(1));
Check(e4[0].Kind == "insider" && e4[0].Taint == 0 && Near(e4[0].Over, s4, 1e-6), $"污点已满时全部超出:{e4[0].Over:0.00}");
Check(Near(e4[0].Fraud, Wallet.OverflowFraud(s4), 1e-9) && e4[0].Fraud <= Tuning.PredictFraudCap, "超出的部分按比例记罪证,有上限");
});
Test("预测市场:多钱包持仓各回各的钱包,限额与内幕按盘口合计", () =>
Test("预测市场:多钱包持仓各回各的钱包,内幕污点各记各的", () =>
{
Func<string, double> pr = a => 1;
var w1 = new Wallet { Address = "0xa" }; var w2 = new Wallet { Address = "0xb" };
@@ -1512,26 +1505,20 @@ internal static class Program
Func<string, Wallet> pick = ad => ad == "0xa" ? w1 : w2;
var bk = new PredictBook();
var m = PredictMarket.Create(++bk.Seq, PredictKind.Event, "e", "t", 0, 4, 5, 5, 0.1); bk.Markets.Add(m);
double half = Tuning.PredictInsiderShare * m.ShareCap * 0.6;
double half = 0.25 * m.ShareCap;
Check(bk.Buy(w1, m, true, m.BuyCost(true, half) * 1.02, 4, pr, true, out _) == PredictResult.Ok, "钱包 A 买入");
Check(bk.Buy(w2, m, false, 10, 4, pr, false, out _) == PredictResult.Opposite, "另一个钱包也不能买对面");
Check(bk.Buy(w2, m, true, m.BuyCost(true, half) * 1.02, 4, pr, true, out _) == PredictResult.Ok, "钱包 B 买入同一边");
Check(bk.PosAll(m.Id).Count == 2 && bk.PosOf(m.Id, "0xa") != null && bk.PosOf(m.Id, "0xb") != null, "每个钱包各一个持仓");
Check(bk.SellAll(w2, m, 4, out double got) == PredictResult.Ok && got > 0 && bk.PosOf(m.Id, "0xb") == null && bk.PosOf(m.Id, "0xa") != null, "卖出只动自己钱包的持仓");
Check(bk.SellAll(w2, m, 4, pr, out double got) == PredictResult.Ok && got > 0 && bk.PosOf(m.Id, "0xb") == null && bk.PosOf(m.Id, "0xa") != null, "卖出只动自己钱包的持仓");
Check(bk.Buy(w2, m, true, m.BuyCost(true, half) * 1.02, 4, pr, true, out _) == PredictResult.Ok, "再买");
// 两边各 0.6×查处线,合计超线:拆钱包躲不过
int caught = 0;
for (ulong sd = 1; sd <= 60; sd++)
{
var b2 = new PredictBook(); b2.Markets.Add(new PredictMarket { Id = m.Id, Kind = m.Kind, Key = m.Key, Title = m.Title, FromDay = 5, ToDay = 5, P0 = m.P0, B = m.B, Budget = m.Budget, QYes = m.QYes, QNo = m.QNo });
foreach (var p in bk.Positions) b2.Positions.Add(new PredictPos { Market = p.Market, Wallet = p.Wallet, Yes = p.Yes, Shares = p.Shares, Cost = p.Cost, Insider = p.Insider });
double a0 = w1.Get(Assets.CRC), b0 = w2.Get(Assets.CRC);
var ev = b2.Night(5, new string[0], x => true, pick, new Rng(sd));
if (ev[0].Kind == "caught") caught++;
else Check(w1.Get(Assets.CRC) > a0 && w2.Get(Assets.CRC) > b0, "赢了各自的钱包都收到兑付");
w1.Balances[Assets.CRC] = a0; w2.Balances[Assets.CRC] = b0;
}
Check(Math.Abs(caught / 60.0 - Tuning.PredictInsiderCatch) < 0.2, $"分到两个钱包的内幕持仓合计后按查处概率被查:{caught}/60");
// 两个钱包的内幕份额各自记在自己的钱包上
double a0 = w1.Get(Assets.CRC), b0 = w2.Get(Assets.CRC), ta = w1.Taint, tb = w2.Taint;
double sa = bk.PosOf(m.Id, "0xa").Shares, sb = bk.PosOf(m.Id, "0xb").Shares;
double ca = bk.PosOf(m.Id, "0xa").Taint, cb = bk.PosOf(m.Id, "0xb").Taint; // 买入时随资金带进持仓的污点,兑付时原样回来
var ev = bk.Night(5, new string[0], x => true, pick, pr, new Rng(1));
Check(ev[0].Kind == "insider" && Near(w1.Get(Assets.CRC) - a0, sa, 1e-6) && Near(w2.Get(Assets.CRC) - b0, sb, 1e-6), "各自的钱包收到兑付");
Check(Near(w1.Taint - ta - ca, sa, 1e-6) && Near(w2.Taint - tb - cb, sb, 1e-6) && Near(ev[0].Taint, sa + sb, 1e-6), "内幕污点各记各的钱包");
});
Test("预测市场:夜间消息反应、情报、新盘口先验", () =>
@@ -1564,7 +1551,7 @@ internal static class Program
// 到期后消息一起清掉
bk.Tips.Add(new PredictTip { Market = closing.Id });
var w = new Wallet { Address = "x" };
bk.Night(6, new string[0], mm => true, _ => w, rng);
bk.Night(6, new string[0], mm => true, _ => w, a => 1, rng);
Check(bk.Tips.Count == 0, "盘口结算后情报作废");
Check(bk.Stats.TryGetValue("Event", out var stE) && stE.Count >= 1 && stE.Hits >= 1, "结算计入按类型的历史");
// gas / CRA / 时代 先验
@@ -1605,8 +1592,8 @@ internal static class Program
var bk = new PredictBook(); var w = new Wallet { Address = "w" }; w.Add(Assets.CRC, 1e5);
var m = PredictMarket.Create(++bk.Seq, PredictKind.Price, "PSC", "PSC", 100, 4, 5, 5, 0.3); bk.Markets.Add(m);
bk.Buy(w, m, yes, 510, 4, a => 1, false, out _);
var ev = bk.Night(5, new string[0], x => outcome, _ => w, new Rng(sd), push);
if (ev[0].Kind == "manip") { if (yes && push > 0) manip++; else Check(false, "不该被判操纵"); Check(ev[0].Fraud > 0, "记罪证"); Check(w.Get(Assets.CRC) < 1e5 - 400, "本金没收"); }
var ev = bk.Night(5, new string[0], x => outcome, _ => w, a => 1, new Rng(sd), push);
if (ev[0].Kind == "manip") { if (yes && push > 0) manip++; else Check(false, "不该被判操纵"); Check(ev[0].Fraud == 0 && Near(ev[0].Taint, ev[0].Amount, 1e-6) && Near(w.Taint, ev[0].Amount, 1e-6), "照常兑付,整笔兑付记污点"); Check(w.Get(Assets.CRC) > 1e5 - 510, "本金不没收"); }
else if (yes && push > 0) win++; else if (push == 0) noPush++;
}
}
@@ -1728,30 +1715,38 @@ internal static class Program
Check(Math.Abs(rugs / 400.0 - Tuning.YieldRugNightly) < 0.05, $"骗局池每晚跑路概率:{rugs / 400.0:0.00}");
});
Test("预测市场:内幕先买后卖也会被查", () =>
Test("预测市场:内幕先买后卖,卖出所得的内幕部分记污点", () =>
{
int caught = 0, n = 400;
for (ulong sd = 1; sd <= (ulong)n; sd++)
{
var bk = new PredictBook(); var w = new Wallet { Address = "w" }; w.Add(Assets.CRC, 1e6);
var m = PredictMarket.Create(++bk.Seq, PredictKind.Event, "e", "t", 0, 4, 7, 7, 0.05); bk.Markets.Add(m);
double target = Tuning.PredictInsiderShare * m.ShareCap * 1.02;
bk.Buy(w, m, true, m.BuyCost(true, target) * (1 + Tuning.PredictFee), 4, a => 1, true, out _);
double cost = bk.PosOf(m.Id, "w").Cost;
double p1 = m.PYes, p2 = p1 + (1 - p1) / 2;
m.QYes = m.QNo + m.B * Math.Log(p2 / (1 - p2)); // 夜里价格又涨了一截
bk.SellAll(w, m, 5, new Rng(sd), out double got, out double fraud);
if (fraud > 0) { caught++; Check(got == 0 && fraud <= Tuning.PredictFraudCap, "被查本金和收益全部没收"); }
else Check(got > cost, "没被查时赚到价差");
}
Check(Math.Abs(caught / (double)n - Tuning.PredictInsiderCatch) < 0.08, $"按查处概率被查:{caught}/{n}");
// 小额内幕、非内幕卖出不查
var bk = new PredictBook(); var w = new Wallet { Address = "w" }; w.Add(Assets.CRC, 1e6);
var m = PredictMarket.Create(++bk.Seq, PredictKind.Event, "e", "t", 0, 4, 7, 7, 0.05); bk.Markets.Add(m);
bk.Buy(w, m, true, m.BuyCost(true, 0.3 * m.ShareCap) * (1 + Tuning.PredictFee), 4, a => 1, true, out double si);
bk.Buy(w, m, true, 100, 4, a => 1, false, out double sn);
double cost = bk.PosOf(m.Id, "w").Cost;
double p1 = m.PYes, p2 = p1 + (1 - p1) / 2;
m.QYes = m.QNo + m.B * Math.Log(p2 / (1 - p2)); // 夜里价格又涨了一截
double t0 = w.Taint;
Check(bk.SellAll(w, m, 5, a => 1, out double got, out double taint, out double over) == PredictResult.Ok && got > cost, "照常卖出,赚到价差");
Check(Near(taint, got * si / (si + sn), 1e-6) && Near(w.Taint - t0, taint, 1e-9) && over == 0, $"内幕份额对应的卖出所得记污点:{taint:0.00} / {got:0.00}");
// 非内幕卖出不加污点
var bk2 = new PredictBook(); var w2 = new Wallet { Address = "w" }; w2.Add(Assets.CRC, 1e6);
var m2 = PredictMarket.Create(++bk2.Seq, PredictKind.Event, "e", "t", 0, 4, 7, 7, 0.05); bk2.Markets.Add(m2);
bk2.Buy(w2, m2, true, 200, 4, a => 1, true, out _);
bk2.Buy(w2, m2, true, 200, 4, a => 1, false, out _);
m2.QYes = m2.QNo + m2.B * Math.Log(0.6 / 0.4);
bk2.SellAll(w2, m2, 5, new Rng(1), out _, out double f2);
Check(f2 == 0, "小额内幕卖出安全");
bk2.SellAll(w2, m2, 5, a => 1, out _, out double t2, out double o2);
Check(t2 == 0 && o2 == 0 && w2.Taint == 0, "非内幕卖出不记污点");
});
Test("钱包:带上限的加污点", () =>
{
Func<string, double> pr = a => 1;
var w = new Wallet { Address = "w" }; w.Add(Assets.CRC, 100);
Check(w.AddTaintCapped(60, pr) == 0 && w.Taint == 60, "价值以内全部加上");
Check(Near(w.AddTaintCapped(60, pr), 20, 1e-9) && w.Taint == 100, "加到价值为止,返回超出的 20");
Check(Near(w.AddTaintCapped(30, pr), 30, 1e-9) && w.Taint == 100, "已满时全部超出");
var empty = new Wallet { Address = "e" };
Check(Near(empty.AddTaintCapped(5, pr), 5, 1e-9) && empty.Taint == 0, "空钱包加不进污点");
Check(w.AddTaintCapped(0, pr) == 0 && w.AddTaintCapped(-3, pr) == 0, "0 和负数不处理");
Check(Near(Wallet.OverflowFraud(100), 100 * Tuning.PredictFraudShare, 1e-9) && Wallet.OverflowFraud(1e9) == Tuning.PredictFraudCap && Wallet.OverflowFraud(0) == 0, "超出额按比例折罪证,单次封顶");
});
Test("日 K:开盘、日内高低、成交量、收盘、均线、保留天数", () =>