refactor(interest): 计息 trace 命名对齐业界最佳实践
- InterestTrace → AccrualTrace:追踪对象是"计息过程"而非泛指的 Interest,与仓库 既有的 SwapCalcTrace(-Trace 后缀约定) 及 AccrualContext/AccrualBoundary/AccrualPolicy 家族一致;AccrualTrace 经 SwapCalcTrace.Write 常驻落盘,二者职责分清(收集器 vs 落盘通道)。 - InterestStep → AccrualTraceEvent、InterestTraceEntry → AccrualTraceEntry: 条目类别对应 QuantLib/Strata 的"事件"概念(AccrualTraceEvent),命名自描述。 - SwapCalcTrace.Persist → Write:"Persist" 易误读为落库持久化;此处只是写入日志通道, 且类内已有私有 Record(string),故用 Write 避免歧义。 纯改名、数值逻辑零改动;YLErpDAL + UnitTestProject 编译 0 错。
This commit is contained in:
@@ -22,8 +22,8 @@ public readonly struct AccrualContext
|
||||
public int Precision { get; }
|
||||
|
||||
/// <summary>可选 trace 收集器;为 null 时不记录(纯计算场景直接传 null,与开关无关)。</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
+16
-16
@@ -11,68 +11,68 @@ namespace YLErp.Core.Interest;
|
||||
/// <para><b>为什么是收集器而不是日志调用</b>:计息数学(SwapInterest / FundingLegAccrual)必须保持纯函数、
|
||||
/// 可单测、不依赖 NLog;但按工程铁律,关键路径日志须<b>无条件常驻落盘</b>(出问题时事后翻日志定位,不能依赖开关)。
|
||||
/// 折中:纯函数把"发生了什么"记录为结构化条目写入本收集器,由<b>适配器(IO 边界)</b>统一经
|
||||
/// <c>SwapCalcTrace.Persist</c> 常驻落盘。落盘职责归一处,计息代码零日志依赖、保持干净。</para>
|
||||
/// <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 InterestTrace
|
||||
public sealed class AccrualTrace
|
||||
{
|
||||
private readonly List<InterestTraceEntry> _entries = new();
|
||||
private readonly List<AccrualTraceEntry> _entries = new();
|
||||
|
||||
/// <summary>已记录的追踪条目(只读)。</summary>
|
||||
public IReadOnlyList<InterestTraceEntry> Entries => _entries;
|
||||
public IReadOnlyList<AccrualTraceEntry> Entries => _entries;
|
||||
|
||||
/// <summary>计息区间起点:标记本次计算的整体边界与年化口径。</summary>
|
||||
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}");
|
||||
|
||||
/// <summary>逐日明细:当日生效利率、计息基数、当日利息、累计利息。这是"为何 accrued N 天而非 M 天"的直接证据。</summary>
|
||||
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}");
|
||||
|
||||
/// <summary>重置日<b>前</b>:生效利率(旧)与计息本金(滚动前)。利率/本金切换的"因"。</summary>
|
||||
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}");
|
||||
|
||||
/// <summary>重置日<b>后</b>:生效利率(新)与计息本金(滚动后,已并本金)。利率/本金切换的"果"。</summary>
|
||||
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}");
|
||||
|
||||
/// <summary>本金增加(利息滚入计息基数):复利段末并本金的瞬间,记录滚入额与并本金后的新基数。</summary>
|
||||
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}");
|
||||
|
||||
/// <summary>平仓缩放:平仓比例、累计已实现、剩余未实现。</summary>
|
||||
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}");
|
||||
|
||||
/// <summary>收尾:最终累计利息与当日利息。</summary>
|
||||
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));
|
||||
|
||||
/// <summary>稳定可 diff 的逐行文本(与 SwapCalcTrace.Day 格式对齐)。</summary>
|
||||
public override string ToString()
|
||||
=> _entries.Count == 0 ? "<empty trace>" : string.Join(Environment.NewLine, _entries.Select(e => e.Line));
|
||||
}
|
||||
|
||||
/// <summary>追踪条目的语义类别,便于程序化筛选(如"只看重置日")。</summary>
|
||||
public enum InterestStep
|
||||
/// <summary>追踪条目的语义类别(对应 QuantLib/Strata 的"事件"概念),便于程序化筛选(如"只看重置日")。</summary>
|
||||
public enum AccrualTraceEvent
|
||||
{
|
||||
Start, DayAccrual, ResetBefore, ResetAfter, Rollover, Unwind, End
|
||||
}
|
||||
|
||||
/// <summary>单条追踪记录:类别 + 日期 + 已渲染文本。</summary>
|
||||
public readonly record struct InterestTraceEntry(InterestStep Step, DateTime Date, string Line);
|
||||
public readonly record struct AccrualTraceEntry(AccrualTraceEvent Step, DateTime Date, string Line);
|
||||
@@ -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;
|
||||
|
||||
@@ -86,11 +86,11 @@ namespace YLErp.Modules.SwapModule
|
||||
public static string DumpForRequest() => _reqBuf.Value?.ToString() ?? "";
|
||||
|
||||
/// <summary>
|
||||
/// 把纯函数产出的 <see cref="InterestTrace"/> 常驻落盘(关键路径日志)。
|
||||
/// 把纯函数产出的 <see cref="AccrualTrace"/> 常驻落盘(关键路径日志)。
|
||||
/// 每条目经 <see cref="Critical"/> 写出——<b>无条件</b>落盘,与开关无关;
|
||||
/// 开关打开时同时进内存 buffer 供实时查看 / 单测断言。这是事后 diff 新旧引擎的主通道。
|
||||
/// </summary>
|
||||
public static void Persist(InterestTrace? trace)
|
||||
public static void Write(AccrualTrace? trace)
|
||||
{
|
||||
if (trace == null) return;
|
||||
foreach (var entry in trace.Entries)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user