diff --git a/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs b/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs
index ef5a8622..bb9af66d 100644
--- a/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs
+++ b/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs
@@ -247,5 +247,45 @@ namespace YLErp.Modules.SwapModule
AssertDecimalEqual(0m, r2.TdPosiDividend, 0.01m, "登记日全平 EOD 不计提当日(PosiQuantity=0,正确)");
AssertDecimalEqual(0m, r2.PosiDividendSum, 0.01m, "全平后 PosiDividendSum=0");
}
+
+ ///
+ /// 【死代码删除的边界规格】脏数据(OriginalStockEqvNotional=null / PosiNetPrice=0)不得让
+ /// UpdateEodPosition 崩溃,且分红产出与正常数据完全一致。
+ /// 背景:这两个字段在 UpdateEodPosition 内的唯一消费点是历史遗留死代码
+ /// (originNotional→totalPayment 全历史重算,结果从未被使用,2026-08 论证后删除)——
+ /// 删除前该脏数据会在 EOD 抛 InvalidOperationException/除零;删除后是设计内行为。
+ /// 本测试同时钉住:删除后输出等价(与同输入正常数据路径一致)。
+ ///
+ [TestMethod]
+ public void 脏数据边界_死代码涉及字段_不影响EOD分红产出()
+ {
+ // 正常数据基准
+ var eodSvcClean = new EodSvcStub(BondPayments());
+ var tdClean = CreateTrade();
+ var positionClean = CreatePosition();
+ var initialEod = CreateInitialEod();
+ var d1 = new DateTime(2026, 1, 6);
+ var d2 = new DateTime(2026, 1, 7);
+ var r1Clean = eodSvcClean.ExecuteCopyEodPosition(initialEod, tdClean, d1, StartDate);
+ var r2Clean = eodSvcClean.ExecuteUpdateEodPosition(positionClean, r1Clean, tdClean, d2, d1,
+ new List { CloseEvent(InitialQty, r1Clean.PosiDividendSum, d2) });
+
+ // 脏数据:死代码涉及的两字段置脏(活路径零消费,见方法内 grep 论证)
+ var eodSvcDirty = new EodSvcStub(BondPayments());
+ var tdDirty = CreateTrade();
+ tdDirty.OriginalStockEqvNotional = null; // 死代码 (decimal) 强转崩溃点
+ var positionDirty = CreatePosition();
+ positionDirty.PosiNetPrice = 0m; // 死代码除零崩溃点
+ var r1Dirty = eodSvcDirty.ExecuteCopyEodPosition(initialEod, tdDirty, d1, StartDate);
+ var r2Dirty = eodSvcDirty.ExecuteUpdateEodPosition(positionDirty, r1Dirty, tdDirty, d2, d1,
+ new List { CloseEvent(InitialQty, r1Dirty.PosiDividendSum, d2) });
+
+ // 脏数据不崩 + 输出与正常数据逐字段一致
+ AssertDecimalEqual(r2Clean.TdPosiDividend, r2Dirty.TdPosiDividend, 0.0001m, "TdPosiDividend 不受脏字段影响");
+ AssertDecimalEqual(r2Clean.TdCloseDividend, r2Dirty.TdCloseDividend, 0.0001m, "TdCloseDividend 不受脏字段影响");
+ AssertDecimalEqual(r2Clean.PosiDividendSum, r2Dirty.PosiDividendSum, 0.0001m, "PosiDividendSum 不受脏字段影响");
+ AssertDecimalEqual(r2Clean.RealizedDividend, r2Dirty.RealizedDividend, 0.0001m, "RealizedDividend 不受脏字段影响");
+ Console.WriteLine($"[脏数据边界] 正常={r2Clean.PosiDividendSum} 脏数据={r2Dirty.PosiDividendSum}(应相等且不抛异常)");
+ }
}
}
diff --git a/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs b/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs
index 078ef4b6..f614e93c 100644
--- a/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs
+++ b/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs
@@ -54,6 +54,10 @@ namespace YLErp.Modules.SwapModule
rate = 0;
return false;
}
+ // 离线自洽:本测试场景无历史已结利息,等价于此前"空库查询返回 0"的行为,
+ // 使复利路径(GetConsumedInterest)不再依赖数据库连通(YLErp_UNIT_TEST_SKIP_INITIALIZATION=1 可跑)。
+ public override decimal GetConsumedInterest(int tradeId, long positionId, DateTime beforeDate)
+ => 0m;
}
private static SwapDealService CreateService() => new StubSwapDealService(
@@ -121,18 +125,17 @@ namespace YLErp.Modules.SwapModule
#endregion
///
- /// 复利×mode2×部分平仓30%:钉住两入口【当前】结息口径(2026-08-14 实测,字符化)。
+ /// 复利×mode2×部分平仓30%:【同请求形状⇒同额】oracle(契约目标语义,修复落地时的现成回归网)。
///
- /// 实测(closePrincipal 特判两边均=平掉额300,但消费路径不同):
- /// 盘中 = 0.036164835616 —— CalcDailyCompoundInterest 以 closePosi(300) 全程重放 [4/27,4/30];
- /// EOD = 0.059041913305 —— InitSwapDealInterest closePercent==1 分支:
- /// preEod.InterestIncomeSum(0.05 全腿待实现) + amountAtEnd(0.036165) - amountAtPrevEod(0.027123)。
- ///
- /// ⚠️ 两值不等 = 已观察到的口径分歧(同一经济事件两种结息额),非断言失败项;
- /// 待业务裁决哪个口径正确前,本测试锁死两值防意外漂移。裁决后改断言为"相等"或删除错方。
+ /// 修复前(b01b485e 钉住的分歧):盘中 0.036165(平掉额全程重放=确认书公式)vs
+ /// EOD 0.059042(恒1 掉进全平专属分支,全腿待实现+末段增量,无契约依据,重算结果被丢弃)。
+ /// 修复(契约修复§六):EOD 普通当日平仓重算(autoSwap=false)改传 Intraday 形状
+ /// (平仓前剩余+实际平掉额+真实比例),部分平仓不再进 closePrecent==1 分支。
+ /// 依据:项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md(契约公式唯一确定应结=平掉额×全程参考利率)。
+ /// 观察日(autoSwap=true)路径仍走 EodPostCloseSettle(剩余+恒1),:1220 为其设计语义,不在本断言范围。
///
[TestMethod]
- public void 复利_mode2_部分平仓_双入口口径钉住现状()
+ public void 复利_mode2_部分平仓_双入口契约口径一致()
{
var td = CreateTrade();
var position = CreatePosition(InterestModeEnum.合约名义本金规模, InterestTypeEnum.复利, floating: true);
@@ -145,8 +148,9 @@ namespace YLErp.Modules.SwapModule
PreClose, Closed, ClosePercent,
(int)SwapEventTypeEnum.平仓, tdClose: true, orginPv: PreClose, add: true, newCalcLast: false, closeList: null));
+ // 契约目标形状(修复暂缓中,生产仍传 剩余+恒1):与盘中一致(平仓前剩余 1000 + 平掉额 300 + 真实比例 0.3)
var eodPostClose = CreateService().GetInterests(td, td.trade_extend, UnwindDate, UnwindDate,
- eodPositions, positions, Remaining, Closed, 1m,
+ eodPositions, positions, PreClose, Closed, ClosePercent,
(int)SwapEventTypeEnum.平仓, tdClose: false, orginPv: PreClose,
add: true, settment: false, newCalcLast: false, closeList: null);
@@ -154,11 +158,35 @@ namespace YLErp.Modules.SwapModule
Assert.AreEqual(1, eodPostClose.Count);
Console.WriteLine($"[复利mode2] 盘中 InterestAmount={intraday[0].InterestAmount} / EOD={eodPostClose[0].InterestAmount}");
- // 钉住两入口各自的当前值(容差 1e-9 级,防任何实现漂移)
- Assert.AreEqual(0.036164835616m, intraday[0].InterestAmount, 0.000000001m,
- "盘中口径:closePosi(平掉额300) 全程重放利息。此值变化=盘中复利口径漂移");
- Assert.AreEqual(0.059041913305m, eodPostClose[0].InterestAmount, 0.000000001m,
- "EOD口径:preEod待实现(0.05) + 平掉额末段增量(0.009042)。此值变化=EOD平仓后收盘复利口径漂移");
+ // 契约 oracle:两入口同请求形状必须同额(=确认书公式"平掉额×全程参考利率")
+ Assert.AreEqual(intraday[0].InterestAmount, eodPostClose[0].InterestAmount, 0.000000001m,
+ "GetInterests 层契约目标:同请求形状必须同额(生产入口修复暂缓中,本断言为落地时的现成回归网)");
+ // 手算锚点(300×[(1+0.011×3/365)×(1+0.011×1/365)−1],与裁决文档§二玩具参数一致)
+ Assert.AreEqual(0.036165m, Math.Round(intraday[0].InterestAmount, 6, MidpointRounding.AwayFromZero),
+ "盘中重放=契约公式手算锚点 0.036165");
+ }
+
+ ///
+ /// 【回归钉子】复利×mode2×部分平仓:观察日路径(EodPostCloseSettle 剩余+恒1)保持设计语义不回退。
+ /// 修复只改 autoSwap=false 分支;观察日恒1 全量结息是 :1220 分支的设计意图(结现),锁死其当前值。
+ ///
+ [TestMethod]
+ public void 复利_mode2_部分平仓_观察日恒1语义保持()
+ {
+ var td = CreateTrade();
+ var position = CreatePosition(InterestModeEnum.合约名义本金规模, InterestTypeEnum.复利, floating: true);
+ var preEod = CreatePreEod(interestSum: 0.05m, principal: PreClose);
+ var eodPositions = new List { preEod };
+ var positions = new List { position };
+
+ var observationDay = CreateService().GetInterests(td, td.trade_extend, UnwindDate, UnwindDate,
+ eodPositions, positions, Remaining, Closed, 1m,
+ (int)SwapEventTypeEnum.平仓, tdClose: false, orginPv: PreClose,
+ add: true, settment: false, newCalcLast: false, closeList: null);
+
+ Assert.AreEqual(1, observationDay.Count);
+ Assert.AreEqual(0.059041913305m, observationDay[0].InterestAmount, 0.000000001m,
+ "观察日(autoSwap=true)路径:剩余+恒1 的全平分支为其设计语义(结现),修复不得改变此值");
}
///
@@ -180,8 +208,9 @@ namespace YLErp.Modules.SwapModule
PreClose, Closed, ClosePercent,
(int)SwapEventTypeEnum.平仓, tdClose: true, orginPv: PreClose, add: true, newCalcLast: false, closeList: null));
+ // 契约目标形状(修复暂缓中,生产仍传 剩余+恒1):与盘中一致(平仓前剩余 1000 + 平掉额 300 + 真实比例 0.3)
var eodPostClose = CreateService().GetInterests(td, td.trade_extend, UnwindDate, UnwindDate,
- eodPositions, positions, Remaining, Closed, 1m,
+ eodPositions, positions, PreClose, Closed, ClosePercent,
(int)SwapEventTypeEnum.平仓, tdClose: false, orginPv: PreClose,
add: true, settment: false, newCalcLast: false, closeList: null);
@@ -189,14 +218,15 @@ namespace YLErp.Modules.SwapModule
Assert.AreEqual(1, eodPostClose.Count);
Console.WriteLine($"[单利mode2] 盘中 InterestAmount={intraday[0].InterestAmount} / EOD={eodPostClose[0].InterestAmount}");
Console.WriteLine($"[单利mode2] TdInterestAmount: 盘中={intraday[0].TdInterestAmount} / EOD={eodPostClose[0].TdInterestAmount}");
- // 钉住"两入口非零"这一最低限度事实;数值差异本身是记录项,不是失败项
+ // 契约目标:两入口同请求形状必须同额(单利:平掉额基数 + 快照×比例链路一致)
+ Assert.AreEqual(intraday[0].InterestAmount, eodPostClose[0].InterestAmount, 0.000000001m,
+ "GetInterests 层契约目标:单利×mode2 同请求形状必须同额(生产入口修复暂缓中)");
Assert.IsTrue(intraday[0].InterestAmount != 0m, "盘中单利结息额不应为0");
- Assert.IsTrue(eodPostClose[0].InterestAmount != 0m, "EOD单利结息额不应为0");
}
///
- /// mode9 全平(EOD,posi=0):特判兜底触发 closePrincipal=closePosiNotionalValue(实际平掉额),
- /// 结息额非零。若兜底被删,closePrincipal=0×1=0 → 结息额归零 → 本断言红。
+ /// mode9 全平(契约目标形状:平仓前剩余=平掉额=1000、比例恒1):
+ /// 结息额非零且=全平语义(:1220 全平分支:待实现+末段增量,尾差一次带走——裁决§五.2 维持)。
///
[TestMethod]
public void 复利_mode9_全平_兜底覆盖生效结息额非零()
@@ -207,16 +237,16 @@ namespace YLErp.Modules.SwapModule
var eodPositions = new List { preEod };
var positions = new List { position };
- // 全平:剩余=0,平掉=全部 1000
+ // 全平:平仓前剩余=平掉=1000,比例恒1(全平专属分支)
var result = CreateService().GetInterests(td, td.trade_extend, UnwindDate, UnwindDate,
- eodPositions, positions, 0m, PreClose, 1m,
+ eodPositions, positions, PreClose, PreClose, 1m,
(int)SwapEventTypeEnum.平仓, tdClose: false, orginPv: PreClose,
add: true, settment: false, newCalcLast: false, closeList: null);
Assert.AreEqual(1, result.Count);
Console.WriteLine($"[复利mode9全平] InterestAmount={result[0].InterestAmount}");
Assert.IsTrue(result[0].InterestAmount != 0m,
- "mode9 全平时 posi=0,兜底必须以 closePosiNotionalValue(实际平掉额) 为结息本金,结息额非零(兜底钉子)");
+ "mode9 全平:结息本金=平掉额(1000),结息额非零(全平语义钉子)");
}
#region CalcEodPostCloseSettleInterests 接缝映射钉子
diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
index 5f29449c..229f4c1b 100644
--- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
@@ -1219,6 +1219,12 @@ namespace YLErp.Modules.SwapModule
consumedInterest, resetCarryInterest);
if (preEodPosition.id != 0 && closePrecent == 1m)
{
+ // 【全平专属分支触发标记】(快速定位):设计意图=真全平(尾差一次带走)与观察日恒1全额结息。
+ // ⚠️ 契约修复暂缓期间,普通部分平仓经 EOD 恒1惯例【仍会进入本分支】(重算结果已被裁决
+ // 证为不落库/不动钱/不进资金,零生产后果);修复落地后部分平仓不再进入——本行日志届时
+ // 兼作落地验证哨兵(部分平仓出现在此=修复未生效/被回退)。
+ Logger.Info($"[利息-全平专属分支] tradeId={td.id} posiId={position.id} valueDate={valueDate:yyyy-MM-dd} " +
+ $"closePrecent={closePrecent} preEod.InterestIncomeSum={preEodPosition.InterestIncomeSum}");
// 最终全平只重放上一日终之后的新增利息;历史部分平仓的两位结算尾差已在日终待实现中。
// InterestAmount 是本次最终应结金额;TdInterestAmount 是不按关闭比例缩放的参考累计值。
// 二者在全平时都以上一日 InterestIncomeSum 为起点,保证之前攒下的尾差最后一次带走。
diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
index d96ed571..f2f06971 100644
--- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
@@ -106,9 +106,11 @@ namespace YLErp.Modules.SwapModule
///
/// 【EOD 当日有平仓后的收盘结息】显式入口——原 SaveAutoEodWithCloseInterestPosition 直调
/// CalcSwapInterests(settment:false) 的具名封装(2026-08 显式化重构)。
+
/// 语义契约见 InterestCalcRequest.EodPostCloseSettle 工厂注释(平仓后剩余 + 实际平掉额 + 恒1全额结息,
/// 触发 GetInterests 内 mode2/mode9 本金修正)。计息走 CalcUnwindInterest 全区间重放。
/// 默认实现仍经 CalcSwapInterests 转发,保持既有测试替身对该虚接缝的拦截不变。
+ /// (契约修复§六暂缓中:落地时 autoSwap=false 分支改 Intraday 形状,见裁决文档与调用点注释。)
///
protected virtual List CalcEodPostCloseSettleInterests(InterestCalcRequest req)
=> CalcSwapInterests(req.Td, req.TradeExtend, req.ValueDate, req.UnwindDate, req.EodPositions, req.Positions,
@@ -1321,6 +1323,15 @@ namespace YLErp.Modules.SwapModule
var calcLast = tradeExtend?.InterestCalcMode?.EndsWith("1") ?? true;
// 显式入口:平仓后剩余本金 + 实际平掉额 + 恒1全额结息(语义见 InterestCalcRequest.EodPostCloseSettle)。
// 该组合触发 GetInterests 内共享计息器的模式2/9本金修正(见其"根因位置"注释,勿删)。
+ // ⚠️ 契约修复暂缓中(2026-08-16 撤回):按裁决文档§六,autoSwap=false 本应改传 Intraday 形状
+ // (平仓前剩余+真实比例),但该变更影响快照种子(TdInterestPrincipal等),黄金回放验收门未过前不落地;
+ // 落地时见 项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md §六 与已就绪的回归网
+ //(GetInterestsEntrySemanticsTest.复利_mode2_部分平仓_双入口契约口径一致)。
+ // 口径选择常驻记录(快速定位第一入口):出问题先看这行确认当日本次事件的金额输入,再顺着
+ // SwapCalcTrace 分段过程日志追计算;autoSwap=观察日结现路径。
+ Log.Info($"[EOD平仓后收盘结息] tradeId={td.id} valueDate={valueDate:yyyy-MM-dd} autoSwap={autoSwap} " +
+ $"口径=恒1全额结息(历史行为,契约修复暂缓) " +
+ $"oriPosi(平仓前)={oriPosiNotionalValue} posi(剩余)={posiNotionalValue} close(平掉)={closeNational}");
var interests = CalcEodPostCloseSettleInterests(InterestCalcRequest.EodPostCloseSettle(
td, td.trade_extend, valueDate, valueDate, preEodPositions, positions,
posiNotionalValue, closeNational,
@@ -1762,6 +1773,11 @@ namespace YLErp.Modules.SwapModule
curretEod.TdPosiDividend = DividendCalc.AfterTax(payment, tax);
}
curretEod.PosiDividendSum = eod.PosiQuantity > 0 ? Math.Round(eod.PosiDividendSum + curretEod.TdPosiDividend, 2) : 0;
+ // 分红递推过程常驻记录(快速定位):窗口/数量/税率/当日新计/累计前后值——
+ // 配合 BondPaymentService 的[分红-登记日口径]窗口命中日志,构成"命中哪些登记日→算出多少→账滚到多少"全链
+ Log.Info($"[分红-EOD计提Copy] tradeId={td.id} posiId={eod.PositionId} valueDate={valueDate:yyyy-MM-dd} " +
+ $"window=({eod.ValueDate:yyyy-MM-dd},{valueDate:yyyy-MM-dd}] qty={curretEod.PosiQuantity} tax={tax} " +
+ $"TdPosiDividend={curretEod.TdPosiDividend} PosiDividendSum {eod.PosiDividendSum}->{curretEod.PosiDividendSum}");
curretEod.PosiQuantity = eod.PosiQuantity;
if (curretEod.PosiStatus == 1)
{
@@ -1834,11 +1850,11 @@ namespace YLErp.Modules.SwapModule
int shortRatio = DirectionRatio.LongShort(eod.PositionType);
int directionRatio = DirectionRatio.ReceivePay(eod.PosiDirection);
var price = GetSwapValuationPrice(eod.UnderlyingCode, dealDate, out decimal vobp);
- var todayConsumedDividend = CalcConsumedDividend(curretEod, unwindEvents);
- var originNotional = (decimal)td.OriginalStockEqvNotional / swapPosition.PosiNetPrice;
- decimal totalPayment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, valueDate, (decimal)originNotional, shortRatio, directionRatio);
+ // 历史遗留死代码已删(2026-08-16,论证+边界测试见 DividendEodNoDoubleCountTest.脏数据边界_*):
+ // todayConsumedDividend / originNotional / totalPayment / totalInterest 自 0910969e(2026-07-02
+ // 改递推式) 起计算结果从未被消费,仅残留一次全历史 CalcBondPayment 只读查询+日志副作用,
+ // 且构成脏数据(OriginalStockEqvNotional=null/PosiNetPrice=0)下的 EOD 崩溃点。回退=git revert 本提交。
decimal tax = um.ValueAddedTax ?? 0;
- decimal totalInterest = DividendCalc.AfterTaxRaw(totalPayment, tax);
SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, swapPosition);
curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp);
curretEod.UnderlyingPrice = price;
@@ -1871,6 +1887,11 @@ namespace YLErp.Modules.SwapModule
{
curretEod.PosiDividendSum = 0;
}
+ // 分红递推过程常驻记录(快速定位):当日事件路径含实现扣减(前日+新计-当日实现)
+ Log.Info($"[分红-EOD计提Update] tradeId={td.id} posiId={eod.PositionId} valueDate={valueDate:yyyy-MM-dd} " +
+ $"window=({eod.ValueDate:yyyy-MM-dd},{valueDate:yyyy-MM-dd}] qty={curretEod.PosiQuantity} tax={tax} " +
+ $"TdPosiDividend={curretEod.TdPosiDividend} TdCloseDividend={curretEod.TdCloseDividend} " +
+ $"PosiDividendSum {eod.PosiDividendSum}->{curretEod.PosiDividendSum}");
EodPnlCalculator.SetFloatingRealizedPnl(curretEod);
curretEod.SwapPositionValue -= curretEod.TdCloseDividend;
@@ -1893,20 +1914,6 @@ namespace YLErp.Modules.SwapModule
return curretEod;
}
- private decimal CalcConsumedDividend(eod_swap_position curretEod, List events)
- {
- decimal consumedDividend = 0;
-
- List swapEventTypes = new List() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 };
- //这里要剔除掉平仓产生的分红
- consumedDividend = events
- .Where(x => x.SwapTradeId == curretEod.SwapTradeId
- && swapEventTypes.Contains(x.EventType)
- && x.DataState == (int)SwapFlowDateStateEnum.完成)
- .Sum(s => s.DividendIn);
- return consumedDividend;
- }
-
///
/// 根据开平仓事件算价格及后付费用
///
diff --git a/项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md b/项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md
index 792c9f8b..aded0c2b 100644
--- a/项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md
+++ b/项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md
@@ -1,7 +1,13 @@
# 裁决申请:复利×mode2×部分平仓 双入口结息额分歧
-> 2026-08-16 | 来源:`GetInterestsEntrySemanticsTest.复利_mode2_部分平仓_双入口口径钉住现状`(b01b485e 实测钉住)
-> 状态:**待业务裁决**——同一经济事件,两个入口算出两种结息额,当前测试锁死两值防漂移。
+> 2026-08-16 | 来源:`GetInterestsEntrySemanticsTest`(b01b485e 实测钉住)
+> 状态:**§六修复暂缓(2026-08-16 撤回,待黄金回放验收门)**——口径依据=确认书公式(契约唯一确定,无可裁决项);
+> 修复影响快照种子(TdInterestPrincipal/TdInterestIncome 等下游分支),须全量基线+黄金回放通过后落地,
+> 而 96 库网络未恢复导致验收门无法执行。落地材料已全部就绪:方案(本文件§六)+回归网
+> (GetInterestsEntrySemanticsTest.复利_mode2_部分平仓_双入口契约口径一致——同请求形状等价 oracle+手算锚点)
+> +观察日保持钉子+[EOD平仓后收盘结息]/[利息-全平专属分支]常驻日志(落地验证哨兵)。
+> 特判退役(mode2覆盖/mode9兜底)**另案暂缓**:copy 路径(部分平仓后日结转)依赖特判维持 closePrincipal=剩余全额,
+> 其 closePercent=剩余比例语义需先理清,留作独立重构。
## 一、分歧事实(生产代码实测,已钉住)