106 lines
4.7 KiB
JavaScript
106 lines
4.7 KiB
JavaScript
/**
|
||
* 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);
|
||
});
|
||
});
|