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:
@@ -11,7 +11,9 @@ namespace UnitTestProject.Modules.SwapModule.Penalty
|
||||
/// ② 重置日前一日平仓(② 几乎整段、窗口首段 0 天);
|
||||
/// ③ 到期日恰为重置日(末段 [到期,到期] 1 天);
|
||||
/// ④ 锚点偏离(td.StartDate=7/31 但腿 PosiStartDate=8/3 的延期/存续腿——重置网格整体不同);
|
||||
/// ⑤ 起息日当天平仓(无 preEod)。
|
||||
/// ⑤ 起息日当天平仓(无 preEod);
|
||||
/// ⑥ 部分平仓 share<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_恒等式成立()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user