Files
zszq-trs/YLErpDAL/Modules/SwapModule/Accrual/AccrualTrace.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

86 lines
6.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>
/// 计息过程追踪收集器(值对象,非日志)。2026-08 自 Core 层(YLErp.Core.Interest)迁入 DAL
/// 与 Simple/CompoundInterestAccrual、AccrualBoundary 同处一域,Core 不再持有计息类型。
///
/// <para><b>为什么是收集器而不是日志调用</b>:计息数学(Simple/CompoundInterestAccrual)必须保持纯函数、
/// 可单测、不依赖 NLog;但按工程铁律,关键路径日志须<b>无条件常驻落盘</b>(出问题时事后翻日志定位,不能依赖开关)。
/// 折中:纯函数把"发生了什么"记录为结构化条目写入本收集器,由<b>适配器(IO 边界)</b>统一经
/// <c>SwapCalcTrace.Write</c> 常驻落盘。落盘职责归一处,计息代码零日志依赖、保持干净。</para>
///
/// <para><b>可 diff</b><see cref="ToString"/> 产出稳定、有序、与 SwapCalcTrace.Day 对齐的逐行文本,
/// 新旧引擎对同一笔交易跑出的 trace 可直接 diff,定位"是计算变了还是重构引入了漂移"。</para>
///
/// <para>所有记录方法均为语义化命名(Day / ResetBefore / Rollover …),调用点一眼即懂,不污染数学可读性。</para>
/// </summary>
public sealed class AccrualTrace
{
private readonly List<AccrualTraceEntry> _entries = new();
/// <summary>已记录的追踪条目(只读)。</summary>
public IReadOnlyList<AccrualTraceEntry> Entries => _entries;
/// <summary>计息区间起点:标记本次计算的整体边界与年化口径。</summary>
public void MarkStart(DateTime start, DateTime end, AccrualBoundary boundary, int annualDays, bool annualized)
=> Add(AccrualTraceEvent.Start, start,
$"START 区间[{start:yyyy-MM-dd},{end:yyyy-MM-dd}] {boundary} annualDays={annualDays} annualized={annualized}");
/// <summary>逐日明细:当日生效利率、计息基数、当日利息、累计利息。这是"为何 accrued N 天而非 M 天"的直接证据。</summary>
public void Day(int idx, DateTime date, decimal rate, decimal basePrincipal, decimal dayInterest, decimal accumulated)
=> Add(AccrualTraceEvent.DayAccrual, date,
$" [{idx}] {date:yyyy-MM-dd} rate={rate:P6} base={basePrincipal:F4} day={dayInterest:F6} acc={accumulated:F6}");
/// <summary>分段明细(多日计息,按重置日分段):段区间[segStart,segEnd)、天数、生效利率、计息基数、段利息、累计利息。
/// 这是分段模型与旧逐日循环对账的核心证据——每段的"前后日期/基数/利率"一目了然。</summary>
public void Segment(int idx, DateTime segStart, DateTime segEnd, int days, decimal rate, decimal basis, decimal segInterest, decimal accumulated)
=> Add(AccrualTraceEvent.DayAccrual, segStart,
$" [seg{idx}] [{segStart:yyyy-MM-dd},{segEnd:yyyy-MM-dd}) days={days} rate={rate:P6} basis={basis:F4} segInterest={segInterest:F6} acc={accumulated:F6}");
/// <summary>EOD 上下文:计息日、重置日标志、平仓比例、基数各分量。供日终单日计息定位"重置日分支选对了吗"。</summary>
public void EodContext(DateTime eodDate, bool isResetDay, decimal unwindFraction, decimal priorAccrued, decimal priorNotional, decimal notional, decimal remainingFraction)
=> Add(AccrualTraceEvent.Start, eodDate,
$" CTX {eodDate:yyyy-MM-dd} reset={isResetDay} unwind={unwindFraction:P2} priorAccrued={priorAccrued:F4} priorNotional={priorNotional:F4} notional={notional:F4} remainingFrac={remainingFraction:P4}");
/// <summary>重置日<b>前</b>:生效利率(旧)与计息本金(滚动前)。利率/本金切换的"因"。</summary>
public void ResetBefore(DateTime resetDate, decimal rateOld, decimal principalBefore)
=> Add(AccrualTraceEvent.ResetBefore, resetDate,
$" RESET↓ {resetDate:yyyy-MM-dd} rate(old)={rateOld:P6} principal(before)={principalBefore:F4}");
/// <summary>重置日<b>后</b>:生效利率(新)与计息本金(滚动后,已并本金)。利率/本金切换的"果"。</summary>
public void ResetAfter(DateTime resetDate, decimal rateNew, decimal principalAfter)
=> Add(AccrualTraceEvent.ResetAfter, resetDate,
$" RESET↑ {resetDate:yyyy-MM-dd} rate(new)={rateNew:P6} principal(after)={principalAfter:F4}");
/// <summary>本金增加(利息滚入计息基数):复利段末并本金的瞬间,记录滚入额与并本金后的新基数。</summary>
public void Rollover(DateTime resetDate, decimal accruedRolled, decimal newBasis)
=> Add(AccrualTraceEvent.Rollover, resetDate,
$" ROLLOVER {resetDate:yyyy-MM-dd} accrued(rolled)={accruedRolled:F6} newBasis={newBasis:F4}");
/// <summary>平仓缩放:平仓比例、累计已实现、剩余未实现。</summary>
public void Unwind(DateTime date, decimal unwindPercent, decimal realized, decimal remainingUnrealized)
=> Add(AccrualTraceEvent.Unwind, date,
$" UNWIND {date:yyyy-MM-dd} pct={unwindPercent:P2} realized={realized:F6} remaining={remainingUnrealized:F6}");
/// <summary>收尾:最终累计利息与当日利息。</summary>
public void MarkEnd(decimal totalAccrued, decimal totalToday)
=> Add(AccrualTraceEvent.End, default,
$"END accrued={totalAccrued:F6} today={totalToday:F6}");
private void Add(AccrualTraceEvent step, DateTime date, string line)
=> _entries.Add(new AccrualTraceEntry(step, date, line));
/// <summary>稳定可 diff 的逐行文本(与 SwapCalcTrace.Day 格式对齐)。</summary>
public override string ToString()
=> _entries.Count == 0 ? "<empty trace>" : string.Join(Environment.NewLine, _entries.Select(e => e.Line));
}
/// <summary>追踪条目的语义类别(对应 QuantLib/Strata 的"事件"概念),便于程序化筛选(如"只看重置日")。</summary>
public enum AccrualTraceEvent
{
Start, DayAccrual, ResetBefore, ResetAfter, Rollover, Unwind, End
}
/// <summary>单条追踪记录:类别 + 日期 + 已渲染文本。</summary>
public readonly record struct AccrualTraceEntry(AccrualTraceEvent Step, DateTime Date, string Line);