namespace YLErp.Modules.SwapModule { /// /// InitUnwind 默认 ClosePercent 计算的回归测试。 /// --------------------------------------------------------------- /// 守卫提交 e2fb456b "fix 平仓(InitUnwind L267)硬编码 1 而不是剩余平仓比例"。 /// /// 旧 bug:InitUnwind 默认把 ClosePercent 硬编码为 1(按"占剩余 100%"), /// 但前端约定 ClosePercent 是"占期初(original)"口径(A),1 表示平掉原始本金的 100%。 /// 多次部分平仓后剩余本金 < 期初本金,此时默认 1 在前端语义上意味着"还要平掉原始全部", /// 与"平掉剩余全部"意图不符,且会触发后端 ToRemainingClosePercent 转换后 >1 被 cap 到 1, /// 表面看无差异但语义混乱,且若前端 / 事件展示直接用此值会出错。 /// /// 修复:ClosePercent = PosiNotionalValue / NotionalValue(占期初口径的"平剩余全部")。 /// 抽出为纯函数 CalcDefaultInitClosePercent 以支持无库单测。 /// [TestClass] public class InitUnwindDefaultClosePercentTest { // ================================================================ // 场景1:未平仓 PosiNotionalValue == NotionalValue → ClosePercent = 1 // ================================================================ [TestMethod] public void 未平仓_剩余等于期初_默认ClosePercent为1() { var result = SwapDealService.CalcDefaultInitClosePercent( notionalValue: 1_000_000m, posiNotionalValue: 1_000_000m); Assert.AreEqual(1m, result, "未平仓:默认应平 100%(占期初)"); } // ================================================================ // 场景2:已平 30%(剩 70%)→ ClosePercent = 0.7 // ================================================================ [TestMethod] public void 已平30_剩70_默认ClosePercent为0_7() { var result = SwapDealService.CalcDefaultInitClosePercent( notionalValue: 1_000_000m, posiNotionalValue: 700_000m); Assert.AreEqual(0.7m, result, 0.0001m, "已平 30% 剩 70%:默认 ClosePercent=0.7(占期初)"); } // ================================================================ // 场景3:除零保护 NotionalValue = 0 → 返回 1(容错) // ================================================================ [TestMethod] public void 期初名义本金为零_返回1_容错不除零() { var result = SwapDealService.CalcDefaultInitClosePercent( notionalValue: 0m, posiNotionalValue: 100_000m); Assert.AreEqual(1m, result, "期初本金为 0 时容错返回 1,不应抛除零异常"); } // ================================================================ // 场景4:GLMS-20260701-0013 真实快照(已平 2 次) // 期初 NotionalValue = 980,000 / 剩余 PosiNotionalValue = 686,000.07 // 期望 ClosePercent ≈ 0.7(686000.07/980000) // ================================================================ [TestMethod] public void GLMS20260701_0013_已平两次_默认ClosePercent约为0_7() { var result = SwapDealService.CalcDefaultInitClosePercent( notionalValue: 980_000m, posiNotionalValue: 686_000.07m); // 686000.07 / 980000 = 0.700000071... Assert.AreEqual(0.7m, result, 0.0001m, "GLMS-20260701-0013 已平两次:默认 ClosePercent 应≈0.7(占期初),旧 bug 会硬编码 1"); } // ================================================================ // 场景5:与 ToRemainingClosePercent 联动验证 // 前端拿 InitUnwind 返回的 A(占期初) 默认值,经 ToRemainingClosePercent 转 B(占剩余), // 应恰好 = 1.0(因为"平剩余全部"在占剩余语义下就是 100%) // ================================================================ [TestMethod] public void 默认A经ToRemainingClosePercent转B应为1_平剩余全部() { const decimal notionalValue = 1_000_000m; const decimal posiNotionalValue = 600_000m; // 已平 40%,剩 60% var defaultA = SwapDealService.CalcDefaultInitClosePercent(notionalValue, posiNotionalValue); var convertedB = SwapDealService.ToRemainingClosePercent(defaultA, notionalValue, posiNotionalValue); Assert.AreEqual(0.6m, defaultA, 0.0001m, "占期初默认 A=0.6"); Assert.AreEqual(1.0m, convertedB, 0.0001m, "A=0.6 经 ToRemainingClosePercent 转换 → B=1.0(占剩余 100% = 平剩余全部),此为占期初/占剩余双语义自洽的关键不变式"); } // ================================================================ // 场景6:全平完(PosiNotionalValue=0)→ ClosePercent=0(边界,实际不会进 InitUnwind) // ================================================================ [TestMethod] public void 全平完剩余为零_ClosePercent为零() { var result = SwapDealService.CalcDefaultInitClosePercent( notionalValue: 1_000_000m, posiNotionalValue: 0m); Assert.AreEqual(0m, result, "剩余本金为 0 时 ClosePercent=0(边界场景,实际全部平完不会再进 InitUnwind)"); } } }