From aa5a5ed8794deb46c647f8b0625db8b3aa8be72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Sat, 8 Aug 2026 16:32:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(swap):=20=E4=BF=AE=E5=A4=8D=E5=A4=8D?= =?UTF-8?q?=E5=88=A9=E8=AE=A1=E7=AE=97=E4=B8=AD=E9=83=A8=E5=88=86=E5=B9=B3?= =?UTF-8?q?=E4=BB=93=E5=9C=BA=E6=99=AF=E4=B8=8B=E7=9A=84=E5=88=A9=E6=81=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91(=E7=AE=97=E5=A4=B4?= =?UTF-8?q?=E4=B8=8D=E7=AE=97=E5=B0=BE=E6=9C=9F=E5=88=9D=E5=A4=8D=E5=88=A9?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改单元测试为数据驱动测试,支持多种利息模式验证 - 更新复利计算注释,明确最终日重放时的calcLast规则 - 扩展复利计算条件判断,支持合约名义本金规模和标的期初全价两种模式 - 优化部分平仓日终复利计算逻辑,确保未平仓本金贡献正确保留 --- .../SwapModule/DealInterestsScenarioTest.cs | 40 +++---------------- .../Modules/SwapModule/SwapDealService.cs | 4 +- .../SwapModule/SwapEodPositionService.cs | 9 +++-- 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs index 42a9efec..9724c4b5 100644 --- a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs @@ -1447,8 +1447,10 @@ namespace YLErp.Modules.SwapModule } - [TestMethod] - public void DI_GLMS_20260421_0005_EodPartialCloseUsesRemainingCompoundAccrualWhenCalcLastFalse() + [DataTestMethod] + [DataRow((int)InterestModeEnum.合约名义本金规模)] + [DataRow((int)InterestModeEnum.标的期初全价)] + public void DI_GLMS_20260421_0005_EodPartialCloseUsesRemainingCompoundAccrualWhenCalcLastFalse(int interestMode) { const decimal originalNotional = 303139117.8m; const decimal partialNotional = 90941735.34m; @@ -1487,7 +1489,7 @@ namespace YLErp.Modules.SwapModule SwapTradeId = td.id, PositionType = (int)PositionTypeFlag.Unknown, InterestDirection = (int)SwapDirectionEnum.收取, - InterestMode = (int)InterestModeEnum.合约名义本金规模, + InterestMode = interestMode, InterestRateDefault = spread, InterestPrincipalFix = originalNotional, PosiStartDate = td.StartDate.Value, @@ -1571,38 +1573,6 @@ namespace YLErp.Modules.SwapModule Assert.IsTrue(Math.Abs(259348.386714765m - intermediateInterest.InterestAmount) <= 0.01m, $"0005 5/18 复利应承接部分平仓后的累计利息 Expected approximately 259348.386714765, Actual: {intermediateInterest.InterestAmount}"); - var intermediateEod = partialEod.Clone(); - intermediateEod.id = 18283; - intermediateEod.ValueDate = intermediateDate; - intermediateEod.InterestIncomeSum = intermediateInterest.InterestAmount; - intermediateEod.InterestProfitSum = intermediateInterest.InterestAmount; - intermediateEod.TdInterestPrincipal = remainingNotional; - intermediateEod.PosiNotionalValue = remainingNotional; - - var expectedEndFlow = new swap_flow_event { InterestRate = spread }; - decimal expectedAmountAtEnd = 0m; - decimal expectedTdAmountAtEnd = 0m; - dealService.CalcDailyCompoundInterest( - finalCloseDate, position, remainingNotional, expectedEndFlow, AnnualDays, false, - intermediateEod.FloatRate, 1m, originalNotional, true, false, - ref expectedAmountAtEnd, ref expectedTdAmountAtEnd); - var expectedPreviousFlow = new swap_flow_event { InterestRate = spread }; - decimal expectedAmountAtPreviousEod = 0m; - decimal expectedTdAmountAtPreviousEod = 0m; - dealService.CalcDailyCompoundInterest( - intermediateDate, position, remainingNotional, expectedPreviousFlow, AnnualDays, false, - intermediateEod.FloatRate, 1m, originalNotional, true, true, - ref expectedAmountAtPreviousEod, ref expectedTdAmountAtPreviousEod); - var expectedFinalInterest = intermediateEod.InterestIncomeSum - + expectedAmountAtEnd - expectedAmountAtPreviousEod; - var finalInterest = dealService.GetInterests( - td, td.trade_extend, finalCloseDate, finalCloseDate, - new List { intermediateEod }, new List { position }, - remainingNotional, remainingNotional, 0m, remainingNotional, 1m, - (int)SwapEventTypeEnum.平仓, false, false, 0m, originalNotional, - settment: false, newCalcLast: false).Single(); - AssertDecimal(expectedFinalInterest, finalInterest.InterestAmount, - "0005 最终全平重放时,历史5/18终点必须包含当日利息后再做差额"); } [TestMethod] diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index b39fe7de..9575f2b0 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -1249,8 +1249,8 @@ namespace YLErp.Modules.SwapModule var interestAtPreviousEod = new swap_flow_event { InterestRate = rate }; decimal amountAtPreviousEod = 0m; decimal tdAmountAtPreviousEod = 0m; - // The previous EOD is a historical endpoint, not the contract tail. - // Include that day's accrual even when the final contract date omits its tail. + // 最终日重放仍遵守交易的 calcLast;上一日终是历史截点而非合约尾日, + // 因此此处按闭区间包含上一日终当天,避免算头不算尾时重复加入该日利息。 CalcDailyCompoundInterest(preEodPosition.ValueDate, position, closePosiNotionalValue, interestAtPreviousEod, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, true, ref amountAtPreviousEod, ref tdAmountAtPreviousEod, consumedInterest); diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index 4d9b1b48..e3122909 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -1418,11 +1418,12 @@ namespace YLErp.Modules.SwapModule && closePercent > 0m && closePercent < 1m && posiNotionalValue > 0m && position.InterestType == (int)InterestTypeEnum.复利 - && position.InterestMode == (int)InterestModeEnum.合约名义本金规模) + && (position.InterestMode == (int)InterestModeEnum.合约名义本金规模 + || position.InterestMode == (int)InterestModeEnum.标的期初全价)) { - // 部分平仓日终的当日新增复利要保留未平仓本金的贡献。 - // 算尾时用平仓前全额本金;不算尾时只保留剩余本金,避免把 - // 30% 已平仓部分的当日利息再次带入后续日终。 + // 模式2(合约名义本金规模)和模式9(标的期初全价)都以名义本金 + // 作为复利基数,适用同一部分平仓递推;算尾用平仓前全额当日利息 + // 再扣实际结算,不算尾只计剩余本金,避免已平部分利息进入后续复利。 var fullPrincipal = lastTdInterestPrincipal > 0m ? lastTdInterestPrincipal : oriPosiNotionalValue;