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" 对新读者是黑话。 // ───────────────────────────────────────────────────────────────────────────── /// /// 计息区间边界(算头 / 算尾)。 /// 用具名值取代两个相邻 bool,物理上杜绝 calcFirst / calcLast 传反这一类历史缺陷。 /// public readonly struct AccrualBoundary { /// 算头:含 startDate。 public bool IncludeStart { get; } /// 算尾:含 endDate。 public bool IncludeEnd { get; } private AccrualBoundary(bool includeStart, bool includeEnd) => (IncludeStart, IncludeEnd) = (includeStart, includeEnd); /// 算头算尾 [start, end]。 public static readonly AccrualBoundary Both = new(true, true); /// 算头不算尾 [start, end)。 public static readonly AccrualBoundary StartOnly = new(true, false); /// 不算头算尾 (start, end]。 public static readonly AccrualBoundary EndOnly = new(false, true); /// 不算头不算尾 (start, end)。 public static readonly AccrualBoundary None = new(false, false); /// 由既有 calcFirst / calcLast 布尔对构造,供旧调用方渐进迁移。 public static AccrualBoundary Of(bool includeStart, bool includeEnd) => new(includeStart, includeEnd); public override string ToString() => $"{(IncludeStart ? "算头" : "不算头")}{(IncludeEnd ? "算尾" : "不算尾")}"; } /// /// 计息结果。Accrued → 记账字段 InterestAmount / InterestProfitSum;AccruedToday → TdInterestAmount。 /// public readonly struct InterestResult { /// 区间累计应计利息。 public decimal Accrued { get; } /// 末日(当日)应计利息。 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}"; } /// /// 利息腿共用数学工具:舍入、应计天数、精度常量。 /// /// 沿革:2026-08 自 Core 层 SwapInterest 迁入 DAL(生产消费面整体搬家)。 /// 原 SwapInterest 的算法方法(AccrueSimple/AccrueCompoundInArrears/ApplyUnwind/AccrueUnrealized) /// 与 AccrualContext/InterestRate 始终未接线(生产计息走本目录 Simple/CompoundInterestAccrual, /// 两者舍入与 rollover 口径已分叉),作为孤儿死代码删除——接线前须先补对账,勿凭记忆重建。 /// /// 为何不复用 Qdp 的 IDayCount: /// a. 语义——Qdp 的 DaysInPeriod = end − start 是写死的半开区间,只能表达四种算头算尾中的一种; /// b. 精度——Qdp 返回 double 年化系数,本系统 decimal 对账; /// c. 依赖方向——Qdp 用自有 Date 类型,引入会让本模块反向依赖定价库。 /// public static class InterestMath { /// 资金腿与保证金腿的生产计息精度(落库/对账均以 12 位为准)。 /// 提升至公共常量,消除 SwapDealService 与 SimpleInterestAccrual 的重复定义。 public const int FundingLegPrecision = 12; /// 应计天数。边界规则由日期区间表达,计息函数内不再出现 flag 分支。 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; } /// 统一舍入:MidpointRounding.AwayFromZero。所有计息路径收口到此处,避免散落的 Math.Round 不一致。 public static decimal Round(decimal value, int precision) => Math.Round(value, precision, MidpointRounding.AwayFromZero); }