换币客户改在交易终端成交:按比例部分成交、买币随机出价、弹出钱包

- 对话只剩"到终端上谈 / 这单我不做":交易终端打开柜台交易页(标签"柜台",只在有客户时出现),客户在柜台前等着
- 柜台交易页:客户来意、数量、市值、出价/要价、单价、地址(卖币可"查地址"进浏览器),25/50/75/100% 或手输数量部分成交(Otc.Part),成交 / 放弃
- 买币客户随机出价(OtcBidMin −5% … OtcBidMax +30%),去掉三档报价与成交概率(OtcPremium、OtcAccept);成交后弹出 PLEDGER,账单 Prefill 填好客户地址
- 卖币:成交后付现金、币进已连接的钱包;客户说最后一句离开(DialogUIManager.StartDialogue)
- 客户被打发或离开时交易作废;不再有"当晚未付作废"
- 增量翻译:只是和已删旧键同文的新键照常翻译
- 实测:买币客户 25% 部分成交(先在 Web3 换出 CRA),PLEDGER 预填地址签名,到账 +668,客户离开;涡轮增压器作用于矿架立刻挖一轮

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Opus 5.5 committed 2026-11-08 15:46:14 +08:00
1 parent 2badc45a5a
commit 9d95e1f760
16 files changed
+455 -223

No files matched your search

+12 -9
View File
@@ -1357,7 +1357,7 @@ internal static class Program
Check(ps > 12 && ps < 20, $"夜里向 SPARK 参考价靠拢:{ps:0.00}");
});
Test("换币客户:卖币的事件币种、污点比例与折扣、收下后的污点;买币的报价档;成交统计", () =>
Test("换币客户:卖币的事件币种、污点比例与折扣、收下后的污点;买币的随机出价;部分成交;成交统计", () =>
{
Func<string, double> pr = a => a == Assets.Ponero ? 50 : 1;
var none = new List<MarketEvent>();
@@ -1397,16 +1397,19 @@ internal static class Program
if (Otc.MakeSell(new List<MarketEvent> { MarketEvent.Crackdown }, false, pr, 500, rng).Sting) stingsCd++;
}
Check(stingsCd > stings * 2, $"镇压期间便衣更多:{stings} → {stingsCd}");
// 买币:市值按房租,三档溢价,越高越难成交
var ob = Otc.MakeBuy(pr, 500, new Rng(7));
Check(Array.IndexOf(Otc.BuyAssets, ob.Asset) >= 0 && ob.Value >= 500 * Tuning.OtcBuyMinRents && ob.Value <= 500 * Tuning.OtcBuyMaxRents && Near(ob.Qty * pr(ob.Asset), ob.Value, 1e-6), "买币的币种与数量");
Check(Otc.Quote(ob, 0) < Otc.Quote(ob, 1) && Otc.Quote(ob, 1) < Otc.Quote(ob, 2) && Otc.Quote(ob, 2) == (int)Math.Round(ob.Value * (1 + Tuning.OtcPremium[2])), "三档报价");
for (int t = 0; t < 3; t++)
// 买币:市值按房租,出价 = 市值 × (1 + 随机溢价),溢价在区间内
for (int i = 0; i < 200; i++)
{
int ok = 0; var r = new Rng((ulong)(t + 11));
for (int i = 0; i < 4000; i++) if (Otc.Accepts(t, r)) ok++;
Check(Math.Abs(ok / 4000.0 - Tuning.OtcAccept[t]) < 0.03, $"第 {t + 1} 档成交概率:{ok}/4000");
var ob = Otc.MakeBuy(pr, 500, rng);
Check(Array.IndexOf(Otc.BuyAssets, ob.Asset) >= 0 && ob.Value >= 500 * Tuning.OtcBuyMinRents && ob.Value <= 500 * Tuning.OtcBuyMaxRents && Near(ob.Qty * pr(ob.Asset), ob.Value, 1e-6), "买币的币种与数量");
Check(ob.Cash >= ob.Value * (1 + Tuning.OtcBidMin) - 1 && ob.Cash <= ob.Value * (1 + Tuning.OtcBidMax) + 1, $"出价在区间内:{ob.Cash} / {ob.Value:0}");
}
// 部分成交:现金、市值按数量比例折算,超过报价按全部算
var whole = new OtcOffer { Side = OtcSide.Sell, Asset = Assets.PSC, Qty = 20, Value = 2000, TaintRatio = 0.5, Cash = 1200, Sting = true };
var half = Otc.Part(whole, 10);
Check(Near(half.Qty, 10, 1e-9) && Near(half.Value, 1000, 1e-9) && half.Cash == 600 && half.TaintRatio == 0.5 && half.Sting, "收一半:现金、市值减半,污点比例和便衣不变");
Check(Near(Otc.Part(whole, 50).Qty, 20, 1e-9) && Otc.Part(whole, 50).Cash == 1200, "超过报价的数量按全部算");
Check(Otc.Part(whole, 0) == null && Otc.Part(whole, -1) == null, "数量不合法");
// 来历:脏的来源按污点比例出现,数量占比合计为 1
var hist = Otc.History(new OtcOffer { TaintRatio = 1 }, new[] { "dirty" }, new[] { "clean" }, new Rng(2));
Check(hist.Count >= 1 && hist.Count <= Tuning.OtcHistoryMax && hist.TrueForAll(kv => kv.Key == "dirty") && Near(hist.Sum(kv => kv.Value), 1, 1e-9), "全脏的币来历都是脏来源");