feat(swap): EQD-6977 复利纯函数支持 carryInInterest——窗口前已结利息随首个重置日资本化并持续留在基数(与全窗口重放轨迹严格一致);默认0零行为变化,含数学不变量测试

This commit is contained in:
hjhan
2026-08-20 11:43:27 +08:00
parent 952295a78a
commit d1985231ee
2 changed files with 121 additions and 2 deletions
@@ -61,6 +61,11 @@ public static class CompoundInterestAccrual
/// 复利多日计息(替换 CalcDailyCompoundInterest 的纯数学部分)。
/// 从 startDate 到 endDate 全程重放,每个重置日把累计利息并入本金。
/// </summary>
/// <param name="carryInInterest">
/// 窗口前已计未结转利息(EQD-6977 罚息承接):在【首个重置日】并入计息基数——
/// 与"持有至到期"全期轨迹严格对齐(恒等式:全期复利 = 平仓日已结利息 + 罚息窗口利息)。
/// 默认 0 时与旧行为逐位一致。与 resetCarryInterest 互斥使用(后者是全平重放的末段存量替代)。
/// </param>
public static InterestResult AccruePeriod(
decimal notional,
IReadOnlyList<(DateTime StartDate, decimal Rate)> segmentRates,
@@ -73,10 +78,14 @@ public static class CompoundInterestAccrual
decimal realizedInterest,
decimal unwindFraction,
out decimal finalBasis,
AccrualTrace? trace = null)
AccrualTrace? trace = null,
decimal carryInInterest = 0m)
{
decimal accrualBasis = notional;
decimal accrued = 0m;
// carryInInterest 是"窗口前已计未结转利息":先并入 accrued,随首个重置日的
// basis = notional + accrued 一并资本化,并在其后每个重置日持续留在基数里
//(与全窗口重放时 accrued 含全部历史利息的轨迹严格一致);最终报告时扣除。
decimal accrued = carryInInterest;
trace?.MarkStart(startDate, endDate, boundary, annualDays, isAnnualized);
@@ -116,6 +125,9 @@ public static class CompoundInterestAccrual
finalBasis = accrualBasis;
// 报告口径只含窗口内增量(carryIn 是窗口前已结利息,由正常平仓流单独结算)
accrued -= carryInInterest;
if (realizedInterest != 0m)
trace?.Unwind(endDate, unwindFraction, realizedInterest * unwindFraction, accrued - realizedInterest * unwindFraction);
accrued -= realizedInterest * unwindFraction;