From db447aa58471d089d9f74fa330d1ba11377d4b02 Mon Sep 17 00:00:00 2001 From: hjhan Date: Sun, 16 Aug 2026 11:03:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor(swap):=20=E4=BF=9D=E8=AF=81=E9=87=91?= =?UTF-8?q?=E8=85=BF=E5=9C=A8=20GetInterests=20=E5=BE=AA=E7=8E=AF=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E6=8F=90=E5=89=8D=E8=B7=AF=E7=94=B1=EF=BC=8C=E8=9E=8D?= =?UTF-8?q?=E8=B5=84=E8=85=BF=E5=88=86=E6=94=AF=E6=A0=91=E5=8E=BB=E9=99=A4?= =?UTF-8?q?=20MarginModes=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 保证金腿(5/6)在最外层路由并 continue:本金(InterestPrincipalFix×closePrecent)、 方向翻转、CalcMarginInterest 收口一处;newClosePercent 在保证金腿恒等于 closePrecent,直接传参 - 融资腿(1/2/9)分支树内 3 处 MarginModes.Contains 归零,后续融资腿改动与保证金解耦 - GetFixedRate 前移为两路共用(纯读函数,已验证无副作用) - 有意跳过 GetFloatRate:CalcMarginInterest 纯固定利率(FundingLegRate.Fixed/ FloatRate 恒 0),浮动取价对保证金无意义;脏数据 FloatRateUnderlyingCode 缺价时不再阻断保证金结算(唯一行为变化,方向更安全) - 验证:SwapModule 537 用例 A/B(带改/不带改)摘要完全一致(71F/459P/7S), 失败项均为本机无库环境失败;内网全量单测待跑 Co-Authored-By: Claude Opus 4.7 --- .../Modules/SwapModule/SwapDealService.cs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 0aab88a2..5f29449c 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -657,27 +657,30 @@ namespace YLErp.Modules.SwapModule // true 跳过 不计利息; false 正常利息 bool swap = InitInterestDate(unwindDate, preDealDate, td, tdClose, out DateTime startDate, out DateTime endDate); - // 计算名义本金 - decimal closePrincipal; - decimal posiPrincipal; - decimal newClosePercent = closePrecent; - var mode = (InterestModeEnum)position.InterestMode; + // 获取利率(保证金/融资腿共用:SwapIntervalList 取当日适用固定利率 + 精度收口) + decimal rate = Math.Round(GetFixedRate(position, unwindDate), InterestCalculationPrecision, MidpointRounding.AwayFromZero); // 做精度调整 原数据有精度误差 + // ── 边界隔离:保证金腿(5/6)在循环最外层路由,后续融资腿分支树不感知保证金概念 ── + // 有意跳过 GetFloatRate:CalcMarginInterest 纯固定利率(FundingLegRate.Fixed)且 FloatRate 恒 0, + // 浮动取价/回写对保证金无意义;即使脏数据填了 FloatRateUnderlyingCode 且缺价,也不应阻断保证金结算。 if (MarginModes.Contains(position.InterestMode)) { - // 保证金腿: 计息基数 = InterestPrincipalFix(保证金余额) - closePrincipal = position.InterestPrincipalFix * closePrecent; - posiPrincipal = position.InterestPrincipalFix; - } - else - { - // 融资腿(1/2/9): 走策略工厂 - var r = FundingLegStrategyFactory.Get(mode) - .CalcNotional(position.InterestPrincipalFix, posiNotionalValue, closePrecent); - closePrincipal = r.ClosePrincipal; - posiPrincipal = r.PosiPrincipal; - newClosePercent = r.ClosePercent; + positionClone.InterestDirection = MarginCalc.FlipDirection(position.InterestDirection); + // 保证金腿: 计息基数 = InterestPrincipalFix(保证金余额),无融资腿差分公式与 orginPv 维度 hack + interests.Add(CalcMarginInterest(td, valueDate, endDate, positionClone, rate, + position.InterestPrincipalFix * closePrecent, position.InterestPrincipalFix, + closePrecent, annualDays, calcFirst, calcLast || newCalcLast, preEodPosition, eventType, add, settment, swap)); + continue; } + + // 计算名义本金(以下仅融资腿 1/2/9:走策略工厂) + var mode = (InterestModeEnum)position.InterestMode; + var r = FundingLegStrategyFactory.Get(mode) + .CalcNotional(position.InterestPrincipalFix, posiNotionalValue, closePrecent); + decimal closePrincipal = r.ClosePrincipal; + decimal posiPrincipal = r.PosiPrincipal; + decimal newClosePercent = r.ClosePercent; + // 根因位置:SwapEodPositionService.SaveAutoEodWithCloseInterestPosition 在平仓后收盘时传入 // “收盘后剩余本金 + closePercent=1”,与盘中“平仓前本金 + 实际关闭比例”不是同一语义。 // GetInterests 同时被盘中试算和 EOD 平仓后收盘调用:后者传入的 @@ -686,29 +689,17 @@ namespace YLErp.Modules.SwapModule // 模式2(合约名义本金规模)的本次结息本金必须始终是实际平仓额,因此无条件覆盖, // 否则会错误地用剩余 70 结算本次平掉的 30。模式9(标的期初全价)的部分平仓 // 仍保留既有的剩余/复利动态本金承接逻辑;仅最终全平时 posi=0,才覆盖以避免结息本金为 0。 - if ((InterestModeEnum)position.InterestMode == InterestModeEnum.合约名义本金规模 - || ((InterestModeEnum)position.InterestMode == InterestModeEnum.标的期初全价 + if (mode == InterestModeEnum.合约名义本金规模 + || (mode == InterestModeEnum.标的期初全价 && posiNotionalValue == 0m)) { closePrincipal = closePosiNotionalValue; } - if (MarginModes.Contains(position.InterestMode)) - { - positionClone.InterestDirection = MarginCalc.FlipDirection(position.InterestDirection); - } - // 获取利率 - decimal rate = Math.Round(GetFixedRate(position, unwindDate), InterestCalculationPrecision, MidpointRounding.AwayFromZero); // 做精度调整 原数据有精度误差 decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone); // 根据场景计算利息 - if (MarginModes.Contains(position.InterestMode)) - { - // 保证金腿(5/6):专属计息,notional 直接取保证金余额,无融资腿差分公式与 orginPv 维度 hack - interests.Add(CalcMarginInterest(td, valueDate, endDate, positionClone, rate, closePrincipal, posiPrincipal, - newClosePercent, annualDays, calcFirst, calcLast||newCalcLast, preEodPosition, eventType, add, settment, swap)); - } - else if (settment) + if (settment) { // 收盘归档场景,使用 CalcEodInterest interests.Add(CalcEodInterest(td, valueDate, positionClone, rate, floatRate, closePrincipal, posiPrincipal, annualDays, calcFirst, calcLast, preEodPosition, eventType, add)); @@ -722,7 +713,7 @@ namespace YLErp.Modules.SwapModule : 0m; interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, - calcLast||newCalcLast, consumedInterest)); + calcLast || newCalcLast, consumedInterest)); } } //当日有平仓或互换记录时,避免重复结算