From be88442c91db2e17acfc8352bd247348e6228ef3 Mon Sep 17 00:00:00 2001 From: hjhan Date: Wed, 1 Jul 2026 18:25:44 +0800 Subject: [PATCH] =?UTF-8?q?test(swap):=20=E6=89=A9=E5=B1=95DealInterests?= =?UTF-8?q?=E6=B5=8B=E8=AF=95-=E5=A4=9A=E6=AC=A1=E4=BA=92=E6=8D=A2?= =?UTF-8?q?=E5=AE=88=E6=81=92+=E9=A2=84=E4=BB=98=E9=87=91ratio=E7=BF=BB?= =?UTF-8?q?=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增2个测试场景: - DI_SWAP_MULTI_001: 攒10天→互换结清→再攒5天→再互换结清 守恒约束: RealizedInterest+InterestIncomeSum=全程利息(17天) 验证多次互换结算后利息不丢失/不重复 - DI_MARGIN_001: 预付金腿(初始预付金,marginTypes)互换结清后 RealizedInterest应为负(ratio翻转:收取→-1→支付方向) 验证cs:789-793的marginTypes ratio翻转逻辑 验证: 89(T0/T1)+7(DealInterests)=96全通过,无回归。 --- .../SwapModule/DealInterestsScenarioTest.cs | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs index ae3c5e42..1631b512 100644 --- a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs @@ -422,6 +422,174 @@ namespace YLErp.Modules.SwapModule Console.WriteLine($"连续5天归档:InterestIncomeSum 从0递增到{runningIncomeSum:F6} = 5×{DailyInterest:F6} ✅"); } + // ================================================================ + // 场景4:互换→收盘→再攒→再互换 守恒验证 + // ================================================================ + + #region 场景4:多次互换结算守恒 + + /// + /// [DI_SWAP_MULTI_001] 攒10天→互换结清→再攒5天→再互换结清 + /// --------------------------------------------------------------- + /// 验证:第一次互换后 InterestIncomeSum≈当天新计(攒的10天付了), + /// 再攒5天后 InterestIncomeSum≈6天(5天新攒+1天当天新计), + /// 第二次互换后 InterestIncomeSum≈当天新计(攒的6天又付了)。 + /// + /// 守恒约束:两次互换结算的 TdCloseInterest 之和 = 全程利息(15天+2天新计)。 + /// + [TestMethod] + public void DI_SWAP_MULTI_001_多次互换结算守恒() + { + var td = CreateTrade(); + var position = CreateInterestPosition(); + + // --- Phase 1: 攒10天(4/27~5/6),到5/6 --- + var date10 = StartDate.AddDays(10); // 5/7 + decimal sum10days = Math.Round(Principal * FixedRate * 10 / AnnualDays, + ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); + + var preEod10 = CreatePreEod(date10.AddDays(-1), sum10days - DailyInterest); // 前日=9天 + // 当天新计让它到10天 + var svc1 = new StubEodPositionService(); + svc1.ExecuteDealInterests(new List { position }, + new List { preEod10 }, date10, td, + new List(), Principal, 0m, 0m, 1m, Principal); + var eod10days = svc1.PersistedPositions[0]; + AssertDecimal(sum10days, eod10days.InterestIncomeSum, "10天后待实现应=10天利息"); + Console.WriteLine($"Phase1: 攒10天 InterestIncomeSum={eod10days.InterestIncomeSum:F6}"); + + // --- Phase 2: 5/7 互换结清 --- + var swapDate1 = date10; // 同天互换 + var svc2 = new StubEodPositionService(); + var swapEvt1 = CreateSwapFlowEvent(swapDate1, sum10days); + var swapResult1 = svc2.ExecuteSaveEodInterestPosition( + eod10days, null, position, td, swapDate1, new List { swapEvt1 }); + + // 互换后待实现≈当天新计(攒的10天付了,但当天又产生1天新计) + decimal dailyInc = Math.Round(Principal * FixedRate / AnnualDays, + ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); + AssertDecimal(dailyInc, swapResult1.InterestIncomeSum, "第一次互换后待实现≈当天新计"); + decimal firstRealized = swapResult1.TdCloseInterest; + Console.WriteLine($"Phase2: 第一次互换 TdCloseInterest={firstRealized:F6}, 待实现={swapResult1.InterestIncomeSum:F6}"); + + // --- Phase 3: 再攒5天 --- + decimal runningSum = swapResult1.InterestIncomeSum; + var runningDate = swapDate1.AddDays(1); + for (int i = 0; i < 5; i++) + { + var svc = new StubEodPositionService(); + var preEod = CreatePreEod(runningDate.AddDays(-1), runningSum); + // 需要 preEod.RealizedInterest 累积 + preEod.RealizedInterest = swapResult1.RealizedInterest; + svc.ExecuteDealInterests(new List { position }, + new List { preEod }, runningDate, td, + new List(), Principal, 0m, 0m, 1m, Principal); + runningSum = svc.PersistedPositions[0].InterestIncomeSum; + runningDate = runningDate.AddDays(1); + } + Console.WriteLine($"Phase3: 再攒5天后 InterestIncomeSum={runningSum:F6}"); + + // --- Phase 4: 再互换结清 --- + var svc4 = new StubEodPositionService(); + var preEodFinal = CreatePreEod(runningDate.AddDays(-1), runningSum); + preEodFinal.RealizedInterest = swapResult1.RealizedInterest; + var swapEvt2 = CreateSwapFlowEvent(runningDate, runningSum); + var swapResult2 = svc4.ExecuteSaveEodInterestPosition( + preEodFinal, null, position, td, runningDate, new List { swapEvt2 }); + decimal secondRealized = swapResult2.TdCloseInterest; + Console.WriteLine($"Phase4: 第二次互换 TdCloseInterest={secondRealized:F6}, 待实现={swapResult2.InterestIncomeSum:F6}"); + + // 守恒:两次互换实现的 + 最终待实现 = 全程天数 × dailyInc + // 全程天数 = 10天(Phase1) + 1天(第一次互换当天新计) + 5天(Phase3) + 1天(第二次互换当天新计) = 17天 + // 但第一次互换的当天新计进了 InterestIncomeSum 没进 TdCloseInterest, + // 第二次互换同理。所以守恒 = RealizedInterest合计 + 最终InterestIncomeSum = 全程利息 + decimal totalDays = 10 + 1 + 5 + 1; // 17天 + decimal expectedTotalInterest = Math.Round(Principal * FixedRate * totalDays / AnnualDays, + ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); + decimal actualTotal = swapResult2.RealizedInterest + swapResult2.InterestIncomeSum; + Console.WriteLine($"守恒: RealizedInterest({swapResult2.RealizedInterest:F6}) + InterestIncomeSum({swapResult2.InterestIncomeSum:F6}) = {actualTotal:F6}"); + Console.WriteLine($"期望: {totalDays}天 × {dailyInc:F6} = {expectedTotalInterest:F6}"); + AssertDecimal(expectedTotalInterest, actualTotal, + "已实现+待实现 应=全程利息(守恒)"); + } + + #endregion + + // ================================================================ + // 场景5:预付金腿(marginTypes ratio 翻转)符号验证 + // ================================================================ + + #region 场景5:预付金腿 ratio 翻转 + + /// + /// [DI_MARGIN_001] 预付金腿互换结清后 RealizedInterest 应为负(支付方向) + /// --------------------------------------------------------------- + /// 预付金腿 InterestDirection=收取(1),但 marginTypes 会把 ratio 翻转为 -1。 + /// SwapPositionValue 应为负(负债),RealizedInterest 也应为负(券商支付)。 + /// 验证 cs:789-793 的 ratio 翻转逻辑。 + /// --------------------------------------------------------------- + /// + [TestMethod] + public void DI_MARGIN_001_预付金腿RealizedInterest为负() + { + var service = new StubEodPositionService(); + var td = CreateTrade(); + var settleDate = new DateTime(2026, 5, 10); + + // 预付金腿(初始预付金 InterestMode=5,InterestDirection=收取) + var marginPosition = new swap_position + { + id = 2001, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown, + InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = (int)InterestModeEnum.初始预付金, + InterestRateDefault = 0.005m, InterestPrincipalFix = 500m, + PosiStartDate = StartDate, PosiMatuirityDate = ExerciseDate, + IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.单利, + IsAnnualized = true, interest_rest_days = 1, interest_rule = 0, + FloatRateUnderlyingCode = null, + InterestSwapInterval = null + }; + + // 攒10天的预付金利息 + decimal marginDaily = Math.Round(500m * 0.005m / AnnualDays, + ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); + decimal margin10days = marginDaily * 10; + var preEod = new eod_swap_position + { + id = 200, SwapTradeId = 1, PositionId = 2001, ValueDate = settleDate.AddDays(-1), + ClientId = 999998, InterestDirection = (int)SwapDirectionEnum.收取, + InterestMode = (int)InterestModeEnum.初始预付金, + InterestIncomeSum = margin10days, InterestProfitSum = margin10days, + RealizedInterest = 0m, InterestRateDefault = 0.005m, + TdInterestPrincipal = 500m, PosiNotionalValue = 500m, + InterestType = (int)InterestTypeEnum.单利, IsAnnualized = true, + interest_rest_days = 1, FloatRate = 0m + }; + + // 互换结清 + var swapEvent = new swap_flow_event + { + id = 3001, SwapTradeId = 1, EventType = (int)SwapFlowEventTypeEnum.互换, + EventDate = settleDate, UnwindDate = settleDate, PositionId = 2001, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestAmount = margin10days, InterestClosePnL = -margin10days, // 预付金 ratio 翻转后为负 + InterestRate = 0.005m, InterestMode = (int)InterestModeEnum.初始预付金, + InterestPrincipal = 500m, FloatRate = 0m, + DataState = (int)SwapFlowDateStateEnum.完成 + }; + + var result = service.ExecuteSaveEodInterestPosition( + preEod, null, marginPosition, td, settleDate, new List { swapEvent }); + + // 预付金 marginTypes 翻转 ratio=-1 + // RealizedInterest = 0 + TdCloseInterest(margin10days) * ratio(-1) = -margin10days + AssertDecimal(-margin10days, result.RealizedInterest, + "预付金腿 RealizedInterest 应为负(ratio翻转后支付方向)"); + Console.WriteLine($"预付金腿 RealizedInterest={result.RealizedInterest:F6}(负=支付)✅"); + Console.WriteLine($"SwapPositionValue={result.SwapPositionValue:F6}(应≈当天新计×ratio=-正)"); + } + + #endregion + #endregion } }