diff --git a/Framework/YLErp.Core/Interest/AccrualContext.cs b/Framework/YLErp.Core/Interest/AccrualContext.cs
deleted file mode 100644
index 2a1f6273..00000000
--- a/Framework/YLErp.Core/Interest/AccrualContext.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace YLErp.Core.Interest;
-
-///
-/// 计息执行上下文:把"与具体金额/利率无关"的横向参数(年化天数、精度、trace 收集器)
-/// 打包成一个只读值对象,避免每个计息方法都重复携带这些参数。
-///
-/// 为何 trace 是"成员"而非散落参数:利息纯函数(AccrueSimple / AccrueCompoundInArrears)
-/// 的核心职责是算账,trace 只是可观测性的旁路。把 trace 作为上下文的成员传入,
-/// 调用点只需传一个 ctx,签名更干净;同时 ctx 是只读值对象,不破坏纯函数
-/// (无共享可变状态 → 线程安全、可重入、可测)。切勿把 trace 设成类的实例/静态字段,
-/// 那会让并发的两笔交易共用同一 trace、并使函数带隐藏状态。
-///
-/// 与 AccrualState(跨日滚动本金状态)/ AccrualPolicy(EOD 会计政策)正交:
-/// 本上下文只描述"如何算 + 往哪记",不持有任何交易进度。
-///
-public readonly struct AccrualContext
-{
- /// 年化天数(365 / 360)。
- public int AnnualDays { get; }
-
- /// 舍入精度位数。默认 11(生产融资腿/保证金腿均显式传入 FundingLegPrecision=12)。
- public int Precision { get; }
-
- /// 可选 trace 收集器;为 null 时不记录(纯计算场景直接传 null,与开关无关)。
- public AccrualTrace? Trace { get; }
-
- public AccrualContext(int annualDays, int precision = 11, AccrualTrace? trace = null)
- => (AnnualDays, Precision, Trace) = (annualDays, precision, trace);
-}
diff --git a/Framework/YLErp.Core/Interest/InterestRate.cs b/Framework/YLErp.Core/Interest/InterestRate.cs
deleted file mode 100644
index b5a6def1..00000000
--- a/Framework/YLErp.Core/Interest/InterestRate.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System;
-
-namespace YLErp.Core.Interest;
-
-///
-/// 利率 + 计息方式(单利 / 复利 / 连续复利)。
-///
-/// 通用金融原语,与互换、衍生品、任何具体业务均无耦合——谁需要算利息都能用。
-/// 利息计算不是互换特有的,所以它不住在 SwapModule,也不带任何 swap 词汇。
-///
-/// 用法(年化时间 t,如 30天/365):
-///
-/// - 计息因子 = ;含息额 = 本金 × 因子;
-/// - 利息 = 本金 × (因子 − 1) = 。
-///
-///
-/// 与 QuantLib 模型一致:单利 / 复利 / 连续复利只是 的一个分支,
-/// 不是三套独立方法。TRS 的"重置日并本金"属于离散复利,用
-/// 按段计息、段末把利息滚入本金即可(见 SwapInterest.AccrueCompoundInArrears),无需 Pow/Exp,decimal 精度无损。
-///
-/// 互换特有的会计态(每日先舍入再乘天数、平仓缩放、跨日滚动本金)不属于本原语,
-/// 请在各自的 accrual 层处理。
-///
-public enum Compounding
-{
- /// 单利:因子 = 1 + r·t。
- Simple,
- /// 复利(理想化闭式):因子 = (1 + r/f)^(f·t),f 为年复利频次。
- Compounded,
- /// 连续复利:因子 = e^(r·t)。
- Continuous
-}
-
-///
-/// 不可变利率值对象。构造即完整,无副作用。
-///
-public readonly struct InterestRate
-{
- /// 年化利率 r。
- public decimal Rate { get; }
-
- /// 计息方式。
- public Compounding Compounding { get; }
-
- /// 年复利频次(仅 使用,其余忽略,默认 1)。
- public int Frequency { get; }
-
- public InterestRate(decimal rate, Compounding compounding, int frequency = 1)
- => (Rate, Compounding, Frequency) = (rate, compounding, frequency);
-
- ///
- /// 计息因子(输入年化时间 t)。
- ///
- /// - :decimal 精确运算。
- /// - / :闭式(double 计算后回 decimal),
- /// 满足通用定价;若要 decimal 精度的离散重置日复利,请用 Simple 按段计息并滚动本金。
- ///
- ///
- public decimal CompoundFactor(decimal t)
- => Compounding switch
- {
- Compounding.Simple => 1m + Rate * t,
- Compounding.Compounded => (decimal)Math.Pow((double)(1m + Rate / Frequency), (double)(Frequency * t)),
- Compounding.Continuous => (decimal)Math.Exp((double)(Rate * t)),
- _ => throw new ArgumentOutOfRangeException(nameof(Compounding))
- };
-
- /// 利息 = 本金 × (因子 − 1)。
- public decimal Interest(decimal principal, decimal t)
- => principal * (CompoundFactor(t) - 1m);
-}
diff --git a/Framework/YLErp.Core/Interest/SwapInterest.cs b/Framework/YLErp.Core/Interest/SwapInterest.cs
deleted file mode 100644
index ddec05bf..00000000
--- a/Framework/YLErp.Core/Interest/SwapInterest.cs
+++ /dev/null
@@ -1,291 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using YLErp.Core.Interest;
-
-namespace YLErp.Derivatives.Interest;
-
-// ─────────────────────────────────────────────────────────────────────────────
-// 词汇表(本文件只允许出现下列用词,同一概念不得出现第二种叫法)
-//
-// 概念 唯一用词 与既有代码的对应
-// ───────────────────────────────────────────────────────────────────
-// 区间起点/终点 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}";
-}
-
-///
-/// 收益互换(TRS)利息腿计算——纯函数。
-///
-/// 层级关系:计息数学(单利/复利/连续复利)是通用金融原语,已抽到
-/// (YLErp.Core.Interest,与互换无关,谁都能用)。
-/// 本类只负责 TRS 特有的会计态:每日先舍入再乘天数的对账口径、平仓缩放、
-/// 跨日滚动本金、预付金/授信模式——这些不是"利率数学",不应塞进通用原语。
-///
-/// 设计约束:
-/// 1. 无副作用——不读写 flowEvent、不取利率、不连库、不碰任何共享可变状态;
-/// 2. 同 input → 同 output,结果仅通过返回值流出;
-/// 3. 正交轴(算头算尾 / 单利复利 / 平仓 / 待实现收益)各自独立,互不耦合;
-/// 4. 调用方负责「取利率 + 构造日期区间 + 落库」,本类只算账。
-/// 由此,corp action 调整价格 / 数量时只需把新的 principal 与 rate 喂入,计息逻辑一行不动。
-///
-/// 领域口径:本系统利息腿是单边融资腿,任一时点只有一个生效利率(见 SwapDealService 的
-/// floateRate 单一入参),不存在 IRS 那种 fixedRate − floatingRate 轧差;
-/// 权益腿盈亏与平仓费用属三腿汇总层,不在本类职责内。
-///
-/// TRS 的"复利"是离散重置日复利:按重置日切段,每段用
-/// 计息、段末把利息滚入本金——本质就是单利按段叠加,decimal 精度无损,无需 Pow/Exp
-/// (见 )。所以本类不另立复利方法,计息只有一种,区别在于"是否滚动本金"。
-///
-/// 为何不复用 Qdp 的 IDayCount:
-/// a. 语义——Qdp 的 DaysInPeriod = end − start 是写死的半开区间,只能表达四种算头算尾中的一种;
-/// b. 精度——Qdp 返回 double 年化系数,本系统 decimal 且日息先 Round 再乘天数,
-/// Round(P*r/365, 11) * n ≠ P*r*(n/365),与 Excel 对账口径不同;
-/// c. 依赖方向——Qdp 用自有 Date 类型,引入会让 YLErp.Core 反向依赖定价库。
-///
-public static class SwapInterest
-{
- /// 默认舍入精度位数(历史值;生产融资腿与保证金腿均用 FundingLegPrecision=12)。
- public const int Precision = 11;
-
- /// 资金腿与保证金腿的生产计息精度(落库/对账均以 12 位为准)。
- /// 提升至公共常量,消除 SwapDealService 与 SimpleInterestAccrual 的重复定义。
- public const int FundingLegPrecision = 12;
-
- /// 年化天数常量(合约字段存的是 int,故不用 enum)。
- public const int Act365 = 365;
-
- public const int Act360 = 360;
-
- /// 应计天数。边界规则由日期区间表达,计息函数内不再出现 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;
- }
-
- /// 把 TRS 年化利率收敛为通用利率原语。
- /// TRS 计息按段均为单利——离散重置日复利靠"段末把利息滚入本金"实现,不引入 Compounded 闭式。
- public static InterestRate ToInterestRate(decimal annualRate)
- => new(annualRate, Compounding.Simple);
-
- /// 单利:计息基数固定,每日利息相同,无逐日循环。
- public static InterestResult AccrueSimple(
- AccrualContext ctx,
- decimal principal,
- decimal rate,
- DateTime startDate,
- DateTime endDate,
- AccrualBoundary boundary)
- {
- var days = AccrualDays(startDate, endDate, boundary);
- var daily = Round(principal * rate / ctx.AnnualDays, ctx.Precision);
- return new InterestResult(Round(daily * days, ctx.Precision), daily);
- }
-
- ///
- /// 离散重置日复利(compounded-in-arrears):按重置日切段,段间把累计利息并入计息基数(滚动本金)。
- /// 每段计息即 得到的 (无逐日循环);
- /// 重置日是唯一并本金的地方。复利与单利只有"是否滚动本金"这一个区别。
- ///
- /// 此模型即 OIS / SOFR / FR007 的 compounded-in-arrears:每个子区间取一次定盘 rᵢ、增长因子
- /// 1 + rᵢ·yfᵢ,段末把 accrued 折进下一期本金——比闭式
- /// 更贴合 FR007 约定且 decimal 无损。注意:它不是 InterestRate 的 Compounded 闭式分支(TRS 下该分支为死路径)。
- ///
- /// 每段可有独立利率(FR007 浮动逐段不同),由适配器按段取定盘后封装为
- /// 传入——取价永远在编排层,原语只吃一个数(与 QuantLib/Strata 同范)。
- /// 必须含一条 ResetDate ≤ startDate 的起始利率。
- ///
- /// trace:经 发射 Start / ResetBefore·ResetAfter(利率切换时) /
- /// Rollover(段末并本金) / End,完整记录"重置日前后、利率切换、本金增加前后"。纯函数保持无日志依赖。
- ///
- /// 重置日 → 该段生效利率(段起点 = 重置日)。
- public static InterestResult AccrueCompoundInArrears(
- AccrualContext ctx,
- decimal principal,
- IReadOnlyList<(DateTime ResetDate, decimal Rate)> resetSchedule,
- DateTime startDate,
- DateTime endDate,
- AccrualBoundary boundary)
- {
- var trace = ctx.Trace;
- trace?.MarkStart(startDate, endDate, boundary, ctx.AnnualDays, annualized: false);
-
- var basis = principal;
- decimal accrued = 0m, accruedToday = 0m;
-
- var segEnds = (resetSchedule ?? Array.Empty<(DateTime, decimal)>())
- .Select(s => s.ResetDate)
- .Where(d => d > startDate && d < endDate)
- .OrderBy(d => d)
- .Append(endDate)
- .ToArray();
-
- // 段起点生效利率:取"不晚于该段起点"的最近一次重置利率。
- decimal RateAt(DateTime segStart)
- => (resetSchedule ?? Array.Empty<(DateTime, decimal)>())
- .Where(s => s.ResetDate <= segStart)
- .OrderByDescending(s => s.ResetDate)
- .Select(s => s.Rate)
- .FirstOrDefault();
-
- var segStart = startDate;
- var segIncludeStart = boundary.IncludeStart;
- var prevRate = RateAt(startDate);
-
- foreach (var segEnd in segEnds)
- {
- var segRate = RateAt(segStart);
- var rateSwitched = segStart != startDate && segRate != prevRate;
- if (rateSwitched) trace?.ResetBefore(segStart, prevRate, basis);
-
- var segBoundary = AccrualBoundary.Of(segIncludeStart, segEnd == endDate && boundary.IncludeEnd);
- var seg = AccrueSimple(ctx, basis, segRate, segStart, segEnd, segBoundary);
-
- accrued += seg.Accrued;
- accruedToday = seg.AccruedToday;
- var newBasis = basis + seg.Accrued; // 仅在重置日并本金
- // 重置日本身不动本金:RESET↑ 的本金应是"重置边界基数"(basis),与 RESET↓ 一致;
- // 段末并本金后的 newBasis 由下方的 ROLLOVER 单独表达,避免重复/误导。
- if (rateSwitched) trace?.ResetAfter(segStart, segRate, basis);
-
- trace?.Rollover(segEnd, seg.Accrued, newBasis);
- basis = newBasis;
- prevRate = segRate;
- segStart = segEnd;
- segIncludeStart = false; // 后续段不算头
- }
-
- var result = new InterestResult(accrued, accruedToday);
- trace?.MarkEnd(result.Accrued, result.AccruedToday);
- return result;
- }
-
- ///
- /// 固定利率复利便捷重载(每段同一 rate),向后兼容旧调用方。
- /// 内部把 resetDates 展平为"每段同率"的 schedule 后委托主方法。
- ///
- public static InterestResult AccrueCompoundInArrears(
- AccrualContext ctx,
- decimal principal,
- decimal rate,
- DateTime startDate,
- DateTime endDate,
- AccrualBoundary boundary,
- IReadOnlyList? resetDates = null)
- {
- var schedule = new List<(DateTime, decimal)> { (startDate, rate) };
- if (resetDates != null)
- foreach (var d in resetDates)
- if (d > startDate && d < endDate)
- schedule.Add((d, rate));
- return AccrueCompoundInArrears(ctx, principal, schedule, startDate, endDate, boundary);
- }
-
- ///
- /// 平仓(Unwind)缩放——全仓唯一缩放点,物理上杜绝 unwindPercent 被重复相乘。
- /// 全平即 unwindPercent = 1,不另设方法。
- ///
- /// 已实现 / 未实现边界:传入的 是平仓前仍「未实现(unrealized)」的
- /// 累计应计利息;本方法按比例缩放后返回「平仓后剩余未实现」部分,并扣除历史累计「已实现(realized)」
- /// 的 。被平仓比例 unwindPercent 对应的那一份 accrued,
- /// 即在此刻「实现(realized)」,由调用方记入 realizedInterest。
- ///
- /// 平仓前累计应计利息(未实现)。
- ///
- /// 平仓比例(0~1,实为 ratio 非百分数)。
- /// 对应既有字段 closePercent;分母口径必须与传入 所依据的持仓数量一致——
- /// 是「本次计算依据的持仓」而非「初始建仓」,历史缺陷正来自这个歧义。
- ///
- /// 已实现利息累计(legacy 字段 consumedInterest):历史各次 unwind 已确认、应从剩余未实现中扣除的部分。
- /// 舍入精度。⚠️ 默认 11(Precision),资金腿务必显式传 =12。
- public static InterestResult ApplyUnwind(
- InterestResult accrued,
- decimal unwindPercent,
- decimal realizedInterest = 0m,
- int precision = Precision)
- {
- var remaining = 1m - unwindPercent;
- return new InterestResult(
- Round(accrued.Accrued * remaining - realizedInterest, precision),
- Round(accrued.AccruedToday * remaining, precision));
- }
-
- /// 待实现收益余额滚动(预付金 / 授信模式)。
- /// 上期待实现收益余额。
- /// 本期新增。
- /// 本期 unwind 应扣减(即本期实现的份额)。
- public static decimal AccrueUnrealized(
- decimal openingUnrealized,
- decimal todayIncome,
- decimal unwindDeduction,
- int precision = Precision)
- => Round(openingUnrealized + todayIncome - unwindDeduction, precision);
-
- /// 统一舍入:MidpointRounding.AwayFromZero。所有计息路径收口到此处,避免散落的 Math.Round 不一致。
- public static decimal Round(decimal value, int precision)
- => Math.Round(value, precision, MidpointRounding.AwayFromZero);
-}
diff --git a/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs b/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs
index 625d2254..6a1d8984 100644
--- a/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.cs
@@ -7,8 +7,6 @@ using YLErp.DBModels;
using YLErp.DBModels.Enums;
using YLErp.Modules.SwapModule;
using YLErp.Modules.SwapModule.Accrual;
-using YLErp.Derivatives.Interest;
-using YLErp.Core.Interest;
namespace UnitTestProject.Modules.SwapModule.Accrual
{
diff --git a/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs b/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
index 305fe802..2f6aed6e 100644
--- a/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
@@ -1,6 +1,5 @@
using Newtonsoft.Json;
using YLErp;
-using YLErp.Derivatives.Interest;
using YLErp.Modules.SwapModule;
using YLErp.Modules.SwapModule.Accrual;
diff --git a/UnitTestProject/Modules/SwapModule/Accrual/SwapInterest_CompoundInArrears_RolloverTimingTests.cs b/UnitTestProject/Modules/SwapModule/Accrual/SwapInterest_CompoundInArrears_RolloverTimingTests.cs
deleted file mode 100644
index 816be349..00000000
--- a/UnitTestProject/Modules/SwapModule/Accrual/SwapInterest_CompoundInArrears_RolloverTimingTests.cs
+++ /dev/null
@@ -1,144 +0,0 @@
-using System.Text.RegularExpressions;
-using YLErp.Core.Interest;
-using YLErp.Derivatives.Interest;
-
-namespace UnitTestProject.Modules.SwapModule.Accrual
-{
- ///
- /// 聚焦测试:AccrueCompoundInArrears 的「本金滚存时机」必须符合确认书规定。
- /// 核心不变量:本金只允许在重置日/段末滚入利息,非重置日不得资本化。
- ///
- /// 与原草稿的关键区别:本版直接通过 AccrualTrace 断言不变量。
- /// 真实实现在每次段末会发出 ROLLOVER 事件并记录 newBasis(见 SwapInterest.cs:215 /
- /// AccrualTrace.Rollover),因此「非重置日是否发生资本化」是可程序化验证的,
- /// 无需仅靠总利息回归来保护(原草稿的自我怀疑"无法断言计息基数"已不成立)。
- ///
- [TestClass]
- public class SwapInterest_CompoundInArrears_RolloverTimingTests
- {
- private const int FundingLegPrecision = 12;
- private const int AnnualDays = 365;
-
- ///
- /// 场景:14天窗口,第8天(01-08)重置一次,利率恒定 3.65%(日利率 0.01%)。
- /// 验证:
- /// (1) 总利息 = 1400.49(第1期700 + 第2期700.49);
- /// (2) ROLLOVER 仅发生在重置日(01-08)与窗口终点(01-15),非重置日(如01-03)绝不滚存;
- /// (3) 重置日 ROLLOVER 的 newBasis = 原始本金 + 前7天利息 = 1,000,700,
- /// 证明第1段计息基数恒为原始本金、段内未提前资本化。
- ///
- [TestMethod]
- public void InterestPrincipal_ShouldRollOnlyOnResetDays_NotOnNonResetDays()
- {
- var startDate = new DateTime(2026, 1, 1);
- var endDate = new DateTime(2026, 1, 15);
-
- var principal = 1_000_000m;
- var rate = 0.0365m;
- var resetDates = new List { new DateTime(2026, 1, 8) };
- var trace = new AccrualTrace();
- var ctx = new AccrualContext(AnnualDays, FundingLegPrecision, trace);
-
- var result = SwapInterest.AccrueCompoundInArrears(
- ctx,
- principal,
- rate,
- startDate,
- endDate,
- AccrualBoundary.Both,
- resetDates);
-
- Assert.AreEqual(1400.49m, Math.Round(result.Accrued, 2));
-
- var rolloverDates = trace.Entries
- .Where(e => e.Step == AccrualTraceEvent.Rollover)
- .Select(e => e.Date)
- .ToList();
-
- var allowed = resetDates.Concat(new[] { endDate }).OrderBy(d => d).ToList();
- CollectionAssert.AreEqual(allowed, rolloverDates.OrderBy(d => d).ToList());
-
- Assert.IsFalse(rolloverDates.Contains(new DateTime(2026, 1, 3)),
- "非重置日发生了本金滚存,违反确认书规定");
-
- var resetRollover = trace.Entries
- .First(e => e.Step == AccrualTraceEvent.Rollover && e.Date == new DateTime(2026, 1, 8));
- var newBasis = ParseNewBasis(resetRollover.Line);
- Assert.AreEqual(principal + 700m, newBasis,
- "重置日滚入的本金应为原始本金 + 前段利息,证明段内未提前资本化");
- }
-
- ///
- /// 极端场景:startDate = endDate(1天),无重置日。
- /// 期望利息 = 本金 × 日利率 = 1,000,000 × 0.0365/365 = 100。
- /// 且唯一 ROLLOVER 必须落在窗口终点(=startDate),无任何内部重置滚存。
- ///
- [TestMethod]
- public void SingleDay_ShouldNotRollInterest_NoResetDay()
- {
- var date = new DateTime(2026, 1, 1);
- var principal = 1_000_000m;
- var rate = 0.0365m;
- var trace = new AccrualTrace();
- var ctx = new AccrualContext(AnnualDays, FundingLegPrecision, trace);
-
- var result = SwapInterest.AccrueCompoundInArrears(
- ctx,
- principal,
- rate,
- date,
- date,
- AccrualBoundary.Both);
-
- Assert.AreEqual(100m, Math.Round(result.Accrued, 2));
-
- var rolloverDates = trace.Entries
- .Where(e => e.Step == AccrualTraceEvent.Rollover)
- .Select(e => e.Date)
- .ToList();
- CollectionAssert.AreEqual(new[] { date }, rolloverDates.ToArray());
- }
-
- ///
- /// 段内无重置日:验证整段等同于单利,且不发生任何内部滚存。
- /// 6天窗口(01-01..01-06)在7天重置周期内,Both 边界含两端 = 6 个计息日,
- /// 期望利息 = 本金 × 日利率 × 6 = 600。
- ///
- [TestMethod]
- public void WithinPeriod_NoRollover_ShouldMatchSimpleInterest()
- {
- var startDate = new DateTime(2026, 1, 1);
- var endDate = new DateTime(2026, 1, 6);
- var principal = 1_000_000m;
- var rate = 0.0365m;
- var trace = new AccrualTrace();
- var ctx = new AccrualContext(AnnualDays, FundingLegPrecision, trace);
-
- var result = SwapInterest.AccrueCompoundInArrears(
- ctx,
- principal,
- rate,
- startDate,
- endDate,
- AccrualBoundary.Both);
-
- // 计息天数必须用边界感知的 AccrualDays,不能拿 (end-start).Days(会少算1天)
- var days = SwapInterest.AccrualDays(startDate, endDate, AccrualBoundary.Both); // = 6
- var expected = Math.Round(principal * rate * days / AnnualDays, FundingLegPrecision, MidpointRounding.AwayFromZero);
- Assert.AreEqual(expected, Math.Round(result.Accrued, 10));
-
- var rolloverDates = trace.Entries
- .Where(e => e.Step == AccrualTraceEvent.Rollover)
- .Select(e => e.Date)
- .ToList();
- CollectionAssert.AreEqual(new[] { endDate }, rolloverDates.ToArray());
- }
-
- private static decimal ParseNewBasis(string line)
- {
- var m = Regex.Match(line, @"newBasis=([0-9.]+)");
- Assert.IsTrue(m.Success, $"ROLLOVER 行缺少 newBasis:{line}");
- return decimal.Parse(m.Groups[1].Value);
- }
- }
-}
diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs
index 34740fc2..2ff0f5ad 100644
--- a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs
+++ b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs
@@ -8,7 +8,6 @@ using YLErp.DBModels.Enums;
using YLErp.Modules.SwapModule;
using YLErp.Modules.SwapModule.Accrual;
using YLErp.Modules.SwapModule.Margin;
-using YLErp.Derivatives.Interest;
namespace UnitTestProject.Modules.SwapModule.Margin
{
diff --git a/YLErpDAL/Modules/SwapModule/ARCHITECTURE.md b/YLErpDAL/Modules/SwapModule/ARCHITECTURE.md
index 1ddf3913..669e64ff 100644
--- a/YLErpDAL/Modules/SwapModule/ARCHITECTURE.md
+++ b/YLErpDAL/Modules/SwapModule/ARCHITECTURE.md
@@ -62,6 +62,14 @@ SwapModule/
│ ├── DirectionRatio 方向因子(LongShort + ReceivePay)
│ └── PositionValueCalc 持仓价值汇总(利息端 + 浮动端)
│
+├── Accrual/ 计息(生产实现,自洽域)
+│ ├── InterestMath 共用数学:Round/AccrualDays/FundingLegPrecision + AccrualBoundary/InterestResult
+│ ├── SimpleInterestAccrual 单利纯函数(AccrueEod 单日 + AccruePeriod 多日)
+│ ├── CompoundInterestAccrual 复利纯函数(EodBasis/AccrueEod/AccruePeriod)
+│ ├── AccrualPolicy 计息政策(算头算尾/单复利/重置周期/年化)
+│ ├── AccrualTrace 计息 trace 收集器(SwapCalcTrace.Write 常驻落盘)
+│ └── FundingLegRate all-in 利率值对象
+│
├── SwapDealService.cs 盘中平仓/互换主逻辑
├── SwapEodPositionService.cs EOD 日终归档主逻辑
├── SwapDealIndexFixer.cs SwapDealService 专用取价器(委托 TryGetFloatRate)
@@ -72,12 +80,16 @@ SwapModule/
```
Interest/
-├── SwapInterest.cs 纯函数库(AccrueSimple/AccrueCompound/ApplyUnwind)
├── IIndexFixer.cs 取价接口
-├── IndexFixerBase.cs 取价日计算工具
-└── Fr007IndexFixer.cs FR007 取价生产实现(调 EodPriceQueryService)
+└── IndexFixerBase.cs 取价日计算工具
```
+> 注:① `Fr007IndexFixer.cs`(FR007 取价生产实现)在 SwapModule 下,不在本目录。
+> ② 2026-08 计息类型(InterestMath/AccrualBoundary/InterestResult/AccrualTrace)已整体迁至 SwapModule/Accrual/,
+> Core 不再持有计息实现。原 Core 层 SwapInterest 的算法方法(AccrueSimple/AccrueCompoundInArrears/ApplyUnwind/
+> AccrueUnrealized/ToInterestRate)与 AccrualContext/InterestRate 从未接线(生产走 Accrual/ 目录),作为孤儿死代码删除——
+> 其舍入/rollover 口径与生产实现已分叉,若将来重建须先补对账测试,勿凭记忆复原。
+
## InterestModeEnum(显式赋值,DB 契约)
```
diff --git a/YLErpDAL/Modules/SwapModule/Accrual/AccrualPolicy.cs b/YLErpDAL/Modules/SwapModule/Accrual/AccrualPolicy.cs
index 9f7e4ec4..324a373b 100644
--- a/YLErpDAL/Modules/SwapModule/Accrual/AccrualPolicy.cs
+++ b/YLErpDAL/Modules/SwapModule/Accrual/AccrualPolicy.cs
@@ -1,5 +1,3 @@
-using YLErp.Derivatives.Interest;
-
namespace YLErp.Modules.SwapModule.Accrual;
///
@@ -11,7 +9,7 @@ namespace YLErp.Modules.SwapModule.Accrual;
///
public sealed class AccrualPolicy
{
- /// 算头算尾约定(复用 SwapInterest 已有的 AccrualBoundary,物理上杜绝 calcFirst/calcLast 传反)。
+ /// 算头算尾约定(AccrualBoundary,物理上杜绝 calcFirst/calcLast 传反)。
public AccrualBoundary Convention { get; }
/// 是否复利(利滚利)。来自 DB 的 InterestTypeEnum;单利=false,复利=true。
diff --git a/YLErpDAL/Modules/SwapModule/Accrual/AccrualState.cs b/YLErpDAL/Modules/SwapModule/Accrual/AccrualState.cs
deleted file mode 100644
index 3ba2c6fb..00000000
--- a/YLErpDAL/Modules/SwapModule/Accrual/AccrualState.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using YLErp.DBModels;
-
-namespace YLErp.Modules.SwapModule.Accrual;
-
-///
-/// 融资腿逐日计息的跨日状态(不可变值对象)。
-/// 这是"待实现利息"在日间滚动的快照,区别于已落库的 swap_flow_event。
-///
-/// 旧字段 → 领域命名映射(DB 列不可改,仅在边界处适配;本类内部一律用下列自描述名):
-///
-/// - TdInterestPrincipal逐日滚动的计息本金 →
-/// - InterestIncomeSum累计待实现利息 →
-/// - consumedInterest历史已实现利息(legacy) →
-/// - ValueDate快照截至日 → (EOD 续接起算日,Bug C / 5-11 跳过需据此判断从哪天接续)。
-///
-///
-public readonly struct AccrualState
-{
- /// 用于计算当日利息的计息本金。单利=名义本金基数;复利=本金+累计利息。
- public decimal AccrualPrincipal { get; }
-
- /// 累计待实现(未平仓)利息。
- public decimal UnrealizedInterest { get; }
-
- /// 历史各次平仓已确认的已实现利息,从剩余待实现中扣除。
- public decimal RealizedInterest { get; }
-
- /// 快照截至日(来自 eod_swap_position.ValueDate)。编排层据此判断计息区间起点,避免 5-11 等"跳过日"误重算。
- public DateTime ValueDate { get; }
-
- public AccrualState(decimal accrualPrincipal, decimal unrealizedInterest, decimal realizedInterest, DateTime valueDate)
- => (AccrualPrincipal, UnrealizedInterest, RealizedInterest, ValueDate) = (accrualPrincipal, unrealizedInterest, realizedInterest, valueDate);
-
- /// 向后兼容:未携带快照日期时(如纯内存构造)用默认日。
- public AccrualState(decimal accrualPrincipal, decimal unrealizedInterest, decimal realizedInterest)
- : this(accrualPrincipal, unrealizedInterest, realizedInterest, default) { }
-
- /// 空状态(新开仓首个计息日之前)。
- public static readonly AccrualState Zero = new(0m, 0m, 0m);
-
- ///
- /// 从上一日日终归档 适配(边界适配:DB 列名 → 领域名)。
- /// 仅映射计息状态;名义本金基数 / 平仓比例 / 已实现利息等由调用方另行传入。
- ///
- public static AccrualState FromPreviousEod(eod_swap_position previousEod)
- => previousEod == null || previousEod.id == 0
- ? Zero
- : new AccrualState(previousEod.TdInterestPrincipal, previousEod.InterestIncomeSum, 0m, previousEod.ValueDate);
-}
diff --git a/Framework/YLErp.Core/Interest/AccrualTrace.cs b/YLErpDAL/Modules/SwapModule/Accrual/AccrualTrace.cs
similarity index 94%
rename from Framework/YLErp.Core/Interest/AccrualTrace.cs
rename to YLErpDAL/Modules/SwapModule/Accrual/AccrualTrace.cs
index 108b2692..271a0bc4 100644
--- a/Framework/YLErp.Core/Interest/AccrualTrace.cs
+++ b/YLErpDAL/Modules/SwapModule/Accrual/AccrualTrace.cs
@@ -1,14 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using YLErp.Derivatives.Interest;
-
-namespace YLErp.Core.Interest;
+namespace YLErp.Modules.SwapModule.Accrual;
///
-/// 计息过程追踪收集器(值对象,非日志)。
+/// 计息过程追踪收集器(值对象,非日志)。2026-08 自 Core 层(YLErp.Core.Interest)迁入 DAL,
+/// 与 Simple/CompoundInterestAccrual、AccrualBoundary 同处一域,Core 不再持有计息类型。
///
-/// 为什么是收集器而不是日志调用:计息数学(SwapInterest / FundingLegAccrual)必须保持纯函数、
+/// 为什么是收集器而不是日志调用:计息数学(Simple/CompoundInterestAccrual)必须保持纯函数、
/// 可单测、不依赖 NLog;但按工程铁律,关键路径日志须无条件常驻落盘(出问题时事后翻日志定位,不能依赖开关)。
/// 折中:纯函数把"发生了什么"记录为结构化条目写入本收集器,由适配器(IO 边界)统一经
/// SwapCalcTrace.Write 常驻落盘。落盘职责归一处,计息代码零日志依赖、保持干净。
diff --git a/YLErpDAL/Modules/SwapModule/Accrual/CompoundInterestAccrual.cs b/YLErpDAL/Modules/SwapModule/Accrual/CompoundInterestAccrual.cs
index 9418aa46..eddbb07a 100644
--- a/YLErpDAL/Modules/SwapModule/Accrual/CompoundInterestAccrual.cs
+++ b/YLErpDAL/Modules/SwapModule/Accrual/CompoundInterestAccrual.cs
@@ -1,6 +1,3 @@
-using YLErp.Core.Interest;
-using YLErp.Derivatives.Interest;
-
namespace YLErp.Modules.SwapModule.Accrual;
///
@@ -9,7 +6,7 @@ namespace YLErp.Modules.SwapModule.Accrual;
///
public static class CompoundInterestAccrual
{
- private const int Precision = SwapInterest.FundingLegPrecision;
+ private const int Precision = InterestMath.FundingLegPrecision;
/// 复利日终计息基数(单一真相源,纯函数与调用方共用):
/// 重置日 = notional + 累计利息×剩余比例(利息并入本金);非重置日 = priorNotional(昨日滚动基数)。
@@ -52,8 +49,8 @@ public static class CompoundInterestAccrual
var totalAccrued = priorAccrued * unwindFraction + dayInterest;
var result = new InterestResult(
- SwapInterest.Round(totalAccrued, Precision),
- SwapInterest.Round(tdInterest, Precision));
+ InterestMath.Round(totalAccrued, Precision),
+ InterestMath.Round(tdInterest, Precision));
trace?.Day(0, eodDate, allInRate, displayBasis, dayInterest, totalAccrued);
trace?.MarkEnd(result.Accrued, result.AccruedToday);
@@ -107,7 +104,7 @@ public static class CompoundInterestAccrual
var segIncludeStart = (si == 0) ? boundary.IncludeStart : true;
var segIncludeEnd = isLastSegment ? boundary.IncludeEnd : false;
- var days = SwapInterest.AccrualDays(segmentRates[si].StartDate, segEnd,
+ var days = InterestMath.AccrualDays(segmentRates[si].StartDate, segEnd,
AccrualBoundary.Of(segIncludeStart, segIncludeEnd));
if (days <= 0) continue;
@@ -124,8 +121,8 @@ public static class CompoundInterestAccrual
accrued -= realizedInterest * unwindFraction;
var result = new InterestResult(
- SwapInterest.Round(accrued, Precision),
- SwapInterest.Round(accrued, Precision));
+ InterestMath.Round(accrued, Precision),
+ InterestMath.Round(accrued, Precision));
trace?.MarkEnd(result.Accrued, result.AccruedToday);
return result;
}
diff --git a/YLErpDAL/Modules/SwapModule/Accrual/InterestMath.cs b/YLErpDAL/Modules/SwapModule/Accrual/InterestMath.cs
new file mode 100644
index 00000000..54b5de29
--- /dev/null
+++ b/YLErpDAL/Modules/SwapModule/Accrual/InterestMath.cs
@@ -0,0 +1,104 @@
+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);
+}
diff --git a/YLErpDAL/Modules/SwapModule/Accrual/SimpleInterestAccrual.cs b/YLErpDAL/Modules/SwapModule/Accrual/SimpleInterestAccrual.cs
index fb1378ab..ac184645 100644
--- a/YLErpDAL/Modules/SwapModule/Accrual/SimpleInterestAccrual.cs
+++ b/YLErpDAL/Modules/SwapModule/Accrual/SimpleInterestAccrual.cs
@@ -1,6 +1,3 @@
-using YLErp.Core.Interest;
-using YLErp.Derivatives.Interest;
-
namespace YLErp.Modules.SwapModule.Accrual;
///
@@ -9,7 +6,7 @@ namespace YLErp.Modules.SwapModule.Accrual;
///
public static class SimpleInterestAccrual
{
- private const int Precision = SwapInterest.FundingLegPrecision;
+ private const int Precision = InterestMath.FundingLegPrecision;
///
/// 单利日终计息(替换 CalcDailySimpleInterestByEod 的纯数学部分)。
@@ -38,8 +35,8 @@ public static class SimpleInterestAccrual
var totalAccrued = priorAccrued + dayInterest;
var result = new InterestResult(
- SwapInterest.Round(totalAccrued, Precision),
- SwapInterest.Round(tdInterest, Precision));
+ InterestMath.Round(totalAccrued, Precision),
+ InterestMath.Round(tdInterest, Precision));
trace?.Day(0, eodDate, allInRate, displayBasis, dayInterest, totalAccrued);
trace?.MarkEnd(result.Accrued, result.AccruedToday);
@@ -85,7 +82,7 @@ public static class SimpleInterestAccrual
var includeStart = effectiveStart == startDate ? boundary.IncludeStart : true;
var isLastSegment = si == segmentRates.Count - 1;
var segBoundary = AccrualBoundary.Of(includeStart, isLastSegment && boundary.IncludeEnd);
- var days = SwapInterest.AccrualDays(effectiveStart, segEnd, segBoundary);
+ var days = InterestMath.AccrualDays(effectiveStart, segEnd, segBoundary);
if (days <= 0) { segStart = segEnd; continue; }
var dailyRate = isAnnualized ? segmentRates[si].Rate / annualDays : segmentRates[si].Rate;
@@ -98,8 +95,8 @@ public static class SimpleInterestAccrual
}
var result = new InterestResult(
- SwapInterest.Round(accrued, Precision),
- SwapInterest.Round(accruedUnscaled, Precision));
+ InterestMath.Round(accrued, Precision),
+ InterestMath.Round(accruedUnscaled, Precision));
trace?.MarkEnd(result.Accrued, result.AccruedToday);
return result;
}
diff --git a/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs b/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs
index f6e1a1ec..40bc9571 100644
--- a/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs
@@ -1,8 +1,5 @@
-using System;
-using System.Collections.Generic;
using System.Text;
-using YLErp.Core.Interest;
-using YLErp.Helpers;
+using YLErp.Modules.SwapModule.Accrual;
namespace YLErp.Modules.SwapModule
{
diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
index 7990a964..7e69e78b 100644
--- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs
@@ -3,7 +3,6 @@ using Newtonsoft.Json;
using YLErp.BLL;
using YLErp.BLL.Eod;
using YLErp.DBModels.Enums;
-using YLErp.Core.Interest;
using YLErp.Derivatives.Interest;
using YLErp.Helpers;
using YLErp.Modules.DataProviderModule;
@@ -50,8 +49,8 @@ namespace YLErp.Modules.SwapModule
return SaveSwapDealInternal(unwindData, eventType, clientCashId, eventResason, approve);
}
- // 待实现利息会进入 decimal(30,12) 日终快照;精度常量统一引用 SwapInterest.FundingLegPrecision,消除重复定义。
- private const int InterestCalculationPrecision = SwapInterest.FundingLegPrecision;
+ // 待实现利息会进入 decimal(30,12) 日终快照;精度常量统一引用 InterestMath.FundingLegPrecision,消除重复定义。
+ private const int InterestCalculationPrecision = InterestMath.FundingLegPrecision;
// 客户现金在 SaveSwapDeal 之前创建,手工结算必须先收敛流水并重算汇总金额。
private void NormalizeManualSettlementAmounts(UnwindData unwindData, int eventType, string eventReason)
@@ -1456,7 +1455,7 @@ namespace YLErp.Modules.SwapModule
SwapCalcTrace.Write(interestTrace);
// flowEvent.InterestPrincipal:当日计息基数(已按平仓比例缩放)——下游 EOD 用它播种次日 TdInterestPrincipal。
- // 复用 CompoundEodBasis 单一真相源(与 AccrueCompoundEod 内部同一公式)。
+ // 复用 CompoundEodBasis 单一真相源(与 CompoundInterestAccrual.AccrueEod 内部同一公式,见其 EodBasis 调用)。
flowEvent.InterestPrincipal = CompoundInterestAccrual.EodBasis(
isResetDay, posiPrincipal, preEodPosition.InterestProfitSum, remainingFraction,
preEodPosition.TdInterestPrincipal) * closePercent;
diff --git a/corp-action-refactor-proposal.md b/corp-action-refactor-proposal.md
index b21d529b..6ec51e4e 100644
--- a/corp-action-refactor-proposal.md
+++ b/corp-action-refactor-proposal.md
@@ -99,7 +99,7 @@ curretEod.PosiQuantity = qty < 0 ? 0 : Math.Abs(qty);
| **数量递推** | `SwapEodPositionService.cs:1897`(qty 递推)、`:1723`/`:1905`(无事件日结转)、`:1631`(首次归档) | 需新增「公司行为数量」第三来源项 | `SwapEodPositionService.cs` |
| **`TdChangedQty`** | 定义 `EodSwapPosition.cs:300`(DisplayName "当日公司行为数量");唯一赋值 `SwapEodPositionService.cs:1650`(恒=0) | 挂进 :1897 递推式(与 `TdCloseQty`:1942 对称),否则与 `PosiQuantity` 永久不自洽 | `SwapEodPositionService.cs` |
| **成本均价** | `SwapEodPositionService.cs:1926-1936`(加权重算,TRS 无 `CostPrice` 字段,等价字段 `PosiGrossPrice`/`PosiNetPrice`) | 送股无成交金额(分子+0、分母增)→ 走 `:1912 else if` 分支价不摊薄,污染盯市;配股有现金需加 `RationedSharesAmount×Price` | `SwapEodPositionService.cs:1912-1937` |
-| **计息基准** | `SwapDealService.cs:893 CalcNotionalByMode`(五种模式分流)、`:1372` `dynomicPrincipal`、`:2384 InterestPrincipalFix` 仅平仓递减 | `posiLong/Short` 经 `PosiNotionalValue` 可自动跟随;**`InterestPrincipalFix` 是独立存量,与数量解耦**,配股缴款需新写入点 | `SwapDealService.cs` |
+| **计息基准** | mode 分流已重构为 `FundingLegs/FundingLegStrategyFactory`(原 `SwapDealService CalcNotionalByMode` 已删;`dynomicPrincipal` 亦随重构消失)、`InterestPrincipalFix` 仅平仓递减 | `posiLong/Short` 经 `PosiNotionalValue` 可自动跟随;**`InterestPrincipalFix` 是独立存量,与数量解耦**,配股缴款需新写入点 | `SwapDealService.cs` + `FundingLegs/` |
| **盯市盈亏** | `SwapEodPositionService.cs:1657/1730/1815/2019`(4 份同构副本 `PosiMtmPnL`)、`:1713 GetSwapValuationPrice`(取除权后价) | 数量突变日若 `PosiQuantity`/`PosiGrossPrice` 未同步除权 → 虚假巨亏;**4 处副本必须一致改** | `SwapEodPositionService.cs` |
| **数据源** | `DividendService.cs:752 GetPositionAmount`(现成 `amount*(1+GiveShareAmount/10)` 送股调整)、`:730 GetRatio`(现成除权价公式) | SwapModule 未复用,需建调用边 | 新增 SwapModule→DividendService 调用 |