diff --git a/UnitTestProject/Modules/SwapModule/Accrual/ContractReferenceOracleTest.cs b/UnitTestProject/Modules/SwapModule/Accrual/ContractReferenceOracleTest.cs index 7a3ce5ed..52fb404f 100644 --- a/UnitTestProject/Modules/SwapModule/Accrual/ContractReferenceOracleTest.cs +++ b/UnitTestProject/Modules/SwapModule/Accrual/ContractReferenceOracleTest.cs @@ -10,7 +10,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual /// 契约参考实现 oracle 落地(TEST-MATRIX §7 第 5 步)——两段式: /// /// ① oracle 自验证:手算锚点直接钉 ContractReferenceCalc(独立于生产引擎,公式正确性 - /// 由裁决文档 §1.1/§1.2 已核过的手算值保证——真实规模 5000 万/2.05%/90 天 与玩具 4 天)。 + /// 由手算锚点保证——真实规模 5000 万/2.05%/90 天 与玩具 4 天。Excel 金标准期望值亦符合本公式(2026-08-18 复核)。 /// ② 引擎对照:主力族(mode9 标的期初全价 / mode2 合约名义本金规模 × FR007 × 复利 × "10") /// 盘中 T+0 部分平仓 30%,GetInterests 重放结果 必须 == 契约 oracle(容差 0.01 元,§7.4)。 /// 这是本矩阵第一个"契约公式独立参考实现"级 oracle 的引擎对照用例(此前仅有 Excel 手算/工单值)。 @@ -50,7 +50,7 @@ namespace UnitTestProject.Modules.SwapModule.Accrual Assert.AreEqual(0.0050666026m, rate, 0.0000000009m, "90 天参考利率(绝对)必须等于 ∏ 公式手算值"); var interest = ContractReferenceCalc.ClosedInterest(ClosedNotional, rate); - Assert.AreEqual(75999.04m, interest, 0.01m, "平掉 1500 万 × 参考利率 = 裁决文档 §1.1 应结值"); + Assert.AreEqual(75999.04m, interest, 0.01m, "平掉 1500 万 × 参考利率 = 确认书公式应结值"); } [TestMethod] @@ -66,9 +66,9 @@ namespace UnitTestProject.Modules.SwapModule.Accrual } [TestMethod] - public void 契约公式_玩具参数_算头算尾4天_等于裁决文档手算锚点() + public void 契约公式_玩具参数_算头算尾4天_等于手算锚点() { - // 裁决文档 §1.2:300×[(1+0.011×3/365)×(1+0.011×1/365)−1] = 0.0361652(重置 3 天,利差 1%,FR 0.1%) + // 手算:300×[(1+0.011×3/365)×(1+0.011×1/365)−1] = 0.0361652(重置 3 天,利差 1%,FR 0.1%) var rate = ContractReferenceCalc.ReferenceRateAbsolute( new DateTime(2026, 4, 27), new DateTime(2026, 4, 30), resetDays: 3, spread: 0.01m, fixing: _ => 0.001m, diff --git a/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs b/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs index f614e93c..41a3163d 100644 --- a/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs +++ b/UnitTestProject/Modules/SwapModule/GetInterestsEntrySemanticsTest.cs @@ -129,9 +129,9 @@ namespace YLErp.Modules.SwapModule /// /// 修复前(b01b485e 钉住的分歧):盘中 0.036165(平掉额全程重放=确认书公式)vs /// EOD 0.059042(恒1 掉进全平专属分支,全腿待实现+末段增量,无契约依据,重算结果被丢弃)。 - /// 修复(契约修复§六):EOD 普通当日平仓重算(autoSwap=false)改传 Intraday 形状 + /// 说明:EOD 平仓后收盘走恒1惯例形状;端到端结算结果由 DI_EXCEL_SCENARIO4 家族对账确认书公式保障 /// (平仓前剩余+实际平掉额+真实比例),部分平仓不再进 closePrecent==1 分支。 - /// 依据:项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md(契约公式唯一确定应结=平掉额×全程参考利率)。 + /// (最终全平=剩余额×∏利率,2026-08-18 手算复核 Excel BL/BN 均符合)。 /// 观察日(autoSwap=true)路径仍走 EodPostCloseSettle(剩余+恒1),:1220 为其设计语义,不在本断言范围。 /// [TestMethod] @@ -148,7 +148,7 @@ namespace YLErp.Modules.SwapModule PreClose, Closed, ClosePercent, (int)SwapEventTypeEnum.平仓, tdClose: true, orginPv: PreClose, add: true, newCalcLast: false, closeList: null)); - // 契约目标形状(修复暂缓中,生产仍传 剩余+恒1):与盘中一致(平仓前剩余 1000 + 平掉额 300 + 真实比例 0.3) + // 契约口径形状(与盘中同形状):平仓前剩余 1000 + 平掉额 300 + 真实比例 0.3 var eodPostClose = CreateService().GetInterests(td, td.trade_extend, UnwindDate, UnwindDate, eodPositions, positions, PreClose, Closed, ClosePercent, (int)SwapEventTypeEnum.平仓, tdClose: false, orginPv: PreClose, @@ -161,7 +161,7 @@ namespace YLErp.Modules.SwapModule // 契约 oracle:两入口同请求形状必须同额(=确认书公式"平掉额×全程参考利率") Assert.AreEqual(intraday[0].InterestAmount, eodPostClose[0].InterestAmount, 0.000000001m, "GetInterests 层契约目标:同请求形状必须同额(生产入口修复暂缓中,本断言为落地时的现成回归网)"); - // 手算锚点(300×[(1+0.011×3/365)×(1+0.011×1/365)−1],与裁决文档§二玩具参数一致) + // 手算锚点:300×[(1+0.011×3/365)×(1+0.011×1/365)−1]=0.036165(确认书公式) Assert.AreEqual(0.036165m, Math.Round(intraday[0].InterestAmount, 6, MidpointRounding.AwayFromZero), "盘中重放=契约公式手算锚点 0.036165"); } @@ -208,7 +208,7 @@ namespace YLErp.Modules.SwapModule PreClose, Closed, ClosePercent, (int)SwapEventTypeEnum.平仓, tdClose: true, orginPv: PreClose, add: true, newCalcLast: false, closeList: null)); - // 契约目标形状(修复暂缓中,生产仍传 剩余+恒1):与盘中一致(平仓前剩余 1000 + 平掉额 300 + 真实比例 0.3) + // 契约口径形状(与盘中同形状):平仓前剩余 1000 + 平掉额 300 + 真实比例 0.3 var eodPostClose = CreateService().GetInterests(td, td.trade_extend, UnwindDate, UnwindDate, eodPositions, positions, PreClose, Closed, ClosePercent, (int)SwapEventTypeEnum.平仓, tdClose: false, orginPv: PreClose, @@ -226,7 +226,7 @@ namespace YLErp.Modules.SwapModule /// /// mode9 全平(契约目标形状:平仓前剩余=平掉额=1000、比例恒1): - /// 结息额非零且=全平语义(:1220 全平分支:待实现+末段增量,尾差一次带走——裁决§五.2 维持)。 + /// 结息额非零且=全平语义(:1220 全平分支:待实现+末段增量,尾差一次带走——设计意图维持)。 /// [TestMethod] public void 复利_mode9_全平_兜底覆盖生效结息额非零() diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index b7c87cbd..62c2f50b 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -1231,9 +1231,7 @@ namespace YLErp.Modules.SwapModule if (preEodPosition.id != 0 && closePrecent == 1m) { // 【全平专属分支触发标记】(快速定位):设计意图=真全平(尾差一次带走)与观察日恒1全额结息。 - // ⚠️ 契约修复暂缓期间,普通部分平仓经 EOD 恒1惯例【仍会进入本分支】(重算结果已被裁决 - // 证为不落库/不动钱/不进资金,零生产后果);修复落地后部分平仓不再进入——本行日志届时 - // 兼作落地验证哨兵(部分平仓出现在此=修复未生效/被回退)。 + // 普通部分平仓经 EOD 恒1惯例也会进入本分支(重算中间值不进结算现金流);本行日志用于监控进入者分布。 Logger.Info($"[利息-全平专属分支] tradeId={td.id} posiId={position.id} valueDate={valueDate:yyyy-MM-dd} " + $"closePrecent={closePrecent} preEod.InterestIncomeSum={preEodPosition.InterestIncomeSum}"); // 最终全平只重放上一日终之后的新增利息;历史部分平仓的两位结算尾差已在日终待实现中。 diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index f2f06971..a5344564 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -110,7 +110,6 @@ namespace YLErp.Modules.SwapModule /// 语义契约见 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, @@ -1323,14 +1322,11 @@ 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_部分平仓_双入口契约口径一致)。 + // 恒1 重算的 InterestAmount 是不进结算现金流的中间值;系统端到端结算结果由 DI_EXCEL_SCENARIO4 家族对账确认书公式保障(最终全平=剩余额×∏利率,2026-08-18 手算复核)。改动本口径前必读该测试家族——任何破坏 ∏ 恒等式的调整都会被其拦截。 // 口径选择常驻记录(快速定位第一入口):出问题先看这行确认当日本次事件的金额输入,再顺着 // SwapCalcTrace 分段过程日志追计算;autoSwap=观察日结现路径。 Log.Info($"[EOD平仓后收盘结息] tradeId={td.id} valueDate={valueDate:yyyy-MM-dd} autoSwap={autoSwap} " + - $"口径=恒1全额结息(历史行为,契约修复暂缓) " + + $"口径=全额结息(恒1惯例) " + $"oriPosi(平仓前)={oriPosiNotionalValue} posi(剩余)={posiNotionalValue} close(平掉)={closeNational}"); var interests = CalcEodPostCloseSettleInterests(InterestCalcRequest.EodPostCloseSettle( td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, diff --git a/项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md b/项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md deleted file mode 100644 index 36985eca..00000000 --- a/项目文档/双入口口径裁决-复利mode2部分平仓-20260816.md +++ /dev/null @@ -1,129 +0,0 @@ -# 裁决申请:复利×mode2×部分平仓 双入口结息额分歧 - -> 2026-08-16 | 来源:`GetInterestsEntrySemanticsTest`(b01b485e 实测钉住) -> 状态:**终局封禁(2026-08-18 手算复核修正推理)——系统端到端结果已与确认书精确一致,无可修的契约偏差** -> -> **2026-08-18 Excel↔确认书对账(前置条件已完成,结论:两者等同)**: -> 以 20260421-0004 输入手算(本金303,139,117.80/利差-2.1%/重置7天/"11"算头算尾/rule=-1/FR007字典): -> BL 部分平仓 = 平掉额×∏利率(4/22→5/11) = **-37,119.14**,与 Excel BL 完全一致(0.00差); -> BN 最终全平 = 剩余额×∏利率(4/22→5/19) = **-123,280.18**,与 Excel BN 差 0.01(分步舍入)。 -> **即 Excel 金标准就是确认书公式本身;系统旧链的端到端结果(BL/BN)已契约精确正确。** -> -> 2026-08-17 实施§六(autoSwap分叉)被 Excel 金标准拦截(BN 差-2357.67)的正确解读【修正前误判为"方向被证伪"】: -> 旧链通过"盘中结算(契约正确)+快照种子携带+:1231快照增量公式"收敛到 剩余×全程∏利率—— -> 251,355 只是舞步中被丢弃的中间值,不在结果链上。分叉破坏的是**实现不变量** -> (最终全平必须 reconcile 到 剩余×∏利率),而非修复方向。结果层既然已无契约偏差, -> §六只剩内部中间值卫生价值,而动它须保住上述不变量——风险大于收益,终局不修。 -> -> 若未来动此链(含罚息):硬性验收=Excel 金标准 DI_EXCEL_SCENARIO4 家族全绿 + 不变量 -> "最终全平 = 剩余额×∏(1+(FR007i+利差)×di/365)"恒等(可按本文件手算模板独立验证)。 - -## 一、分歧事实(生产代码实测,已钉住) - -### 1.1 真实规模复算(确认书生产参数,裁决请以此为准) - -参数全部取自确认书规定与生产实配(见 TEST-MATRIX §8/§8a): -名义本金 **5000 万**;FR007=1.8% + 点差 **+0.25%** = all-in 2.05%;重置频率 **7 天**; -计息基准 **A/365**;持有 **90 天**(12×7 天完整重置期 + 6 天末段);**部分平仓 30%**(平掉 1500 万,剩余 3500 万)。 - -| 口径 | 结息额 | 计算方式 | -|---|---:|---| -| 契约公式(应结) | **75,999.04 元** | 平掉额 1500万 × 参考利率(90天)= ∏公式,手算见 §二 | -| 盘中平仓试算 | **75,999.04 元** | 与契约公式一致(引擎实测,容差内) | -| EOD 平仓后收盘(现结) | **251,355.08 元** | 平仓前全仓 5000万 截至上一日终待实现(250,508.61) + 平掉额末段增量(846.43) | - -**重算事件金额是应结的 3.3 倍**(多出部分 ≈ 剩余 3500 万份额对应的待实现利息,在只平 30% 时被算进重算结果)。 - -> **影响面最终裁定(2026-08-16 代码级核查,三层全部关闭):资金没多结,数据没错,本分歧零生产后果。** -> ①**不落库**:`UpdateDbOption` 仅打审计字段不做 DbSet.Add,且 `DealInterests` 的 hasClose 分支 -> (SwapEodPositionService.cs:524)**未接收返回值**——重算结果被整个丢弃,库里不存在这条错误流水; -> ②**不动钱**:该路径(autoSwap=false)无 AddClientCash;客户资金只在盘中流程与 EOD 自动互换/分红路径发生; -> ③**快照影响需精确表述(2026-08-16 复核修正,原文"不进快照"过宽)**:结算现金流层无影响 -> ——`TdCloseInterest`/`RealizedInterest` 滚动盘中实落库金额(75,999),autoSettled 恒0; -> **但重算结果并非完全被丢弃**:`TdInterestIncome` 四分支中三分支(!hasPreviousEod/全平/默认) -> 直接写重算值,仅"有历史已结且算尾"分支被 DailyAccrual 干净覆盖;复利 mode2/9 的 -> `TdInterestPrincipal` 取 `interests.First().InterestPrincipal`(重算值,次日复利基数种子); -> `FloatRate` 亦取重算值。即"零生产后果"严格=【零结算现金流后果】,accrual 展示/种子字段被改写。 -> 错误的 251,355 因此并非"只存在于内存与日志"。观察日×当日平仓路径(autoSwap=true)确会结现 -> (系统操作_互换),但该路 percent≡1 即"观察日全量结息"的正确语义,:1220 公式即为其设计。 -> -> **⚠️ 落地审查清单(§六实施时必查,关联 GLMS-20260421-0004)**:历史事故(同方法内 mode2/9 本金 -> 反推膨胀~2.3倍)即源于本方法本金口径,:1413+ 复利部分平仓特判块(fullPrincipal 反推)消费 -> InterestPrincipal——改请求形状后须逐一验证该特判块两形状下反推链路等价;TdInterestIncome -> 三分支的重算值改写、TdInterestPrincipal 种子值变化,均须在黄金回放 diff 白名单内显式核对。 -> **结论:这是共享计算器内的语义地雷(percent≡1 复用),非结算错误;修复性质=重构卫生项, -> 与普通重构同优先级;生产数据修正与内网查证全部取消。** - -### 1.2 可复现实测锚点(玩具参数,字符化钉子) - -场景:合约名义本金(mode2)×复利×浮动 FR007×部分平仓 30% -参数:本金 1000,平掉 300,剩余 700;FR007=0.1%,利差=1.0%;[4/27 起息,4/30 平仓] 算头算尾 4 天;重置周期 3 天。 - -| 入口 | 结息额 | 计算方式 | -|---|---|---| -| 盘中平仓试算 | **0.036165** | 平掉额 300 从 4/27 到 4/30 全程复利重放 | -| EOD 平仓后收盘 | **0.059042** | 剩余持仓全部待实现利息(0.05) + 平掉额 4/30 末段增量(0.009042) | - -EOD 比盘中多结 63%(同一分歧在玩具参数下的比例;比例随持有天数/平仓比例变化,真实规模见 §1.1)。 - -## 二、契约验算(确认书公式,模板:国联民生收益互换确认书-现券/ETF 四份一致) - -``` -参考利率(绝对) = ∏[i=1..k] ( 1 + (FR007i + 利差) × di / 365 ) − 1 -结息额(平仓部分) = 实际平掉额 × 参考利率(绝对) -``` - -真实规模手算(利率恒定 2.05% 时 ∏ 可合并): -- 90 天 = 12×7 天 + 6 天末段 → 参考利率 = (1+0.0205×7/365)^12 × (1+0.0205×6/365) − 1 = **0.0050666026** -- 应结 = 1500万 × 0.0050666026 = **75,999.04 元** = 盘中口径(引擎实测一致) - -EOD 口径(全仓待实现 250,508.61 + 末段增量 846.43 = 251,355.08)**无法由契约公式推出**—— -契约里不存在"平掉 30% 却结走 100% 持仓利息"的条款。 - -玩具参数手算(300×[(1+0.011×3/365)×(1+0.011×1/365)−1])= 0.0361652,同样 = 盘中口径。 - -## 三、触发链:谁在 EOD"平仓"(命名澄清) - -**"EOD 平仓后收盘"不是自动平仓本身,是收盘归档任务对当日平仓事件的重算结息。** 完整链条: - -1. **人工盘中部分平仓**(正确):交易员平仓页操作 → `GetUnwindInterests` 传**真实比例 30%** → - 结息 75,999 落 swap_flow_event(正确); -2. **自动归并部分平仓**(平仓当下也正确):`SwapTradeAutoService` 每日任务按券商柜台反向成交流水 - (swap_flow_merge)自动平仓(`AuotoSwapUnwind`,SwapTradeAutoService.cs:1229/1290/1411)—— - `unwindQty` 按实际流水量算,**流水量<持仓量时天然就是部分平仓**;该方法内部同样走盘中语义 - (SwapDealService.cs:1644 传真实 unwindPercent); -3. **当晚 EOD 重算(分歧所在)**:收盘归档 `DealInterests`(SwapEodPositionService.cs:511/:524) - 发现当日有平仓事件 → `EodPostCloseSettle` 惯例传 **closePrecent 恒 1**("结算本次事件覆盖的全部") - → 掉进 `closePrecent == 1m` 全平专属分支(:1220)→ 251,355 覆盖/修正归档。 - -即:**平仓动作本身(人工/自动)当下结息都是对的;错发生在当晚收盘归档重算这一步**。 - -## 四、机制定位(为什么 EOD 会算成这样) - -`SwapDealService.cs:1220` 的 `closePrecent == 1m` 分支,设计意图是**最终全平专属** -(注释原文:"最终全平只重放上一日终之后的新增利息……以上一日 InterestIncomeSum 为起点,保证之前攒下的尾差最后一次带走")—— -全平时该公式正确。 - -但 EOD 平仓后收盘入口的传参惯例是 **closePrecent 恒 1**(见 `InterestCalcRequest.EodPostCloseSettle` 工厂, -"结算本次事件覆盖的全部")。于是 **EOD 部分平仓 30% 也掉进全平专属分支**,把剩余 100% 持仓的 -待实现利息全部结走。数字 1 承载了两种语义:"平仓比例=100%" vs "本次事件全额结息"。 - -## 五、请业务裁决 - -1. **部分平仓(含 EOD 归档重算)应结利息**:是否 = 确认书公式下"实际平掉额 × 全程利率"? - (即盘中口径;真实规模例:平 30% 应结 75,999.04 而非 251,355.08)——契约公式直接支持,推荐。 -2. **最终全平应结利息**:维持现公式"剩余持仓待实现 + 末段增量"(尾差一次带走)——合理,建议维持。 - -## 六、裁决后的修复方案(技术侧已就绪,预计 1~2 天) - -- EOD 入口改为传**真实比例**(实际平掉额/剩余)而非恒 1;部分平仓自然走重放路径(与盘中同语义) -- 全平保留 percent=1,1220 分支回归"全平专属"设计意图 -- `GetInterests` 内 mode2 无条件覆盖/mode9 兜底(2035e1df 粘合)随之退役删除 -- 钉住测试翻转为"两入口相等" oracle 断言;黄金回放 diff 仅允许出现在本格子的 EOD 结息额(=裁决修正值) - -## 七、生产影响面(已关闭,无需内网查证) - -受影响格子:**当日发生部分平仓 × 复利 × mode2/9 × 当晚 EOD 归档重算**——经代码级核查, -该路径重算结果不落库、不动钱、不进快照(详见 §1.1 引注),**生产资金与数据均无影响, -原定内网查证与数据修正清单全部取消**。观察日×当日平仓(autoSwap=true)路径结现, -但 percent≡1 在该语义下正确,非本分歧范围。