From 4d68133dcb8403f1732b303e8f10a1512e48a233 Mon Sep 17 00:00:00 2001 From: hjhan Date: Wed, 19 Aug 2026 10:30:40 +0800 Subject: [PATCH] =?UTF-8?q?test(swap):=20EQD-6968=20=E8=A1=A5=E4=B8=8D?= =?UTF-8?q?=E7=AE=97=E5=A4=B4=E4=B8=8D=E7=AE=97=E5=B0=BE(00)=E7=BB=84?= =?UTF-8?q?=E5=90=88=E2=80=94=E2=80=94CalcFirst=3Dfalse=20=E9=9B=B6?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E7=9B=B2=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Red_Compound_NoHeadNoTail:00+缺平仓日价 → 放行(calcLast 同 false,与 10 同口径) - CalcFirst_Simple_NoHeadDropsExactlyStartDayInterest:单利下 00 与 10 利息差 恰好=开始日一天利息(精确断言,钉 CalcFirst 回归)。closeRate 取 7/6 定盘同值, 免疫既有取价细节——排查中发现:无日终快照+00 时 fetchAfter=interestStart-1 会 跳过开始日取价、首段利率用种子(GetFloatRate 回写的尾日定盘),与 10 的 fetchAfter=开始日-1 不同(FIX 日志可复现,niche 场景:首个日终前平仓)。 Run 运行器扩参:settment / exerciseDate(供后续 EOD 与到期日用例)。 验证:GLMS20260817Fr007UnwindMorningTest 25/25 内存全绿 --- .../GLMS20260817Fr007UnwindMorningTest.cs | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs b/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs index ec4d2d65..a4c65c4b 100644 --- a/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs +++ b/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs @@ -97,7 +97,7 @@ namespace YLErp.Modules.SwapModule private static OptUserInfo MakeOptUser() => new(0, nameof(GLMS20260817Fr007UnwindMorningTest), OptUserFrom.UnitTest); - private static trade BuildTrade(DateTime closeDate, string calcMode = "10") + private static trade BuildTrade(DateTime closeDate, string calcMode = "10", DateTime? exerciseDate = null) { var extend = new trade_extend { @@ -117,7 +117,7 @@ namespace YLErp.Modules.SwapModule TradeType = "债券TRS", TradeDate = TradeDate, StartDate = StartDate, - ExerciseDate = closeDate.AddDays(1), + ExerciseDate = exerciseDate ?? closeDate.AddDays(1), TradeStatus = "已平仓", ValidState = "Valid", trade_extend = extend @@ -183,7 +183,9 @@ namespace YLErp.Modules.SwapModule int interestRule = 0, bool newCalcLast = false, Dictionary market = null, - DateTime? omitDate2 = null) + DateTime? omitDate2 = null, + bool settment = false, + DateTime? exerciseDate = null) { var cd = closeDate ?? CloseDate; var omit = new HashSet(); @@ -195,7 +197,7 @@ namespace YLErp.Modules.SwapModule else if (!includeCloseDate) omit.Add(cd.Date); var svc = new StubSwapDealService(MakeOptUser(), omit, closeRate, market); - var td = BuildTrade(cd, calcMode); + var td = BuildTrade(cd, calcMode, exerciseDate); var position = BuildPosition(interestType, cd, restDays, interestRule); var eodList = preEod == null ? new List() @@ -207,7 +209,7 @@ namespace YLErp.Modules.SwapModule new List { position }, Notional, Notional, closePrecent, (int)SwapEventTypeEnum.平仓, false, Notional, - false, settment: false, newCalcLast: newCalcLast, closeList: null); + false, settment: settment, newCalcLast: newCalcLast, closeList: null); Assert.AreEqual(1, interests.Count, "应返回恰好 1 条利息事件"); return new Outcome { Fe = interests[0] }; } @@ -421,5 +423,35 @@ namespace YLErp.Modules.SwapModule preEod: BuildPreEod(new DateTime(2026, 7, 13))), "单利 算尾(11)+当日非重置日+当日缺价 → 应放行"); } + + // ── 不算头不算尾(calcMode="00"):CalcFirst=false 组合 ── + // 对尾日 FR007 行为与"10"一致(calcLast 同 false);另以单利精确断言钉 CalcFirst 语义—— + // "00" 比"10"恰好少计开始日一天的利息(单利无基数效应,差值可精确到分毫)。 + + [TestMethod] + public void Red_Compound_NoHeadNoTail_WithoutCloseDateFr007_Succeeds() + { + AssertNoThrow(Run(InterestTypeEnum.复利, includeCloseDate: false, calcMode: "00", + preEod: BuildPreEod(new DateTime(2026, 7, 13))), + "不算头不算尾(00)+缺平仓日价 → 应放行(与10同口径,尾日不参与计息)"); + } + + [TestMethod] + public void CalcFirst_Simple_NoHeadDropsExactlyStartDayInterest() + { + // closeRate=0.0142 与 7/6 定盘相同:使"00"首段种子利率与"10"首段取价利率一致, + // 断言只对"少计开始日一天"敏感,免疫无日终快照时 fetchAfter=interestStart-1 + // 跳过 7/6 取价、首段用种子利率的既有取价细节。 + var w10 = Run(InterestTypeEnum.单利, includeCloseDate: true, calcMode: "10", closeRate: 0.0142); + var w00 = Run(InterestTypeEnum.单利, includeCloseDate: true, calcMode: "00", closeRate: 0.0142); + Assert.IsFalse(w10.Threw, "10 不应抛:" + w10.Ex?.Message); + Assert.IsFalse(w00.Threw, "00 不应抛:" + w00.Ex?.Message); + // 开始日 7/6 属首段:all-in = spread(-0.0155) + 定盘(0.0142) = -0.0013; + // 单利下 00 与 10 的利息差 = 恰好首日一天利息(年化 A365)。 + var expectedStartDayInterest = Notional * (Spread + 0.0142m) / AnnualDays; + Assert.AreEqual(expectedStartDayInterest, w10.Fe.InterestAmount - w00.Fe.InterestAmount, 0.0000001m, + "不算头(00)应恰好少计开始日一天利息(CalcFirst 回归锚)"); + } } } +