Files
zszq-trs/YLErpWeb/fe-tests/historicalReconciliation.test.js

106 lines
4.7 KiB
JavaScript
Raw Permalink 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.
/**
* historicalReconciliation.test.js — 历史对账 + 独立规格差分骨架(离线,无需数据库)
* ============================================================================
* 运行:cd YLErpWeb/fe-tests && npx jest historicalReconciliation.test.js
*
* 数据库边界(关键):
* - 本测试运行时【零数据库依赖】。历史数据来自 fixtures/historical_swap_events.json
* 该 fixture 可离线提交进仓库。
* - 唯一的数据库触碰是 tools/exportHistoricalSwapEvents.js —— 开发期一次性导出脚本,
* 把真实 swap_event.data 灌进上面的 fixture。它不属于 npm test 路径,CI 不执行。
*
* 设计要点:
* - referenceOracle:基于 unwindSwapTrade.js:329 的 calcCloseAmount 核心【独立重写】的纯函数
* "规格"),不信任任何既有实现。用于校验"当前公式产出" == "落库/记录值"。
* - 对已知差异字段(如 swapLongShort.js 的 SwapMarginAmount 无符号)通过
* record.knownDiscrepancies 冻结现状:仅当"差异量"变化时才报红,既不掩盖也不误报。
* - 这是特征测试(characterization):先把现状冻住,未来任何重构只要改变了产出,
* 测试立即变红,逼你查根因(绝不自动 re-baseline)。
*/
const fs = require('fs');
const path = require('path');
const round2 = v => Math.round((Number(v) || 0) * 100) / 100;
// 独立规格:等价于 unwindSwapTrade.js:329-367 的 calcCloseAmount 金额聚合核心。
// 注意:这里只看 SwapCloseAmount / SwapRealizedPnL / SwapMarginRebatePnl / SwapMarginAmount
// 四个金额字段;TradingAmount / FeeAvg 等展示字段不在对账范围内(且 swapCalc 也未实现)。
function referenceOracle({ floatPosition, interestList, marginList }) {
const fp = floatPosition || {};
const interestLegs = interestList || [];
const marginLegs = marginList || [];
let closeAmount = round2(fp.FloatPnlSum || 0);
let realizedPnL = closeAmount;
let marginRebatePnl = 0;
let marginAmount = 0;
interestLegs.forEach(x => {
const amt = round2(x.InterestClosePnL || 0);
closeAmount = round2(closeAmount + amt);
realizedPnL = round2(realizedPnL + amt);
});
marginLegs.forEach(x => {
const amt = round2(x.InterestClosePnL || 0);
// 与 unwindSwapTrade.js:355 同口径:InterestDirection==1 ? -1 : 1
const interestRatio = x.InterestDirection == 1 ? -1 : 1;
closeAmount = round2(closeAmount + amt);
marginRebatePnl = round2(marginRebatePnl + amt);
realizedPnL = round2(realizedPnL + amt);
marginAmount = round2(marginAmount + (Number(x.InterestPrincipal) || 0) * interestRatio);
});
return {
SwapCloseAmount: closeAmount,
SwapRealizedPnL: realizedPnL,
SwapMarginRebatePnl: marginRebatePnl,
SwapMarginAmount: marginAmount,
};
}
const TOL = 1e-6;
function expectClose(actual, expected, msg) {
expect(Math.abs(actual - expected)).toBeLessThanOrEqual(TOL, msg || '');
}
const fixturePath = path.join(__dirname, 'fixtures', 'historical_swap_events.json');
const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
const RECON_FIELDS = ['SwapCloseAmount', 'SwapRealizedPnL', 'SwapMarginRebatePnl', 'SwapMarginAmount'];
describe('历史对账:当前公式产出 == 落库/记录值(离线,无 DB)', () => {
fixture.records.forEach(rec => {
test(`[${rec.id}] ${rec.desc}`, () => {
const got = referenceOracle(rec);
const exp = rec.expected || {};
const tolerated = rec.knownDiscrepancies || {};
RECON_FIELDS.forEach(field => {
if (!(field in exp)) return; // 记录未提供该字段则跳过
const actual = got[field];
const recorded = exp[field];
if (tolerated[field] != null) {
// 冻结已知差异:仅当"差异量"本身变化时才报红(行为漂移告警)
const deltaNow = Math.abs(actual - recorded);
expectClose(deltaNow, tolerated[field],
`${field} 已知差异量应稳定为 ${tolerated[field]}(当前 ${deltaNow}`);
} else {
expectClose(actual, recorded, `${field} 应等于落库值`);
}
});
});
});
});
// 该 describe 仅做"骨架自洽性"校验:确保 fixture 里的手搓样本与独立规格自洽,
// 否则说明 fixture 写错(不是生产 bug)。真实历史差异由上面对账 + 导出脚本覆盖。
describe('骨架自洽:fixture 样本与独立规格一致(非生产断言)', () => {
test('UNWIND_STD 全部字段自洽', () => {
const rec = fixture.records.find(r => r.id === 'UNWIND_STD');
const got = referenceOracle(rec);
expectClose(got.SwapCloseAmount, 51500);
expectClose(got.SwapMarginAmount, -50000);
});
});