namespace YLErp.Modules.SwapModule.Accrual; /// /// 单利计息纯函数——EOD 单日 + intraday 多日。 /// 单利特征:本金全程恒定(无并本金),按重置日分段取利率。 /// public static class SimpleInterestAccrual { private const int Precision = InterestMath.FundingLegPrecision; /// /// 单利日终计息(替换 CalcDailySimpleInterestByEod 的纯数学部分)。 /// EOD 无差分:basis = priorNotional(昨日终滚动计息基数)。 /// 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( InterestMath.Round(totalAccrued, Precision), InterestMath.Round(tdInterest, Precision)); trace?.Day(0, eodDate, allInRate, displayBasis, dayInterest, totalAccrued); trace?.MarkEnd(result.Accrued, result.AccruedToday); return result; } /// /// 单利多日计息(替换 CalcDailySimpleInterest 的纯数学部分)。 /// 本金全程恒定,按重置日分段取利率。 /// Accrued = 缩放累计(InterestAmount),AccruedToday = 未缩放累计(TdInterestAmount)。 /// 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 = InterestMath.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( InterestMath.Round(accrued, Precision), InterestMath.Round(accruedUnscaled, Precision)); trace?.MarkEnd(result.Accrued, result.AccruedToday); return result; } }