Files
zszq-trs/项目文档/MarkClosePnl字段语义统一决策文档.md
hjhan 5a731c4442 docs(swap): MarkClosePnl字段语义统一决策文档
修复ab70531f让MarkClosePnl不再含分红后,梳理出消费端3处PosiPnl公式
不一致(①不减分红/②③减分红,2024~2026分批引入),其中②③对新数据
会多减一份分红导致结算单/收益偏小。

给出3种改法权衡(修正公式/数据迁移/TotalClosePnl分离)+建议分阶段路径,
列出4个需团队决策的问题。含字段引用全景表便于实施定位。
2026-06-26 13:41:32 +08:00

160 lines
8.9 KiB
Markdown
Raw Permalink 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.
# MarkClosePnl 字段语义统一:决策文档
> **状态**:待团队决策 | **日期**2026-06-26 | **前置**:[互换分红损益字段语义与重复计算分析.md](./互换分红损益字段语义与重复计算分析.md)、修复提交 `ab70531f`2026-06-26MarkClosePnl 不再含分红)
---
## 一、问题背景:一个字段,三种语义
`swap_flow_event.MarkClosePnl`(浮动端平仓盈亏·盯市)在历史上**同时承担了三个互斥的职责**,导致各处使用公式不一致。2026-06-26 的修复(`ab70531f`)让它"不再含分红",但**消费端的公式没有同步调整**,新数据下会产生新的偏差。
### MarkClosePnl 的三种历史语义
| 语义 | 含义 | 谁在用 |
|---|---|---|
| **A. 纯价差盯市** | `MarkClosePnl = (平仓价-成本价)×量` | 修复后(`ab70531f`)的目标语义 |
| **B. 价差+费+分红 总和** | 历史上前端 `calcFloatClosePnl` 把费和分红加进去 | 界面"浮动"列(用户误以为是总盈亏)|
| **C. 收益计算基数** | 各处用不同公式减/不减分红得到 PosiPnl | 结算单、收益列表 |
**根因**2024-05-09 从山证 v2.3.0 拷贝(`f9d8a256`)时,`MarkClosePnl` 入库的就是 B(总和),但后续不同开发者按各自理解写了 C 的多种公式,造成不一致。
---
## 二、各处使用现状与不一致清单(含引入时间)
### 2.1 写入点(MarkClosePnl 怎么生成)
| 位置 | 公式 | 引入 | 语义 |
|---|---|---|---|
| `SwapDealService.cs:1101` 自动平仓 | `(平仓价-成本价)×量` | `f9d8a256` 2024-05-09 | **修复后=纯价差** |
| `SwapDealService.cs:1103` 自动平仓(加费) | 上面 `+费×-1` | `f9d8a256` | 修复后=价差-费 |
| `SwapEodPositionService.cs:463` 自动互换分红 | `=0`(修复前=`PosiDividendSum`| `ab70531f` 2026-06-26 | **修复后=0(不含分红)** |
| 前端 `incomeSwapTrade.js:133` 手动互换 | `calcFloatClosePnl``+DividendIn` | `ab70531f` | **修复后=纯价差** |
| 前端 `unwindSwapTrade.js:203` 手动平仓 | 同上 | `ab70531f` | **修复后=纯价差** |
> **关键**`ab70531f` 已把 4 个写入点统一为"纯价差/价差-费",不含分红。
### 2.2 消费点(PosiPnl / 收益公式)—— ⚠ 不一致集中区
| # | 位置 | 公式 | 减分红? | 引入 | 修复后影响 |
|---|---|---|---|---|---|
| ① | `SwapFlowEvent.cs:384` | `PosiPnl = MarkClosePnl - TradingFee` | **否** | `f9d8a256` 2024-05-09 | 新数据偏大(没减分红)|
| ② | `TradeSettleBillGenerator.cs:110` 结算单 | `PosiPnl = -(MarkClosePnl - tradingFee - DividendIn)` | **是** | `48cf0592` 2026-05-06 | **新数据多减一份分红** ⚠ |
| ③ | `SwapFlowEventService.cs:528` 收益列表 | `PosiPnl = MarkClosePnl - TradeFee - DividendIn - TradingFee` | **是** | `829334a4` 2025-05-30 | **新数据多减一份分红** ⚠ |
**核心风险(②③)**:修复前 MarkClosePnl 含分红,减去 DividendIn 正好还原纯价差;修复后 MarkClosePnl **不含**分红了,再减 DividendIn 就**多减了一份**,结算单和收益列表金额偏小。
### 2.3 当"总和"用的地方
| 位置 | 公式 | 引入 | 含义 |
|---|---|---|---|
| `SwapEodPositionService.cs:681` | `amount = MarkClosePnl + CloseFee + DividendIn` | `f9d8a256` 2024-05-09 | 判是否产生资金记录(三者总和)|
| `SwapFlowEventService.cs:529` | `NetSettmentAmount = MarkClosePnl + InterestClosePnL` | `083848fb` 2025-05-28 | 净结算额 |
| `SwapEodPositionService.cs:1610` | `TdCloseMtmPnl = Σ MarkClosePnl` | `f9d8a256` 2024-05-09 | eod 盯市列累加 |
### 2.4 界面展示(eventlist/SwapflowList/EodPositionRisks
"浮动端平仓盈亏"在界面**拆成 3 个独立列**,没有"总和"列:
- `MarkClosePnl` → 列名"浮动端平仓盈亏·**浮动**"
- `DividendIn` → 列名"浮动端平仓盈亏·**分红**"
- `CloseFee` → 列名"浮动端平仓盈亏·**费用**"
> 用户历史上看到的"浮动列"= 总盈亏,是 buggy 的 MarkClosePnl 凑出来的;修复后该列=纯价差,会变小。**"三者总和"从未有独立列**。
---
## 三、历史数据问题
已入库的 `swap_flow_event.MarkClosePnl` 不可逆——**历史记录含分红,新记录不含**。这意味着任何消费点公式,对历史和新数据只能选一种正确:
| 公式策略 | 历史数据(含分红) | 新数据(纯价差) |
|---|---|---|
| 不减 DividendIn(如 ①)| 偏大 | 偏大 |
| 减 DividendIn(如 ②③)| 正确(抵消)| **偏小(多减)** |
**没有一种公式能同时让历史和新数据都对**,除非:
- (a) 数据迁移:刷新历史 MarkClosePnl 还原为纯价差,或
- (b) 加版本标记:区分"修复前/后"的事件,分别用不同公式
---
## 四、三种改法方案(供团队选择)
### 方案一:修正消费点公式(最小改动,推荐作为第一步)
**做法**:把 MarkClosePnl 的语义**正式定为"纯价差盯市"**,修正 ②③ 去掉 `-DividendIn`
| 位置 | 改动 |
|---|---|
| ② `TradeSettleBillGenerator.cs:110` | `-(MarkClosePnl - tradingFee)`(去掉 -DividendIn|
| ③ `SwapFlowEventService.cs:528` | `MarkClosePnl - TradeFee - TradingFee`(去掉 -DividendIn|
| ① `SwapFlowEvent.cs:384` | 保持,或补 `-DividendIn` 与 ③ 统一(需业务确认收益口径)|
-**优点**:改动小(3 行),新数据全部正确,语义立即清晰
-**缺点**:**历史数据仍不一致**(见第三节),需配合数据迁移或接受历史偏差
- 🎯 **适合**:快速止血,让修复(ab70531f)真正生效,不让新数据出错
### 方案二:数据迁移 + 方案一(彻底统一)
**做法**:在方案一基础上,对历史 `swap_flow_event` 执行数据迁移:
```sql
-- 把历史 MarkClosePnl 还原为纯价差(减去当时混入的分红和费)
UPDATE swap_flow_event
SET MarkClosePnl = MarkClosePnl - DividendIn - CloseFee
WHERE EventType IN (2,3,4) AND DataState = 100
AND EventDate < '2026-06-26'; -- 修复提交前的事件
```
(实际迁移需先 SELECT 验证范围、备份、分批执行)
-**优点**:历史与新数据语义完全统一,所有公式一套即可
-**缺点**:改库有风险,需备份+回滚预案;eod 历史表(RealizedMtmPnL 等)是否也需重算需评估
- 🎯 **适合**:团队接受改历史数据,追求长期干净
### 方案三:新增 TotalClosePnl 字段,职责分离(最长远正确)
**做法**:让每个字段职责单一:
1. `MarkClosePnl` 永远=纯价差(修复已做到,语义固定)
2. 新增 `[NotMapped] decimal TotalClosePnl => MarkClosePnl + DividendIn + CloseFee`,专给"总和"场景(结算单、资金记录判断、界面总和列)
3. 界面如需"总盈亏"列,显式绑定 `TotalClosePnl`,而非靠 MarkClosePnl 隐式承担
-**优点**:根除"一字段三语义"债务,未来不会再出现"改一处引发另一处偏差"
-**缺点**:改动面最大(涉及结算单/资金记录/界面多处),需排期
- 🎯 **适合**:作为技术债治理的长期目标,可与方案一/二分阶段实施
---
## 五、建议路径
**推荐分阶段推进**,兼顾止血与长期:
1. **立即(方案一)**:修正 ②③ 消费点公式,避免修复后新数据出错(结算单/收益金额偏小)。这是 `ab70531f` 修复的必要补全,不做的话修复只做了一半。
2. **近期(方案二)**:评估数据迁移可行性,统一历史数据语义。
3. **中期(方案三)**:引入 TotalClosePnl,彻底消除语义债务。
---
## 六、需团队决策的问题
1. **收益(PosiPnl)口径**:① 不减分红、②③ 减分红——哪个是业务正确的"收益"定义?(决定公式统一方向)
2. **历史数据**:是否接受数据迁移(方案二)?还是接受历史偏差、只保证新数据正确?
3. **界面"总盈亏"列**:是否需要显式新增一列展示 `MarkClosePnl+DividendIn+CloseFee` 总和?(决定是否走方案三)
4. **实施优先级**:方案一是否立即执行(它阻塞 ab70531f 修复的完整生效)?
---
## 附录:字段引用全景(便于实施时定位)
| 文件:行 | 用法 | 语义 |
|---|---|---|
| `SwapFlowEvent.cs:384` | `PosiPnl = MarkClosePnl - TradingFee` | 收益(不减分红) |
| `SwapFlowEvent.cs:211` | 字段定义 | 入库列 |
| `TradeSettleBillGenerator.cs:110,112,116` | 减 DividendIn / 取负 / 净结算 | 收益+结算 |
| `SwapFlowEventService.cs:287,378,429,509,528,529` | 赋值/取负/收益/净结算 | 多用途 |
| `SwapEodPositionService.cs:463,681,694,1610` | 赋0/总和/累加/eod盯市 | 写入+累加 |
| `SwapDealService.cs:101,275,1101,1103,1219,1350` | 赋值 | 写入 |
| `SwapEndConfirmService.cs:96` | `SwapCloseAmount = MarkClosePnl` | 展示 |
| `RealTimeClientBanlanceService.cs:1337,1343` | `+ MarkClosePnl` 累加 | 收益累加 |
| `SwapFlowService.cs:381` | 导出格式化 | 导出 |
| 前端 `eventlist.js:247`/`SwapflowList.js:556` | 界面"浮动"列 | 展示 |