test(swap): EQD-6977 罚息兜底补share<1恒等式用例——50%平仓+无preEod场景钉merger复刻GetInterests本金口径的接缝(标的期初全价CalcNotional=posiNotional×closePercent vs 重放基数按closePosiNotionalValue缩放);现有用例全closePercent=1m,两处口径若有偏差在share=1下不可见、share<1时①混入本金差;变异验证:CalcNotional临时忽略closePercent唯此用例红(1败7过)证明鉴别力;RunFee加closePercent参数默认1m存量调用零影响

This commit is contained in:
hjhan
2026-08-21 07:09:57 +08:00
parent 3728d45cc4
commit 8b66875098
@@ -11,7 +11,9 @@ namespace UnitTestProject.Modules.SwapModule.Penalty
/// ② 重置日前一日平仓(② 几乎整段、窗口首段 0 天);
/// ③ 到期日恰为重置日(末段 [到期,到期] 1 天);
/// ④ 锚点偏离(td.StartDate=7/31 但腿 PosiStartDate=8/3 的延期/存续腿——重置网格整体不同);
/// ⑤ 起息日当天平仓(无 preEod)
/// ⑤ 起息日当天平仓(无 preEod)
/// ⑥ 部分平仓 share&lt;1 + 无 preEod 兜底——钉 merger 复刻 GetInterests 本金口径的接缝
/// (现有用例全部 closePercent=1m,重放基数与复刻本金的口径偏差在 share=1 下不可见)。
///
/// 一致性前提(与现实世界对齐):冻结利率 = 当前重置区间(含 unwind-1 的区间)的在役利率,
/// 即"历史末段利率 = 冻结利率";历史各段定盘不同(体现真实 FR007 利率历史)。
@@ -67,7 +69,7 @@ namespace UnitTestProject.Modules.SwapModule.Penalty
private static decimal RunFee(trade td, swap_position p, decimal settledAmount,
eod_swap_position? preEod, DateTime unwind, bool settled, decimal spread,
decimal interestPrincipal = 0m, bool maturityCalcLast = true)
decimal interestPrincipal = 0m, bool maturityCalcLast = true, decimal closePercent = 1m)
{
var e = new swap_flow_event
{
@@ -78,7 +80,8 @@ namespace UnitTestProject.Modules.SwapModule.Penalty
PenaltyInterestFeeMerger.Merge(
td, new List<swap_position> { p }, new List<swap_flow_event> { e },
unwind, AnnualDays, settled, maturityCalcLast: maturityCalcLast,
posiNotionalValue: Notional, closePosiNotionalValue: Notional, closePercent: 1m,
posiNotionalValue: Notional, closePosiNotionalValue: Notional * closePercent,
closePercent: closePercent,
getSpread: _ => spread, getPreEod: _ => preEod, tryGetFixing: (d, c) => spread);
return e.InterestFee;
}
@@ -192,6 +195,30 @@ namespace UnitTestProject.Modules.SwapModule.Penalty
"无preEod+已有重置:兜底取事件基数后 ① 精确,全期=实结+罚息(修复前差≈3.17元)");
}
[TestMethod]
public void preEod兜底_share对齐本金口径_恒等式成立()
{
// 接缝守卫:merger 的 closePrincipal 走 CalcNotional 复刻 GetInterests 口径
// (标的期初全价 = posiNotional×closePercent),而重放基数由调用方以
// closePosiNotionalValue 缩放——两处口径若有偏差,share=1 时不可见、
// share<1 时 ① 里会混入本金差。本用例以 50% 平仓钉死该对齐。
var start = new DateTime(2026, 8, 5); var unwind = new DateTime(2026, 8, 20); var maturity = new DateTime(2026, 9, 30);
var hist = new decimal[] { 0.0216m, 0.0144m }; // 8/5 段 2.16% / 8/19 段 1.44%(=冻结)14 天重置
var share = 0.5m;
var closedNotional = Notional * share;
// 被平份额的实结与重放基数:复利对 notional 线性,直接按半额本金重放
var elapsed = AccrueOnGrid(start, unwind, AccrualBoundary.StartOnly, hist, notional: closedNotional, period: 14);
var replayFinalBasis = closedNotional + AccrueOnGrid(start, new DateTime(2026, 8, 18), AccrualBoundary.Both, hist, notional: closedNotional, period: 14);
var fee = RunFee(CreateTrade(start, maturity), CompoundLeg(start, maturity, hist[^1], periodDays: 14), elapsed,
preEod: null, unwind: unwind, settled: false, spread: hist[^1],
interestPrincipal: replayFinalBasis, maturityCalcLast: false, closePercent: share);
var full = AccrueOnGrid(start, maturity, AccrualBoundary.StartOnly, hist, notional: closedNotional, period: 14);
Assert.AreEqual((double)full, (double)(elapsed + fee), 0.01,
"部分平仓+无preEod:兜底①按被平份额缩放精确,全期(被平份额)=实结+罚息(口径漂移时此式必挂)");
}
[TestMethod]
public void _无preEod_恒等式成立()
{