第十一阶段 stage 3:自营混币器

- Domain/OwnMixer:注资开张、NPC 客户存入、按匿名集洗污点出款(和普通混币器同一套抽象,匿名集主要是本金和收入)、手续费收入取回全额带污点、本金带自己的污点;客流按"费率 + 出款剩下的污点"和 Tumbler 比,乘黑市声望;风声累积按概率查封(全部没收、记 FENCING、停业)、便衣存入(记 FENCING)、卷款跑路(全额污点、记 FRAUD、黑市声望下降、永久关闭)
- 灰网页"自营混币器"标签(买下场外换币台就有;TerminalWindow 拆成 partial,页面在 TerminalWindow.OwnMix.cs):状态、费率、口碑、风声、匿名集、各币种本金/收入/客户在途;费率、开张/暂停、注资(账单转账)、取回、卷款跑路(合约签名)
- 夜间结算、日志、报纸新闻;浏览器显示"自营混币器"地址名
- Tuning.OwnMix*(research/mixsim 标定),单元测试,手册与 8 种语言
- 实测:注资 20 PSC 开张,三晚各一笔存入、一次便衣

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Opus 5.5 committed 2026-11-08 16:14:28 +08:00
1 parent 9d95e1f760
commit 71002cbced
17 files changed
+820 -16

No files matched your search

@@ -1419,6 +1419,55 @@ internal static class Program
Check(desk.Volume == 1000 && desk.Deals == 2, "成交统计");
});
Test("自营混币器:注资取回的污点、匿名集与清洗率、客流、出款、查封、卷款跑路", () =>
{
Func<string, double> pr = a => 1;
var w = new Wallet { Address = "w" }; w.Add(Assets.CRT, 30000);
var m = new OwnMixer();
Check(m.Inject(w, Assets.CRT, 20000, pr) && m.Pool(Assets.CRT).Capital == 20000 && m.Pool(Assets.CRT).CapTaint == 0, "干净的币注资,本金不带污点");
Check(!m.Inject(w, Assets.CRT, 1e6, pr) && !m.Inject(w, Assets.SPARK, 1, pr), "余额不够、币种不支持被拒");
Check(OwnMixer.RoundRate(40000) > OwnMixer.RoundRate(10000) && OwnMixer.RoundRate(1e9) <= Tuning.MixerCleanRate + 1e-9 && OwnMixer.RoundRate(0) == 0, "匿名集越大清洗率越高,封顶基准清洗率");
m.Start(pr);
Check(m.Open && Near(m.Rating, OwnMixer.Remain(OwnMixer.RoundRate(20000)), 1e-9), "开张时口碑按匿名集估");
double rr = 0.29;
var cheap = new OwnMixer { Fee = 0.01, Rating = 0.3 }; var dear = new OwnMixer { Fee = 0.08, Rating = 0.3 }; var dirty = new OwnMixer { Fee = 0.01, Rating = 0.9 };
Check(cheap.Share(0.03, rr, true) > dear.Share(0.03, rr, true) && cheap.Share(0.03, rr, true) > dirty.Share(0.03, rr, true), "费率低、出款干净的分到的客流多");
Check(cheap.Share(0.03, rr, false) > cheap.Share(0.03, rr, true), "对手关停时客流更多");
// 营业几晚:有存入、有手续费,到期的出款
var rng = new Rng(4);
double fees0 = m.FeesTotal;
int deposits = 0, payouts = 0;
for (int day = 40; day < 50; day++)
foreach (var e in m.Night(day, pr, 60, Tuning.MixerFee, rr, true, 1, false, rng))
{
if (e.Kind == "deposit") deposits++;
if (e.Kind == "payout") payouts++;
if (e.Kind == "seized") Check(m.Pools.Count == 0 && m.Pending.Count == 0, "查封清空池子");
}
Check(deposits > 0 && payouts > 0 && m.FeesTotal > fees0, $"营业中有存入和出款:{deposits} / {payouts},手续费 {m.FeesTotal - fees0:0}");
Check(m.Pending.All(x => x.ReadyDay >= 50), "到期的都出款了");
// 取回:收入全额带污点,本金按本金的污点
var m2 = new OwnMixer(); var w2 = new Wallet { Address = "w2" }; w2.Add(Assets.PSC, 1000); w2.Taint = 500;
m2.Inject(w2, Assets.PSC, 1000, pr);
Check(Near(m2.Pool(Assets.PSC).CapTaint, 500, 1e-9) && w2.Taint < 1e-9, "脏币注资,污点跟着进本金");
m2.Pool(Assets.PSC).Earned = 100;
var w3 = new Wallet { Address = "w3" }; w3.Add(Assets.CRC, 10000);
Check(m2.Withdraw(w3, Assets.PSC, 100, pr, out double o1) && Near(w3.Taint, 100, 1e-9) && o1 == 0, "先取收入:全额带污点");
Check(m2.Withdraw(w3, Assets.PSC, 500, pr, out _) && Near(w3.Taint, 100 + 250, 1e-9), "再取本金:按本金的污点比例");
Check(!m2.Withdraw(w3, Assets.PSC, 1000, pr, out _), "不能超过可取回的数量");
m2.Pending.Add(new OwnMixDeposit { Asset = Assets.PSC, Amount = 300, Taint = 200, Rounds = 2, ReadyDay = 99 });
Check(Near(m2.Free(Assets.PSC), 500, 1e-9) && Near(m2.Total(Assets.PSC), 800, 1e-9), "客户在途的币不能取");
// 卷款跑路:连同客户的币全部卷走、全额带污点,永久关闭
var w4 = new Wallet { Address = "w4" };
var got = m2.Rug(w4, pr, out double o4);
Check(Near(w4.Get(Assets.PSC), 800, 1e-9) && Near(w4.Taint, 800, 1e-9) && m2.Closed && !m2.Open && got.Count == 1, "卷走全部、全额污点、关闭");
Check(m2.Night(100, pr, 60, Tuning.MixerFee, rr, true, 1, false, new Rng(1)).Count == 0, "关闭后不再有夜间事件");
// 查封:风声足够大时被查封,停业若干天
var m3 = new OwnMixer { Heat = 1e6 }; m3.Inject(new Wallet { Address = "x", Balances = { [Assets.CRT] = 5000 } }, Assets.CRT, 5000, pr); m3.Start(pr);
var ev = m3.Night(60, pr, 60, Tuning.MixerFee, rr, true, 1, false, new Rng(2));
Check(ev.Any(e => e.Kind == "seized" && e.Crime > 0 && e.Crime <= Tuning.CrimeEventCap) && m3.Seized(60 + Tuning.OwnMixSeizeDays) && !m3.Seized(61 + Tuning.OwnMixSeizeDays) && !m3.Open, "风声大时被查封、记罪证、停业");
});
Test("预测市场:LMSR 定价、最大亏损、买卖", () =>
{
// 流动性:第 38 天在 5% 的盘口押 50,均价比现价贵约 10%(与先验无关,50% 盘口押 50 约 0.5%)