const fs = require('fs'); const path = require('path'); const vm = require('vm'); function createNumberFormat(precision) { const formatter = (value) => Number(Number(value || 0).toFixed(precision)); formatter.precision = precision; return formatter; } function loadUnwindHelpers() { const filePath = path.join(__dirname, '../wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js'); const code = fs.readFileSync(filePath, 'utf8') + '\nmodule.exports = { swapPosiFeeCalc, consPosiFeeType };'; const stockEqvNotional = createNumberFormat(2); const sandbox = { module: { exports: {} }, exports: {}, console, require, window: { otcformat: { options: {} } }, otcformat: { options: {}, trading: { premiumRateP: { precision: 4 }, tradePrice: { precision: 4 }, notional: { precision: 6 }, StockEqvNotional: stockEqvNotional, marginRateP: { precision: 4 }, umpriceP: { precision: 4 } }, fixed6: createNumberFormat(6) }, model: { ValueDate: '2026-07-27', FlowEvents: [], StructureType: '', TradeStartDate: '' }, isUseApproval: false, Vue: function (options) { return options; }, FastVue: { vueDatePicker() { return {}; }, vueNumberInput() { return {}; } }, tradeHelper: { IsBond() { return false; } }, main: { post() { return { done() { return this; } }; }, message() { } }, SwapCalc: { roundHalfAwayFromZero(value) { return value; }, calcCloseQtyByOriginalPercent() { return 0; } }, _: { round(value, precision) { return Number(Number(value || 0).toFixed(precision || 0)); } } }; sandbox.window.otcformat = sandbox.otcformat; vm.runInNewContext(code, sandbox, { filename: filePath }); return sandbox.module.exports; } function expectClose(actual, expected, tolerance) { expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance || 1e-6); } describe('unwindSwapTrade 基础费率计算', () => { const { swapPosiFeeCalc, consPosiFeeType } = loadUnwindHelpers(); test('百分比模式按平仓名义本金计算并保留两位', () => { const result = swapPosiFeeCalc.calcTradingFee(consPosiFeeType.Percent, 0.1234, 1000000, 5000); expectClose(result, 1234.00); }); test('单位数量模式按平仓数量计算并保留两位', () => { const result = swapPosiFeeCalc.calcTradingFee(consPosiFeeType.Unit, 1.235, 1000000, 10); expectClose(result, 12.35); }); test('未知模式默认按百分比模式处理', () => { const result = swapPosiFeeCalc.calcTradingFee(99, 0.1, 200000, 10); expectClose(result, 200.00); }); }); describe('base-rate pending trading fee', () => { const { swapPosiFeeCalc, consPosiFeeType } = loadUnwindHelpers(); test('unit rate follows actual close quantity instead of an inconsistent close percent', () => { const result = swapPosiFeeCalc.calcTradingFeePending( 2000, consPosiFeeType.Unit, 0.2, 4200, 3000, 10000, 10000, 0.42); expectClose(result, 600.00); }); test('percentage rate follows actual close notional value', () => { const result = swapPosiFeeCalc.calcTradingFeePending( 2000, consPosiFeeType.Percent, 0.2, 300000, 4200, 1000000, 10000, 0.42); expectClose(result, 600.00); }); test('a manually adjusted original pending fee is allocated by actual close quantity', () => { const result = swapPosiFeeCalc.calcTradingFeePending( 1500, consPosiFeeType.Unit, 0.2, 4200, 3000, 10000, 10000, 0.42); expectClose(result, 450.00); }); test('partial close fee and pending fee both use the rounded opening fee allocation', () => { const tradingFee = swapPosiFeeCalc.calcAllocatedTradingFee( 113.46, consPosiFeeType.Percent, 1.1234, 4039.2, 4000, 10098, 10000); const pendingFee = swapPosiFeeCalc.calcTradingFeePending( 113.46, consPosiFeeType.Percent, 1.1234, 4039.2, 4000, 10098, 10000, 0.4); expectClose(tradingFee, 45.38); expectClose(pendingFee, 45.38); }); test('without a configured base rate, the legacy close-percent calculation remains', () => { const result = swapPosiFeeCalc.calcTradingFeePending( 2000, consPosiFeeType.Percent, 0, 300000, 3000, 1000000, 10000, 0.42); expectClose(result, 840.00); }); });