根因(GLMS-JIATT-20260805):InterestCalcMode='10'(算头不算尾,calcLast=false)时, CalcDailyCompoundInterest 循环的 'if(!calcLast && accrueDate==endDate) continue' 会跳过平仓日。若平仓日恰好是重置日(i%period==0),取价代码块被一并跳过, 导致 flowEvent.FloatRate 落库为旧周期利率,传染后续 EOD 复利计算。 修复:把重置日的 FR007 取价提前到 calcFirst/calcLast 跳过判断之前—— calcLast 只应跳过'计息',不应跳过'重置日利率取价'。同时循环外用最终 floatRate 兜底赋值 flowEvent.FloatRate,确保落库值反映最后重置日的利率。 验证: - CI_007 合成测试(平仓日=重置日):修复前 FloatRate=旧值(FAIL),修复后=新值(PASS) - 连库验证(GLMS-JIATT-20260805 8/4平仓):FloatRate 0.0123→0.0213(8/3新值) - 利息金额不变(calcLast 不计当天利息,Amount 不受影响,只修正 FloatRate 字段) - 全套 swap 测试无新增回归(334通过,2失败均为pre-existing单利/EOD路径) 附:CI_007 复现测试 + GLMS20260805 FR007/EOD 诊断工具 + 文档 longRatio 状态更新
27 KiB
互换(TRS)计算口径一致性:残留问题与治理路线
配套文档:
互换分红损益字段语义与重复计算分析.md(2026-06,根因分析) 本次分析目的:在最新提交e3c473ba(分红收益改由 EOD 单一可信源)之后, 复核"分散计算 / 前后端重复算 / 精度口径不统一"这类 BUG 是否仍在其它地方存在, 并给出"如何避免 + 后续逐渐解决"的路线。 分析日期:2026-08-06
⚠️ 更新状态(2026-08-07)
本文档最初分析日期为 2026-08-06。第七章、第八章 8.1 关于 income 页
longRatio的论断已被代码修复采纳,原文描述已过时,阅读时请注意:
文档原文论断 当前代码状态 修复提交 income 页漏乘 longRatio(多空方向),空头会算出相反符号✅ 已修复:前端 incomeSwapTrade.js:200,208、后端FrontendCalcReference.CalcIncome:100,108均已补longRatiod78d1f48(2026-08-07)income 空头分支无测试覆盖 ✅ 已补: 收取空头_价格上涨_应为亏损+ jest 对应用例d78d1f48FrontendCalcReference.CalcIncome注释写"无 longRatio"✅ 已更新为含 longRatio 的公式 d78d1f48仍有效的部分(治理路线主体,代码尚未动):
- 第三章 A 节:两页
MarkClosePnl仍各自内联(incomeSwapTrade.js:208/unwindSwapTrade.js:306),未接入共享的swapCalc.calcMarkClosePnl——"单一可信源已有却不采纳"仍在。- 第三章 B 节:硬编码精度魔法数字全部仍在(
SwapFlowService:116-118Round(4)、SwapDealService:1561F10、SwapTradeAutoService:458/460Round(10))。- 第三章 C 节 / 第四章 / 第五章:swapCalc 未接入生产、精度集中化、分阶段治理路线——仍是有效的后续路线。
阅读建议:第三~六章按"仍有效的治理路线"读;第七、八章按"历史论证记录"读(结论已被采纳落地)。
一、这类 BUG 的本质(统一定义)
最新修复的"分红收益误显 -36,160",根因不是某一个 if 写错,而是一种结构性缺陷:
金融计算缺乏「单一可信源 / 统一口径」 —— 同一个业务量在 前端 JS / 后端 C# / EOD 批处理 三条路径里被各自重算, 且价格基准、方向符号、精度位数都零散硬编码,导致:
- 前后端(或同一功能的两页)对同一量算出不同值;
- 分红等分量被重复计入(MarkClosePnl 含分红 → 汇总翻倍);
- 金额/价格精度不统一,相邻环节差 1~2 位小数。
它表现为 3 个子模式:
| 子模式 | 例子 | 状态 |
|---|---|---|
| ① 分散计算、无单一可信源 | 分红前端自算(getDivindIn)vs EOD PosiDividendSum |
最新提交已根治(方案C) |
| ② 字段语义重叠→重复累加 | MarkClosePnl 含分红,汇总 RealizedPnl 翻倍 |
自动路径已按方向A改造;前端两页仍有口径差 |
| ③ 硬编码精度/魔法数字 | Math.Round(...,4/10)、ToString("F10") vs ConsGlobal.* |
大量残留 |
二、最新提交(e3c473ba)做了什么(已修复)
- 把"浮动端平仓盈亏·分红
DividendIn"从前端getDivindIn自算(期初持仓×totalInterest) 改为后端SwapDealService.GetPreEodDividendSum读 EODPosiDividendSum(单一可信源)。 - 前端
unwindSwapTrade.js/incomeSwapTrade.js的getDivindIn删除自算逻辑,只保留后端值。 GetBondPayMentInterest标记废弃(保留接口供历史调用)。- 配套绿灯验收测试
GLMS20260105PartialCloseDividendBugTest.cs。
结论:子模式①(分红分散计算)在"预览页展示"这条链路已根治。但②④③ 仍在。
三、同类问题在别处是否还存在(带 file:line 证据)
A. 前端两页 MarkClosePnl 口径不一致 —— 最危险、最具体的残留 ⚠️
2026-08-06 修正:初版称"平仓页用净价、互换页用全价"——经核对代码不准确。 实际两页都用全价:
unwindSwapTrade.js:111把initPosiNetPrice绑到PosiGrossPrice(变量名有误导性),incomeSwapTrade.js:103/207用initPosiGrossPrice(也是全价)。真正差异见下表与 A.1。
同一字段 MarkClosePnl(盯市盈亏),实现对照(来源:团队金标准 YLErpDAL/Helpers/FrontendCalcReference.cs):
| 实现 | 位置 | 公式 | 价格基准 | 方向 | 数量基准 |
|---|---|---|---|---|---|
| 平仓页 | unwindSwapTrade.js:313 |
CloseQty × (价 − 期初全价) × floatRatio × longRatio |
全价(变量名骗人) | 含 longRatio |
CloseQty |
| 互换页 | incomeSwapTrade.js:207 |
positionAmount × (价 − 期初全价) × floatRatio |
全价 | 无 longRatio |
positionAmount=PositionQty×ContractSize |
| 后端 | SwapDealService.cs:1613 |
(价 − PosiGrossPrice) × unwindQty × floatRatio × longRatio |
全价 | 含 longRatio |
unwindQty |
| 金标准 | FrontendCalcReference.CalcUnwind:44 / CalcIncome:107 |
同上(CalcIncome 注释明示"无 longRatio") |
全价 | unwind 有 / income 无 | — |
真正的风险点(已用红测试证明,见 A.1):
- 互换页缺
longRatio(多空方向) —— 这是实打实的 bug。一旦PositionType=2(空头), unwind 页与后端会乘−1,而 income 页不乘 → 同一笔空头两页算出相反符号。 债券 TRS(国联民生等)普遍支持空头,并非边缘场景。 - 数量基准不同(设计使然,非 bug):平仓页用
CloseQty(本次平仓量),互换页用PositionQty×ContractSize(剩余持仓量,因互换是全额置换剩余持仓)。语义不同但各自自洽,需业务确认是否期望一致。 - 中间取整:income 无
×10000/10000步骤(FrontendCalcReference注释明示),但末端都保留 2 位, 干净输入下等价(parity 已证明),属低风险。
共享的 swapCalc.calcMarkClosePnl(swapCalc.js:113,全价+longRatio+scale)已存在,
但两个生产页面都没调用——仍是"单一可信源已有却不采纳"。
会进库吗? 会。手动平仓/互换的 MarkClosePnl 由前端算好传入,后端直接存库不重算
(SwapDealService:1500/1991 的 ValidateFrontendPnL 仅"只读告警、不阻断")。
因此 income 页空头符号错误会直接落到 swap_flow_event.MarkClosePnl,并带偏 FloatPnlSum/SwapRealizedPnL。
A.1 为什么一直没暴露?—— 红测试证据(2026-08-06 补)
三层叠加导致这个 bug 长期潜伏:
- 校验同源、永远自洽:
ValidateFrontendPnL用FrontendCalcReference.CalcIncome重算比对, 而CalcIncome本身就没longRatio,所以"被校验的前端"和"校验用的公式"完全一致,永远不告警。 - 特征化测试全是多头:
FrontendCalcCharacterizationTest的 income 场景FC_006~009全部PositionType=1(多头);唯一空头场景FC_005是 unwind。income 的空头分支从未被触发。 - 告警不阻断 + 生产数据里"空头做互换"相对少见,进一步降低暴露概率。
已落地红测试(证明 bug 真实存在 + 证明改动可修复):
YLErpWeb/fe-tests/markClosePnlShortConsistency.test.js(jest,与parity.test.js同风格)YLErpWeb/fe-tests/_proof_short_income.js(纯 Node 可跑,无需依赖)- 运行结果:多头场景两页一致(PASS,掩盖了问题);空头场景
unwind=−300/income=+300(FAIL,符号相反); 给 income 补longRatio后income=−300与 unwind 一致(PASS)。 → 既证明"当前代码对空头不一致(有问题)",也证明"给 income 补longRatio即可修复"。
B. 后端硬编码精度魔法数字 —— 与集中常量冲突/不绑定的残留 ⚠️
集中常量(Framework/YLErp.Core/ConsGlobal.cs):PriceRound=11、SwapDeliveryPriceRound=9、
MoneyRound=2;模块内 InterestCalculationPrecision=12、EodInterestStoragePrecision=12。
残留的裸数字(不引用上述常量):
| 位置 | 写法 | 应参照 | 问题 |
|---|---|---|---|
SwapFlowService.cs:116-118 |
Math.Round(price, 4)(ClosePrice/SettlePrice/ReferencePrice) |
PriceRound=11 / SwapDeliveryPriceRound=9 |
价格存储 4 位 vs 全系统 9~11 位,口径不一致 |
SwapDealService.cs:1543 |
unwindPriceFee.ToString("F10") |
— | 硬编码 10 位 |
SwapTradeAutoService.cs:458/460 |
Math.Round(..., 10)(净均价) |
PriceRound=11 |
差 1 位 |
SwapTradeAutoService.cs:788/1409 |
Math.Round(..., 4)(费用/平仓费) |
MoneyRound=2 |
费用 4 位 vs 全系统金额 2 位,错配 |
分红链路多处 Math.Round(...,2):SwapEodPositionService.cs:1647/1711/1713/2003、SwapDealService.cs:1658/1882/1883 |
裸 2 |
ConsGlobal.MoneyRound |
目前恰等于 2,但属硬编码,MoneyRound 一旦配置化即失真 |
说明:第 4 行(费用 4 位 vs 金额 2 位)是真实精度错配,不是巧合一致;其余价格/净均价是"差 1 位"的隐患。
C. 交叉校验基础设施已建,但未"落地到生产" —— 治理杠杆闲置
swapCalc.js的calcUnwind/calcIncome(:135/:170)是**"参考规格,未接入生产代码"**—— 注释明确写着生产 Vue 只调用 4 个叶子函数,聚合逻辑仍是各页内联。fe-tests/parity.test.js+swapCalc.test.js已能冻结 8 个 FC 场景,但只覆盖已迁移的 4 个叶子函数。- 这正是"避免反复打补丁"的关键设施,却没把生产聚合逻辑迁过去。
D. 横向同类风险(建议扫描,本次未深入)
互换之外的 期货 / 期权 / 定价引擎(greeks)/ 估值报告 等模块, 很可能也存在"前端重算 + 后端重算""硬编码精度"的同构问题,需专项扫描。
四、如何避免(规范 / 治理层)
- 单一可信源原则(最高优先级)
- 每个金融量只允许一个权威计算点:分红→EOD
PosiDividendSum;盯市→swapCalc.calcMarkClosePnl; 金额聚合→swapCalc.calcFloatPnlSum/calcUnwind/calcIncome。 - 前端只展示、不重算;手动平仓/互换落库时由后端重算而非信任前端传值。
- 每个金融量只允许一个权威计算点:分红→EOD
- 精度集中化(红线)
- 任何
Math.Round/ToString("F")必须引用ConsGlobal.*或GetStorageDeliveryPriceRound(underlyingInstrumentType, code), 禁止裸数字。代码评审把"裸精度数字"列为 blocking 项。
- 任何
- 字段语义单一职责(已定义,需固化)
MarkClosePnl=纯价差盯市、DividendIn=纯分红、RealizedPnl=不重叠分量之和。- 写进评审清单 + 用集成测试守护。
- 自动守护测试(把已建设施用起来)
- 扩展
parity.test.js的"生产表达式逐字抄录 vsSwapCalc"模式到所有关键公式; - 后端补"分红守恒 / 持仓守恒"集成测试:
ΔSwapPositionValue + ΔRealizedPnl == 0(参考分析文档附录 A.2 的 SQL 思路,转成SwapEodPositionServiceIntegrationTest断言)。
- 扩展
- 防回归:将
swapCalc.calcUnwind/calcIncome接入生产,删除两页内联实现。
五、后续逐渐解决的路线(分阶段、低风险)
- 阶段 0(已具备):
parity.test.js/swapCalc.test.js/GLMS20260105PartialCloseDividendBugTest.cs框架。 - 阶段 1(低风险、先消除最危险口径):
让
incomeSwapTrade.js:207与unwindSwapTrade.js:313统一调用swapCalc.calcMarkClosePnl(全价 +longRatio+scale),并确认与后端PosiGrossPrice口径一致; 新增"净价≠全价""空头 longRatio"两个 parity 场景守护。改动小、收益高。 - 阶段 2(消除魔法数字):
把
SwapFlowService/SwapTradeAutoService/SwapDealService里的裸Math.Round(...,4/10)、ToString("F10")替换为ConsGlobal.*/GetStorageDeliveryPriceRound;分红的裸2改为ConsGlobal.MoneyRound。 逐文件改,每改一处跑 parity + 现有单测。 - 阶段 3(后端收口):
手动平仓/互换落库时,后端对
MarkClosePnl/FloatPnlSum/SwapRealizedPnL重算(与swapCalc金标准一致), 不再信任前端;同时跑"持仓守恒"SQL 校验历史数据是否已被两页口径差污染。 - 阶段 4(横向扫描): 用脚本/Explore 扫描 期货、期权、定价引擎、估值报告 模块,查找"前端重算+后端重算""硬编码精度"同类结构,建立清单逐个治理。
- 阶段 5(文档固化):
更新
互换分红损益字段语义与重复计算分析.md,把"已修复 / 待修复"状态机化,作为新人评审清单与回归基线。
六、一句话结论 【longRatio 部分已于 d78d1f48 修复】
最新修复根治了"分红预览"这一条链路的分散计算;
互换页(income)漏乘该问题已于longRatiod78d1f48修复(income 现已含 longRatio,空头符号与平仓一致)。当前仍残留的同类结构:
- 前端平仓页/互换页的
MarkClosePnl仍各自内联,未接入共享的swapCalc.calcMarkClosePnl(单一可信源已有却不采纳);- 后端仍有费用 4 位 vs 金额 2 位等硬编码精度错配(
SwapFlowServiceRound(4)、SwapDealServiceF10 等)。治理的关键不是再打补丁,而是把已建好的
swapCalc单一可信源 + parity 守护真正接入生产, 并按上述 5 个阶段低风险推进。
七、如何确认"income 错 / unwind 对"(而非相反)+ 改动安全性 【✅ 已由 d78d1f48 修复,本章留作论证记录】
这一章回答一个关键质疑:两页口径不同,凭什么断定是 income 漏了
longRatio、而不是 unwind 多算了? 以及:给 income 补longRatio会不会把正确逻辑改坏、或造成"双重翻转"?
✅ 更新(2026-08-07):本章的论断已被团队采纳并落地。
d78d1f48按本章论证给 income 补了longRatio(前端incomeSwapTrade.js:208、后端FrontendCalcReference.CalcIncome:108),并补了空头测试。 本章原"待业务背书的 Working Hypothesis"已成为既成事实,保留作论证记录与防回归参考。
⚠️ 状态说明(2026-08-07,原文):本章关于"income 漏 longRatio → 错 / unwind 对"的论断,原是待业务背书的 Working Hypothesis。 后经代码铁证(7.2/7.3)直接采纳修复,见上方更新。
7.1 两个方向乘子是独立轴(这是避免误判的前提)
floatRatio = PayDirection==1(收取) ? +1 : -1—— 跟随收付方向(FrontendCalcReference.cs:35)longRatio = PositionType==1(多头) ? +1 : -1—— 跟随多空方向(FrontendCalcReference.cs:36)
二者在本系统里互不决定:一个"空头"完全可以 PayDirection=收取。
7.2 裁决性证据:FC_005(空头平仓)冻结基线
UnitTestProject/.../FrontendCalcCharacterizationTest.cs 的 FC_005:
输入:PositionType=2(空头) + PayDirection=1(收取)
注释:floatRatio=1(收取), longRatio=-1(空头)
期望:MarkClosePnl = 1000×(105−100)×1×(−1) = −5000 (空头涨价=亏损,经济正确)
它证明两件事:
- 空头下
floatRatio仍是 +1(方向不靠floatRatio编码)→ 所以 income 只乘floatRatio(+1)而漏longRatio(-1), 对同一个空头会算出 +5000,与权威 −5000 相反 → income 错、unwind 对。 - 因为两轴独立,给 income 补
longRatio是纠正、不是双重翻转("floatRatio 已编码方向"的担忧不成立)。
7.3 三条互相独立的证据链(任一都足以定罪)
| # | 证据 | 来源 | 结论 |
|---|---|---|---|
| 1 | 盯市盈亏空头必须翻转符号(空头跌价才盈利)——会计不变式 | 业务数学,独立于代码 | income 漏方向乘子→空头符号必错 |
| 2 | 后端平仓结算 :1620 重算并覆写 MarkClosePnl=…*floatRatio*longRatio(入库存后端口径);SwapIncome:1980-2013 不重算、原样存前端值 |
SwapDealService.cs |
系统自身的权威定义含 longRatio,income 偏离它 |
| 3 | SwapIncome:1994 用前端 SwapRealizedPnl(由 MarkClosePnl 派生)做 AddClientCash(-SwapRealizedPnl) |
SwapDealService.cs |
空头 income 不仅显示错,实际现金流方向也错(非纯展示) |
7.4 下游是否会"双重翻转 / 补偿性 hack"?
SwapFlowEventService.cs:589对所有流水事件的MarkClosePnl/DividendIn/CloseFee/...统一取反(全局视角翻转,firm book→client view), 不针对 income 或空头。修复后 income 与 unwind 走同一套取反,对称性不变 → 安全。- 全仓检索仅此一处
MarkClosePnl=-MarkClosePnl,无针对 income/空头的补偿性符号翻转 → 不存在"加了 longRatio 反而翻错"的 hack。
7.5 改动安全性论证(如何确保不影响现有正确逻辑)
修复 = 给 income 的 MarkClosePnl(含 FrontendCalcReference.CalcIncome:107)补 × longRatio,使其与权威 unwind 公式口径一致。
- 静态保证(零回归):对所有多头(现有
FC_006~009及全部生产多头 income)longRatio=+1, 乘积不变 → 数值逐位相同,现有正确行为完全不动。改动是"对多头的恒等变换 + 对空头的纠错",是严格的超集。 - 回归护栏:
- 跑现有
FC_001~009+SwapFrontendPnlValidateTest+SwapIncomeScenarioTest→ 全绿(均为多头,不受影响)。 - 新增
FC_010(空头 income,PayDirection=1, PositionType=2)镜像FC_005,锁定纠正后行为,并断言income(空头)==unwind(空头)(parity)。 - jest 红测试
markClosePnlShortConsistency.test.js的"空头"用例由 FAIL 转 PASS,多头用例保持 PASS。
- 跑现有
- 剩余风险(非逻辑正确性,需业务决策):
- 历史脏数据:过去"空头+互换"事件已用错符号落库;修复后新事件正确,跨时间对比会出现不连续。需决定:回溯校正(改
MarkClosePnl+重算SwapRealizedPnl+对账AddClientCash历史)还是标注留痕。 - 改动范围:严格限定在 income 页
:207与CalcIncome:107补longRatio,切忌顺手改floatRatio或其他页面。
- 历史脏数据:过去"空头+互换"事件已用错符号落库;修复后新事件正确,跨时间对比会出现不连续。需决定:回溯校正(改
- 前置确认:建议先让业务/量化签字"income 的
MarkClosePnl应含多空方向(与平仓一致)",再动手——因为结论虽由代码+基线铁证支撑,但涉及客户现金流,需业务背书。
八、当前行为实录与待确认项(2026-08-07)【8.1 longRatio 部分已由 d78d1f48 修复】
本章只记录"代码现在实际怎么做"(可验证事实),并明确列出"哪些还无法判定谁对"。 与第七章(论断层)的区别:第七章给出了"income 错 / unwind 对"的论证,但该论断尚未取得业务/量化背书, 且涉及"价差基准是否应在收益结算后滚动"这一更基础的产品定义。因此把它们在本章降格为"待确认假设", 先如实记录现状,避免过早定性。 文档定位:当前不修改代码、不加测试,仅留痕。正确性待业务/量化逐项确认后再回填。
✅ 更新(2026-08-07):8.1 关于"income 不含 longRatio"的实录已过时——
d78d1f48已补 longRatio。 8.2(价差基准不滚动)、8.4(待确认问题清单中除 longRatio 外的价差基准/多次结算问题)仍待业务确认。
8.1 收益结算(income)页当前行为实录 【✅ longRatio 部分已修复】
- 界面入口:交易详情页头部操作区「收益结算」按钮(权限
交易管理_收益互换),打开/swaptrade2/SwapIncome/(SwapIncome.cshtml+incomeSwapTrade.js)。与「平仓」按钮(SwapUnwind.cshtml)外观相似, 但没有平仓比例、没有事件日期——本质是"期间结算、头寸保留",而非关闭头寸。 - 价差损益
MarkClosePnl当前公式(代码事实):incomeSwapTrade.js:104initPosiGrossPrice = this.floatPosition.PosiGrossPrice:199-200floatRatio = PayDirection==1 ? 1 : -1;longRatio = PositionType==1 ? 1 : -1:208MarkClosePnl = positionAmount × (deliveryPrice − initPosiGrossPrice) × floatRatio × longRatio(positionAmount = PositionQty × ContractSize,deliveryPrice = getStorageDeliveryPrice())- 后端同口径
FrontendCalcReference.CalcIncome:100,108:已含longRatio(d78d1f48修复)。
- 事实结论(已更新):income 页当前已含
floatRatio × longRatio(d78d1f48),与 unwind/后端口径一致。 原文"只用 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 |
✅ 已含(d78d1f48 修复,incomeSwapTrade.js:208 / CalcIncome:108) |
|
| 空头 income 符号 | ✅ 已修正(d78d1f48,空头涨价=亏损,与平仓一致) |
|
| 价差基准是否滚动 | 当前不滚动(恒为 P0) | 收益结算应是"增量(从上次结算价)"还是"绝对(从 P0)"?——产品定义未确认 |
| 多次收益结算重复计入 | 当前若对同一开放持仓做多次收益结算,每次都按 (当前价−P0) 计,首段会被重复计入(数学推导) | 业务实际是否允许/发生过"同一持仓多次收益结算"?——需生产数据确认 |
| 下游现金流 | SwapIncome:2007 AddClientCash(-SwapRealizedPnl) 用前端值记账(code 实证) |
若公式口径需改,历史已落库金额是否需追溯校正? |
8.4 待确认问题清单(请业务 / 量化 / 产品逐项答复)
- 空头收益结算的符号约定:空头 TRS 做期间结算,客户现金流方向应是"空头涨价=亏"(与平仓一致,即含
longRatio)还是相反? 请给出业务样例与会计分录。 - 收益结算的价差口径:期间结算的
MarkClosePnl应是"本段增量"(结算后把持仓基准滚动到本次结算价)还是"从开仓期初价累计"? 两者在"单次结算后随即平仓"时数值相同,但在多次结算 / 结算后仍保留持仓时相差巨大。 - 是否存在"同一开放持仓多次收益结算"的真实业务场景?若有,当前"不滚动基准"会导致重复计入,必须修; 若无(每次结算后即关仓),则当前行为无害、仅作防御性加固。
- 多空与收付是否允许非对角组合(
多头+收取/空头+支付)?若存在,则"收支/多空"两字段非冗余、必须分别存储; 若不存在(vanilla TRS 永远反向),则可视为冗余但当前仍各存各的。
8.5 验证脚本(用于把"待确认"转为"已确认")
-- (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点已由 d78d1f48 补测试填补】
- 覆盖空洞:
FrontendCalcCharacterizationTest的 income 场景FC_006~009全是PositionType=1(多头); 唯一空头场景FC_005是 unwind。income 的空头分支从未被构造。 ✅ 已填补(d78d1f48):新增收取空头_价格上涨_应为亏损测试(C#)+ jest 对应用例。 - 校验同源:
ValidateFrontendPnL(SwapDealService.cs:2004)用BuildFrontendValidationDiffs以同一CalcIncome(也无 longRatio) 重算比对,前端错值 == 后端重算 → diff 恒为 0 → 永不告警("预言机与被测代码共享同一 bug"盲区)。 - 真实数据隐形:golden
dividend_trade_1875/1891.json中,空头块为swap_position持仓行(MarkClosePnl=0),非结息事件; 真实样本从未走空头 income。 - 多头恒等变换:income 漏的是
longRatio,而多头longRatio=+1是恒等变换,故整个多头组合 (≈100% 真实数据 + 100% 回归)下两页数值一致,bug 不可见。
注:第 1–4 点解释的是"longRatio 不一致为何没被抓到"。而 8.2/8.4 的"基准不滚动"问题, 即便被测也需"多次收益结算"样本才能触发,现有单笔 income / 单笔 close 测试同样覆盖不到——属另一类盲区。