Stage 6.2: Poinbase perpetual futures (PSC, SPARK) with slippage, funding, insurance fund, ADL, debt and seizure

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 00:26:50 +08:00
1 parent 98e80412e6
commit b249747855
10 files changed
+578 -2

No files matched your search

@@ -808,6 +808,76 @@ internal static class Program
Check(!ChainAddress.Valid("0x12345") && !ChainAddress.Valid("0xzzzzzzzz") && !ChainAddress.WellFormed("12345678ab"), "格式不对的地址不合法");
});
Test("永续合约:开平仓、强平、基金与欠款、自动减仓、没收", () =>
{
Func<string, double> pr = a => a == Assets.PSC ? 100 : 1;
Wallet NewAcct(double crc, double taint = 0) { var w = new Wallet { Address = "pb" }; w.Add(Assets.CRC, crc); w.Taint = taint; w.Basis = crc; return w; }
// 滑点:名义价值占池深的比例,买入往上、卖出往下
var bs = new PerpBook();
double pool = Tuning.PoolDepth(1);
Check(Math.Abs(bs.Exec(100, 2000, true, 1) - 100 * (1 + 2000 / (Tuning.PerpDepthMult * pool))) < 1e-9 && bs.Exec(100, 2000, false, 1) < 100 && bs.Exec(100, 1e12, true, 1) <= 150.0001, "滑点:买入上滑、卖出下滑,封顶 50%");
// 滑点吃掉了强平线之间的空间:开仓被拒
var bt = new PerpBook { DepthMult = 0.05 }; var at = NewAcct(5000);
Check(bt.Open(at, "PSC", true, 1000, 5, 100, 1, pr) == PerpResult.Thin && bt.Positions.Count == 0 && at.Get(Assets.CRC) == 5000, "滑点大到一开就被强平的单子被拒,不扣钱");
// 开仓与手续费、保证金、污点
var b = new PerpBook { DepthMult = 1e12 }; var a = NewAcct(2000, 1000);
Check(b.Open(a, "PSC", true, 1000, 6, 100, 1, pr) == PerpResult.Leverage && b.Open(a, "PSC", true, 3000, 3, 100, 1, pr) == PerpResult.Funds, "杠杆超限、余额不足被拒");
Check(b.Open(a, "PSC", true, 1000, 5, 100, 1, pr) == PerpResult.Ok, "开 5× 多头");
var pos = b.Of("PSC");
double fee = 5000 * Tuning.PerpFee;
Check(Math.Abs(pos.Size - 50) < 1e-9 && Math.Abs(a.Get(Assets.CRC) - (1000 - fee)) < 1e-9 && Math.Abs(pos.Taint - 500) < 1e-9, $"名义 5000,手续费 {fee},污点 500 随保证金进仓位");
Check(b.Open(a, "PSC", false, 100, 2, 100, 1, pr) == PerpResult.Opposite, "不能同时多空");
Check(Math.Abs(pos.LiqPrice - (50 * 100 - 1000) / (50 * (1 - Tuning.PerpMaint))) < 1e-9 && pos.LiqPrice > 85 && pos.LiqPrice < 90, $"强平价 {pos.LiqPrice:0.0}(约 −11%)");
// 平仓盈利:干净的新币,污点原额退回
double paid = b.Close(a, "PSC", 110, 2, out double d0);
double expect = 1000 + 50 * 10 - 50 * 110 * Tuning.PerpFee;
Check(Math.Abs(paid - expect) < 1e-6 && d0 == 0 && Math.Abs(a.Taint - 1000) < 1e-6 && b.Positions.Count == 0, $"盈利平仓 {paid:0.00},污点全额退回");
// 平仓亏损:污点按剩下的比例退回
var a2 = NewAcct(2000, 1000); var b2 = new PerpBook { DepthMult = 1e12 };
b2.Open(a2, "PSC", true, 1000, 5, 100, 1, pr);
double paid2 = b2.Close(a2, "PSC", 95, 2, out _);
Check(paid2 > 700 && paid2 < 760 && a2.Taint < 1000 - 500 * (1 - paid2 / 1000) + 1, $"亏损平仓 {paid2:0.0},污点按比例少回");
// 夜间强平:权益不到 10% 名义价值;剩余进基金
var b3 = new PerpBook { DepthMult = 1e12 }; var a3 = NewAcct(5000);
b3.Open(a3, "PSC", true, 1000, 5, 100, 1, pr);
double fund0 = b3.InsuranceFund;
var ev = b3.Night(2, new Dictionary<string, double> { ["PSC"] = 88 }, a3, pr);
Check(ev.Any(e => e.Kind == "liq") && b3.Positions.Count == 0 && b3.Debt == 0, "−12% 被强平,没有欠款");
Check(b3.InsuranceFund > fund0, "剩余权益进保险基金");
var b3b = new PerpBook { DepthMult = 1e12 }; var a3b = NewAcct(5000);
b3b.Open(a3b, "PSC", true, 1000, 5, 100, 1, pr);
Check(!b3b.Night(2, new Dictionary<string, double> { ["PSC"] = 95 }, a3b, pr).Any(e => e.Kind == "liq") && b3b.Positions.Count == 1, "−5% 不强平");
// 穿仓:基金先垫,不够记欠款;有欠款不能开仓;账户 CRC 每晚先还
var b4 = new PerpBook { DepthMult = 1e12, InsuranceFund = 100 }; var a4 = NewAcct(1100);
b4.Open(a4, "SPARK", true, 1000, 3, 10, 1, pr);
double crc4 = a4.Get(Assets.CRC);
b4.Night(2, new Dictionary<string, double> { ["SPARK"] = 5 }, a4, pr);
Check(b4.Positions.Count == 0 && b4.Debt > 0 && b4.InsuranceFund < 1 + Tuning.PerpFundDrift, $"大跌穿仓,基金见底,欠款 {b4.Debt:0}");
Check(b4.Open(a4, "SPARK", true, 10, 1, 5, 2, pr) == PerpResult.Debt, "有欠款不能开仓");
double debt0 = b4.Debt; a4.Add(Assets.CRC, 100);
b4.Night(3, new Dictionary<string, double> { ["SPARK"] = 5 }, a4, pr);
Check(Math.Abs(b4.Debt - (debt0 * (1 + Tuning.PerpDebtInterest) - (crc4 + 100))) < 1e-6 || b4.Debt < debt0 * (1 + Tuning.PerpDebtInterest), "欠款计息,账户 CRC 先还");
// 逾期没收、核销、拉黑
var b5 = new PerpBook { DepthMult = 1e12, Debt = 500, DebtSince = 1 }; var a5 = NewAcct(0);
Check(!b5.Night(5, new Dictionary<string, double> { ["PSC"] = 100 }, a5, pr).Any(e => e.Kind == "seize"), "没到 7 天不没收");
Check(b5.Night(8, new Dictionary<string, double> { ["PSC"] = 100 }, a5, pr).Any(e => e.Kind == "seize"), "逾期触发没收");
double wo = b5.ApplySeizure(200, 8);
Check(wo > 300 && b5.Debt == 0 && b5.Banned(20) && !b5.Banned(8 + Tuning.PerpBanDays + 1), $"没收抵掉 200,核销 {wo:0},拉黑 {Tuning.PerpBanDays} 天");
// 资金费:价格高于 7 日均价时多头付、空头收
var b6 = new PerpBook { DepthMult = 1e12 }; var a6 = NewAcct(5000);
for (int d = 1; d <= 5; d++) b6.Night(d, new Dictionary<string, double> { ["PSC"] = 100 }, a6, pr);
b6.Open(a6, "PSC", true, 1000, 2, 100, 5, pr);
double m0 = b6.Of("PSC").Margin;
b6.Night(6, new Dictionary<string, double> { ["PSC"] = 106 }, a6, pr);
Check(b6.Rate("PSC") > 0 && b6.Of("PSC").Margin < m0, $"溢价时多头付资金费 {b6.Rate("PSC"):P2}");
// 自动减仓:NPC 大行情穿仓抽干基金,盈利的空头只拿到一部分
var b7 = new PerpBook { DepthMult = 1e12, InsuranceFund = 500 }; var a7 = NewAcct(5000);
b7.Open(a7, "SPARK", false, 1000, 3, 10, 1, pr);
b7.Night(2, new Dictionary<string, double> { ["SPARK"] = 10 }, a7, pr);
var ev7 = b7.Night(3, new Dictionary<string, double> { ["SPARK"] = 3 }, a7, pr);
Check(ev7.Any(e => e.Kind == "adl") && b7.Positions.Count == 0, "基金见底,盈利空头被自动减仓");
});
Test("CRA 质押池:计息、解锁期、污点随币、螺旋", () =>
{
Func<string, double> price = a => a == Assets.PSC ? 100 : 1;