diff --git a/UnitTestProject/Modules/SwapModule/SwapUnwindPrepayPrincipalBugTdd.cs b/UnitTestProject/Modules/SwapModule/SwapUnwindPrepayPrincipalBugTdd.cs
index fcaa469f..ccf6c195 100644
--- a/UnitTestProject/Modules/SwapModule/SwapUnwindPrepayPrincipalBugTdd.cs
+++ b/UnitTestProject/Modules/SwapModule/SwapUnwindPrepayPrincipalBugTdd.cs
@@ -212,6 +212,111 @@ namespace YLErp.Modules.SwapModule
Assert.AreEqual(100_000m, total, "多次部分平仓合计应=保证金本金 10万");
}
+ // ---- 盘中路径 CalcDailySimpleInterest 的 closePercent^N 指数级缩小 bug ----
+ // 生产铁证 GLMS-20260701-0006:预付金腿 Fix=9,180,000、interest_rest_days=7、单利、不计息。
+ // 平仓弹窗(swaptrade2/GetUnwindInterestList → 盘中路径 CalcDailySimpleInterest)返回:
+ // 100% → 9,180,000 (对) 50% → 71,718.75 (错) 10% → 0.918 (错)
+ // 数学关系精确成立:9,180,000×0.5^7 = 71,718.75、9,180,000×0.1^7 = 0.918。
+ // 根因:CalcDailySimpleInterest 非重置日 else 分支
+ // flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
+ // tdDynomicPrincipal = flowEvent.InterestPrincipal; // ★把"已×closePercent"的值回填
+ // 使下一个非重置日再乘一次 closePercent → InterestPrincipal = Fix × closePercent^N(N=计息天数),
+ // 而正确应为 Fix × closePercent(线性,与日终 CalcDailySimpleInterestByEod:1164-1165 只乘一次一致)。
+ // 现有 6 个用例 interest_rest_days=1 且 UnwindDate=StartDate+1(calcDays=1),循环首尾都被 continue 跳过、
+ // 从不进 else,故漏掉此 bug;本组用例用 restDays=7、跨多日、带 eod 归档触发 else 累积复现之。
+ private const decimal ProdPrepayFix = 9_180_000m;
+ private static readonly DateTime ProdPosiStart = new(2026, 7, 2);
+ private static readonly DateTime ProdEodValueDate = new(2026, 7, 4);
+ private static readonly DateTime ProdUnwindDate = new(2026, 7, 13);
+
+ ///
+ /// 盘中路径复现:restDays=7、PosiStart→Unwind 跨 11 天、eod 归档到 07-04。
+ /// 与生产 GLMS-20260701-0006 完全对齐,buggy 代码产出 Fix × closePercent^7。
+ ///
+ private swap_flow_event CalcUnwindMultiDay(decimal closePercent, decimal fix = ProdPrepayFix, int restDays = 7,
+ decimal rate = 0m)
+ {
+ var extend = new trade_extend
+ {
+ TradeId = 1,
+ ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
+ {
+ AnnualDays = AnnualDays,
+ InterestCalcMode = "10", // 算头不算尾(与生产一致)
+ SettlementRules = 0
+ })
+ };
+ var td = new trade
+ {
+ id = 1, TradeNumber = "UT-PREPAY-EXP", ClientId = 999998,
+ TradeType = "收益互换", TradeDate = ProdPosiStart, StartDate = ProdPosiStart,
+ ExerciseDate = ProdUnwindDate.AddYears(1), TradeStatus = "确认成交", ValidState = "Valid",
+ StockEqvNotional = (double)fix, Notional = (double)fix,
+ trade_extend = extend
+ };
+ var position = new swap_position
+ {
+ id = 1001, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown,
+ InterestDirection = (int)SwapDirectionEnum.收取,
+ InterestMode = (int)InterestModeEnum.初始预付金,
+ InterestRateDefault = rate, InterestPrincipalFix = fix,
+ PosiStartDate = ProdPosiStart, PosiMatuirityDate = ProdUnwindDate.AddYears(1),
+ IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.单利,
+ IsAnnualized = true, interest_rest_days = restDays,
+ interest_rule = 0, FloatRateUnderlyingCode = null,
+ InterestSwapInterval = "[]"
+ };
+ var eod = new List
+ {
+ new eod_swap_position
+ {
+ id = 7, SwapTradeId = 1, PositionId = 1001,
+ ValueDate = ProdEodValueDate,
+ TdInterestPrincipal = fix, // 生产 eod_swap_position(35774) TdInterestPrincipal=9,180,000
+ PosiNotionalValue = fix,
+ InterestProfitSum = 0m, FloatRate = 0m
+ }
+ };
+ var interests = _svc.GetInterests(td, td.trade_extend, ProdUnwindDate, ProdUnwindDate,
+ eod, new List { position },
+ fix, fix, fix, fix, closePercent,
+ (int)SwapEventTypeEnum.平仓,
+ false, false, 0, fix, false, settment: false, newCalcLast: false, closeList: null);
+ Assert.AreEqual(1, interests.Count, "预付金腿应生成 1 条 flow_event");
+ return interests[0];
+ }
+
+ [TestMethod]
+ public void 部分平仓50_盘中重置周期7天_应返还本金应线性缩放而非指数级()
+ {
+ var fe = CalcUnwindMultiDay(0.5m);
+ Console.WriteLine($"[TDD][盘中50%] 实测 InterestPrincipal={fe.InterestPrincipal} (buggy=71,718.75, 期望=4,590,000)");
+ // 正确:Fix × closePercent = 9,180,000 × 0.5 = 4,590,000(100%返 9,180,000 的一半)。
+ // buggy:Fix × 0.5^7 = 71,718.75(生产实测),被指数级缩小 ~64 倍。
+ Assert.AreEqual(4_590_000m, fe.InterestPrincipal,
+ "50% 平仓: 应返还本金应=Fix×0.5=4,590,000,不应被 closePercent^7 缩成 71,718.75");
+ }
+
+ [TestMethod]
+ public void 部分平仓10_盘中重置周期7天_应返还本金应线性缩放而非指数级()
+ {
+ var fe = CalcUnwindMultiDay(0.1m);
+ Console.WriteLine($"[TDD][盘中10%] 实测 InterestPrincipal={fe.InterestPrincipal} (buggy=0.918, 期望=918,000)");
+ // 正确:Fix × 0.1 = 918,000。buggy:Fix × 0.1^7 = 0.918(生产实测),缩小 100 万倍。
+ Assert.AreEqual(918_000m, fe.InterestPrincipal,
+ "10% 平仓: 应返还本金应=Fix×0.1=918,000,不应被 closePercent^7 缩成 0.918");
+ }
+
+ [TestMethod]
+ public void 全平_盘中重置周期7天_应返还本金应等于保证金本金()
+ {
+ // closePercent=1 → 1^N=1,指数 bug 对 100% 无影响(故用户看 100% 正常),此用例锚定不回归。
+ var fe = CalcUnwindMultiDay(1m);
+ Console.WriteLine($"[TDD][盘中100%] 实测 InterestPrincipal={fe.InterestPrincipal} (期望=9,180,000)");
+ Assert.AreEqual(ProdPrepayFix, fe.InterestPrincipal,
+ "100% 平仓: 应返还本金应=Fix=9,180,000(closePercent=1 时指数 bug 不显现,须保持正确)");
+ }
+
[TestMethod]
public void 多次部分平仓_计息基数也被根因修复_利息基于保证金本金()
{
diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
index 12b6442c..51f0ae9b 100644
--- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
@@ -1034,8 +1034,14 @@ namespace YLErp.Modules.SwapModule
}
else
{
+ // 单利:计息基数(tdDynomicPrincipal)逐日恒定,非重置日与重置日对本金的处理必须一致——
+ // 显示本金 InterestPrincipal = 基数 × closePercent(线性,只缩放一次),
+ // 计息基数 TdInterestPrincipal = 基数(不缩放)。
+ // 修复前此处曾写 tdDynomicPrincipal = flowEvent.InterestPrincipal(已含 closePercent),
+ // 使下一个非重置日再乘一次 closePercent,累积成 InterestPrincipal = Fix × closePercent^N,
+ // 导致部分平仓"应返还本金"被指数级缩小(50%→Fix×0.5^7、10%→Fix×0.1^7)。
+ // 与日终 CalcDailySimpleInterestByEod(baseInterestPrincipal 只乘一次)对齐。
flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
- tdDynomicPrincipal = flowEvent.InterestPrincipal;
TdInterestPrincipal = tdDynomicPrincipal;
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);