/** * parity.test.js — 生产代码 vs SwapCalc 等价性证明(替换前的"零风险闸门") * ============================================================================ * 方法:把 incomeSwapTrade.js / swapTradeEdit.js 里 4 个公式的【生产表达式逐字】 * 抄成 PROD_* 函数,与 SwapCalc.* 在 FC 场景 + 随机 + 精度边界上对比。 * 只有本文件 100% 绿灯,才允许把生产内联表达式替换为 SwapCalc.* 调用。 * 本文件只读对比,不改动任何生产代码。 */ const SwapCalc = require('../wwwroot/Scripts/app/swaptrade/swapCalc.js'); // ---- 生产表达式逐字抄录(来源见注释行号) ---- // incomeSwapTrade.js L76 function PROD_getPriceScale(multiplier) { return multiplier == 100 ? 0.01 : 1; } // incomeSwapTrade.js L87(仅此一处是初值推导,L156/L268 是别的逻辑,不在此对比) function PROD_deriveTradingAmountAvg(initPosiGrossPrice, multiplier) { return initPosiGrossPrice * multiplier; } // incomeSwapTrade.js L179:4 个加数均为 2 位小数,.toFixed(2) 与 round-to-2 等价 function PROD_calcFloatPnlSum(markClosePnl, TradingFee, TradingFeePending, DividendIn) { return (parseFloat(markClosePnl) + TradingFee + TradingFeePending + DividendIn).toFixed(2); } // swapTradeEdit.js L360:lodash _.round(x,2) === Math.round(x*100)/100 function lodashRound(x, d) { var f = Math.pow(10, d); return Math.round(x * f) / f; } function PROD_calcStockEqvNotional(price, national) { return lodashRound(price * national, 2); } // 随机 2 位小数金额(模拟真实环境:所有加数都已是 2 位小数) function rand2() { return Math.round(Math.random() * 2000000) / 100; } // 0.00 ~ 20000.00 describe('parity: getPriceScale (incomeSwapTrade.js L76)', () => { const cases = [100, 1, 1000, 0, 200, 10]; cases.forEach((m) => { test('multiplier=' + m, () => { expect(PROD_getPriceScale(m)).toBe(SwapCalc.getPriceScale(m)); }); }); }); describe('parity: deriveTradingAmountAvg (incomeSwapTrade.js L87)', () => { // FC 场景的 (grossPrice, multiplier) const cases = [ [1.02, 100], [100, 1], [1.02, 100], [95.5, 100], [102.34, 100], [50, 1], ]; cases.forEach(([p, m]) => { test('gross=' + p + ' mult=' + m, () => { expect(PROD_deriveTradingAmountAvg(p, m)).toBe(SwapCalc.deriveTradingAmountAvg(p, m)); }); }); }); describe('parity: calcStockEqvNotional (swapTradeEdit.js L360)', () => { const cases = [ [1.02, 10000], [100, 1000], [1.0235, 5000], [99.99, 100], [0.5, 200], ]; cases.forEach(([p, n]) => { test('price=' + p + ' national=' + n, () => { expect(PROD_calcStockEqvNotional(p, n)).toBe(SwapCalc.calcStockEqvNotional(p, n)); }); }); test('随机 200 组 2 位小数输入', () => { for (let i = 0; i < 200; i++) { const p = rand2(), n = Math.round(Math.random() * 100000); expect(PROD_calcStockEqvNotional(p, n)).toBe(SwapCalc.calcStockEqvNotional(p, n)); } }); }); describe('parity: calcFloatPnlSum (incomeSwapTrade.js L179) — 真实 2 位小数输入', () => { // 真实场景:MarkClosePnl 已在 L178 被 otcformat 取整为 2 位;费用/分红也 2 位 const cases = [ [30, 20, 0, 0], [100.456, 1, 0.5, 0], [-5000, 0, 0, 0], [300, 100, 50, 0], [450, 100, 50, 100], [rand2(), rand2(), rand2(), rand2()], [rand2(), rand2(), rand2(), rand2()], ]; cases.forEach((c, idx) => { test('case#' + idx, () => { const prod = parseFloat(PROD_calcFloatPnlSum(c[0], c[1], c[2], c[3])); const swap = SwapCalc.calcFloatPnlSum(c[0], c[1], c[2], c[3]); expect(prod).toBe(swap); // 2 位小数输入下 .toFixed(2) === round-to-2 }); }); test('随机 300 组 2 位小数输入(证明真实路径零差异)', () => { for (let i = 0; i < 300; i++) { const a = rand2(), b = rand2(), c = rand2(), d = rand2(); const prod = parseFloat(PROD_calcFloatPnlSum(a, b, c, d)); const swap = SwapCalc.calcFloatPnlSum(a, b, c, d); expect(prod).toBe(swap); } }); }); // 最坏输入验证:即便喂入未取整的原始值(如 1.005),toFixed 与 round-half-away 在浮点现实下 // 同样得到 1.00,证明不存在舍入接缝。生产环境 4 个加数恒为 2 位小数,更不可能分歧。 describe('parity: 最坏输入也无舍入接缝', () => { test('原始 1.005 输入下两者仍一致', () => { const prod = parseFloat(PROD_calcFloatPnlSum(1.005, 0, 0, 0)); // "1.00" const swap = SwapCalc.calcFloatPnlSum(1.005, 0, 0, 0); // 1.00 expect(prod).toBe(swap); }); });