Files
zszq-trs/项目文档/互换价格字段命名规范决策文档.md
2026-07-02 18:09:27 +08:00

292 lines
18 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.
# 互换价格字段命名规范决策
> 本文档是**命名规范的决策与论证**,回答"应该叫什么、为什么这么叫"。
> 现状梳理(字段流向、存储显示规则)见姊妹文档 [互换交易价格字段存储与显示规范.md](互换交易价格字段存储与显示规范.md)。
> 适用范围:互换业务(收益互换)涉及的价格、方向、时点字段。期权/远期业务字段不在本文档范围。
---
## 一、为什么命名必须先定下来
现状是**同一概念多套名字、名字与含义颠倒**,已经反复造成 Bug。三条最严重的证据:
1. **`PosiNetPrice` 名为"Net",实为"含费全价"**。真正的净价是 `PosiNetNoFeePrice`。连现状文档(《互换交易价格字段存储与显示规范》)都把它误标成"期初净价"——文档作者都被字段名骗了。
2. **两套并行命名无对应关系**:持仓表 `PosiNetPrice` ↔ 流水表 `TradingAmountFeeAvg`,持仓表 `PosiGrossPrice` ↔ 流水表 `TradingAmountAvg`,名字里没有一处对得上。
3. **前端同一中文标签绑不同字段**`成交净价` 一处绑 `TradingAmountNet`,另一处绑 `TradingAmountNetAvg``期初标的价格` 录入页绑 `PosiGrossPrice`,平仓明细页绑 `PosiNetPrice`
命名不定下来,后续每个改动都要反复核对数据流向,且极易引入新 Bug。本文档的目的是**把每个维度的命名一次性钉死,给出对比理由**。
---
## 二、三个维度的决策
互换价格有三个正交维度,必须分别命名:
| 维度 | 取值 | 现状混乱 |
|---|---|---|
| **A. 价格种类** | 全价 / 净价 | Net/Gross 颠倒;Posi vs TradingAmount 两套 |
| **B. 是否含交易费** | 含费 / 不含费 | Fee 标记位置在两套体系里相反 |
| **C. 时点** | 期初 / 期末 / 盯市 | Start/Begin/End/Initial/Final/Spot/Open 散布,无统一规则 |
外加一个**方向维度**(收取方/支付方),目前用中文枚举,trade_swap 老表用 Get/Pay。
下面逐项决策。
---
## 三、维度 A:价格种类 —— 采用 Clean / Dirty
### 候选对比
| 方案 | 优点 | 缺点 | 评价 |
|---|---|---|---|
| **`Clean / Dirty`** | 债券行业标准术语;歧义最小;与中债估值表 `dirty_price_close`/`net_price`、量化库常见命名一致 | 非 OTDer 可能需要学习成本 | **✅ 推荐** |
| `Net / Gross`(现状持仓表) | 简短 | **本项目已颠倒**PosiNetPrice 是含费全价);Net/Gross 在金融里本身就有"净值/毛额"歧义,不专指债券净价全价 | ❌ 已证明不可行 |
| `Clean / Full` | Full 含义尚可 | 不如 Dirty 标准;中债表用 dirty,不统一 | △ |
| `净价 / 全价`(中文直译) | 业务人员熟悉 | C# 字段用中文不合规;与现有英文体系混搭更乱 | ❌ |
### 决策
**全价用 `Dirty`,净价用 `Clean`。**
理由:
1. **行业术语,零歧义**。Clean/Dirty 是债券领域全世界通用的无歧义术语(clean price = 不含应计利息,dirty price = 含应计利息)。
2. **与数据源一致**。录入默认价格来源 `china_bond_valuation` 表用的就是 `dirty_price_close`(日终估值全价)和 `net_price`(估价净值)。字段命名与数据源对齐,减少心智负担。
3. **现状 Net/Gross 已被证明失败**。它在本项目里语义颠倒,且 Net 在不同字段里含义不同(PosiNetPrice 的 Net = 含费,PosiNetNoFeePrice 的 Net = 净价),是混乱根源,必须废弃。
### 与"费"维度组合后的完整 4 象限见第五节。
---
## 四、维度 B:费用 —— 采用 Fee / NoFee
### 先澄清:"费"是什么
**"费" = 交易费用 / 佣金(TradingFee**,由 `TradeFeeHelper.CalcPriceWithFee(price, fee, qty)` 计算:`含费价 = 不含费价 + 单位佣金`。它**不是**应计利息(应计利息体现在 Clean/Dirty 维度),也**不是**利息腿费用。
### 候选对比
| 方案 | 优点 | 缺点 | 评价 |
|---|---|---|---|
| **`Fee / NoFee`(后缀)** | 现状已在用(PosiNetNoFeePrice/TradingAmountFeeAvg);直白 | — | **✅ 推荐**(沿用现有约定,零迁移成本) |
| `Gross / Net`(用 Net 表示含费) | — | 与维度 A 的 Clean/Dirty 严重冲突,两套词打架 | ❌ |
| `WithFee / WithoutFee` | 更明确 | 太长;后缀冗余 | △ |
| `PreFee / PostFee` | 金融衍生品常见 | 偏衍生品语境,互换收益端用着别扭 | △ |
### 决策
**含费用 `Fee` 后缀,不含费用 `NoFee` 后缀。**
理由:现状流水表已经是这套(`TradingAmountAvg` 不含费 / `TradingAmountFeeAvg` 含费 / `TradingAmountNetAvg` 净价不含费 / `TradingAmountNetFeeAvg` 净价含费),**这个约定本身没问题**,问题是持仓表用了另一套颠倒的 Posi 命名。统一到 Fee/NoFee 即可。
---
## 五、维度 A+B 组合:价格 4 象限最终命名
### 决策表
| 业务含义 | **规范命名** | 现状持仓表 | 现状流水表 |
|---|---|---|---|
| 全价 · 不含费 | `XxxDirtyPrice` | `PosiGrossPrice` | `TradingAmountAvg` |
| 全价 · 含费 | `XxxDirtyFeePrice` | `PosiNetPrice` ⚠️ | `TradingAmountFeeAvg` |
| 净价 · 不含费 | `XxxCleanPrice` | `PosiNetNoFeePrice` | `TradingAmountNetAvg` |
| 净价 · 含费 | `XxxCleanFeePrice` | `PosiNetFeePrice` | `TradingAmountNetFeeAvg` |
其中 `Xxx` 是时点前缀(见第六节)。
### 命名规则一句话
> **`{时点}{Dirty|Clean}{Fee|}{Price}`** —— Dirty/Clean 选价格种类,Fee 出现表示含费、不出现表示不含费。
例:`EntryDirtyPrice`(期初全价不含费)、`EntryDirtyFeePrice`(期初全价含费)、`ExitCleanPrice`(期末净价不含费)。
---
## 六、维度 C:时点 —— 采用 Entry / Exit+ Market
这是你重点要求对比的部分。先看现状,再逐词对比。
### 现状:时点词散布(统计自实体字段)
| 现状词 | 出现次数(实体字段) | 用在什么字段 |
|---|---|---|
| `Start` | 36 | StartDate(起始日) |
| `Spot` | 28+4 | SpotPrice(期初价,期权/远期)、GetSpotPrice/PaySpotPricetrade_swap |
| `Open` | 3 | OpenPrice、OpenTradePrice(开仓价) |
| `Init/Initial` | 4 | InitYtm、InitialSpotPrice[NotMapped] |
| `Final` | 10 | FinalPrice(期末价)、GetFinalPrice/PayFinalPrice |
| `End` | 12 | EndDate(结束日) |
| `Close` | 21 | ClosePrice(收盘价,行情侧) |
| `Maturity` | 14 | MaturityDate(到期日) |
| `Entry/Exit` | **0** | 未使用 |
**现状问题**:表达"期初价"用了 `Spot``Open``Init``Initial` 四种;表达"期末价"用了 `Final``End``Close` 三种;且 `Close` 还和"行情收盘价"语义重叠。
### 候选词逐个对比
#### 期初价(开仓成本价)候选
| 方案 | 含义直觉 | 优点 | 缺点 | 评价 |
|---|---|---|---|---|
| **`Entry`** | 入场/建仓 | 交易术语,明确"进入持仓那一刻";与 `Exit` 对称;**不与现有任何日期/价格词冲突** | 全新词,需迁移 | **✅ 推荐** |
| `Start` | 开始 | 与 `StartDate`(起始日)一致,延续性最好 | **与日期混淆**`StartPrice` 容易被误读成"起始日当天的价格"而非"建仓成本价";价格 vs 日期语义纠缠 | △ |
| `Begin` | 开始 | 同 Start | 同 Start;且与 Start 二选一造成二次混乱(现已有 Start,再加 Begin 是雪上加霜) | ❌ |
| `Initial` / `Init` | 初始 | 语义尚可 | `Init` 是缩写不规整;现状 `InitYtm` 已用,但 `InitialPrice` 太长;**Init/Initial 两种并存**(现状就有 `InitYtm``InitialSpotPrice`)本身就是混乱证据 | ❌ |
| `Spot` | 即期 | 现状 trade_swap 在用 | **语义错误**Spot 在金融里指"即期(spot vs forward",不是"期初";互换里强行用 Spot 表示期初是误用;且与"即期价格"概念冲突 | ❌ |
| `Open` | 开仓 | 有"开仓"意味 | `OpenPrice` 在期权语境指"开盘价"open=开盘 vs close=收盘),互换用 Open 会与行情术语打架 | ❌ |
#### 期末价(平仓/到期价)候选
| 方案 | 含义直觉 | 优点 | 缺点 | 评价 |
|---|---|---|---|---|
| **`Exit`** | 出场/了结 | 与 Entry 对称;明确"退出持仓那一刻"**不与 Close/Maturity 冲突** | 全新词,需迁移 | **✅ 推荐** |
| `Final` | 最终 | 现状在用(FinalPrice | `Final` 在期权语境常指"到期(final/expiration)",与互换"平仓了结"语义有偏差;且 Final 给人"永远结束"的感觉,但互换可能多次部分平仓(每次都是 Exit) | △ |
| `End` | 结束 | 简短 | 与 `EndDate`(结束日)混淆,同 Start 的问题 | ❌ |
| `Close` | 收盘 | 行情侧在用(ClosePrice | **语义已被占用**:Close = 收盘行情价;互换平仓价用 Close 会和"每日盯市收盘价"混淆。这是现状混乱之一 | ❌ |
| `Maturity` | 到期 | 债券标准术语 | 只适用于"持有到期",不适用于"提前平仓";互换平仓≠到期 | ❌(仅 MaturityDate 用于到期日) |
### 决策
**期初用 `Entry`,期末用 `Exit`,每日盯市用 `Market`。**
### 为什么是 Entry/Exit 而不是其他 —— 三条核心理由
**理由 1Entry/Exit 是唯一一组"互相对称、且不与现有词冲突"的方案。**
| 对称组 | 冲突情况 |
|---|---|
| **Entry / Exit** | ✅ 零冲突,现有代码 0 处使用 |
| Start / End | ❌ 与 StartDate/EndDate 日期字段冲突 |
| Open / Close | ❌ 与行情开盘价/收盘价冲突 |
| Initial / Final | △ Initial 太长,Final 偏"到期"语义 |
| Begin / End | ❌ 与 Start 重复造词 |
互换价格的核心心智模型是"**Entry(建仓成本)vs Exit(了结价格)**"——盈亏 = Exit Entry。Entry/Exit 直接表达这个对偶关系,其他词都带着别的语义包袱。
**理由 2:互换"多次部分平仓"语义下,Entry/Exit 比 Start/End/Final 更准确。**
互换持仓期间可以有多次部分平仓(每次部分平仓都是一次 Exit),不是"一次性 Start→End"。`Final`(最终)暗示"只有最后一次",`Start`/`End` 暗示"单一起止点",都不符合"多次 Exit"的现实。`Exit` 天然支持"多次出场"语义(每次平仓 = 一次 Exit)。
**理由 3:与"盯市价"清晰分层。**
| 时点 | 命名 | 含义 |
|---|---|---|
| 建仓时 | `Entry*` | 成本价(固定) |
| 平仓时 | `Exit*` | 了结成交价(每次平仓一个) |
| 持仓期间每日 | `Market*` | 盯市价(随行情变动) |
三者职责清晰:Entry 是成本基线,Exit 是实现盈亏的时点,Market 是浮动盈亏的基准。现状 `UnderlyingPrice`(盯市价)改为 `MarketDirtyPrice` 后,与 Entry/Exit 形成 `Entry / Market / Exit` 完整时点链。
### 收益率字段同理
| 含义 | 规范命名 | 现状 |
|---|---|---|
| 期初 YTM | `EntryYtm` | InitYtm |
| 平仓 YTM | `ExitYtm` | ytm / Ytm |
---
## 七、维度 D:方向 —— 收取/支付方的命名
### 现状
| 位置 | 现状命名 | 问题 |
|---|---|---|
| `SwapDirectionEnum`V2 在用) | `收取 / 支付`(中文) | C# 字段用中文不合规;与英文代码混排突兀 |
| `trade_swap`(老表,已弃用互换主流程) | `Get* / Pay*`(如 GetSpotPrice/PaySpotPrice | Get 语义弱,不像金融术语;你已指出这是老互换遗留 |
| `PositionTypeFlag`(多空) | `Long / Short` | ✅ 这个没问题,标准术语 |
| 业务文档/前端 | "收取方/支付方"、"固定端/浮动端" | 中文展示 OK |
### 候选对比(英文方向词)
| 方案 | 优点 | 缺点 | 评价 |
|---|---|---|---|
| **`Rcv / Pay`** | 金融工程标准(IRS 收付固定方叫 Receive/Pay);FINCAD/Bloomberg/量化库通用;3字母缩写简洁 | Rcv 需要新人熟悉"是 receive 缩写" | **✅ 推荐** |
| `Receive / Pay` | 不缩写,更明确 | Receive 较长,字段名 `ReceiveDirtyPrice` 偏长 | △ |
| `Get / Pay`(现状老表) | 现状 | Get 不是金融术语,语义弱("获得"太泛);与 Pay 不对称(Pay 是金融词,Get 是口语) | ❌ |
| `Long / Short` | 标准术语 | **语义不同**Long/Short 是多头/空头(PositionType),不是收取/支付方(SwapDirection);互换里收取方可能是空头。**两者不能混用** | ❌(维度不同) |
| `Buy / Sell` | 通用 | 互换不是买卖关系,是交换现金流;Buy/Sell 易误导 | ❌ |
### 决策
**英文方向词用 `Rcv / Pay`,仅用于代码字段名(如 `RcvEntryDirtyPrice`);枚举值和前端展示保持中文"收取/支付"。**
理由:
1. **金融标准**。利率互换(IRS)行业里固定方/浮动方标准叫法就是 Receive fixed / Pay fixedFINCAD、Bloomberg、主流量化库都用 Rcv/Pay。
2. **纠正老表 Get 的错误**。trade_swap 的 `Get/Pay` 里 Get 不是金融词,是早期随手起的,正好借命名规范统一为 Rcv/Pay。
3. **不与 Long/Short 冲突**。Rcv/Pay 是 SwapDirection(收取/支付哪一端现金流),Long/Short 是 PositionType(多空),两个维度独立,不能互相替代。
4. **中文枚举保留**`SwapDirectionEnum.收取/支付` 已被业务文档、前端、监管报表广泛使用,强行改英文枚举值成本高且无收益。只在新英文字段名里用 Rcv/Pay。
> 注:当前 V2 互换主流程(swap_position/eod_swap_position)已经不按"收取/支付腿"存价格(价格存在不分腿的持仓记录里,方向用 `PosiDirection` 字段标记),所以 `Rcv/Pay` 前缀**只在需要显式区分双腿的场景**(如 trade_swap 录入、双边互换)使用,不是所有字段都加。
---
## 八、最终命名总表(一锤定音)
### 8.1 价格 4 象限(含时点前缀)
| 业务含义 | 规范命名 | 现状(持仓/流水) |
|---|---|---|
| 期初全价·不含费 | `EntryDirtyPrice` | PosiGrossPrice / — |
| 期初全价·含费 | `EntryDirtyFeePrice` | PosiNetPrice ⚠️ / — |
| 期初净价·不含费 | `EntryCleanPrice` | PosiNetNoFeePrice / — |
| 期初净价·含费 | `EntryCleanFeePrice` | PosiNetFeePrice / — |
| 期末/平仓全价·不含费 | `ExitDirtyPrice` | — / TradingAmountAvg |
| 期末/平仓全价·含费 | `ExitDirtyFeePrice` | — / TradingAmountFeeAvg |
| 期末/平仓净价·不含费 | `ExitCleanPrice` | — / TradingAmountNetAvg |
| 期末/平仓净价·含费 | `ExitCleanFeePrice` | — / TradingAmountNetFeeAvg |
| 盯市全价 | `MarketDirtyPrice` | UnderlyingPrice |
| 期初收益率 | `EntryYtm` | InitYtm |
| 期末收益率 | `ExitYtm` | ytm |
### 8.2 方向
| 业务含义 | 代码字段名 | 枚举/展示值 |
|---|---|---|
| 收取方 | `Rcv*`(如需区分腿) | 收取(SwapDirectionEnum.收取) |
| 支付方 | `Pay*`(如需区分腿) | 支付(SwapDirectionEnum.支付) |
| 多头 | `Long`PositionTypeFlag.Long | 多头 |
| 空头 | `Short`PositionTypeFlag.Short | 空头 |
### 8.3 命名规则速记
> **价格 = `{Entry|Exit|Market}{Dirty|Clean}{Fee|}Price`**
> **方向 = `Rcv|Pay`SwapDirection)与 `Long|Short`PositionType)是两个独立维度,不混用**
> **收益率 = `{Entry|Exit}Ytm`**
---
## 九、落地策略(渐进式,不一次性改)
直接全局重命名风险极高(DB 列 + EF 实体 + ~15 服务类 + ~20 前端文件 + 交易确认书 + Excel 导入)。建议分阶段:
| 阶段 | 动作 | 风险 | 产出 |
|---|---|---|---|
| **0. 文档定调(本文档)** | 命名规则钉死,团队达成共识 | 零 | 本文档 |
| **1. 注释止血** | 在现状最混乱的字段(PosiNetPrice/PosiGrossPrice)加 `[Obsolete]` + XML 注释,写明真实含义与规范名 | 零 | 防止再被字段名误导 |
| **2. 前端统一取值封装** | 新增 `swapPriceHelper.js`,把分散的 `item.PosiGrossPrice` 等收敛成 `getEntryDirtyPrice(item)`;先修"同标签绑不同字段"的混乱 | 低 | 前端混乱点消除 |
| **3. 新代码强制规范** | 新增字段/新功能必须用规范命名;对外 API 新增 DTO 用规范名,内部旧字段靠 Mapper 转换 | 低 | 增量规范化 |
| **4. 大重构(慎重)** | DB 列重命名 + EF 映射 + 全量替换,配合数据迁移。建议趁大版本升级做 | 高 | 彻底统一 |
**阶段 1 最该立刻做**:给 `PosiNetPrice`(含费全价,非净价)和 `PosiGrossPrice`(不含费全价)加注释,因为连现状文档都被它们误导过。
---
## 附录:现状混乱全证据清单(决策依据)
> 以下每条都有代码/DDL/前端文件佐证,是上述决策的事实基础。
| # | 混乱点 | 关键证据位置 |
|---|---|---|
| 1 | PosiNetPrice 名为 Net 实为含费全价 | `SwapTradeService.cs:372-373` 赋值:PosiNetPrice←TradingAmountFeeAvg(含费均价) |
| 2 | PosiGrossPrice 名为 Gross 实为不含费全价 | 同上:PosiGrossPrice←TradingAmountAvg(不含费均价) |
| 3 | 同字段两表 DDL 注释矛盾 | PosiNetPriceswap_position 注释"持仓净价"eod_swap_position 注释"期初标的价格" |
| 4 | 持仓表/流水表两套命名无对应 | Posi*Net/Grossvs TradingAmount*Avg/FeeAvg),名字对不上 |
| 5 | 前端同标签绑不同字段 | `成交净价` 绑 TradingAmountNet(303) 也绑 TradingAmountNetAvg(508),见 SwapflowList.js |
| 6 | 作者自曝字段语义错位 | `SwapIncome.cshtml:155``SwapDealService.cs:279` 注释:"TradingAmountNetAvg 字段名为期末语义,实装期初值" |
| 7 | trade_swap 第三套命名 | GetSpotPrice/PaySpotPrice/GetFinalPrice/PayFinalPrice,与 Posi* 平行无对应 |
| 8 | 时点词散布 | 期初价用了 Spot/Open/Init/Initial 四种;期末价用 Final/End/Close 三种 |
| 9 | Close 语义被占用 | ClosePrice=行情收盘价 vs 互换平仓价混用 |
| 10 | Get 不是金融术语 | trade_swap 的 Get/PayGet 语义弱,与 Pay 不对称 |
| 11 | 现状文档自身被误导 | 《互换交易价格字段存储与显示规范》把 PosiNetPrice 标为"期初净价",实为含费全价 |