搬迁(算法体逐字未动,仅换命名空间与归属): - 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 已留警告)。
105 lines
5.6 KiB
C#
105 lines
5.6 KiB
C#
namespace YLErp.Modules.SwapModule.Accrual;
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 词汇表(本文件只允许出现下列用词,同一概念不得出现第二种叫法)
|
||
//
|
||
// 概念 唯一用词 与既有代码的对应
|
||
// ───────────────────────────────────────────────────────────────────
|
||
// 区间起点/终点 Start / End startDate / endDate
|
||
// 计息 Accrue CalcDailySimpleInterest / CalcDailyCompoundInterest
|
||
// 平仓 Unwind unwindPercent(既有字段 closePercent)
|
||
// 已实现利息 Realized realizedInterest(legacy 字段 consumedInterest)
|
||
// 待实现收益 Unrealized 预付金模式下的待实现收益余额
|
||
// 计息基数 principal principal / dynomicPrincipal
|
||
// 年化天数 annualDays tradeExtend.ExtendObj.AnnualDays
|
||
//
|
||
// 入参一律沿用既有代码的字段名,调用点两边读起来同名,不产生心智翻译成本。
|
||
// 出参改用自描述名(Accrued / AccruedToday),因为 "Td" 对新读者是黑话。
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 计息区间边界(算头 / 算尾)。
|
||
/// 用具名值取代两个相邻 bool,物理上杜绝 calcFirst / calcLast 传反这一类历史缺陷。
|
||
/// </summary>
|
||
public readonly struct AccrualBoundary
|
||
{
|
||
/// <summary>算头:含 startDate。</summary>
|
||
public bool IncludeStart { get; }
|
||
|
||
/// <summary>算尾:含 endDate。</summary>
|
||
public bool IncludeEnd { get; }
|
||
|
||
private AccrualBoundary(bool includeStart, bool includeEnd)
|
||
=> (IncludeStart, IncludeEnd) = (includeStart, includeEnd);
|
||
|
||
/// <summary>算头算尾 [start, end]。</summary>
|
||
public static readonly AccrualBoundary Both = new(true, true);
|
||
|
||
/// <summary>算头不算尾 [start, end)。</summary>
|
||
public static readonly AccrualBoundary StartOnly = new(true, false);
|
||
|
||
/// <summary>不算头算尾 (start, end]。</summary>
|
||
public static readonly AccrualBoundary EndOnly = new(false, true);
|
||
|
||
/// <summary>不算头不算尾 (start, end)。</summary>
|
||
public static readonly AccrualBoundary None = new(false, false);
|
||
|
||
/// <summary>由既有 calcFirst / calcLast 布尔对构造,供旧调用方渐进迁移。</summary>
|
||
public static AccrualBoundary Of(bool includeStart, bool includeEnd) => new(includeStart, includeEnd);
|
||
|
||
public override string ToString()
|
||
=> $"{(IncludeStart ? "算头" : "不算头")}{(IncludeEnd ? "算尾" : "不算尾")}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计息结果。Accrued → 记账字段 InterestAmount / InterestProfitSum;AccruedToday → TdInterestAmount。
|
||
/// </summary>
|
||
public readonly struct InterestResult
|
||
{
|
||
/// <summary>区间累计应计利息。</summary>
|
||
public decimal Accrued { get; }
|
||
|
||
/// <summary>末日(当日)应计利息。</summary>
|
||
public decimal AccruedToday { get; }
|
||
|
||
public InterestResult(decimal accrued, decimal accruedToday)
|
||
=> (Accrued, AccruedToday) = (accrued, accruedToday);
|
||
|
||
public static readonly InterestResult Zero = new(0m, 0m);
|
||
|
||
public override string ToString() => $"Accrued={Accrued}, AccruedToday={AccruedToday}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 利息腿共用数学工具:舍入、应计天数、精度常量。
|
||
///
|
||
/// <para><b>沿革</b>:2026-08 自 Core 层 SwapInterest 迁入 DAL(生产消费面整体搬家)。
|
||
/// 原 SwapInterest 的算法方法(AccrueSimple/AccrueCompoundInArrears/ApplyUnwind/AccrueUnrealized)
|
||
/// 与 AccrualContext/InterestRate 始终未接线(生产计息走本目录 Simple/CompoundInterestAccrual,
|
||
/// 两者舍入与 rollover 口径已分叉),作为孤儿死代码删除——接线前须先补对账,勿凭记忆重建。</para>
|
||
///
|
||
/// <para>为何不复用 Qdp 的 IDayCount:
|
||
/// a. 语义——Qdp 的 DaysInPeriod = end − start 是写死的半开区间,只能表达四种算头算尾中的一种;
|
||
/// b. 精度——Qdp 返回 double 年化系数,本系统 decimal 对账;
|
||
/// c. 依赖方向——Qdp 用自有 Date 类型,引入会让本模块反向依赖定价库。</para>
|
||
/// </summary>
|
||
public static class InterestMath
|
||
{
|
||
/// <summary>资金腿与保证金腿的生产计息精度(落库/对账均以 12 位为准)。
|
||
/// 提升至公共常量,消除 SwapDealService 与 SimpleInterestAccrual 的重复定义。</summary>
|
||
public const int FundingLegPrecision = 12;
|
||
|
||
/// <summary>应计天数。边界规则由日期区间表达,计息函数内不再出现 flag 分支。</summary>
|
||
public static int AccrualDays(DateTime startDate, DateTime endDate, AccrualBoundary boundary)
|
||
{
|
||
var s = boundary.IncludeStart ? startDate : startDate.AddDays(1);
|
||
var e = boundary.IncludeEnd ? endDate : endDate.AddDays(-1);
|
||
var days = (int)(e - s).TotalDays + 1; // 含两端
|
||
return days < 0 ? 0 : days;
|
||
}
|
||
|
||
/// <summary>统一舍入:MidpointRounding.AwayFromZero。所有计息路径收口到此处,避免散落的 Math.Round 不一致。</summary>
|
||
public static decimal Round(decimal value, int precision)
|
||
=> Math.Round(value, precision, MidpointRounding.AwayFromZero);
|
||
}
|