修复ab70531f让MarkClosePnl不再含分红后,梳理出消费端3处PosiPnl公式 不一致(①不减分红/②③减分红,2024~2026分批引入),其中②③对新数据 会多减一份分红导致结算单/收益偏小。 给出3种改法权衡(修正公式/数据迁移/TotalClosePnl分离)+建议分阶段路径, 列出4个需团队决策的问题。含字段引用全景表便于实施定位。
8.9 KiB
MarkClosePnl 字段语义统一:决策文档
状态:待团队决策 | 日期:2026-06-26 | 前置:互换分红损益字段语义与重复计算分析.md、修复提交
ab70531f(2026-06-26,MarkClosePnl 不再含分红)
一、问题背景:一个字段,三种语义
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 执行数据迁移:
-- 把历史 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 字段,职责分离(最长远正确)
做法:让每个字段职责单一:
MarkClosePnl永远=纯价差(修复已做到,语义固定)- 新增
[NotMapped] decimal TotalClosePnl => MarkClosePnl + DividendIn + CloseFee,专给"总和"场景(结算单、资金记录判断、界面总和列) - 界面如需"总盈亏"列,显式绑定
TotalClosePnl,而非靠 MarkClosePnl 隐式承担
- ✅ 优点:根除"一字段三语义"债务,未来不会再出现"改一处引发另一处偏差"
- ⚠ 缺点:改动面最大(涉及结算单/资金记录/界面多处),需排期
- 🎯 适合:作为技术债治理的长期目标,可与方案一/二分阶段实施
五、建议路径
推荐分阶段推进,兼顾止血与长期:
- 立即(方案一):修正 ②③ 消费点公式,避免修复后新数据出错(结算单/收益金额偏小)。这是
ab70531f修复的必要补全,不做的话修复只做了一半。 - 近期(方案二):评估数据迁移可行性,统一历史数据语义。
- 中期(方案三):引入 TotalClosePnl,彻底消除语义债务。
六、需团队决策的问题
- 收益(PosiPnl)口径:① 不减分红、②③ 减分红——哪个是业务正确的"收益"定义?(决定公式统一方向)
- 历史数据:是否接受数据迁移(方案二)?还是接受历史偏差、只保证新数据正确?
- 界面"总盈亏"列:是否需要显式新增一列展示
MarkClosePnl+DividendIn+CloseFee总和?(决定是否走方案三) - 实施优先级:方案一是否立即执行(它阻塞
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 |
界面"浮动"列 | 展示 |