Files
zszq-trs/YLErpDAL/Modules/SwapModule/Accrual/SimpleInterestAccrual.cs
T
hjhan 9d8cfb5ad0 refactor(accrual): 拆分FundingLegAccrual→SimpleInterestAccrual/CompoundInterestAccrual
单利与复利语义完全不同(单利本金恒定/复利重置日并本金),
拆成两个独立静态类,各自只含自己的方法:

SimpleInterestAccrual:
- AccrueEod (原AccrueSimpleEod)
- AccruePeriod (原AccrueSimplePeriod)

CompoundInterestAccrual:
- EodBasis (原CompoundEodBasis)
- AccrueEod (原AccrueCompoundEod)
- AccruePeriod (原AccrueCompoundPeriod)

方法名去掉Simple/Compound前缀(类名已携带类型),消除冗余
SwapModule零回归(7基线/510通过)
2026-08-12 13:40:47 +08:00

107 lines
4.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using YLErp.Core.Interest;
using YLErp.Derivatives.Interest;
namespace YLErp.Modules.SwapModule.Accrual;
/// <summary>
/// 单利计息纯函数——EOD 单日 + intraday 多日。
/// 单利特征:本金全程恒定(无并本金),按重置日分段取利率。
/// </summary>
public static class SimpleInterestAccrual
{
private const int Precision = SwapInterest.FundingLegPrecision;
/// <summary>
/// 单利日终计息(替换 CalcDailySimpleInterestByEod 的纯数学部分)。
/// EOD 无差分:basis = priorNotional(昨日终滚动计息基数)。
/// </summary>
public static InterestResult AccrueEod(
decimal priorAccrued,
decimal priorNotional,
decimal unwindFraction,
FundingLegRate rate,
AccrualPolicy policy,
DateTime eodDate,
AccrualTrace? trace = null)
{
var basis = priorNotional;
var displayBasis = basis * unwindFraction;
var allInRate = rate.AllInRate;
var dayInterest = displayBasis * allInRate;
var tdInterest = basis * allInRate;
if (policy.IsAnnualized)
{
dayInterest /= policy.AnnualDays;
tdInterest /= policy.AnnualDays;
}
var totalAccrued = priorAccrued + dayInterest;
var result = new InterestResult(
SwapInterest.Round(totalAccrued, Precision),
SwapInterest.Round(tdInterest, Precision));
trace?.Day(0, eodDate, allInRate, displayBasis, dayInterest, totalAccrued);
trace?.MarkEnd(result.Accrued, result.AccruedToday);
return result;
}
/// <summary>
/// 单利多日计息(替换 CalcDailySimpleInterest 的纯数学部分)。
/// 本金全程恒定,按重置日分段取利率。
/// Accrued = 缩放累计(InterestAmount)AccruedToday = 未缩放累计(TdInterestAmount)。
/// </summary>
public static InterestResult AccruePeriod(
decimal priorAccrued,
decimal notional,
decimal unwindFraction,
IReadOnlyList<(DateTime StartDate, decimal Rate)> segmentRates,
DateTime startDate,
DateTime endDate,
DateTime priorValueDate,
AccrualBoundary boundary,
int annualDays,
bool isAnnualized,
AccrualTrace? trace = null)
{
var displayBasis = notional * unwindFraction;
decimal accrued = priorAccrued; // 缩放累计 → InterestAmount
decimal accruedUnscaled = priorAccrued; // 未缩放累计 → TdInterestAmount
trace?.MarkStart(startDate, endDate, boundary, annualDays, isAnnualized);
var segStart = startDate;
for (int si = 0; si < segmentRates.Count; si++)
{
var segEnd = si < segmentRates.Count - 1
? segmentRates[si + 1].StartDate
: endDate;
var effectiveStart = segStart > priorValueDate ? segStart : priorValueDate.AddDays(1);
if (effectiveStart > segEnd) { segStart = segEnd; continue; }
// calcFirst 只跳过 startDate 本身;其余天(含重置日、ValueDate+1)只要 > ValueDate 恒纳入。
var includeStart = effectiveStart == startDate ? boundary.IncludeStart : true;
var isLastSegment = si == segmentRates.Count - 1;
var segBoundary = AccrualBoundary.Of(includeStart, isLastSegment && boundary.IncludeEnd);
var days = SwapInterest.AccrualDays(effectiveStart, segEnd, segBoundary);
if (days <= 0) { segStart = segEnd; continue; }
var dailyRate = isAnnualized ? segmentRates[si].Rate / annualDays : segmentRates[si].Rate;
var segInterest = displayBasis * dailyRate * days;
accrued += segInterest;
accruedUnscaled += notional * dailyRate * days;
trace?.Segment(si, effectiveStart, segEnd, days, segmentRates[si].Rate, displayBasis, segInterest, accrued);
segStart = segEnd;
}
var result = new InterestResult(
SwapInterest.Round(accrued, Precision),
SwapInterest.Round(accruedUnscaled, Precision));
trace?.MarkEnd(result.Accrued, result.AccruedToday);
return result;
}
}