Commit Graph
199 Commits
Author SHA1 Message Date
hjhanandClaude Opus 4.7 07764e7e3f test(swap): 契约参考实现落地,引擎主力族盘中重放首次对齐确认书公式 oracle
ContractReferenceCalc 独立实现 §8a 确认书公式(∏/重置期切分/末段收口/
利率确定日=重置日上一营业日),禁止引用生产计息类防同源;4 例手算锚点
自验证(真实规模 5000万/2.05%/90天=75999.04 与玩具锚点)。

引擎对照首批 3 例全绿:mode9/mode2 × 复利 × "10" × T+0 部分平仓30%,
盘中重放与契约 oracle 容差 0.01 元内逐分吻合——盘中口径=确认书公式
自此有机器强制,后续补格直接复用 oracle 供给(TEST-MATRIX §7.5 已登记)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-16 12:19:28 +08:00
hjhan c94a3e8c97 test(swap): 铺 §7-1 守恒不变量护栏,守 EOD平仓后收盘×部分平仓 裸格(§6 空洞1)
新增 4 个免库纯内存测试(GetInterestsEntrySemanticsTest):①部分平仓 剩余持仓前递且 期初=剩余+平掉;②全平 剩余前递=0;③两次部分平仓 逐日守恒 期初-剩余前递=平掉;④ClosePercentMath 多次平仓累计比例=1-∏(1-各次剩余口径)纯数学。

守恒断言落在 preEod.PosiNotionalValue(剩余前递)而非 InterestPrincipal——后者在 InitSwapDealInterest:1164 后被利息算法重赋值(1197 复利特判/1266 单利路径),语义随入口/模式变,非稳健观测点;剩余前递由 CalcUnwindInterest:1084 在 preEod.id==0 时写入,与利息算法无关,最稳健。

用途:作为 GetInterests 早路由(Tier A)合入的前置护栏——funding-leg(mode2)不触发早路由 continue,故本类任何回归都直接暴露早路由对 EOD平仓后收盘路径的破坏。验证:YLErp_UNIT_TEST_SKIP_INITIALIZATION=1 dotnet test --filter Name~守恒 全过;整类仅 2 个旧复利测试因无 96 库失败,与本次无关。
2026-08-16 11:17:37 +08:00
hjhan 44e24b3172 refactor(margin): MarginModes 反转依赖 ConsTrade.InterestMarginModels 并删除 premiumModes 硬编码副本
- MarginModes.All/ForLinq/FixedAmountAndMargin 改为由框架常量 ConsTrade.InterestMarginModels 派生,消除 DAL 层独立重写集合(单一真源在 Core 层,本类退化为形态包装,避免新增保证金形态时口径分裂)
- SwapEodPositionService.premiumModes 局部 new List 字面量替换为 MarginModes.ForLinq(DAL 内部冗余副本消除)
- 重写 MarginModes.cs 过时的“复制 6 次/待收敛”注释,改为记录依赖方向与收敛历史;ForLinq 注释改为“派生自框架常量,仅做形态适配”
- 更新 MarginModesTest 类注释与一致性测试注释以反映派生关系
- 验证:YLErpDAL/UnitTestProject 编译 0 错误;MarginModesTest 5/5 通过
2026-08-16 10:47:45 +08:00
hjhan ef37c8e71d refactor(swap): 合并多空名义本金参数为单一 posiTotalNotional 并加固测试
- DealInterests 的 posiLongNotionalValue + posiShortNotionalValue 合并为 posiTotalNotional(调用点以 posiLongNotional+posiShortNotional 求和传入),净减一个参数

- SwapDealService / SwapEodPositionService / InterestCalcRequest 同步收敛多空死管道参数

- 19 个测试调用点适配新签名

- SwapEodPositionServiceIntegrationTest 参数计数断言由裸数字改为参数名集合断言(CollectionAssert.AreEquivalent,对增删/重排/改名敏感)
2026-08-14 17:30:46 +08:00
hjhan 4a3fee9292 refactor(swap)+test: 删 GetInterests/CalcSwapInterests 死参数 needPrice/grossPrice;补工厂→接缝映射钉子
死参数收口(另一半):
- SwapDealService.GetInterests 删 needPrice/grossPrice(体内零消费,2026-08 验证);
  InitSwapDealInterest.needPrice 同为死参数一并删
- SwapEodPositionService.CalcSwapInterests 签名+转发同步;两个 EOD 生产调用点
  (SaveAutoEodInterestPosition/SaveEodInterestPositionCopy) 重排实参;
  CalcEodPostCloseSettleInterests/GetIntradayUnwindInterests 委托同步
- 14 个测试文件 ~44 处直调点机械更新(8 处 override 签名 + 36 处调用实参)
- 注意:EOD 编排链(DealInterests→Save*家族)的 grossPrice(期初不含费价)有真实用途,保留未动

新增钉子:CalcEodPostCloseSettleInterests 工厂→接缝参数映射测试——
CalcSwapInterestsCapture 捕获 stub 断言 EodPostCloseSettle 的完整转发契约
(posi=平仓后剩余/closePosi=平掉额/恒1/settment:false/orginPv 等 11 项)。
该段位置转发含三个相邻同型 decimal,编译器不查错位,此测试兜底。

验证:定向 241 测试通过(含 T0/T1 Excel 验证期望值、EntrySemantics 精确值钉子——
任何 decimal 错位即红);全量 903=145失败/746通过/12跳过,与基线逐位一致。
2026-08-14 16:57:56 +08:00
hjhan 018d7e777f refactor(swap): InterestCalcRequest 参数对象——GetInterests 双显式入口收敛为单参数
新增 InterestCalcRequest(SwapModule 根):
- 承载 GetInterests 全部有效入参(needPrice/grossPrice 死参数不承载),私有构造;
- 仅两个场景工厂可构造:IntradayUnwind(平仓前剩余/实际平掉额/B语义比例)、
  EodPostCloseSettle(平仓后剩余/实际平掉额/恒1全额结息)——工厂形参名即场景语义,
  物理上防止两套名义本金语义混传(6fdc7d80 错账的温床);
- GetIntradayUnwindInterests / CalcEodPostCloseSettleInterests 签名收敛为单参数 req,
  生产调用点(GetUnwindInterests / SaveAutoEodWithCloseInterestPosition)改工厂构造;
- 原 20 参 GetInterests / 19 参 CalcSwapInterests 保留为底层实现与测试兼容层(十余处测试直调,不动)。

验证:定向 140 测试通过;全量 902(+3 字符化测试)= 145失败/745通过/12跳过,
与基线逐位一致,零回归。
2026-08-14 15:57:52 +08:00
hjhan b01b485ee4 test(swap): GetInterests 双入口语义字符化测试——并实测发现复利口径分歧
钉住三组现状(Step3 特判降级的前置回归网):
- 复利×mode2×部分平仓30%:盘中=0.036164835616(closePosi=平掉额300 全程重放);
  EOD=0.059041913305(preEod待实现0.05 + 平掉额末段增量0.009042,closePercent==1 分支)
  ⚠️ 两值不等=同一经济事件两种结息额的口径分歧,已留档待业务裁决(勿当既定正确)
- 单利×mode2:双入口数值留档(Console),断言非零
- 复利×mode9 全平(posi=0):兜底覆盖生效,结息额非零(兜底钉子)

基建复用 GetInterestsUnitTest_T0 口径(T+0、4/27起息、11算头算尾、FR007内存取价stub)。
2026-08-14 15:52:52 +08:00
hjhan e2431e9d44 refactor(accrual): 计息类型整体迁入 DAL——新增 Accrual/InterestMath,删 Core 未接线孤儿
搬迁(算法体逐字未动,仅换命名空间与归属):
- 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 已留警告)。
2026-08-14 15:31:04 +08:00
张名锐 11a7aabce5 Merge branch 'glms/feature/0812_zmr_divPower' into glms/feature/1.4.2 2026-08-14 14:51:56 +08:00
hjhan 58bd27c596 test(EQD-7004): 补自动平仓路径多次部分平仓不重复计入的实证测试——驱动真实 BondPaymentService.CalcPayment(reg_date 口径×当次平仓量 unwindQty),验证总额按登记日持仓分摊、不自洽多算 2026-08-14 14:24:23 +08:00
hjhan 896a5c3dcc test(EQD-7004): DividendEodNoDoubleCountTest 的 EodSvcStub.CalcBondPayment 由线性假公式改为桥接真实 BondPaymentService(reg_date 口径),使登记日全平/盘中互换端到端测试真正跑生产票息计算 2026-08-14 13:37:52 +08:00
hjhan 6a25e4a2d8 test(EQD-7004): 补 reg_date 口径端到端测试——EOD引擎按债权登记日计提,登记日T+1全平/部分平仓经GetPreEodDividendSum正确读取 2026-08-14 13:13:17 +08:00
hjhan 37b889c3bf test(dividend): 补全平端到端测试——登记日全平按交易场所规定不享有当日分红
全平场景端到端测试(DividendIn 由 GetPreEodDividendSum 真实算,非喂常量)。
登记日当日全平(盘中平仓→收盘持仓0),按各交易场所规定不享有登记日当日分红(股权登记日以收盘在册为准)。
系统行为正确:①DividendIn 读 T-1(=T日前待实现,正确不含当日);②EOD 全平 PosiQuantity=0 不计提当日。
断言:应得(T日前待实现)==实拿(DividendIn)(不享有当日符合规定)+ EOD 不计提当日。

合并原 d9a55fd1 + 830c72c4:前者误判'丢失缺陷',后者按业务规则修正为'不享有、系统正确'。
2026-08-14 12:49:45 +08:00
hjhan 9bc6344f18 test(dividend): 补端到端测试——盘中互换DividendIn真实算+保存+EOD,验证不重复不丢失
填补 MU_001 缺口:MU_001 的互换 DividendIn 是测试喂的常量,本测试由生产方法 GetPreEodDividendSum 真实算出(读 EOD 快照),再喂给 EOD——覆盖"预览算 DividendIn + EOD 扣减"完整链路。

验证:盘中收益互换(不扣持仓) → DividendIn=GetPreEodDividendSum(读T-1) → 保存 → EOD:TdCloseDividend 扣减 DividendIn(不重复) + 当日新计进 PosiDividendSum(不丢失) + 守恒(全程新计-全程实现=末尾PosiDividendSum)。

内存 stub(DealSvcStub+EodSvcStub),不连库,进 CI。
2026-08-14 10:28:10 +08:00
hjhan bdba5fbecd fix(EQD-7004): 补全分红登记日口径修复的多次付息累计测试与Trace日志
原修复 9df39491 仅覆盖单次登记日(4/3)的两个单点修复,本次补全 EQD-7004 的完整验证与排查手段:

- 测试 GLMS20260105_0006 新增 4 个场景:5 期 reg_date 过滤口径各自命中正确子集;五期票息累计=5x36160=180800;auto 实现归0后下次登记日重新累加;4 期挂账累计读 144640

- 分红读取关键链路加 Debug 级日志(生产可关):BondPaymentService.GetBondPayments(reg_date 过滤区间+命中条数)、SwapDealService.GetPreEodPositionByDate(EOD 定位+回退)、GetPreEodDividendSum(取EOD日期+PosiDividendSum)、方案C 平仓预览/收益结算两处 DividendIn 赋值

实跑 dotnet test 全 GREEN(6/6)。任务编号 EQD-7004。
2026-08-14 10:05:01 +08:00
hjhan b2cbbff9e7 refactor(margin): 删除未接线的保证金死代码预留抽象
- 删除 MarginAccount 整类、IMarginResolver 子树(MarginForm/CashMargin/CreditMargin/GuaranteeMargin)、MarginBalance 只读 struct:全仓零生产实例化,结构上无法适配 CalcMarginInterest 的盘中差分模型。

- 删除仅覆盖上述死代码的 MarginLegTest(不损失活代码覆盖率)。

- 保留被 CalcMarginInterest 实际使用的 MarginCalc/MarginModes 活代码。

- 同步修剪 ARCHITECTURE.md 树图与对账表,清理 AccrualContext.cs 过期注释。
2026-08-14 08:55:37 +08:00
tengyufan 8afd449c40 feat(ClientBlack): 新增客户黑名单审批流程
- 黑名单新增、删除、批量导入、批量移出在配置审批节点后,审批通过才生效
- 新增黑名单待提交后可提交审批;仅首节点未审批时可撤回,进入后续节点禁止撤回
- 增加黑名单审批列表、审批弹窗、审批操作日志及状态筛选
- 支持审批配置中的“黑名单审批”流程及对应菜单、模块权限、操作权限
- 黑名单审批中禁止同名记录覆盖备注;已在黑名单中的同名导入会明确提示本次将修改备注
- 未配置审批节点时保持原有直接生效行为
- 增加黑名单审批状态机单测,39/39 通过
- 修正撤回全部失败时仍提示“撤回审批成功”的问题
2026-08-13 17:21:04 +08:00
hjhan 9df39491da fix(dividend): 分红按债权登记日(reg_date)口径计提,手动互换读取当日EOD快照 (GLMS-20260105-0006)
- BondPayment 实体补映射 reg_date(债权登记日,对应 bond_payment_info.reg_date 列)
- BondPaymentService.GetBondPayments 改按 reg_date 过滤(原错用 pay_date_PL/支付日;pay_date_PL 是理论付息日,仍属支付日,非登记日)
- SwapDealService.GetPreEodPositionByDate 由 ValueDate < dealDate 改 <=,登记日当天手动互换读当日EOD分红而非T-1
- 新增 GLMS20260105_0006_RegisterDateDividendTest 回归测试(手工合成内存,先RED复现后GREEN 2/2)

根因:bond_payment_info 表仅有 pay_date_PL(理论付息日)/pay_date_act(实际付息日) 两个支付日,缺登记日;
代码用支付日判定票息归属,且手动互换只读 T-1 EOD 快照;登记日≠支付日(恰差一工作日)时缺陷被掩盖,
导致对话框分红收益显示 0。
2026-08-13 12:31:45 +08:00
hjhan dea9c4cba5 docs(margin): 修复保证金计息重构后的过时文档与注释
按最新代码(CalcMarginInterest 已接生产、orginPv hack 已删、MarginAccount 未接线、保证金精度=12)修正:
- 删除 orginPv 维度重映射的孤儿 summary(方法已删)
- SwapDealService 注释:去掉 orginPv(InitSwapDealInterest)/保证金腿 引用
- SwapInterest/AccrualContext/InterestRate:去掉 Precision=11 是"保证金腿"、"保证金场景"等错误归因(保证金实际跑精度12)
- MarginAccount/MarginBalance:标注"尚未接线",生产入口指向 CalcMarginInterest,去掉"余额×利率×天数"过度简化
- 测试注释:去掉"无 orginPv/差分"(盘中保留差分)、"提交2 待切换"(已完成)

仅文档/注释,零代码行为变化。
2026-08-13 10:58:47 +08:00
张名锐 5a51d66970 refactor(dividend): 优化除权除息计算逻辑并改进数据类型精度
- 将除权除息相关数值字段从 double 类型改为 decimal 类型以提高精度
- 重构了红利比率计算方法 GetRatio,新增 GetRatioDecimal 方法使用 decimal 计算
- 修改价格和持仓数量计算逻辑,统一使用 decimal 进行高精度运算
- 更新数据库查询逻辑,将篮子标的判断从 IsBasket() 方法改为 CommodityCode 条件
- 优化 AddDividendInfos 方法中的批量处理逻辑,增加业务键冲突检测
- 添加数据源标识字段 DataSource 和来源更新时间字段 SourceUpdatedAt
- 新增 FindExDividendByBusinessKey 和 MergeNonZeroDividendValues 辅助方法
- 更新结算服务中除权除息信息的获取方式,使用字典查找替代 LINQ JOIN
- 修复前端保存除权信息时的响应处理逻辑
- 为基金类型也开放除权功能,不仅限于股票类型
- 添加单元测试验证篮子标的查询翻译逻辑的正确性
2026-08-13 10:53:57 +08:00
hjhan 9b29211d3c refactor(margin): 保证金计息切换到 CalcMarginInterest + 删 orginPv 外部维度 hack
- GetInterests 保证金分支(5/6)切到 CalcMarginInterest,不再走融资腿通用 CalcEodInterest/CalcUnwindInterest
- 删除 InitSwapDealInterest 的保证金 orginPv 维度 hack(保证金已不走该方法,成为死代码)
- CalcMarginInterest 修正三处与旧管线的对齐(全量回归发现):
  · 加 endDate 参数(盘中用 InitInterestDate 的 endDate,否则少算天数)
  · calcLast 合并 newCalcLast(与 CalcUnwindInterest 一致,算尾)
  · 盘中保留 accrualBasis 差分(posiPrincipal 对齐状态路径相关,单一本金变量无法覆盖;orginPv 内部按 PreviousBalance 算,消除外部维度 hack)
- 更新 Shadow/GoldenReplay 测试调用点(加 endDate)

修正说明:盘中“消除差分”不可行——SPC_006(position 已对齐) 与 PrepaidPrincipalCloseTrace(position 未对齐) 期望相反,差分 accrualBasis 经 orginPv 自适应两种状态。CalcMarginInterest 真实收益收敛为:保证金领域独立 + orginPv 内聚 + 消除浮动/分段/复利死分支,而非消除差分。EOD 路径无差分(用昨日终本金)。

验证:全量 SwapModule 522/522 通过(0 失败);真实库黄金回放 60 条 0 差异。
2026-08-13 10:39:03 +08:00
hjhan 7243684839 test(margin): 新增真实库黄金回放验证——96库60条EOD逐日比对,新旧管线0差异
- MarginInterestGoldenReplayTest:连 192.168.2.96 测试库,对真实保证金交易(GLMS-20260701-0008/-0013/-0006)逐日 EOD 比对旧管线 GetInterests(settment=true) vs 新方法 CalcMarginInterest
- 结果:60 条腿×日比对,InterestAmount/TdInterestAmount 严格一致(容差1e-6),0 不一致
- 覆盖真实边界:追加预付金多行(base 135555/200000 各自计息)、InterestProfitSum 累计滚动、跨 EOD 续接
- 为提交2(切换生产+删 orginPv hack)建立真实数据安全网
2026-08-13 09:59:48 +08:00
hjhan a86b496f87 test(margin): 新增 CalcMarginInterest 影子对账——保证金计息消除 orginPv/差分,与旧管线数值一致
- 新增 CalcMarginInterest(SwapDealService):保证金专属计息,复用 SimpleInterestAccrual 纯函数,notional 直接取保证金余额(EOD=昨日终本金/盘中=今日本金),消除融资腿差分公式 accrualBasis=TdInterestPrincipal+posiPrincipal-orginPv 与 orginPv 维度 hack(对保证金 accrualBasis 恒等于 posiPrincipal,差分冗余)
- 保留累计语义(priorAccrued+增量),满足下游 SwapEodPositionService 字段契约
- 新增 MarginInterestShadowTest:5 场景对账(EOD 续接/首日、盘中全平/部分平仓/互换),新旧 InterestAmount/TdInterestAmount 严格一致
- 本提交仅影子对账,生产路径未改(GetInterests 仍走 CalcEodInterest/CalcUnwindInterest);下一步提交2 切换生产 + 删 orginPv hack

零生产风险;编译 0 错误;影子对账 5/5 + 现有保证金/利息回归 60/60 通过。
2026-08-13 09:37:42 +08:00
hjhan 70550af29a refactor(funding-leg): 清理CalcNotional死参数posiLong/posiShort+修正过时InterestMode注释
- 删除 IFundingLegStrategy.CalcNotional 的 posiLong/posiShort 死参数(多空存续腿界面已禁用,三个实现均不读取),同步三个实现签名、SwapDealService 唯一调用点、FundingLegStrategyTest 7 处调用
- 修正 SwapPosition/SwapFlowEvent/EodSwapPosition 的 InterestMode 字段注释(去掉已删的 3/4,补全 5/6/9)
- 重写 SwapUnwindFloatingLegDiagnosticTdd 过时类注释

零行为变化;编译 0 错误;FundingLegStrategyTest 11/11 通过。
2026-08-13 08:51:21 +08:00
hjhan 50bc6562da chore: 删除误提交的 TdCarryInCharacterizationTest 占位测试(沙箱漂移产生的无意义文件) 2026-08-12 18:29:51 +08:00
hjhan dfdc890749 test: 补全 SaveAutoSwapDeal 的测试 seam——修复6个AS_*测试失败
- SwapEodPositionService: AddSwapEvent 虚方法(已存在)替换 line 741 的 new SwapEventService 直调

- SwapEodPositionService: 新增 PersistFlowEvent 虚方法,替换 line 749/760 的 DbContext.swap_flow_event.Add 直调

- TestableSwapEodPositionService: PersistFlowEvent 改为 override

- BondTrsAutoSwapScenarioTest.AutoSwapEodService: override PersistFlowEvent 同步到 PersistedFlowEvents

测试: 511→517 通过,7→0 失败
2026-08-12 18:23:51 +08:00
hjhan 84820a3203 test: 修复预存在测试失败-AddClientCashInCashOut no-op override + FR007取价失败兜底
- TestableSwapEodPositionService: override AddClientCashInCashOut 与 AddClientCash 同构 no-op,修复6个AS_*测试客户信息未找到异常

- GLMS20260805ClosePercentDiffDiagnoseTest: FR007取价失败走 Inconclusive(与连不上库同语义,外部数据依赖不应判为测试失败)

AS_* 仍有深层失败(自动互换未触发,interestList=0),属预存在的测试数据问题,需单独排查
2026-08-12 18:08:47 +08:00
hjhan f1f7cae832 refactor(accrual): 抽取BuildSegmentRates共享分段取率+清理needPrice死参数
- 抽取 BuildSegmentRates:统一单利/复利分段取率循环,参数化 fetchAfterDate (单利传 ValueDate 仅取新段,复利传 null 全程取)

- 修复 BuildSegmentRates static→instance:访问实例属性 IndexFixer

- 清理 CalcDaily 层 needPrice 死参数:4个 CalcDaily* 方法签名移除 needPrice;上层 CalcSwapInterests/GetInterests 保留 (virtual seam/位置参数兼容)

- 修复 6 处调用点 needPrice 参数传递

- 新增影子测试:单利+FR007浮动+部分平仓+历史归档,验证 segmentRates 一致

- 新增 TdCarryInCharacterizationTest:钉死部分平仓 TdInterestAmount carry-in 行为

测试: 511通过 / 7预存在失败(数据依赖) / 9跳过
2026-08-12 18:02:36 +08:00
hjhan b9362ea700 refactor(swap): Phase1 提取UnwindNormalizer+TradingFeeCalc出SwapDealService
SwapDealService的10个private static纯逻辑方法搬到两个新文件:
- UnwindNormalizer.cs: NormalizeNotionalValues/FullCloseRequest/Recalculate/IsFullClose/SettledInterestAmounts/EventUnwindDate (6个)
- TradingFeeCalc.cs: CalcInitTradingFee/CalcInitTradingFeePending (2个)

SwapDealService内21处调用点加类名前缀, 反射测试改为直接调用(public)
SwapModule零回归(7基线/510通过)
2026-08-12 16:26:12 +08:00
hjhan 9d321378ac cleanup(accrual): 删除orginPv死参数(3个方法签名+所有调用点)
源码实证orginPv在5/6个CalcDaily方法中是死参数(方法体不读取):
- CalcDailyCompoundInterest: 删 decimal orginPv(3个调用点同步删)
- CalcDailyCompoundInterestByEod: 删(1个调用点+4个测试同步删)
- CalcDailySimpleInterestByEod: 删(1个调用点+2个测试同步删)

保留orginPv的方法(真实消费者):
- CalcDailySimpleInterest: 差分公式 posiPrincipal - orginPv
- GetInterests/CalcUnwindInterest/InitSwapDealInterest: 转发链

SwapModule零回归(7基线/510通过)
2026-08-12 13:58:43 +08:00
hjhan 9d8cfb5ad0 refactor(accrual): 拆分FundingLegAccrual→SimpleInterestAccrual/CompoundInterestAccrual
单利与复利语义完全不同(单利本金恒定/复利重置日并本金),
拆成两个独立静态类,各自只含自己的方法:

SimpleInterestAccrual:
- AccrueEod (原AccrueSimpleEod)
- AccruePeriod (原AccrueSimplePeriod)

CompoundInterestAccrual:
- EodBasis (原CompoundEodBasis)
- AccrueEod (原AccrueCompoundEod)
- AccruePeriod (原AccrueCompoundPeriod)

方法名去掉Simple/Compound前缀(类名已携带类型),消除冗余
SwapModule零回归(7基线/510通过)
2026-08-12 13:40:47 +08:00
hjhan c86633479d refactor(accrual): 接线SimpleInterest/CompoundInterest内部 + 分段修复 + 去重
CalcDailySimpleInterest/CalcDailyCompoundInterest内部逐日循环→分段纯函数(签名不变):
- AccrueSimplePeriod: AccruedToday改未缩放累计(TdInterestAmount口径)
- AccrueCompoundPeriod: 新增out finalBasis供flowEvent.InterestPrincipal精确赋值
- 修复5处分段边界bug(includeStart/includeEnd/resetCarryInterest interestPeriod=1场景)
- 提取BuildSegmentRates消除SimpleInterest/CompoundInterest取率重复
- 影子测试补AccruedToday断言关闭测试盲区

SwapModule零回归(7基线/510通过)
2026-08-12 13:09:17 +08:00
hjhan 85746d7ee9 test(accrual): 新增同日双次部分平仓字符化测试 + EOD基数快照replay诊断
- SwapUnwindSameDayDoublePartialTest: 内存Stub驱动同日两次部分平仓(30%/50%),
  验证基数线性累加(0.3N+0.35N=0.65N),推翻"基数不滚动"假设;缺口仅在
  调用方是否传陈旧notional,已文档化
- GLMS20260805ClosePercentDiffDiagnoseTest: 从Resources快照反序列化离线重跑
  EOD基数诊断,去DB化;无快照Inconclusive,保留Assert.Fail作8/5 bug护栏
2026-08-12 11:28:40 +08:00
hjhan ff937f24e7 refactor(accrual): EOD去退化差分——删除baseNotional死参数
EOD路径 orginPv≡posiPrincipal(调用点line1091/1096实证),notional==baseNotional恒成立,
差分项 notional-baseNotional 恒为0,baseNotional 是死参数。

简化:
- AccrueSimpleEod: 删 notional+baseNotional 两参数,basis=priorNotional 直赋值
- AccrueCompoundEod: 删 baseNotional 参数,非重置日 basis=priorNotional
- SimpleEodBasis 整个删除(不再需要)
- CompoundEodBasis 去掉 baseNotional 参数
- EodContext trace 去掉 baseNotional
- 差分注释块删除(EOD不再有差分)

差分逻辑 notional-baseNotional 只该存在于 unwind 专用路径(Period函数本就没有baseNotional)
净减20行(18增/38删),SwapModule零回归(7基线/508通过/8跳过)
2026-08-12 10:58:53 +08:00
hjhan 2f7caa7a1f refactor(accrual): referenceNotional→baseNotional, notional统一
- referenceNotional → baseNotional(更短更清晰)
- 新代码统一用 notional(不用 principal):
  QuantLib/Strata 对 swap leg 用 notional 是业界标准
- DB字段(InterestPrincipalFix等)保持不动(legacy)
- 注释标注 legacy 对照(orginPv/originalPv/dynomicPrincipal)

验证: 编译0错误, 7影子测试全过, 全量523测试7失败(基线一致)。
2026-08-12 10:20:13 +08:00
hjhan ce6c0812e1 refactor(accrual): 参数命名对齐业界标准(QuantLib/Strata)
FundingLegAccrual 4个方法的参数命名全面纠正:

| 旧名              | 新名               | 理由 |
|-------------------|-------------------|------|
| originalPv        | referenceNotional | Pv(现值)概念错误,实际是参考本金 |
| closeRatio/closePercent | unwindFraction | 统一,对齐 ApplyUnwind.unwindPercent |
| consumedInterest  | realizedInterest  | 对齐 AccrualState.RealizedInterest |
| priorUnrealized   | priorAccrued      | 对齐 InterestResult.Accrued |
| priorAccrualPrincipal | priorNotional  | 简洁,对齐 QuantLib notional |
| positionPrincipal | notional          | 简化,去掉冗余前缀 |
| dynomicPrincipal  | accrualBasis      | 修正typo+用业界术语 |

同步更新:
- SwapDealService.CalcDailySimpleInterestByEod 调用点
- 3个影子测试文件命名参数
- SwapInterest.Round 改为 public(所有Round收口一处)

删除半成品残留: SwapInterest_CompoundInArrears_RolloverTimingTests.cs

验证: 编译0错误, 7个影子测试全过, 全量520测试7失败(基线一致)。
2026-08-12 10:11:26 +08:00
hjhan 431ac9b502 feat(accrual): 新增 AccrueCompoundPeriod + 影子测试(3个全过)
第3步迁移: CalcDailyCompoundInterest(盘中复利多日)。

新增纯函数 AccrueCompoundPeriod:
- 从 PosiStartDate 到 endDate 全程重放
- 每个重置日并本金: principal + replayed interest
- 末日(endDate)为重置日且 resetCarryInterest!=0 时用存量替代
- consumedInterest × closePercent 在末尾扣除
- 分段模型替代逐日循环, 段内本金恒定+利率恒定

影子测试(3个,全过):
- 固定利率全平+末日重置日: 旧新一致
- 部分平仓30%+consumedInterest扣除: 旧新一致
- 算头算尾(calcMode=11): 旧新一致

清理: 删除 SwapInterestAccrueCompoundInArrearsTest(半成品残留,
依赖已回退的 SwapInterest 签名)。
修复: SwapInterestAccrueCompoundInArrearsTest 缺 using(已随文件删除)。

验证: 编译0错误, 全量520测试7失败(基线一致)。
4个CalcDaily*方法的新架构纯函数全部就绪, 旧方法暂保留对比。
2026-08-12 09:48:49 +08:00
hjhan 963b5e66af feat(accrual): 新增 AccrueSimplePeriod + 影子测试
第2步迁移: CalcDailySimpleInterest(盘中单利多日)。

新增纯函数 AccrueSimplePeriod:
- 单利特征: 计息本金恒定(差分 = priorAccrualPrincipal + positionPrincipal - originalPv)
- 按重置日分段, 每段用 AccrualDays 算天数×日利息(无逐日循环)
- 续接 priorValueDate 之后的日期

修复: SwapInterest.Round 是 private, 新方法改用 Math.Round。
修复: IReadOnlyList 无 IndexOf, 改用 for 循环索引。

影子测试(2个,全过):
- 固定利率无归档: 旧新一致(差分本金=0,利息=0,符合旧逻辑)
- 有归档续接+部分平仓50%: 旧新一致

验证: 编译0错误, 全量517测试7失败(基线一致)。
2026-08-12 09:27:20 +08:00
hjhan 217c2d5826 feat(accrual): 新增 FundingLegAccrual.AccrueCompoundEod + 影子测试
第1步迁移: CalcDailyCompoundInterestByEod(EOD复利单日)。

新增纯函数 AccrueCompoundEod:
- 重置日: 本金 = positionPrincipal + priorUnrealized × remainingPercent
- 非重置日: 本金 = priorAccrualPrincipal + positionPrincipal - originalPv
- InterestAmount = priorUnrealized × closeRatio + dayInterest
- TdInterestAmount = dayInterest(不含 priorUnrealized)

影子测试(2个,全过):
- 重置日场景: 旧新 InterestAmount/TdInterestAmount 一致
- 非重置日场景: 同上

旧方法暂不删——新旧并存,对比验证后再替换调用点。
验证: 编译0错误, 全量515测试7失败(基线一致)。
2026-08-12 09:15:01 +08:00
hjhan 5a7869e0c9 fix(return-leg): PositionValueCalc.ratio 改为 int, 统一方向因子类型
ratio(收取=1/支付=-1)是方向因子, 应该是 int。
和 MtmCalc/DirectionRatio/MarginCalc 的 int 保持一致。

调用点(SwapEodPositionService 4处)原 ratio 是 decimal,
加 (int) 显式转换(值恒为 1或-1, 零精度损失)。

排查确认: 这是唯一的类型不一致, 其他纯函数参数类型均已统一。
验证: 编译0错误, 全量513测试7失败(基线一致)。
2026-08-11 16:59:22 +08:00
hjhan 16c44d3b50 refactor(return-leg): 抽取 PositionValueCalc 持仓价值汇总
SwapEodPositionService 8处 SwapPositionValue inline 公式替换:
- 4处 InterestProfitSum * ratio + PosiProfitSum → PositionValueCalc.Calc(带ratio)
- 4处 InterestProfitSum + PosiProfitSum → PositionValueCalc.Calc(默认ratio=1)

持仓价值 = 利息端收益 + 浮动端收益, 8处副本→1个纯函数。
这是最后一个高性价比的纯函数提取。

验证: 编译0错误, 全量513测试7失败(基线一致)。
2026-08-11 16:50:21 +08:00
hjhan 29df4c480d refactor: 删除 CalcNotionalByMode, 保证金/融资腿计息基数内联到调用点
CalcNotionalByMode 已完成历史使命:
- 融资腿(1/2/9)迁入 FundingLegStrategyFactory
- 保证金(5/6)迁入 MarginModes.Contains 分支
- 死代码(3/4/7/8)已删

调用点(GetInterests:843)现在内联两条路径:
- 保证金: closePrincipal = Fix × closePercent (用 MarginModes.Contains 判断)
- 融资腿: FundingLegStrategyFactory.Get(mode).CalcNotional(...)

删除:
- CalcNotionalByMode 方法(含注释)
- 2个用反射调私有方法的诊断测试(验证的逻辑已被FundingLegStrategyTest覆盖)

验证: 编译0错误, 全量509测试7失败(基线一致)。
2026-08-11 13:13:32 +08:00
hjhan 47422b9024 feat(margin): MarginAccount 加计息入口 AccrueInterest
保证金账户现在具备独立计息能力:
AccrueInterest(rate, start, end, boundary, annualDays)
委托 SwapInterest.AccrueSimple(余额×利率×天数/年化)。

保证金利息是券商对客户保证金存款付息, 方向与融资腿相反。
Margin 模块现在完全独立于 CalcNotionalByMode——
有自己的余额管理 + 计息入口, 不依赖融资腿框架。

测试: 3个计息用例(7天/零余额/释放后减半)。
CalcNotionalByMode 的 mode 5/6 case 仍在(下游路径共用),
待 EOD 归档改走 Margin 路径后再删。
验证: 编译0错误, 全量511测试7失败(基线一致)。
2026-08-11 13:03:38 +08:00
hjhan e9b54bdcf6 cleanup: 删除死代码枚举 mode 3/4/7/8 及相关引用
mode 3(持仓名义本金)/4(持仓市值): 零引用死代码
mode 7/8(多空存续名义本金): 界面已禁用, 无历史数据(用户确认)

删除:
- InterestModeEnum: 移除 持仓名义本金/持仓市值/多头存续名义本金/空头存续名义本金
  枚举值保持显式(1/2/5/6/9), DB契约不变
- CalcNotionalByMode: 移除 mode 7/8 的 case
- ConsTrade.InterestModels/InterestNotionalModels: 移除 mode 7/8
- 删除3个 mode 7/8 相关测试方法(场景已不存在)

CalcNotionalByMode 现在只剩 mode 5/6(保证金)的 case,
待 Margin 独立计息入口建成后整体删除。
验证: 编译0错误, 全量508测试7失败(基线一致)。
2026-08-11 13:00:52 +08:00
hjhan cbe5b58577 refactor: 新增 DirectionRatio 方向因子纯函数, 替换7处三元式
多空方向(PositionType==Long?1:-1)和收付方向(xxx==收取?1:-1)
在两个上帝类里重复 15+处, +1/-1 写反是常见 bug 源。

新增 ReturnLegs/DirectionRatio.cs:
- LongShort(positionType): 多头=+1, 空头=-1
- ReceivePay(direction): 收取=+1, 支付=-1

替换:
- SwapEodPositionService: 6处 shortRatio 三元式(含注释1处)
- SwapDealService: 1处 shortRatio + 1处 directionRatio

验证: 编译0错误, 全量511测试7失败(基线一致)。
2026-08-11 12:30:53 +08:00
hjhan e35a738d2e refactor(funding-leg): 修复2个类名+补DividendCalc注释
采纳命名审查建议(优先级最高的2项):

1. UnderlyingFullPriceLeg → UnderlyingEntryFullPriceLeg
   补齐'期初(Entry)'——建仓时点全价, 非当前全价, 金融上是不同计息口径

2. FixedNotionalLeg → FixedAmountLeg
   去掉误导性的Notional——'固定值'是合同写死的数额, 不一定是名义本金,
   避免与ContractNotionalLeg的Notional概念撞车

3. DividendCalc 补充注释: 业务含义(债券票息+股票分红统一用Dividend)、
   税务口径(还原不含税再扣税)、命名保持理由

验证: 编译0错误, 全量507测试7失败(基线一致)。
2026-08-11 11:24:05 +08:00
hjhan c52d439469 refactor(return-leg): 4处增值税inline公式替换为 DividendCalc
SwapEodPositionService 4处 payment/(1+tax)*(1-tax) inline 替换:
- 1804/1906/2094: Math.Round(...) → DividendCalc.AfterTax(payment, tax)
- 1893: 中间计算(不四舍五入) → DividendCalc.AfterTaxRaw(total, tax)

修复 MtmCalcTest: UnrealizedPnl 第6参数 ratio 从 1m 改为 1(int)。
(1+tax) inline 公式出现次数: 4 → 0。

验证: 编译0错误, 全量507测试7失败(基线一致)。
2026-08-11 11:12:15 +08:00
hjhan 963f7a04e5 refactor(return-leg): 新增 DividendCalc 分红增值税后计算 + 修复 MtmCalc 参数类型
DividendCalc: 增值税后票息 = payment/(1+tax)*(1-tax),
原 SwapEodPositionService 4处重复此公式(1804/1894/1906/2095)。
本次先建纯函数+测试, 4处inline替换下次做。

附带修复: MtmCalc shortRatio 参数从 decimal 改为 int
(原代码6处声明中5处是int, 仅1696是decimal, 统一为int)。
1696行 shortRatio 声明同步改为 int。

验证: 编译0错误, DividendCalc 4个测试全过,
全量507测试7失败(基线一致)。
2026-08-11 11:04:08 +08:00
hjhan 946cbbb66d refactor: InterestLeg 改名 FundingLeg(业界标准 Financing Leg)
命名: InterestLeg→FundingLeg, 消除'interest'与通用'利息'歧义。
Funding精确表达'融资成本'(客户付券商的杠杆成本 spread+FR007)。

改名清单:
- IInterestLegStrategy → IFundingLegStrategy
- InterestLegStrategyFactory → FundingLegStrategyFactory
- InterestLegStrategyTest → FundingLegStrategyTest
- 命名空间 InterestLegs → FundingLegs
- 目录 InterestLegs/ → FundingLegs/
- SwapDealService 引用同步更新

验证: 编译0错误, 全量503测试7失败(基线一致)。
2026-08-11 10:52:04 +08:00
hjhan a26f848287 refactor(return-leg): 抽取 MtmCalc 标的端盯市计算
把 SwapEodPositionService 4处重复的 UnderlyingMarketValue 公式和
4处 PosiMtmPnL 公式抽成 MtmCalc 纯函数:
- MarketValue(price, qty, contractSize, shortRatio): 标的市值
- UnrealizedPnl(price, costGrossPrice, qty, contractSize, shortRatio, ratio): 盯市未实现盈亏

原代码4处副本(1731/1813/1898/2101 + 1741/1814/1899/2102)全部替换。
验证: 编译0错误, 全量503测试7失败(基线一致)。
2026-08-11 10:45:43 +08:00