diff --git a/Framework/YLErp.Core/Interest/AccrualContext.cs b/Framework/YLErp.Core/Interest/AccrualContext.cs index cc1a494c..3cdbf5fb 100644 --- a/Framework/YLErp.Core/Interest/AccrualContext.cs +++ b/Framework/YLErp.Core/Interest/AccrualContext.cs @@ -22,8 +22,8 @@ public readonly struct AccrualContext public int Precision { get; } /// 可选 trace 收集器;为 null 时不记录(纯计算场景直接传 null,与开关无关)。 - public InterestTrace? Trace { get; } + public AccrualTrace? Trace { get; } - public AccrualContext(int annualDays, int precision = 11, InterestTrace? trace = null) + public AccrualContext(int annualDays, int precision = 11, AccrualTrace? trace = null) => (AnnualDays, Precision, Trace) = (annualDays, precision, trace); } diff --git a/Framework/YLErp.Core/Interest/InterestTrace.cs b/Framework/YLErp.Core/Interest/AccrualTrace.cs similarity index 78% rename from Framework/YLErp.Core/Interest/InterestTrace.cs rename to Framework/YLErp.Core/Interest/AccrualTrace.cs index f27a5999..d2c6d0bf 100644 --- a/Framework/YLErp.Core/Interest/InterestTrace.cs +++ b/Framework/YLErp.Core/Interest/AccrualTrace.cs @@ -11,68 +11,68 @@ namespace YLErp.Core.Interest; /// 为什么是收集器而不是日志调用:计息数学(SwapInterest / FundingLegAccrual)必须保持纯函数、 /// 可单测、不依赖 NLog;但按工程铁律,关键路径日志须无条件常驻落盘(出问题时事后翻日志定位,不能依赖开关)。 /// 折中:纯函数把"发生了什么"记录为结构化条目写入本收集器,由适配器(IO 边界)统一经 -/// SwapCalcTrace.Persist 常驻落盘。落盘职责归一处,计息代码零日志依赖、保持干净。 +/// SwapCalcTrace.Write 常驻落盘。落盘职责归一处,计息代码零日志依赖、保持干净。 /// /// 可 diff 产出稳定、有序、与 SwapCalcTrace.Day 对齐的逐行文本, /// 新旧引擎对同一笔交易跑出的 trace 可直接 diff,定位"是计算变了还是重构引入了漂移"。 /// /// 所有记录方法均为语义化命名(Day / ResetBefore / Rollover …),调用点一眼即懂,不污染数学可读性。 /// -public sealed class InterestTrace +public sealed class AccrualTrace { - private readonly List _entries = new(); + private readonly List _entries = new(); /// 已记录的追踪条目(只读)。 - public IReadOnlyList Entries => _entries; + public IReadOnlyList Entries => _entries; /// 计息区间起点:标记本次计算的整体边界与年化口径。 public void MarkStart(DateTime start, DateTime end, AccrualBoundary boundary, int annualDays, bool annualized) - => Add(InterestStep.Start, start, + => Add(AccrualTraceEvent.Start, start, $"START 区间[{start:yyyy-MM-dd},{end:yyyy-MM-dd}] {boundary} annualDays={annualDays} annualized={annualized}"); /// 逐日明细:当日生效利率、计息基数、当日利息、累计利息。这是"为何 accrued N 天而非 M 天"的直接证据。 public void Day(int idx, DateTime date, decimal rate, decimal basePrincipal, decimal dayInterest, decimal accumulated) - => Add(InterestStep.DayAccrual, date, + => Add(AccrualTraceEvent.DayAccrual, date, $" [{idx}] {date:yyyy-MM-dd} rate={rate:P6} base={basePrincipal:F4} day={dayInterest:F6} acc={accumulated:F6}"); /// 重置日:生效利率(旧)与计息本金(滚动前)。利率/本金切换的"因"。 public void ResetBefore(DateTime resetDate, decimal rateOld, decimal principalBefore) - => Add(InterestStep.ResetBefore, resetDate, + => Add(AccrualTraceEvent.ResetBefore, resetDate, $" RESET↓ {resetDate:yyyy-MM-dd} rate(old)={rateOld:P6} principal(before)={principalBefore:F4}"); /// 重置日:生效利率(新)与计息本金(滚动后,已并本金)。利率/本金切换的"果"。 public void ResetAfter(DateTime resetDate, decimal rateNew, decimal principalAfter) - => Add(InterestStep.ResetAfter, resetDate, + => Add(AccrualTraceEvent.ResetAfter, resetDate, $" RESET↑ {resetDate:yyyy-MM-dd} rate(new)={rateNew:P6} principal(after)={principalAfter:F4}"); /// 本金增加(利息滚入计息基数):复利段末并本金的瞬间,记录滚入额与并本金后的新基数。 public void Rollover(DateTime resetDate, decimal accruedRolled, decimal newBasis) - => Add(InterestStep.Rollover, resetDate, + => Add(AccrualTraceEvent.Rollover, resetDate, $" ROLLOVER {resetDate:yyyy-MM-dd} accrued(rolled)={accruedRolled:F6} newBasis={newBasis:F4}"); /// 平仓缩放:平仓比例、累计已实现、剩余未实现。 public void Unwind(DateTime date, decimal unwindPercent, decimal realized, decimal remainingUnrealized) - => Add(InterestStep.Unwind, date, + => Add(AccrualTraceEvent.Unwind, date, $" UNWIND {date:yyyy-MM-dd} pct={unwindPercent:P2} realized={realized:F6} remaining={remainingUnrealized:F6}"); /// 收尾:最终累计利息与当日利息。 public void MarkEnd(decimal totalAccrued, decimal totalToday) - => Add(InterestStep.End, default, + => Add(AccrualTraceEvent.End, default, $"END accrued={totalAccrued:F6} today={totalToday:F6}"); - private void Add(InterestStep step, DateTime date, string line) - => _entries.Add(new InterestTraceEntry(step, date, line)); + private void Add(AccrualTraceEvent step, DateTime date, string line) + => _entries.Add(new AccrualTraceEntry(step, date, line)); /// 稳定可 diff 的逐行文本(与 SwapCalcTrace.Day 格式对齐)。 public override string ToString() => _entries.Count == 0 ? "" : string.Join(Environment.NewLine, _entries.Select(e => e.Line)); } -/// 追踪条目的语义类别,便于程序化筛选(如"只看重置日")。 -public enum InterestStep +/// 追踪条目的语义类别(对应 QuantLib/Strata 的"事件"概念),便于程序化筛选(如"只看重置日")。 +public enum AccrualTraceEvent { Start, DayAccrual, ResetBefore, ResetAfter, Rollover, Unwind, End } /// 单条追踪记录:类别 + 日期 + 已渲染文本。 -public readonly record struct InterestTraceEntry(InterestStep Step, DateTime Date, string Line); +public readonly record struct AccrualTraceEntry(AccrualTraceEvent Step, DateTime Date, string Line); diff --git a/YLErpDAL/Modules/SwapModule/Accrual/FundingLegAccrual.cs b/YLErpDAL/Modules/SwapModule/Accrual/FundingLegAccrual.cs index cba457b8..593a1500 100644 --- a/YLErpDAL/Modules/SwapModule/Accrual/FundingLegAccrual.cs +++ b/YLErpDAL/Modules/SwapModule/Accrual/FundingLegAccrual.cs @@ -48,7 +48,7 @@ public static class FundingLegAccrual FundingLegRate rate, AccrualPolicy policy, DateTime eodDate, - InterestTrace? trace = null) + AccrualTrace? trace = null) { var baseTdInterestPrincipal = priorAccrualPrincipal + positionPrincipal - originalPv; var baseInterestPrincipal = baseTdInterestPrincipal * closeRatio; diff --git a/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs b/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs index 93dd6011..f6e1a1ec 100644 --- a/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs +++ b/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs @@ -86,11 +86,11 @@ namespace YLErp.Modules.SwapModule public static string DumpForRequest() => _reqBuf.Value?.ToString() ?? ""; /// - /// 把纯函数产出的 常驻落盘(关键路径日志)。 + /// 把纯函数产出的 常驻落盘(关键路径日志)。 /// 每条目经 写出——无条件落盘,与开关无关; /// 开关打开时同时进内存 buffer 供实时查看 / 单测断言。这是事后 diff 新旧引擎的主通道。 /// - public static void Persist(InterestTrace? trace) + public static void Write(AccrualTrace? trace) { if (trace == null) return; foreach (var entry in trace.Entries) diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index dd61e24f..3234ae5a 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -1593,7 +1593,7 @@ namespace YLErp.Modules.SwapModule annualDays: annualDays, isAnnualized: position.IsAnnualized); // 完整计息 trace:收集器由适配器创建,随后经 SwapCalcTrace 常驻落盘(关键路径日志,无条件)。 - var interestTrace = new InterestTrace(); + var interestTrace = new AccrualTrace(); var result = FundingLegAccrual.AccrueSimpleEod( priorUnrealized: preEodPosition.InterestProfitSum, priorAccrualPrincipal: preEodPosition.TdInterestPrincipal, @@ -1606,7 +1606,7 @@ namespace YLErp.Modules.SwapModule trace: interestTrace); InterestAmount = result.Accrued; TdInterestAmount = result.AccruedToday; - SwapCalcTrace.Persist(interestTrace); + SwapCalcTrace.Write(interestTrace); } ///