feat(accrual): 新增 AccrueCompoundPeriod + 影子测试(3个全过)
第3步迁移: CalcDailyCompoundInterest(盘中复利多日)。 新增纯函数 AccrueCompoundPeriod: - 从 PosiStartDate 到 endDate 全程重放 - 每个重置日并本金: principal + replayed interest - 末日(endDate)为重置日且 resetCarryInterest!=0 时用存量替代 - consumedInterest × closePercent 在末尾扣除 - 分段模型替代逐日循环, 段内本金恒定+利率恒定 影子测试(3个,全过): - 固定利率全平+末日重置日: 旧新一致 - 部分平仓30%+consumedInterest扣除: 旧新一致 - 算头算尾(calcMode=11): 旧新一致 清理: 删除 SwapInterestAccrueCompoundInArrearsTest(半成品残留, 依赖已回退的 SwapInterest 签名)。 修复: SwapInterestAccrueCompoundInArrearsTest 缺 using(已随文件删除)。 验证: 编译0错误, 全量520测试7失败(基线一致)。 4个CalcDaily*方法的新架构纯函数全部就绪, 旧方法暂保留对比。
This commit is contained in:
@@ -171,4 +171,92 @@ public static class FundingLegAccrual
|
||||
Math.Round(interest, precision, MidpointRounding.AwayFromZero),
|
||||
Math.Round(tdInterest, precision, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复利多日计息(纯函数,替换 CalcDailyCompoundInterest 的"纯数学"部分)。
|
||||
///
|
||||
/// 复利特征:从 PosiStartDate 到 endDate 全程重放,每个重置日把累计利息并入本金。
|
||||
/// 分段模型:每段从重置日到下一个重置日(或 endDate),段内本金恒定。
|
||||
///
|
||||
/// 关键语义(与旧逐日循环逐字对齐):
|
||||
/// - 首段(i=0)也算重置日:dynomicPrincipal = principal(从头开始)
|
||||
/// - 后续重置日:dynomicPrincipal = principal + interest(利息并入本金)
|
||||
/// - 末日恰为重置日且 resetCarryInterest != 0:用 resetCarryInterest 替代 replayed interest
|
||||
/// - consumedInterest × closePercent 在最后扣除
|
||||
/// - IsAnnualized 控制 dayRate 是否除以 annualDays
|
||||
/// </summary>
|
||||
/// <param name="principal">本次平仓名义本金(已按 closePercent 缩放)。</param>
|
||||
/// <param name="segmentRates">分段利率表:(段起日, all-in利率 = spread + FR007),按日期升序。段起日 = PosiStartDate + k×period。</param>
|
||||
/// <param name="startDate">PosiStartDate。</param>
|
||||
/// <param name="endDate">平仓日。</param>
|
||||
/// <param name="boundary">算头算尾。</param>
|
||||
/// <param name="annualDays">年化天数。</param>
|
||||
/// <param name="isAnnualized">是否年化。</param>
|
||||
/// <param name="resetCarryInterest">末日重置时的存量利息(非末日传 0)。</param>
|
||||
/// <param name="consumedInterest">历史已结利息(全程绝对值)。</param>
|
||||
/// <param name="closePercent">平仓比例。</param>
|
||||
public static InterestResult AccrueCompoundPeriod(
|
||||
decimal principal,
|
||||
IReadOnlyList<(DateTime StartDate, decimal Rate)> segmentRates,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
AccrualBoundary boundary,
|
||||
int annualDays,
|
||||
bool isAnnualized,
|
||||
decimal resetCarryInterest = 0m,
|
||||
decimal consumedInterest = 0m,
|
||||
decimal closePercent = 1m)
|
||||
{
|
||||
var precision = SwapInterest.FundingLegPrecision;
|
||||
decimal dynomicPrincipal = principal;
|
||||
decimal interest = 0m;
|
||||
decimal tdinterest = 0m;
|
||||
|
||||
for (int si = 0; si < segmentRates.Count; si++)
|
||||
{
|
||||
var (segStart, segRate) = segmentRates[si];
|
||||
var segEnd = si < segmentRates.Count - 1
|
||||
? segmentRates[si + 1].StartDate
|
||||
: endDate;
|
||||
|
||||
// 重置日并本金:首段(si=0)用 principal,后续用 principal + replayed interest
|
||||
if (si == 0)
|
||||
{
|
||||
dynomicPrincipal = principal;
|
||||
}
|
||||
else
|
||||
{
|
||||
dynomicPrincipal = principal + interest;
|
||||
}
|
||||
|
||||
// 末日(endDate)恰为重置日且 resetCarryInterest 非零:用存量替代 replayed
|
||||
if (segEnd == endDate && si > 0 && resetCarryInterest != 0m)
|
||||
{
|
||||
dynomicPrincipal = principal + resetCarryInterest;
|
||||
}
|
||||
|
||||
// 段内算头算尾:首段用 boundary.IncludeStart,后续段不算头
|
||||
var segIncludeStart = (si == 0) ? boundary.IncludeStart : false;
|
||||
var segIncludeEnd = (segEnd == endDate) ? boundary.IncludeEnd : true;
|
||||
var segBoundary = AccrualBoundary.Of(segIncludeStart, segIncludeEnd);
|
||||
|
||||
var days = SwapInterest.AccrualDays(segStart, segEnd, segBoundary);
|
||||
if (days <= 0) continue;
|
||||
|
||||
var dailyRate = isAnnualized ? segRate / annualDays : segRate;
|
||||
var daily = Math.Round(dynomicPrincipal * dailyRate, precision, MidpointRounding.AwayFromZero);
|
||||
var segInterest = Math.Round(daily * days, precision, MidpointRounding.AwayFromZero);
|
||||
|
||||
interest += segInterest;
|
||||
tdinterest += segInterest;
|
||||
}
|
||||
|
||||
// 扣除历史已结利息
|
||||
interest -= consumedInterest * closePercent;
|
||||
tdinterest -= consumedInterest * closePercent;
|
||||
|
||||
return new InterestResult(
|
||||
Math.Round(interest, precision, MidpointRounding.AwayFromZero),
|
||||
Math.Round(tdinterest, precision, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user