Files
zszq-trs/YLErpWeb/fe-tests/_proof_short_income.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

76 lines
3.9 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.
/**
* _proof_short_income.js — 红测试原型(纯 Node 可跑,无需 jest 依赖)
* ============================================================================
* 目的:证明"income 页 MarkClosePnl 缺 longRatio"是一个真实存在、但当前测试未覆盖的不一致。
*
* 公式逐字抄录自团队金标准 YLErpDAL/Helpers/FrontendCalcReference.cs
* CalcUnwind (unwindSwapTrade.js) : 含 longRatio
* CalcIncome (incomeSwapTrade.js) : 无 longRatio ← 差异点
*
* 跑法:node YLErpWeb/fe-tests/_proof_short_income.js
*/
'use strict';
// ---- unwind 页公式(对应 FrontendCalcReference.CalcUnwind:42-47,含 longRatio----
function calcUnwindMarkClosePnl(input) {
const scale = input.multiplier === 100 ? 0.01 : 1;
const floatRatio = input.payDirection === 1 ? 1 : -1;
const longRatio = input.positionType === 1 ? 1 : -1;
const v = input.closeQty * (input.tradingAmountAvg * scale - input.posiGrossPrice) * floatRatio * longRatio * 10000;
const rounded = Math.round(v) / 10000;
return Math.round(rounded * 100) / 100; // toFixed(2)
}
// ---- income 页公式(对应 FrontendCalcReference.CalcIncome:105-108,无 longRatio----
function calcIncomeMarkClosePnl(input) {
const scale = input.multiplier === 100 ? 0.01 : 1;
const floatRatio = input.payDirection === 1 ? 1 : -1;
// 注意:此处按照当前生产代码,没有乘以 longRatio
const v = input.positionQty * input.contractSize * (input.tradingAmountAvg * scale - input.posiGrossPrice) * floatRatio;
return Math.round(v * 100) / 100;
}
// ---- 修复后 income 公式(补上 longRatio,与 unwind / 后端一致)----
function calcIncomeMarkClosePnlFixed(input) {
const scale = input.multiplier === 100 ? 0.01 : 1;
const floatRatio = input.payDirection === 1 ? 1 : -1;
const longRatio = input.positionType === 1 ? 1 : -1;
const v = input.positionQty * input.contractSize * (input.tradingAmountAvg * scale - input.posiGrossPrice) * floatRatio * longRatio;
return Math.round(v * 100) / 100;
}
// 同一笔债券 TRS:期初全价 1.02,期末(互换/平仓价)105(×100形态→1.05),价差 0.03
const base = { multiplier: 100, posiGrossPrice: 1.02, tradingAmountAvg: 105, contractSize: 1 };
const longCase = { ...base, closeQty: 10000, positionQty: 10000, payDirection: 1, positionType: 1 };
const shortCase = { ...base, closeQty: 10000, positionQty: 10000, payDirection: 1, positionType: 2 };
function check(name, cond) {
console.log(` [${cond ? 'PASS' : 'FAIL'}] ${name}`);
return cond;
}
console.log('=== 场景A:多头(PositionType=1)—— 两页理应一致 ===');
const aU = calcUnwindMarkClosePnl(longCase);
const aI = calcIncomeMarkClosePnl(longCase);
console.log(` unwind=${aU} income=${aI}`);
let allPass = true;
allPass &= check('多头:unwind == income', aU === aI);
console.log('=== 场景B:空头(PositionType=2)—— 当前代码两页符号相反(红)===');
const bU = calcUnwindMarkClosePnl(shortCase);
const bI = calcIncomeMarkClosePnl(shortCase);
console.log(` unwind=${bU} income=${bI} (空头价格涨应亏损,unwind 正确为负,income 错为正)`);
allPass &= check('空头:unwind == income (当前代码会 FAIL → 证明 bug 存在)', bU === bI);
console.log('=== 场景C:空头 + 修复后 income(补 longRatio)—— 应一致(绿)===');
const bIf = calcIncomeMarkClosePnlFixed(shortCase);
console.log(` unwind=${bU} incomeFixed=${bIf}`);
allPass &= check('空头:unwind == incomeFixed (修复后 PASS → 证明改动可修复)', bU === bIf);
console.log('');
if (allPass) {
console.log('✅ 全部通过(若场景B也PASS,说明已修复或无空头场景)');
} else {
console.log('❌ 场景B 失败 = 当前代码在「空头+income」下两页算出相反符号 → 真实不一致,且现有 FC_001~009 全为多头未覆盖。');
}