diff --git a/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs b/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs index e4dccf28..861aa11e 100644 --- a/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs @@ -401,6 +401,10 @@ namespace YLErp.Modules.SwapModule PositionType = 0, EventType = (int)SwapEventTypeEnum.平仓, EventDate = new DateTime(2026, 7, 9), + // UnwindDate 必须显式设置:ResolveInterestLegPositionsAsOf 自 a1cdb2cd 起按 UnwindDate + // (经济生效日)分桶,而非 EventDate(簿记日)。生产平仓事件总会设 UnwindDate + // (InitUnwind:327、AuotoSwapUnwind:1590)。此处同日场景:UnwindDate == EventDate。 + UnwindDate = new DateTime(2026, 7, 9), InterestMode = (int)InterestModeEnum.初始预付金, InterestPrincipal = 3000m }; @@ -410,6 +414,7 @@ namespace YLErp.Modules.SwapModule PositionType = 1, EventType = (int)SwapEventTypeEnum.平仓, EventDate = new DateTime(2026, 7, 9), + UnwindDate = new DateTime(2026, 7, 9), TradingAmount = 3000000m }; var originalWithFloat = new List @@ -430,5 +435,84 @@ namespace YLErp.Modules.SwapModule Assert.AreEqual(10000m, beforeClose.InterestPrincipalFix); Assert.AreEqual(7000m, onCloseDate.InterestPrincipalFix); } + + /// + /// [SPC_008] EventDate ≠ UnwindDate 时,ResolveInterestLegPositionsAsOf 按 UnwindDate(经济生效日)分桶。 + /// ---------------------------------------------------------------------------- + /// 锁定 a1cdb2cd 的修复价值:平仓事件的簿记日(EventDate)可能滞后于经济生效日(UnwindDate) + /// (如 T+N 结算、手动补录)。重放预付金腿 as-of 本金时,分桶必须按 UnwindDate: + /// - settleDate < UnwindDate → 平仓"未发生",as-of=原始本金 + /// - settleDate >= UnwindDate → 平仓"已生效",as-of=实时剩余本金 + /// 修复前按 EventDate 分桶:settleDate 落在 [UnwindDate, EventDate) 区间时,会被误判为"未发生"。 + /// 本测试构造 EventDate=7/11、UnwindDate=7/9,验证 settleDate=7/10 时已按 UnwindDate 生效。 + /// + [TestMethod] + public void SPC_008_EventDateDiffersFromUnwindDate_BucketsByUnwindDate() + { + const long originalPositionId = 2; + var original = new swap_position + { + id = originalPositionId, PosiDirection = 0, + InterestDirection = (int)SwapDirectionEnum.收取, + InterestMode = (int)InterestModeEnum.初始预付金, + InterestPrincipalFix = 10000m + }; + var realtime = new swap_position + { + PositionId = originalPositionId, + InterestMode = (int)InterestModeEnum.初始预付金, + InterestPrincipalFix = 7000m + }; + // 关键:EventDate(簿记 7/11) 滞后于 UnwindDate(经济生效 7/9) —— T+N 结算/补录常见 + var close = new swap_flow_event + { + PositionId = originalPositionId, + PositionType = 0, + EventType = (int)SwapEventTypeEnum.平仓, + EventDate = new DateTime(2026, 7, 11), + UnwindDate = new DateTime(2026, 7, 9), + InterestMode = (int)InterestModeEnum.初始预付金, + InterestPrincipal = 3000m + }; + var floatClose = new swap_flow_event + { + PositionId = 1, + PositionType = 1, + EventType = (int)SwapEventTypeEnum.平仓, + EventDate = new DateTime(2026, 7, 11), + UnwindDate = new DateTime(2026, 7, 9), + TradingAmount = 3000000m + }; + var originalWithFloat = new List + { + original, + new swap_position { id = 1, PosiDirection = 1, PosiNotionalValue = 10000000m } + }; + var flows = new[] { close, floatClose }; + + // settleDate=7/8(经济生效日前)→ as-of=原始 10000 + var beforeEffective = SwapDealService.ResolveInterestLegPositionsAsOf( + originalWithFloat, new List { realtime }, flows, new DateTime(2026, 7, 8)) + .Single(x => x.id == originalPositionId); + Assert.AreEqual(10000m, beforeEffective.InterestPrincipalFix, + "7/8(经济生效日前):平仓未发生,as-of 本金应=原始 10000"); + + // settleDate=7/9(经济生效日当天)→ as-of=实时剩余 7000 + var onEffectiveDate = SwapDealService.ResolveInterestLegPositionsAsOf( + originalWithFloat, new List { realtime }, flows, new DateTime(2026, 7, 9)) + .Single(x => x.id == originalPositionId); + Assert.AreEqual(7000m, onEffectiveDate.InterestPrincipalFix, + "7/9(经济生效日):平仓已生效,as-of 本金应=实时剩余 7000"); + + // 【关键·锁定 a1cdb2cd】settleDate=7/10(生效后、簿记前)→ 应按 UnwindDate 判为已生效 =7000 + // 修复前按 EventDate(7/11) 分桶:7/10 < 7/11 → 误判"未发生" → 返回 10000(错误) + // 修复后按 UnwindDate(7/9) 分桶:7/10 >= 7/9 → 已生效 → 返回 7000(正确) + var afterEffectiveBeforeBook = SwapDealService.ResolveInterestLegPositionsAsOf( + originalWithFloat, new List { realtime }, flows, new DateTime(2026, 7, 10)) + .Single(x => x.id == originalPositionId); + Assert.AreEqual(7000m, afterEffectiveBeforeBook.InterestPrincipalFix, + "7/10(生效后、簿记前):必须按 UnwindDate 判已生效 → 7000。" + + "若返回 10000,说明 a1cdb2cd 修复被回滚(退回按 EventDate 分桶)。"); + } } }