From 7b70030216c69a1d359c658e6ff9873426b306aa Mon Sep 17 00:00:00 2001 From: hjhan Date: Wed, 19 Aug 2026 11:31:19 +0800 Subject: [PATCH] =?UTF-8?q?test(swap):=20=E8=A1=A5=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=E5=88=A9=E7=8E=87=E6=90=BA=E5=B8=A6=E5=A5=91=E7=BA=A6=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=8D=95=E5=88=A9+=E4=B8=8D=E7=AE=97=E5=A4=B4+?= =?UTF-8?q?=E6=9C=89preEod=20=E7=9B=B2=E5=8C=BA=E5=AE=9A=E8=B0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 该组合此前零覆盖。定谳结论:带 preEod 时 fetchAfter=preEod.ValueDate + seed=preEod.FloatRate 是设计分工(≤上一日终的重置日沿用快照携带利率,>的重新 取价),与无日终场景的根本区别是种子有正确来源——开始日EOD因窗口为空走正常 取价,快照 FloatRate=开始日定盘(链条起点),并非旧 bug 复发。 - CarryForward_Simple_NoHead_PreEodAtStartCarriesFirstPeriodRate: preEod{ValueDate=开始日, FloatRate=开始日定盘} → PricedDates=0(不重复取价)、 事件利率=快照利率、金额=上日待实现+3天×(spread+快照利率) 精确断言 - Eod_NoHead_StartDay_SnapshotRateCarriesFirstFixing:开始日EOD("00",窗口 为空)→快照利率=首重置日定盘,钉死携带链条起点 Run 扩展:Outcome 暴露 Svc(取价审计) + BuildPreEod 支持指定利率。 验证:GLMS20260817Fr007UnwindMorningTest 33/33;全量 978 例 145 败与基线 diff=0 --- .../GLMS20260817Fr007UnwindMorningTest.cs | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs b/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs index cd559fdb..ead98451 100644 --- a/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs +++ b/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs @@ -91,6 +91,7 @@ namespace YLErp.Modules.SwapModule { public swap_flow_event Fe; public Exception Ex; + public StubSwapDealService Svc; public bool Threw => Ex != null; } @@ -156,14 +157,14 @@ namespace YLErp.Modules.SwapModule }; } - private static eod_swap_position BuildPreEod(DateTime valueDate) + private static eod_swap_position BuildPreEod(DateTime valueDate, decimal floatRate = 0.01425m) { return new eod_swap_position { id = 5001, PositionId = 1001, ValueDate = valueDate, - FloatRate = 0.01425m, + FloatRate = floatRate, InterestProfitSum = -100000m, TdInterestPrincipal = Notional, InterestIncomeSum = -150000m @@ -211,7 +212,7 @@ namespace YLErp.Modules.SwapModule (int)SwapEventTypeEnum.平仓, false, Notional, false, settment: settment, newCalcLast: newCalcLast, closeList: null); Assert.AreEqual(1, interests.Count, "应返回恰好 1 条利息事件"); - return new Outcome { Fe = interests[0] }; + return new Outcome { Fe = interests[0], Svc = svc }; } catch (Exception ex) { @@ -473,6 +474,40 @@ namespace YLErp.Modules.SwapModule "事件利率必须与平仓时刻(尾日价发布前后)无关"); } + // ── 快照利率携带契约(carry-forward):带 preEod 时不重复取 ≤ValueDate 的重置日 ── + // fetchAfterDate=preEod.ValueDate + seed=preEod.FloatRate 是设计分工:≤上一日终的重置日 + // 沿用快照携带的"截至 ValueDate 生效利率"(真实 EOD 快照由当日重置日再定盘写入), + // >上一日终的重新取价。与无日终场景的根本区别:种子有正确来源,不需要强制重取首重置日。 + + [TestMethod] + public void CarryForward_Simple_NoHead_PreEodAtStartCarriesFirstPeriodRate() + { + // preEod.ValueDate=开始日(7/6),FloatRate=7/6定盘0.0142(模拟开始日EOD快照的真实语义) + var o = Run(InterestTypeEnum.单利, includeCloseDate: true, calcMode: "00", + closeDate: new DateTime(2026, 7, 10), preEod: BuildPreEod(StartDate, 0.0142m)); + Assert.IsFalse(o.Threw, "不应抛:" + o.Ex?.Message); + Assert.AreEqual(0, o.Svc.PricedDates.Count, + "≤上一日终(7/6)的重置日不重复取价——首段利率由快照携带(carry-forward 契约)"); + Assert.AreEqual(0.0142m, o.Fe.FloatRate, + "首段(也是末段)利率=快照携带的 7/6 定盘"); + // 不算头不算尾:计息日 [7/7,7/10) 共 3 天 @ (spread+0.0142);单利重放含上日待实现(-100000) + Assert.AreEqual(-100000m + Notional * (Spread + 0.0142m) * 3m / AnnualDays, o.Fe.InterestAmount, 0.0000001m, + "金额=上日待实现+3天×(spread+快照利率),首段未误用种子外的任何值"); + } + + [TestMethod] + public void Eod_NoHead_StartDay_SnapshotRateCarriesFirstFixing() + { + // 链条起点钉死:开始日当天 EOD("00",窗口为空 interestStart=7/7>interestEnd=7/6)—— + // 窗口为空时"不算尾不取价"分支不命中,走正常取价,快照 FloatRate=开始日定盘。 + // 次日重放才能以 fetchAfter=开始日 + 携带利率=开始日定盘 正确续算(见上一用例)。 + var o = Run(InterestTypeEnum.单利, includeCloseDate: true, calcMode: "00", + closeDate: StartDate, settment: true); + AssertNoThrow(o, "开始日EOD(00) 不应抛"); + Assert.AreEqual(0.0142m, o.Fe.FloatRate, + "开始日EOD快照利率=当日(首重置日)定盘——窗口为空不触发不取价分支"); + } + // ── EOD 收盘归档路径(settment=true,此前全套件仅覆盖盘中 settment:false)── // EOD 不取尾日价的依赖链:InitInterestDate 到期日回拨(endDate=D-1) + CalcEodInterest 的 // calcToday=false(valueDate==到期日且不算尾) 整体跳过 ByEod 重算——ByEod 的取价