- incomeSwapTrade.js / swapTradeEdit.js 改为调用 SwapCalc.*(缩放/取整/全价推导) - 新增 fe-tests 守卫:4 个历史 bug 回归 + 8 场景对齐 C# FrontendCalcReference 金标准 + otcformat.js 精度配置守卫 - 新增 .git/hooks/pre-commit 自动运行前端守卫(失败阻断提交)
97 lines
4.7 KiB
JavaScript
97 lines
4.7 KiB
JavaScript
/**
|
||
* 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);
|
||
});
|
||
});
|