/** * _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 全为多头未覆盖。'); }