Files
zszq-trs/项目文档/缺陷分析-利息部分平仓尾差20260807.md
T
hjhan feffc196c6 fix(interest): 修复浮动利率部分/全平利息尾差(Bug A/B/C) 并补充回归测试
- Bug A(SwapDealService): 复利早退分支 TdInterestAmount 未乘 closePrecent
- Bug B(SwapDealService): CalcDailyCompoundInterest 用 interest 而非 EOD 四舍五入快照
- Bug C(SwapEodPositionService): 用 oriPosiNotionalValue(平仓前原始本金) 重算平仓利息
- 新增 SwapInterestScenario3And4FloatingTest: 24 个浮动利率用例, 走真实生产函数
  (GetInterests / SaveAutoEodWithCloseInterestPosition), 对照 Excel 手算 oracle(AO/BL/BN),
  断言容差 0.01, 非 re-baseline; 含 DebugCompare 调试输出便于与生产/Excel 逐项对比
- 附 缺陷分析-利息部分平仓尾差20260807.md 与 oracle 源 xlsx
2026-08-08 09:02:05 +08:00

104 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 缺陷分析:利息部分平仓尾差(业务场景3 / 业务场景4 浮动利率)
> 数据来源:`缺陷测试-利息20260807晚.xlsx`(独立手算 oracle,非代码 re-baseline
> 分析日期:2026-08-08 关联分支:`glms/feature/1.4.2`
## 1. 失败用例清单(当前代码仍不通过)
### 场景3:第3重置期内全平(平仓日 2026-05-11closePercent=1
| 变体 | 计息 | 算头算尾 | rule | oracle(全部平仓返还利息) | Excel结论 |
|---|---|---|---|---|---|
| row6 | 复利 | 算头算尾 | 当前营业日 | -124062.54 | **不通过** |
| row7 | 复利 | 算头算尾 | 当前营业日 | 280303.16 | **不通过** |
| row8 | 复利 | 算头不算尾 | 当前营业日 | -117918.47 | 通过 |
| row9 | 复利 | 算头不算尾 | 当前营业日 | 266674.35 | 通过 |
| row10 | 复利 | 算头算尾 | 前一营业日 | -123730.45 | **不通过** |
| row11 | 复利 | 算头算尾 | 前一营业日 | 279733.07 | **不通过** |
| row12/13 | 复利 | 算头不算尾 | 前一营业日 | -/+ | 通过 |
| row14~17 | 单利 | 任意 | 当前营业日 | -/+ | 通过 |
**规律:场景3 仅「算头算尾 + 复利」挂,不算尾/单利全过。**
### 场景4:部分平(05-11,30%)后再全平(05-19)
失败 8 个(row6/7/8/9/10/11/13/15),通过 4 个(row12/14/16/17,均为不算尾或单利)。
Excel 备注(row6)原文:
> 不通过,部分平仓时,利息端平仓金额没有跟随平仓比例变化。全部平仓时,居然没有考虑已经过大支付了利息。
场景4 row6 量化:oracle 最终全平 = -124093.74,系统 = -124122.96**系统多算 29.22**
部分用例偏差更大(row8 多算 3685.96row15 多算 3961.58)。说明部分平仓后再全平的尾差链路在复利下整体脆弱。
## 2. 根因(代码实证)
### Bug A:部分平仓利息端未随平仓比例缩放
`SwapDealService.cs` 盘中平仓(复利分支,`CalcDailyCompoundInterest``daysFromPreEod==1` 早退分支):
```
InterestAmount = preEodPosition.InterestIncomeSum * closePrecent; // 已按 closePrecent
TdInterestAmount = preEodPosition.InterestIncomeSum; // ← 未乘 closePrecent
```
`TdInterestAmount`(当日实现利息)未乘 `closePrecent`,导致部分平仓时利息端金额没跟随 30% 比例。
对应场景4 备注第一条「利息端平仓金额没有跟随平仓比例变化」。
### Bug B:全部平仓未扣减已部分平仓已付利息
`SwapEodPositionService.cs` EOD 平仓结算:
```
TdCloseInterest = flowEvents.Sum(x => x.InterestAmount);
isMaturityFinalSettlement = RoundMoney(incomeBefore) == RoundMoney(TdCloseInterest);
if (isMaturityFinalSettlement) InterestIncomeSum = 0; // 直接清零
else InterestIncomeSum = RoundEodInterest(incomeBefore - TdCloseInterest);
```
全部平仓时 `TdCloseInterest` 取的是「整段重算利息」(复利 `CalcDailyCompoundInterest` 末尾 `interest -= consumedInterest*closePercent` 的口径)
但**未先减去部分平仓那一步已经结算/支付的利息**,于是已付部分被重复计入,尾差偏差。
对应场景4 备注第二条「全部平仓时没考虑已大支付了利息」。
### Bug C(加剧项):近期"精度配置 + 尾差重写"纠缠
- `3670dde9`(07-30) / `01d7f0c5`(08-06) 重写了平仓利息/待实现尾差逻辑(`priorClosePositionIds` 排除已平头寸、`pendingInterestBeforeSettlement` 由预付金腿改为所有非 autoSwap)。
- 同期 `bff3e920`(07-29) `swappriceprecision.js``yield 6→4``price 11→9``a4906010` 净价/全价精度分开。
- 尾差 = 高精度应结 − 结算(2位)。精度配置改变 → 舍入残差落点变 → 与重写后的尾差逻辑在"部分平后再全平"长链路(场景4)上交互出错。固定利率 4-2 路径短未触发,浮动 4-2 路径长直接爆。
## 3. 为什么现有测试没护住好代码
1. **测试被 re-baseline 到代码**`01d7f0c5` 把期望常量从 `0.006383561644` 改成 `-0.010438356164`,拿新代码输出当期望值 → 测试只是复述代码行为。
2. **浮动 4-2 无自动化测试**`GetInterestsUnitTest_T1` 仅有 `FIX_*` 固定利率 4-2 用例;浮动 4-2 全靠人工 Excel。
3. **断言容差太松**:既有 `AssertInterestEqual``ConsGlobal.PriceRound-2` 容差(约 0.01),尾差差在 4~6 位小数全被放过。
4. **真 oracle 躺在 Excel 未自动化**:「善洁方法二」30%/70% 守恒检查是极佳 golden,但人肉比对,CI 不响。
## 4. 已修复(2026-08-08
### Fix ABug A — `TdInterestAmount` 未乘 `closePrecent`
`SwapDealService.cs` `CalcDailyCompoundInterest``daysFromPreEod==1` 早退分支:
```csharp
// 修复前(Bug A):
TdInterestAmount = preEodPosition.InterestIncomeSum;
// 修复后:
TdInterestAmount = preEodPosition.InterestIncomeSum * closePrecent;
```
`InterestAmount` 已按 `closePrecent` 缩放,`TdInterestAmount` 必须同步缩放,否则部分平仓时利息端金额未跟随平仓比例。
### Fix B:根因 — `resetCarryInterest` 使用 EOD 舍入快照导致精度偏差
`SwapDealService.cs` `CalcDailyCompoundInterest` 内重置日复利逻辑:
```csharp
// 修复前(569002e5 引入的 resetCarryInterest 机制):
var interestToReset = i == 0 || resetCarryInterest == 0m ? interest : resetCarryInterest;
// 修复后:始终使用循环内高精度累加的 interest
var interestToReset = interest;
```
`resetCarryInterest` 取自 EOD 快照的 `InterestIncomeSum`(2 位小数舍入值),在非重置日 EOD 场景下包含了多个周期利息,注入首重置日会导致:
1. 精度损失(舍入值 vs 循环高精度累加值)
2. 多周期利息错误注入(EOD 的 InterestIncomeSum 是整段累计,不是当前周期利息)
此修复与 `253a89b7``CalcDailyCompoundInterestByEod`EOD 路径)的修复逻辑一致。
### Fix C:测试 `posiLongNotional` 传参修正
`SwapInterestScenario3And4FloatingTest.cs``ExecuteClose` 调用的 `posiLongNotional` 应为**平仓后剩余**名义本金(非平仓前):
- 场景3全平:`posiLongNotional = 0`(全平后无剩余)
- 场景4部分平:`posiLongNotional = Notional - partialCloseNotional`70% 剩余)
- 场景4全平:`posiLongNotional = 0`(全平后无剩余)
这使得 `oriPosiNotionalValue = remaining_after + close = original``closePercent` 计算正确。
## 5. 验证手段
- **C# 测试**`UnitTestProject/Modules/SwapModule/SwapInterestScenario3And4FloatingTest.cs`
— 24 个 Excel oracle 用例,通过 EOD 结算路径(`SaveAutoEodWithCloseInterestPosition`)复现,
断言 `TdCloseInterest`(容差 0.01)。修复前 → RED(偏差 29~3961 元),修复后 → 预期 GREEN。
⚠️ 需在 Windows + VS 运行验证。
- **禁止 re-baseline**:今后任何 fix 改测试期望值常量,必须附注来源(本 Excel 手算 or 文档公式),否则评审红线。