refactor(swap): InitInterestDate 删除死子句 td.StartDate>interestStart + 触发场景注释归因修正

分析定谳:interestStart 经三条赋值路径(=开始日/不算头+1天/preSettleDate 且仅当
≥interestStart 才覆盖)恒 ≥ td.StartDate,第二 OR 子句恒 false——死代码删除。
注释归因同时修正两侧旧错误:
- 原注释把"不算头首日"笼统挂在返回 true 上——实际由第一子句
  interestStart>interestEnd 兜住(StartDate+1>StartDate);
- 候选修正案"观察日当日已结息(preSettleDate 覆盖窗口末)"亦不精确——日期相等
  时函数返回 false,当日已结息的利息归零在 GetInterests closeList 净额层;
  只有相等叠加到期日不算尾回拨(endDate-1)才严格大于而触发。
新增三条 InitInterestDate 直测钉边界:不算头首日空窗/同日已结息日期相等非空/
同日已结息+到期日回拨空窗。

验证:GLMS20260817Fr007UnwindMorningTest 36/36;全量 981 例 145 败与基线 diff=0
This commit is contained in:
hjhan
2026-08-19 11:36:53 +08:00
parent 7b70030216
commit f8049f81f8
3 changed files with 45 additions and 4 deletions
@@ -508,6 +508,41 @@ namespace YLErp.Modules.SwapModule
"开始日EOD快照利率=当日(首重置日)定盘——窗口为空不触发不取价分支");
}
// ── 窗口判定语义(InitInterestDate 直测;死子句 td.StartDate>interestStart 删除后的边界钉死)──
[TestMethod]
public void WindowSemantics_NoHeadStartDay_EmptyViaFirstClause()
{
var svc = new StubSwapDealService(MakeOptUser(), new HashSet<DateTime>());
var td = BuildTrade(StartDate, "00"); // ExerciseDate=7/7
bool empty = svc.InitInterestDate(StartDate, null, td, tdClose: false,
out var start, out var end);
Assert.IsTrue(empty, "不算头首日:interestStart=7/7 > interestEnd=7/6 → 窗口为空(第一子句兜住)");
Assert.AreEqual(StartDate, end);
}
[TestMethod]
public void WindowSemantics_SameDaySettle_EqualDates_NotEmpty()
{
var svc = new StubSwapDealService(MakeOptUser(), new HashSet<DateTime>());
var td = BuildTrade(new DateTime(2026, 7, 10), "10"); // ExerciseDate=7/11
bool empty = svc.InitInterestDate(new DateTime(2026, 7, 10), new DateTime(2026, 7, 10), td, tdClose: false,
out var start, out var end);
Assert.IsFalse(empty, "当日已结息(日期相等)窗口非空——利息归零由 GetInterests closeList 净额层处理,不在此判定");
Assert.AreEqual(new DateTime(2026, 7, 10), start);
Assert.AreEqual(new DateTime(2026, 7, 10), end);
}
[TestMethod]
public void WindowSemantics_SameDaySettle_OnMaturityRollback_Empty()
{
var svc = new StubSwapDealService(MakeOptUser(), new HashSet<DateTime>());
var td = BuildTrade(CloseDate, "00", exerciseDate: CloseDate); // 到期日=7/20 且不算尾
bool empty = svc.InitInterestDate(CloseDate, CloseDate, td, tdClose: false,
out var start, out var end);
Assert.IsTrue(empty, "当日已结息+到期日不算尾回拨:interestStart=7/20 > interestEnd=7/19 → 窗口为空");
}
// ── EOD 收盘归档路径(settment=true,此前全套件仅覆盖盘中 settment:false)──
// EOD 不取尾日价的依赖链:InitInterestDate 到期日回拨(endDate=D-1) + CalcEodInterest 的
// calcToday=false(valueDate==到期日且不算尾) 整体跳过 ByEod 重算——ByEod 的取价
@@ -655,7 +655,8 @@ namespace YLErp.Modules.SwapModule
// 计算计息区间
int interestPeriod = position.interest_rest_days ?? 1;
// true=计息窗口为空(不计利息,利率与金额归零;典型场景=互换当日已结息,但判定只看日期窗口,与事件类型无关)
// true=计息窗口为空(不计利息,利率与金额归零;典型触发=不算头首日/不算尾到期日回拨翻转,
// 判定只看日期窗口与事件类型无关;当日已结息日期相等时窗口非空,归零由下方 closeList 净额层处理)
bool interestWindowEmpty = InitInterestDate(unwindDate, preDealDate, td, tdClose, out DateTime startDate, out DateTime endDate);
// 获取利率(保证金/融资腿共用:SwapIntervalList 取当日适用固定利率 + 精度收口)
@@ -365,8 +365,11 @@ namespace YLErp.Modules.SwapModule
/// <param name="interestMode">计息方式</param>
/// <param name="interestStart">计息开始日期</param>
/// <param name="interestEnd">计息结束日期</param>
/// <returns>true=计息窗口为空(interestStart&gt;interestEnd,本次不计利息,调用方将利率与金额归零;
/// 典型场景=互换当日已结息/不算头首日,但判定只看日期窗口,与事件类型无关);false=正常计息。</returns>
/// <returns>true=计息窗口为空(interestStart&gt;interestEnd,本次不计利息,调用方将利率与金额归零
/// 典型触发=①不算头首日(valueDate==StartDate → StartDate+1&gt;StartDate) ②不算尾到期日回拨后窗口翻转
/// (interestEnd=到期日−1 &lt; interestStart)。判定只看日期窗口,与事件类型无关。
/// 注:当日已结息(preSettleDate==valueDate)日期相等时本函数返回 false——利息归零由
/// GetInterests 的 closeList 净额层处理,不在本判定。</returns>
public bool InitInterestDate(DateTime valueDate, DateTime? preSettleDate, trade td, bool tdClose, out DateTime interestStart, out DateTime interestEnd)
{
interestStart = td.StartDate.Value;
@@ -383,7 +386,9 @@ namespace YLErp.Modules.SwapModule
{
interestEnd = interestEnd.AddDays(-1);
}
if (interestStart > interestEnd || td.StartDate > interestStart)
// 原第二 OR 子句 td.StartDate > interestStart 恒 falseinterestStart 经上面调整恒 ≥ td.StartDate
// =开始日 / 不算头+1天 / preSettleDate 且仅当 ≥interestStart 才覆盖),死代码已删(2026-08-19)。
if (interestStart > interestEnd)
{
interestStart = interestEnd;
return true;//不记利息