refactor(interest): FundingLegAccrual 删除危险骨架,仅保留纯函数 AccrueSimpleEod
移除标注"切勿在未迁移前接入生产"的 AccrueDay/AccruePeriod 骨架(含 IIndexFixer/ Func 取价),保留与旧实现逐字对齐的纯函数,消除两套计息逻辑并存的隐患。
This commit is contained in:
@@ -4,60 +4,17 @@ using YLErp.Derivatives.Interest;
|
||||
namespace YLErp.Modules.SwapModule.Accrual;
|
||||
|
||||
/// <summary>
|
||||
/// 融资腿计息编排层(NEW,替换 SwapDealService 内 CalcDaily* 家族的"纯数学"部分)。
|
||||
/// 融资腿计息编排层——纯数学部分(替换 SwapDealService 内 CalcDaily* 家族的纯计算)。
|
||||
///
|
||||
/// <para>职责边界(与 SwapInterest 原语、SwapDealService 适配器三者正交):</para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>本类:持有跨日 <see cref="AccrualState"/>,逐日循环,经 IIndexFixer 取当日率并封装为 <see cref="FundingLegRate"/>,
|
||||
/// 调用 SwapInterest 原子原语算账,处理平仓缩放 / 已实现扣除,replay 求 Δ。</description></item>
|
||||
/// <item><description>本类:持有已解析的 <see cref="FundingLegRate"/> 与 <see cref="AccrualPolicy"/>,执行单利日终计息纯函数。</description></item>
|
||||
/// <item><description>SwapInterest:原子 "本金×利率×天数/年化" 纯函数,无状态。</description></item>
|
||||
/// <item><description>SwapDealService.GetInterests:仅做 DB 读、swap_flow_event 构造与落库(IO)。</description></item>
|
||||
/// <item><description>SwapDealService:负责 DB 读、取率、swap_flow_event 构造与落库(IO)。</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>命名规范:本文件内所有概念一律使用自描述英文名。旧代码 typo 一律不出现:</para>
|
||||
/// <list type="table">
|
||||
/// <item><term>closePrecent</term><description>→ <see cref="ApplyPartialClose"/> 的 closeRatio(且语义固定为"占剩余持仓比例")</description></item>
|
||||
/// <item><term>orginPv</term><description>→ OriginalPv(原始名义本金,用于保证金腿差分基数)</description></item>
|
||||
/// <item><term>floateRate</term><description>→ FloatRate(FR007 浮动利率,拼写修正)</description></item>
|
||||
/// <item><term>dynomicPrincipal</term><description>→ AccrualPrincipal(逐日滚动计息本金)</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>重要——以下 <see cref="AccruePeriod"/> 当前为"结构骨架":</para>
|
||||
/// 精确的跨日不变量(T+1 本金缩到剩余、重置日本金保留、consumedInterest 扣除、保证金腿差分公式)
|
||||
/// 依 branch-merge-analysis §7 的 DI_* 不变量,必须在把 <c>_0808</c>(人工已过)的利息核心并入后,
|
||||
/// 从 CalcDaily* 迁移而来并以 Excel oracle 验收。骨架故意只跑"朴素逐日累加",
|
||||
/// <b>不等价于已验证口径——切勿在未迁移前接入生产</b>。
|
||||
/// </summary>
|
||||
public static class FundingLegAccrual
|
||||
{
|
||||
// 中性锚点日期:AccrueDay 只需"同日起止 → 1 天"的语义,不依赖真实时钟,保持纯函数。
|
||||
private static readonly DateTime Epoch = new(2000, 1, 1);
|
||||
|
||||
// 资金腿计息精度(生产口径)。与 SwapDealService.InterestCalculationPrecision 一致。
|
||||
// 注意:SwapInterest.Precision=11 仅服务于保证金腿(MarginAccount),与资金腿 12 不一致属已知 TODO;
|
||||
// 资金腿所有落库/对账均以 12 为准,此处显式锁定,避免静默引入尾差。
|
||||
private const int ProductionPrecision = 12;
|
||||
|
||||
/// <summary>
|
||||
/// 单日原子计息("AccrueDay")。委托 SwapInterest.AccrueSimple(同一天、Convention 边界 → 1 天),
|
||||
/// 直接复用 SwapInterest 已有的 <see cref="InterestResult"/>(Accrued/AccruedToday)作为返回,
|
||||
/// 不再另立结果类型。编排层按日把"当天本金 + 当天率"喂入此入口,使浮动利率逐日不同也能正确累积。
|
||||
/// 复利时收盘本金 = 当日本金 + 当日利息(利滚利),该推导在 <see cref="AccruePeriod"/> 内完成,
|
||||
/// 不塞进返回结构,保持与 SwapInterest 单一结果类型的契约一致。
|
||||
/// </summary>
|
||||
/// <param name="accrualPrincipal">当日计息本金(来自 <see cref="AccrualState.AccrualPrincipal"/>)。</param>
|
||||
/// <param name="dailyRate">当日生效年利率(由调用方经 IIndexFixer + 合约利差构造后传入,已含 spread)。</param>
|
||||
/// <param name="policy">计息政策(含算头算尾 / 年化天数 / 单复利)。</param>
|
||||
public static InterestResult AccrueDay(
|
||||
decimal accrualPrincipal,
|
||||
decimal dailyRate,
|
||||
AccrualPolicy policy,
|
||||
int precision = SwapInterest.Precision)
|
||||
{
|
||||
// 单日计息:start == end,按 policy.Convention 决定首日是否计;Both 时恰 1 天。
|
||||
return SwapInterest.AccrueSimple(accrualPrincipal, dailyRate, Epoch, Epoch, policy.Convention, policy.AnnualDays, precision);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单利日终计息(纯函数,替换 SwapDealService.CalcDailySimpleInterestByEod 的"纯数学"部分)。
|
||||
///
|
||||
@@ -65,7 +22,7 @@ public static class FundingLegAccrual
|
||||
/// <list type="bullet">
|
||||
/// <item><description>计息基数 baseTdInterestPrincipal = priorAccrualPrincipal + positionPrincipal − originalPv;</description></item>
|
||||
/// <item><description>当日利息 = baseTdInterestPrincipal × closeRatio × rate.AllInRate,年化则再 ÷ AnnualDays;</description></item>
|
||||
/// <item><description>累计未实现 = priorUnrealized + 当日利息;末位按资金腿精度 12 舍入。</description></item>
|
||||
/// <item><description>累计未实现 = priorUnrealized + 当日利息;末位按资金腿精度 <see cref="SwapInterest.FundingLegPrecision"/> 舍入。</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>取率与重置日重取浮动利率由适配器(CalcDailySimpleInterestByEod)负责,并封装为 <see cref="FundingLegRate"/> 传入;
|
||||
@@ -102,83 +59,7 @@ public static class FundingLegAccrual
|
||||
|
||||
var totalUnrealized = priorUnrealized + dayInterest;
|
||||
return new InterestResult(
|
||||
Math.Round(totalUnrealized, ProductionPrecision, MidpointRounding.AwayFromZero),
|
||||
Math.Round(tdInterest, ProductionPrecision, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逐日计息编排(骨架)。从 <paramref name="startDate"/> 到 <paramref name="endDate"/> 逐日循环,
|
||||
/// 推进 <see cref="AccrualState"/>,并返回期末状态。
|
||||
/// </summary>
|
||||
/// <param name="opening">期初状态(通常来自上一日日终,见 <see cref="AccrualState.FromPreviousEod"/>)。</param>
|
||||
/// <param name="startDate">计息区间起点。</param>
|
||||
/// <param name="endDate">计息区间终点。</param>
|
||||
/// <param name="dailyRate">取计息日生效年利率的委托(适配器处由 IIndexFixer + 合约利差构造;测试可传 day => 0.0134m)。</param>
|
||||
/// <param name="policy">计息政策。</param>
|
||||
/// <param name="closeRatio">可选平仓比例(占剩余,0~1)。非空则在期末应用平仓缩放。</param>
|
||||
public static AccrualState AccruePeriod(
|
||||
AccrualState opening,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
Func<DateTime, decimal> dailyRate,
|
||||
AccrualPolicy policy,
|
||||
decimal? closeRatio = null)
|
||||
{
|
||||
var state = opening;
|
||||
|
||||
for (var day = startDate.Date; day <= endDate.Date; day = day.AddDays(1))
|
||||
{
|
||||
var rate = dailyRate(day);
|
||||
var dayResult = AccrueDay(state.AccrualPrincipal, rate, policy);
|
||||
|
||||
// 收盘本金:复利时并入当日利息(利滚利),单利时维持原基数。
|
||||
var nextPrincipal = state.AccrualPrincipal + (policy.IsCompound ? dayResult.AccruedToday : 0m);
|
||||
var nextUnrealized = state.UnrealizedInterest + dayResult.AccruedToday;
|
||||
state = new AccrualState(nextPrincipal, nextUnrealized, state.RealizedInterest);
|
||||
|
||||
// ── PORT(合并 _0808 后从 CalcDaily* 迁移,受 DI_* 不变量 + Excel oracle 验收)──
|
||||
// 1. 重置日(距 StartDate 每 resetPeriodDays 天):复利时累计利息并入本金(已在 ClosingAccrualPrincipal 体现);
|
||||
// 但"重置日部分平仓后 EOD 本金必须保留计算出的复利本金、不得被 closeRatio 二次缩放"。
|
||||
// 2. 部分平仓次日(T+1):AccrualPrincipal 必须缩到剩余(× (1-closeRatio));
|
||||
// 重置日 / 算尾日不得二次缩放,否则剩余本金被打折。
|
||||
// 3. consumedInterest(历史已实现)在平仓日经 SwapInterest.ApplyUnwind 扣除。
|
||||
// 4. 保证金腿差分基数 OriginalPv:dynomicPrincipal = AccrualPrincipal + PositionPrincipal - OriginalPv,此处分支处理。
|
||||
// 5. replay 求 Δ:本 EOD 状态 − 上一 EOD 状态,由调用方(适配器层)负责,不在纯数学内。
|
||||
}
|
||||
|
||||
if (closeRatio.HasValue)
|
||||
{
|
||||
state = ApplyPartialClose(state, closeRatio.Value, state.RealizedInterest, policy);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 部分 / 全部平仓缩放:把剩余待实现按 (1-closeRatio) 保留,并扣除历史已实现;
|
||||
/// 计息本金同步缩到剩余持仓。委托 SwapInterest.ApplyUnwind / AccrueUnrealized(与 Margin 共用同一纯函数)。
|
||||
/// </summary>
|
||||
/// <param name="state">平仓前状态(未实现部分)。</param>
|
||||
/// <param name="closeRatio">平仓比例(占剩余持仓,0~1;1=全平)。</param>
|
||||
/// <param name="realizedInterest">历史已实现利息累计,从剩余未实现中扣除。</param>
|
||||
/// <param name="policy">计息政策(精度取 SwapInterest.Precision)。</param>
|
||||
public static AccrualState ApplyPartialClose(
|
||||
AccrualState state,
|
||||
decimal closeRatio,
|
||||
decimal realizedInterest,
|
||||
AccrualPolicy policy)
|
||||
{
|
||||
// 待实现利息缩放:unrealized × (1-closeRatio) − realizedInterest
|
||||
var scaled = SwapInterest.ApplyUnwind(
|
||||
new InterestResult(state.UnrealizedInterest, state.UnrealizedInterest),
|
||||
closeRatio, realizedInterest, SwapInterest.Precision);
|
||||
|
||||
// 计息本金按剩余持仓缩放(全平 closeRatio=1 → 归零)
|
||||
var principalRemaining = Math.Round(
|
||||
state.AccrualPrincipal * (1m - closeRatio),
|
||||
SwapInterest.Precision,
|
||||
MidpointRounding.AwayFromZero);
|
||||
|
||||
return new AccrualState(principalRemaining, scaled.Accrued, realizedInterest);
|
||||
Math.Round(totalUnrealized, SwapInterest.FundingLegPrecision, MidpointRounding.AwayFromZero),
|
||||
Math.Round(tdInterest, SwapInterest.FundingLegPrecision, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user