Merge remote-tracking branch 'origin/glms/feature/1.4.2' into glms/feature/1.4.2

This commit is contained in:
张名锐
2026-08-07 15:31:53 +08:00
@@ -178,6 +178,10 @@
> 这一章回答一个关键质疑:两页口径不同,凭什么断定是 income 漏了 `longRatio`、而不是 unwind 多算了?
> 以及:给 income 补 `longRatio` 会不会把正确逻辑改坏、或造成"双重翻转"?
> ⚠️ **状态说明(2026-08-07**:本章关于"income 漏 longRatio → 错 / unwind 对"的论断,目前是**待业务背书的 Working Hypothesis**
> 尚未取得业务/量化签字。且比这更基础的"收益结算的价差基准是否应在结算后滚动"的产品定义仍未确认(见第八章)。
> **在业务下定论前,本章仅作论证记录,不视为最终定性**——第八章才是如实记录"代码现在实际怎么做"的事实层。
### 7.1 两个方向乘子是**独立轴**(这是避免误判的前提)
- `floatRatio = PayDirection==1(收取) ? +1 : -1` —— 跟随**收付方向**`FrontendCalcReference.cs:35`
@@ -228,3 +232,98 @@
- **历史脏数据**:过去"空头+互换"事件已用错符号落库;修复后新事件正确,跨时间对比会出现不连续。需决定:回溯校正(改 `MarkClosePnl`+重算 `SwapRealizedPnl`+对账 `AddClientCash` 历史)还是标注留痕。
- **改动范围**:严格限定在 income 页 `:207``CalcIncome:107``longRatio`,切忌顺手改 `floatRatio` 或其他页面。
4. **前置确认**:建议先让业务/量化签字"income 的 `MarkClosePnl` 应含多空方向(与平仓一致)",再动手——因为结论虽由代码+基线铁证支撑,但涉及客户现金流,需业务背书。
---
## 八、当前行为实录与待确认项(2026-08-07)
> 本章**只记录"代码现在实际怎么做"**(可验证事实),并明确列出"哪些还无法判定谁对"。
> 与第七章(论断层)的区别:第七章给出了"income 错 / unwind 对"的论证,但该论断**尚未取得业务/量化背书**,
> 且涉及"价差基准是否应在收益结算后滚动"这一更基础的产品定义。因此把它们在本章降格为"待确认假设",
> 先如实记录现状,避免过早定性。
> 文档定位:**当前不修改代码、不加测试,仅留痕**。正确性待业务/量化逐项确认后再回填。
### 8.1 收益结算(income)页当前行为实录
- **界面入口**:交易详情页头部操作区「**收益结算**」按钮(权限 `交易管理_收益互换`),打开 `/swaptrade2/SwapIncome/`
`SwapIncome.cshtml` + `incomeSwapTrade.js`)。与「**平仓**」按钮(`SwapUnwind.cshtml`)外观相似,
但**没有平仓比例、没有事件日期**——本质是"期间结算、头寸保留",而非关闭头寸。
- **价差损益 `MarkClosePnl` 当前公式(代码事实)**
- `incomeSwapTrade.js:104` `initPosiGrossPrice = this.floatPosition.PosiGrossPrice`
- `:199/207` `floatRatio = PayDirection==1 ? 1 : -1`
- `:207` `MarkClosePnl = positionAmount × (deliveryPrice initPosiGrossPrice) × floatRatio`
`positionAmount = PositionQty × ContractSize``deliveryPrice = getStorageDeliveryPrice()`
- 后端同口径 `FrontendCalcReference.CalcIncome:107`**无 `longRatio`**`CalcIncome:92` 注释明示"无 longRatio"。
- **事实结论**income 页当前**只用 `floatRatio`(收付方向)翻转,未乘 `longRatio`(多空方向)**。
### 8.2 价差基准 `swap_position.PosiGrossPrice` 当前行为实录
- **开仓时**`PosiGrossPrice = TradingAmountAvg`(成交/期初全价,`SwapTradeService.cs:396`),规范名 `EntryDirtyPrice`
`FrontendCalcReference.cs:15/156`)。
- **收益结算后**`UpdateInitalPosition``SwapDealService.cs:2276-2281`)对"互换"分支**只更新费用 `PosiTradingFee`,不改 `PosiGrossPrice`**
`SwapIncome``:2005`)直接落库、**不调用** `UpdateInitalPosition`
- **平仓后**`SaveSwapDealInternal``:2212`)只改 `Quantity/PositionQty`,不改动基准价含义。
- **EOD 日终**`SwapEodPositionService` 操作的是快照表 `eod_swap_position`,其 `PosiGrossPrice` 为成本基准、
`eod.Clone()` 跨日结转(`CopyEodPosition:1688`),每日行情价只写入 `UnderlyingPrice``:1716`),**不覆盖成本基准**。
活表 `swap_position.PosiGrossPrice` 最终由 EOD 回写(`UpdateSwapPosition:62` / `UpdateSwapPositionWithRealTime:85`),
但回写的仍是"成本基准"而非当日行情价。
- **事实结论**`swap_position.PosiGrossPrice` 在持仓生命周期内**恒等于开仓期初价 P0,从不滚动到上一次结算价**。
### 8.3 已确认事实 vs 待确认假设 对照
| 项 | 已确认事实(代码可验证) | 待确认假设(需业务/量化背书) |
|----|--------------------------|------------------------------|
| income 是否含 `longRatio` | 当前**不含**code 实证) | 是否**应该**含(与平仓一致)?——第七章论证"应含",但**未获业务签字** |
| 空头 income 符号 | 当前公式对空头会得出与平仓/后端**相反**的符号 | 这是否是"错误"?取决于产品对空头收益结算现金流的符号约定 |
| 价差基准是否滚动 | 当前**不滚动**(恒为 P0) | 收益结算应是"增量(从上次结算价)"还是"绝对(从 P0"?——**产品定义未确认** |
| 多次收益结算重复计入 | 当前若对**同一开放持仓做多次收益结算**,每次都按 (当前价−P0) 计,**首段会被重复计入**(数学推导) | 业务实际是否允许/发生过"同一持仓多次收益结算"?——**需生产数据确认** |
| 下游现金流 | `SwapIncome:2007` `AddClientCash(-SwapRealizedPnl)` 用前端值记账(code 实证) | 若公式口径需改,历史已落库金额是否需追溯校正? |
### 8.4 待确认问题清单(请业务 / 量化 / 产品逐项答复)
1. **空头收益结算的符号约定**:空头 TRS 做期间结算,客户现金流方向应是"空头涨价=亏"(与平仓一致,即含 `longRatio`)还是相反?
请给出业务样例与会计分录。
2. **收益结算的价差口径**:期间结算的 `MarkClosePnl` 应是"本段增量"(结算后把持仓基准滚动到本次结算价)还是"从开仓期初价累计"?
两者在"单次结算后随即平仓"时数值相同,但在**多次结算 / 结算后仍保留持仓**时相差巨大。
3. **是否存在"同一开放持仓多次收益结算"的真实业务场景**?若有,当前"不滚动基准"会导致重复计入,必须修;
若无(每次结算后即关仓),则当前行为无害、仅作防御性加固。
4. **多空与收付是否允许非对角组合**`多头+收取` / `空头+支付`)?若存在,则"收支/多空"两字段非冗余、必须分别存储;
若不存在(vanilla TRS 永远反向),则可视为冗余但当前仍各存各的。
### 8.5 验证脚本(用于把"待确认"转为"已确认"
```sql
-- (a) 是否存在「空头 + 走收益结算/互换路径」且 MarkClosePnl 非 0 的事件
SELECT trade_id, position_id, event_type, position_type, pay_direction,
mark_close_pnl, value_date
FROM swap_event
WHERE position_type = 2
AND event_type IN ('互换','结息') -- 按实际枚举值调整
AND mark_close_pnl <> 0
ORDER BY value_date DESC;
-- (b) 同一持仓是否被多次收益结算(判断是否触发"基准不滚动 → 重复计入")
SELECT position_id, COUNT(*) cnt, MIN(value_date) first_dt, MAX(value_date) last_dt
FROM swap_event
WHERE event_type IN ('互换','结息') -- 按实际枚举值调整
GROUP BY position_id
HAVING COUNT(*) > 1
ORDER BY cnt DESC;
```
- 若 (a) 返回 0 行 → 当前无"空头收益结算"样本,longRatio 不一致问题**未实际触发**;
- 若 (b) 返回 0 行 → 当前业务每次结算后即关仓,"基准不滚动"**无害**
- 若 (b) 有行 → 立即按"结算后把 `swap_position.PosiGrossPrice` 滚动到本次结算价"评估修复。
### 8.6 多轮回归未暴露的原因(实证,非推断)
1. **覆盖空洞**`FrontendCalcCharacterizationTest` 的 income 场景 `FC_006~009` **全是 `PositionType=1`(多头)**
唯一空头场景 `FC_005` 是 unwind。income 的空头分支从未被构造。
2. **校验同源**`ValidateFrontendPnL``SwapDealService.cs:2004`)用 `BuildFrontendValidationDiffs` 以**同一 `CalcIncome`(也无 longRatio**
重算比对,前端错值 == 后端重算 → diff 恒为 0 → 永不告警("预言机与被测代码共享同一 bug"盲区)。
3. **真实数据隐形**golden `dividend_trade_1875/1891.json` 中,空头块为 `swap_position` 持仓行(`MarkClosePnl=0`),非结息事件;
真实样本从未走空头 income。
4. **多头恒等变换**income 漏的是 `longRatio`,而多头 `longRatio=+1` 是恒等变换,故整个多头组合
(≈100% 真实数据 + 100% 回归)下两页数值一致,bug 不可见。
> 注:第 14 点解释的是"longRatio 不一致为何没被抓到"。而 8.2/8.4 的"基准不滚动"问题,
> 即便被测也需"多次收益结算"样本才能触发,现有单笔 income / 单笔 close 测试同样覆盖不到——属另一类盲区。