搬迁(算法体逐字未动,仅换命名空间与归属): - 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 已留警告)。
104 lines
4.1 KiB
C#
104 lines
4.1 KiB
C#
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;
|
||
}
|
||
}
|