Files
zszq-trs/YLErpWeb/fe-tests/markClosePnlShortConsistency.test.js
T
hjhan aaeb4bddf8 docs(swap): 新增互换 MarkClosePnl 口径一致性审计与空头回归红测试(不改生产代码)
- 项目文档/互换计算口径一致性_残留问题与治理路线.md:定义 BUG 本质(分散计算/字段重叠/硬编码精度),
  定位 income 页缺 longRatio 导致空头符号相反的残留点,并给出如何确认 income 错/unwind 对 的裁决链与改动安全性论证。
- YLErpWeb/fe-tests/_proof_short_income.js:纯 Node 红测试原型,证明多头两页一致(掩盖)、空头符号相反、补 longRatio 后一致。
- YLErpWeb/fe-tests/markClosePnlShortConsistency.test.js:jest 格式红测试(与 parity.test.js 同风格),作为后续修复的回归护栏。

说明:仅审计与测试资产,本次未修改任何生产代码(前端页面/后端 C# 均未改动)。
2026-08-06 22:00:44 +08:00

74 lines
3.7 KiB
JavaScript
Raw 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.
/**
* markClosePnlShortConsistency.test.js — 空头场景下 unwind/income 两页 MarkClosePnl 一致性(红测试)
* ============================================================================
* 状态:当前为 RED(证明 income 页 MarkClosePnl 缺 longRatio 的真实不一致)。
* 修复 incomeSwapTrade.js:207 与 YLErpDAL/Helpers/FrontendCalcReference.CalcIncome:107
* 补上 longRatio 后,本文件应全部转 GREEN。
*
* 背景:
* - 团队金标准 FrontendCalcReference 明确记录两页差异:unwind 含 longRatio
* income 无 longRatioCalcIncome:107,注释"无 longRatio")。
* - 现有特征化测试 FrontendCalcCharacterizationTest FC_001~009 的 income 场景
* FC_006~009)全部 PositionType=1(多头),唯一空头场景 FC_005 是 unwind
* 因此 income 的空头分支从未被覆盖 → bug 长期未被发现。
* - 后端 ValidateFrontendPnL 用同一 CalcIncome 重算比对,公式同源故永远自洽,抓不到。
*
* 公式逐字抄录(来源见注释行号),与生产代码一致;不改动任何生产文件。
*/
const SwapCalc = require('../wwwroot/Scripts/app/swaptrade/swapCalc.js');
// unwind 页 MarkClosePnlunwindSwapTrade.js:313,含 longRatio
function PROD_unwindMarkClosePnl({ closeQty, deliveryPrice, initPosiNetPrice, payDirection, positionType }) {
const floatRatio = payDirection === 1 ? 1 : -1;
const longRatio = positionType === 1 ? 1 : -1;
let v = Math.round(closeQty * (deliveryPrice - initPosiNetPrice) * floatRatio * longRatio * 10000) / 10000;
return Number(v.toFixed(2));
}
// income 页 MarkClosePnlincomeSwapTrade.js:207,无 longRatio
function PROD_incomeMarkClosePnl({ positionAmount, deliveryPrice, initPosiGrossPrice, payDirection }) {
const floatRatio = payDirection === 1 ? 1 : -1;
let v = positionAmount * (deliveryPrice - initPosiGrossPrice) * floatRatio;
return Number(v.toFixed(2));
}
// 金标准(swapCalc.calcMarkClosePnl,对齐 C# FrontendCalcReference.CalcUnwind,含 longRatio
function GOLD({ closeQty, tradingAmountAvg, scale, entryPrice, payDirection, positionType }) {
const floatRatio = payDirection === 1 ? 1 : -1;
const longRatio = positionType === 1 ? 1 : -1;
return SwapCalc.calcMarkClosePnl(closeQty, tradingAmountAvg, scale, entryPrice, floatRatio, longRatio);
}
// 同一笔债券 TRS:期初全价 1.02,期末 105(×100形态→1.05),价差 0.03;数量 10000
const CASE = {
closeQty: 10000, positionAmount: 10000,
deliveryPrice: 105, initPosiNetPrice: 1.02, initPosiGrossPrice: 1.02,
tradingAmountAvg: 105, scale: 0.01, entryPrice: 1.02,
payDirection: 1,
};
describe('MarkClosePnl 两页一致性(多头,应一致)', () => {
test('多头:unwind == income == gold', () => {
const u = PROD_unwindMarkClosePnl({ ...CASE, positionType: 1 });
const i = PROD_incomeMarkClosePnl({ ...CASE, });
const g = GOLD({ ...CASE, positionType: 1 });
expect(u).toBe(i);
expect(i).toBe(g);
});
});
describe('MarkClosePnl 两页一致性(空头,当前 RED)', () => {
test('空头:unwind == income(当前 FAIL → 证明 income 缺 longRatio 的 bug', () => {
const u = PROD_unwindMarkClosePnl({ ...CASE, positionType: 2 });
const i = PROD_incomeMarkClosePnl({ ...CASE, });
// 空头价格涨应亏损:unwind = -300income 当前 = +300(符号反了)
expect(u).toBe(i);
});
test('空头:income 应等于 gold(补 longRatio 后才会 PASS', () => {
const i = PROD_incomeMarkClosePnl({ ...CASE, });
const g = GOLD({ ...CASE, positionType: 2 });
expect(i).toBe(g);
});
});