167 lines
6.4 KiB
JavaScript
167 lines
6.4 KiB
JavaScript
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 {}; }
|
|
},
|
|
swapPricePrecision: {
|
|
createVueInputComponent() { return {}; },
|
|
getCommonInputFormat() { return {}; },
|
|
normalizeCommon(type, value) { return value; },
|
|
formatCommon(type, value) { return value; },
|
|
getInputFormat(type, field, fallback) { return fallback; },
|
|
roundForSubmit(value) { return value; },
|
|
shiftDecimal(value) { return value; },
|
|
format(value) { return value; }
|
|
},
|
|
tradeHelper: { IsBond() { return false; } },
|
|
main: {
|
|
post() {
|
|
return {
|
|
done() { return this; }
|
|
};
|
|
},
|
|
message() { }
|
|
},
|
|
SwapCalc: {
|
|
roundHalfAwayFromZero(value) { return value; },
|
|
calcCloseQtyByOriginalPercent() { return 0; }
|
|
},
|
|
// 收付方向符号:纯函数零依赖,直接喂真实模块(映射冻结由 unwindLegSign.test.js 覆盖)
|
|
UnwindLegSign: require('../wwwroot/Scripts/app/swaptrade/unwindLegSign.js'),
|
|
_: {
|
|
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('事件日期与平仓日期双向同步', () => {
|
|
const source = fs.readFileSync(
|
|
path.join(__dirname, '..', 'wwwroot', 'Scripts', 'app', 'swaptrade', 'unwindSwapTrade.js'),
|
|
'utf8'
|
|
);
|
|
const viewSource = fs.readFileSync(
|
|
path.join(__dirname, '..', 'Views', 'SwapTrade2', 'SwapUnwind.cshtml'),
|
|
'utf8'
|
|
);
|
|
|
|
test('事件日期变更时同步平仓日期并触发利息重算', () => {
|
|
expect(source).toMatch(/setValueDate\(e\)[\s\S]*deal\.UnwindDate\s*=\s*e[\s\S]*getInterestList\(\)/);
|
|
});
|
|
|
|
test('平仓日期可选,变更时同步事件日期', () => {
|
|
expect(viewSource).toMatch(/vue-datepicker[^>]*v-model="deal\.UnwindDate"[^>]*v-on:input="setUnwindDate"/);
|
|
expect(source).toMatch(/setUnwindDate\(e\)[\s\S]*this\.setValueDate\(e\)/);
|
|
});
|
|
|
|
test('预览利息请求使用事件日期作为计算日期', () => {
|
|
expect(source).toMatch(/valueDate:\s*thisObj\.deal\.ValueDate[\s\S]*unwindDate:\s*thisObj\.deal\.ValueDate/);
|
|
});
|
|
});
|
|
|
|
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('a manually adjusted pending fee does not override the base-rate close fee', () => {
|
|
const tradingFee = swapPosiFeeCalc.calcTradingFee(
|
|
consPosiFeeType.Unit, 0.123456, 0, 10000);
|
|
const pendingFee = swapPosiFeeCalc.calcTradingFeePending(
|
|
1235.56, consPosiFeeType.Unit, 0.123456, 0, 10000, 0, 10000, 1);
|
|
|
|
expectClose(tradingFee, 1234.56);
|
|
expectClose(pendingFee, 1235.56);
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|