From 2035e1df4cafcb1da7c51c9b5b68aceef3d2f7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Wed, 12 Aug 2026 14:40:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(swap):=20=E4=BF=AE=E5=A4=8DEOD=E5=B9=B3?= =?UTF-8?q?=E4=BB=93=E5=90=8E=E6=94=B6=E7=9B=98=E6=97=B6=E7=BB=93=E6=81=AF?= =?UTF-8?q?=E6=9C=AC=E9=87=91=E8=AE=A1=E7=AE=97=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正SwapDealService.GetInterests方法中平仓后结息本金语义错位问题 - 区分EOD平仓后收盘与盘中平仓前本金传参的不同语义 - 针对模式2合约名义本金规模强制覆盖为实际平仓额 - 保持模式9标的期初全价在全平时的零值处理逻辑 - 添加债券份额调整公司行动支持和记录日期资格判断 - 实现EOD仓位复制和更新时的公司行动应用逻辑 --- YLErpDAL/Modules/SwapModule/SwapDealService.cs | 12 ++++++++++-- .../Modules/SwapModule/SwapEodPositionService.cs | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 367cef46..307f3b5f 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -860,9 +860,17 @@ namespace YLErp.Modules.SwapModule posiPrincipal = r.PosiPrincipal; newClosePercent = r.ClosePercent; } + // 根因位置:SwapEodPositionService.SaveAutoEodWithCloseInterestPosition 在平仓后收盘时传入 + // “收盘后剩余本金 + closePercent=1”,与盘中“平仓前本金 + 实际关闭比例”不是同一语义。 + // GetInterests 同时被盘中试算和 EOD 平仓后收盘调用:后者传入的 + // posiNotionalValue 是收盘后的剩余本金,closePosiNotionalValue 才是本次实际平掉的本金。 + // 例如平仓前 100、平掉 30、收盘后剩余 70 时,EOD 传入 posi=70、close=30、closePercent=1。 + // 模式2(合约名义本金规模)的本次结息本金必须始终是实际平仓额,因此无条件覆盖, + // 否则会错误地用剩余 70 结算本次平掉的 30。模式9(标的期初全价)的部分平仓 + // 仍保留既有的剩余/复利动态本金承接逻辑;仅最终全平时 posi=0,才覆盖以避免结息本金为 0。 if ((InterestModeEnum)position.InterestMode == InterestModeEnum.合约名义本金规模 - || (InterestModeEnum)position.InterestMode == InterestModeEnum.标的期初全价 - && posiNotionalValue == 0m) + || ((InterestModeEnum)position.InterestMode == InterestModeEnum.标的期初全价 + && posiNotionalValue == 0m)) { closePrincipal = closePosiNotionalValue; } diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index c70476bb..f8d9dabb 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -1293,6 +1293,11 @@ namespace YLErp.Modules.SwapModule var tradeExtend = td.trade_extend.ExtendObj; // oriPosiNotionalValue 是平仓前规模,posiNotionalValue 是收盘后剩余规模,closeNational 是本次关闭规模。 // 例如 30% 平仓:303139117.80 = 212197382.46 + 90941735.34。 + // 注意:此处的 posiNotionalValue 与盘中 GetUnwindInterests 传给 GetInterests 的语义不同: + // 盘中传平仓前的当前剩余本金,EOD 此处传平仓后的剩余本金;后面又以 closePercent=1 + // 调用共享计息器。因此策略的 "posiNotional × closePercent" 在本例会得到 212197382.46, + // 而本次实际应结的平仓本金是 closeNational=90941735.34。该语义错位由 + // SwapDealService.GetInterests 的模式2无条件修正、模式9全平零值兜底分流处理,不能删除。 decimal oriPosiNotionalValue = posiLongNotional + posiShortNational + closeNational; decimal posiNotionalValue = posiLongNotional + posiShortNational; // ratio 只负责把腿内原始金额转换为本方盈亏方向,不参与计息金额本身的计算。 @@ -1360,6 +1365,8 @@ namespace YLErp.Modules.SwapModule List preEodPositions = new List(); preEodPositions.Add(eodPayPosition); var calcLast = tradeExtend?.InterestCalcMode?.EndsWith("1") ?? true; + // 此处 closePercent=1 表示 EOD 计算本次事件时走全额结息;它不是 closeNational / oriPosiNotionalValue。 + // 与上方“收盘后剩余本金”同时传入会触发共享计息器的模式2/9本金修正,见 GetInterests。 var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: autoSwap || calcLast); // TdInterestAmount:计息器返回的全腿当日/累计参考值,用于拆出 EOD 的当日新增。 // interestAmountBeforeSettlement:本次事件发生前理论应结的高精度利息。