From e819ad02c54ef822c493e8fb5a757fb7b3cfaa1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Fri, 7 Aug 2026 22:13:58 +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=E7=9A=84=E5=88=A9=E6=81=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了从开始日期到结束日期的天数计算 - 添加了从前一日末到结束日期的天数计算 - 修复了特定条件下利息本金和浮动利率的赋值 - 修正了利息金额和累计利息金额的计算 - 添加了四舍五入处理以确保精度准确性 - 修复了利息平仓损益的计算逻辑 --- .../ConsumedInterestScenarioTest.cs | 42 +++++++++++++++++++ .../Modules/SwapModule/SwapDealService.cs | 15 +++++++ 2 files changed, 57 insertions(+) diff --git a/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs b/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs index 0d603c62..ca3ea968 100644 --- a/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/ConsumedInterestScenarioTest.cs @@ -431,5 +431,47 @@ namespace YLErp.Modules.SwapModule AssertDecimal(expectedDailyInterest, result.InterestPrincipal * (result.InterestRate + result.FloatRate.Value) / AnnualDays); } + + [TestMethod] + public void CI_009_NonResetUnwindWithCalcLastFalseUsesPreviousEodPendingInterest() + { + const decimal pendingInterest = 10019.043756537721m; + const decimal remainingPrincipal = 30041492.070122881942m; + var startDate = new DateTime(2026, 7, 28); + var unwindDate = new DateTime(2026, 8, 7); + var td = CreateTrade(); + td.StartDate = startDate; + td.TradeDate = startDate; + var position = new swap_position + { + id = 1001, SwapTradeId = td.id, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestMode = (int)InterestModeEnum.标的期初全价, + InterestType = (int)InterestTypeEnum.复利, + InterestRateDefault = 0.001234m, + PosiStartDate = startDate, PosiMatuirityDate = ExerciseDate, + IsAnnualized = true, interest_rest_days = 7, + FloatRateUnderlyingCode = "FR007" + }; + var preEod = new eod_swap_position + { + id = 1, PositionId = position.id, ValueDate = unwindDate.AddDays(-1), + TdInterestPrincipal = remainingPrincipal, + InterestIncomeSum = pendingInterest, + InterestProfitSum = pendingInterest, + FloatRate = 0.0213m + }; + var service = new StubSwapDealService( + new OptUserInfo(0, nameof(ConsumedInterestScenarioTest), OptUserFrom.UnitTest), 0.0213, 0m); + + var result = service.GetInterests(td, td.trade_extend, unwindDate, unwindDate, + new List { preEod }, new List { position }, + remainingPrincipal, remainingPrincipal, 0m, remainingPrincipal, 1m, + (int)SwapEventTypeEnum.平仓, false, false, 0m, remainingPrincipal, + add: false, settment: false, newCalcLast: false).Single(); + + AssertDecimal(pendingInterest, result.InterestAmount, + "calcLast=false must not accrue unwind-date interest after the previous EOD"); + } } } diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 42704fed..b69b225b 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -1205,6 +1205,21 @@ namespace YLErp.Modules.SwapModule var floateRate = preEodPosition.FloatRate; if (position.InterestType == (int)InterestTypeEnum.复利) { + var daysFromStart = (endDate - position.PosiStartDate).Days; + var daysFromPreEod = preEodPosition.id != 0 + ? (endDate - preEodPosition.ValueDate).Days + : 0; + if (!calcLast && daysFromPreEod == 1 && daysFromStart % (position.interest_rest_days ?? 1) != 0) + { + interest.InterestPrincipal = preEodPosition.TdInterestPrincipal * closePrecent; + interest.FloatRate = preEodPosition.FloatRate; + InterestAmount = preEodPosition.InterestIncomeSum * closePrecent; + TdInterestAmount = preEodPosition.InterestIncomeSum; + interest.InterestAmount = Math.Round(InterestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero); + interest.TdInterestAmount = Math.Round(TdInterestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero); + interest.InterestClosePnL = interest.InterestAmount * interestRatio; + return interest; + } var remainingPercent = preEodPosition.TdInterestPrincipal > 0m ? closePosiNotionalValue / preEodPosition.TdInterestPrincipal : 1m;