docs: 任务4 EOD续接风险分析 + SwapDealService方向比例收口
新增 docs/eod-continuation-proposal.md: - 记录4个风险(并本金起点/consumedInterest语义/resetCarry/全平重放) - 记录验证方案(6场景影子测试) - 标注待立项,不混入日常重构 SwapDealService 收尾: 3处内联方向比例→DirectionRatio.ReceivePay - line389: PosiDirection收取?-1:1 → -ReceivePay - line1104/1235: InterestDirection==1?1:-1 → ReceivePay SwapModule零回归(7基线/510通过)
This commit is contained in:
@@ -386,7 +386,7 @@ namespace YLErp.Modules.SwapModule
|
||||
floatEvent.PositionQty = 0;
|
||||
floatEvent.ContractSize = position.ContractSize;
|
||||
floatEvent.TradingAmount = floatEvent.Quantity * floatEvent.ContractSize;
|
||||
var ratio = position.PosiDirection == (int)SwapDirectionEnum.收取 ? -1m : 1m;
|
||||
var ratio = -DirectionRatio.ReceivePay(position.PosiDirection);
|
||||
floatEvent.TradingFeePending = CalcInitTradingFeePending(oriPosition, position, unwindData);
|
||||
floatEvent.DataState = (int)SwapFlowDateStateEnum.完成;
|
||||
floatEvent.InterestMode = position.InterestMode;
|
||||
@@ -1109,7 +1109,7 @@ namespace YLErp.Modules.SwapModule
|
||||
interest.InterestAmount = Math.Round(interestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
|
||||
interest.TdInterestAmount = Math.Round(tdInterestAmount, InterestCalculationPrecision, MidpointRounding.AwayFromZero);
|
||||
// 计算InterestClosePnL(方向:收取=1为正,支付=-1为负)
|
||||
var interestRatio = position.InterestDirection == 1 ? 1m : -1m;
|
||||
var interestRatio = DirectionRatio.ReceivePay(position.InterestDirection);
|
||||
interest.InterestClosePnL = interest.InterestAmount * interestRatio;
|
||||
|
||||
if (add) UpdateDbOption(interest);
|
||||
@@ -1240,7 +1240,7 @@ namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
decimal InterestAmount = 0;
|
||||
decimal TdInterestAmount = 0;
|
||||
var interestRatio = position.InterestDirection == 1 ? 1m : -1m;
|
||||
var interestRatio = DirectionRatio.ReceivePay(position.InterestDirection);
|
||||
var floateRate = preEodPosition.FloatRate;
|
||||
if (position.InterestType == (int)InterestTypeEnum.复利)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# 任务 4:盘中复利从 EOD 续接(而非 PosiStartDate 全程重放)
|
||||
|
||||
## 状态:待立项(高风险,需专项验证)
|
||||
|
||||
## 现状
|
||||
|
||||
`CalcDailyCompoundInterest` 从 `position.PosiStartDate` 全程重放到 `endDate`,每个重置日把累计利息并入本金(复利),最后扣 `consumedInterest * closePercent`。
|
||||
|
||||
**调用链**:`InitSwapDealInterest` → `CalcDailyCompoundInterest(endDate, PosiStartDate→endDate 全程重放)`
|
||||
|
||||
**问题**:交易存续期长(数月~数年)时,每次盘中平仓都从起息日重放,计算量随天数线性增长。
|
||||
|
||||
## 提议
|
||||
|
||||
改为从上一日终快照(`preEodPosition`)续接:
|
||||
- 起点 = `preEodPosition.ValueDate + 1`
|
||||
- 初始本金 = `preEodPosition.TdInterestPrincipal`(已含历史滚入利息)
|
||||
- 只算 `ValueDate+1` 到 `endDate` 的增量利息
|
||||
|
||||
## 风险分析(为什么不能直接改)
|
||||
|
||||
### 风险 1:并本金起点不同导致终值不等
|
||||
|
||||
| | 全程重放(当前) | EOD 续接(提议) |
|
||||
|---|---|---|
|
||||
| 起点 | `principal`(原始平仓名义本金) | `preEod.TdInterestPrincipal`(已滚利息) |
|
||||
| 滚法 | 每段 `basis = principal + accrued` | 每段 `basis = preEod.TdInterestPrincipal + segmentAccrued` |
|
||||
|
||||
两段路径在**中间重置日的四舍五入路径不同**(精度 12 的 Round 作用在不同的中间值上),终值**不一定逐分相等**。
|
||||
|
||||
### 风险 2:consumedInterest 语义翻转
|
||||
|
||||
- 全程重放:总利息 - consumedInterest × closePercent = 增量
|
||||
- EOD 续接:直接算增量,**不需要**扣 consumedInterest
|
||||
|
||||
如果 EOD 快照的 `InterestIncomeSum` 与 consumedInterest 口径不完全一致,直接去掉扣减会引入误差。
|
||||
|
||||
### 风险 3:resetCarryInterest 耦合
|
||||
|
||||
当前逻辑:`resetCarryInterest`(上一日终待实现 × remainingPercent)只在 `endDate` 恰好是重置日时并入本金。EOD 续接模式下,重置日的判定、remainingPercent 的计算、carry 的注入时机都不同。
|
||||
|
||||
### 风险 4:全平重放逻辑(lines 1293-1304)
|
||||
|
||||
`InitSwapDealInterest` 在 `closePrecent == 1m` 时做 **两次** `CalcDailyCompoundInterest` 重放(截至平仓日 + 截至上一日终),取差值。EOD 续接模式下这段逻辑需要完全重新设计。
|
||||
|
||||
## 验证方案(立项前提)
|
||||
|
||||
1. 构造测试用例:同一笔复利交易,跨越 ≥2 个重置周期,有 preEod 快照
|
||||
2. 用**旧全程重放**算出 `(InterestAmount, TdInterestAmount, finalBasis)`
|
||||
3. 用**新 EOD 续接**算出同样三个值
|
||||
4. 断言差额 < 0.01(到分)
|
||||
5. 覆盖场景:
|
||||
- 部分平仓(closePercent < 1)
|
||||
- 全平(closePercent == 1)
|
||||
- 平仓日 = 重置日
|
||||
- 平仓日 ≠ 重置日
|
||||
- 有/无 consumedInterest
|
||||
- 有/无 resetCarryInterest
|
||||
|
||||
## 建议排期
|
||||
|
||||
单独 sprint 处理,不混入日常重构。改动范围:
|
||||
- `CompoundInterestAccrual.AccruePeriod` 新增 `startBasis` 参数(或新方法 `AccrueFromEod`)
|
||||
- `CalcDailyCompoundInterest` wrapper 改为传 `preEod.TdInterestPrincipal` 作为起点
|
||||
- `InitSwapDealInterest` 全平重放逻辑简化(不再需要两次重放取差值)
|
||||
- `consumedInterest` 扣减逻辑移除或调整
|
||||
Reference in New Issue
Block a user