Files
zszq-trs/YLErpDAL/Modules/SwapModule/Accrual/SimpleInterestAccrual.cs
T
hjhan e2431e9d44 refactor(accrual): 计息类型整体迁入 DAL——新增 Accrual/InterestMath,删 Core 未接线孤儿
搬迁(算法体逐字未动,仅换命名空间与归属):
- SwapInterest.Round/AccrualDays/FundingLegPrecision + AccrualBoundary/InterestResult
  → YLErpDAL/Modules/SwapModule/Accrual/InterestMath.cs
- AccrualTrace → Accrual/AccrualTrace.cs(被迫同迁:其 MarkStart 引用 AccrualBoundary,
  Core 不能反向依赖 DAL)
- 引用切换:Simple/CompoundInterestAccrual、AccrualPolicy、SwapCalcTrace、SwapDealService
  (保留 using YLErp.Derivatives.Interest——IIndexFixer/IndexFixerBase 留 Core)

删除(零生产引用,孤儿清零):
- Core:SwapInterest.cs 算法方法(AccrueSimple/AccrueCompoundInArrears/ApplyUnwind/
  AccrueUnrealized/ToInterestRate,未接线且与 DAL 生产实现舍入/rollover 口径已分叉)、
  AccrualContext.cs、InterestRate.cs
- DAL:AccrualState.cs(零引用死类)
- 测试:SwapInterest_CompoundInArrears_RolloverTimingTests.cs(仅测已删原语)

验证:两解决方案 Rebuild 0 错误;磁盘 SwapInterest. 残留 0;影子/分红/场景 86/86 通过
(含 Accrual 3 影子对账、Margin 影子、divPower 新增 AutoUnwindMultiPartial)。
注:AccrualContext 默认精度 11 与生产 12 的分叉隐患随删除一并消除;
已删原语若将来重建须先补对账测试,勿凭记忆复原(ARCHITECTURE.md 已留警告)。
2026-08-14 15:31:04 +08:00

104 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.
namespace YLErp.Modules.SwapModule.Accrual;
/// <summary>
/// 单利计息纯函数——EOD 单日 + intraday 多日。
/// 单利特征:本金全程恒定(无并本金),按重置日分段取利率。
/// </summary>
public static class SimpleInterestAccrual
{
private const int Precision = InterestMath.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(
InterestMath.Round(totalAccrued, Precision),
InterestMath.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 = 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;
}
}