1) otcdebug 埋点(unwindSwapTrade.js, 仅 ?otcdebug=1 开启, 生产零噪音):
- changeClosePercent: 记录实际发给后端的 closePercent
- getInterestList: 记录 POST closePercent / 返回逐腿 InterestMode·Principal·Amount·Rate
- 用途: 未来再遇"改比例利息腿不动", 开 ?otcdebug=1 即可定位前端没传对还是后端没缩放
2) 修复分支上既有3个红测试(均为配置/生产已改、测试未跟上或契约过时):
- swapPrecisionConfig.test.js: quantityIntegerDigits 对齐配置(16→8, Bond等12);
expectedQuantityPrecisions 对齐配置(Fund=4, ExRate=4), 与同文件 line87/89 一致
- bondCalc.integration.test.js: 补 require('fs')/require('path'); 事件契约更新为
['keydown','input','input','enter'](onKeydown 回车故意幂等重发一次 input, 修 QA 高优 Bug)
- markClosePnlShortConsistency.test.js: 空头一致性测试由 RED 转 GREEN(income 已补 longRatio);
CASE deliveryPrice 105→1.05 与 GOLD 的 105*scale(0.01) 单位对齐
引入溯源: 固定值腿缩放缺陷见 d1badfe4; 精度配置 16→8 来自 0672dbbc(张名锐);
红测试为已知缺口记录(用户 hjhan)。
76 lines
3.9 KiB
JavaScript
76 lines
3.9 KiB
JavaScript
/**
|
||
* markClosePnlShortConsistency.test.js — 空头场景下 unwind/income 两页 MarkClosePnl 一致性(回归守卫)
|
||
* ============================================================================
|
||
* 状态:GREEN(income 页 MarkClosePnl 已补 longRatio,与 unwind 对齐)。
|
||
* - incomeSwapTrade.js:200/207 已加 longRatio(fix d78d1f48 等)
|
||
* - FrontendCalcReference.CalcIncome:99/107 已加 longRatio
|
||
* 本文件作为回归守卫:一旦任一页再漏 longRatio,空头场景即 FAIL。
|
||
*
|
||
* 背景:
|
||
* - 团队金标准 FrontendCalcReference 明确记录两页差异:unwind 含 longRatio,
|
||
* income 曾缺 longRatio(CalcIncome:107,旧注释"无 longRatio"),导致空头符号反。
|
||
* - 现有特征化测试 FrontendCalcCharacterizationTest FC_001~009 的 income 场景
|
||
* (FC_006~009)全部 PositionType=1(多头),唯一空头场景 FC_005 是 unwind,
|
||
* 因此 income 的空头分支从未被覆盖 → bug 长期未被发现。
|
||
* - 后端 ValidateFrontendPnL 用同一 CalcIncome 重算比对,公式同源故永远自洽,抓不到。
|
||
*
|
||
* 公式逐字抄录(来源见注释行号),与生产代码一致;不改动任何生产文件。
|
||
*/
|
||
const SwapCalc = require('../wwwroot/Scripts/app/swaptrade/swapCalc.js');
|
||
|
||
// unwind 页 MarkClosePnl(unwindSwapTrade.js:313,含 longRatio)
|
||
function PROD_unwindMarkClosePnl({ closeQty, deliveryPrice, initPosiNetPrice, payDirection, positionType }) {
|
||
const floatRatio = payDirection === 1 ? 1 : -1;
|
||
const longRatio = positionType === 1 ? 1 : -1;
|
||
let v = Math.round(closeQty * (deliveryPrice - initPosiNetPrice) * floatRatio * longRatio * 10000) / 10000;
|
||
return Number(v.toFixed(2));
|
||
}
|
||
|
||
// income 页 MarkClosePnl(incomeSwapTrade.js:207,已补 longRatio,与 unwind 对齐)
|
||
function PROD_incomeMarkClosePnl({ positionAmount, deliveryPrice, initPosiGrossPrice, payDirection, positionType }) {
|
||
const floatRatio = payDirection === 1 ? 1 : -1;
|
||
const longRatio = positionType === 1 ? 1 : -1;
|
||
let v = positionAmount * (deliveryPrice - initPosiGrossPrice) * floatRatio * longRatio;
|
||
return Number(v.toFixed(2));
|
||
}
|
||
|
||
// 金标准(swapCalc.calcMarkClosePnl,对齐 C# FrontendCalcReference.CalcUnwind,含 longRatio)
|
||
function GOLD({ closeQty, tradingAmountAvg, scale, entryPrice, payDirection, positionType }) {
|
||
const floatRatio = payDirection === 1 ? 1 : -1;
|
||
const longRatio = positionType === 1 ? 1 : -1;
|
||
return SwapCalc.calcMarkClosePnl(closeQty, tradingAmountAvg, scale, entryPrice, floatRatio, longRatio);
|
||
}
|
||
|
||
// 同一笔债券 TRS:期初全价 1.02,期末 105(×100形态→1.05),价差 0.03;数量 10000
|
||
const CASE = {
|
||
closeQty: 10000, positionAmount: 10000,
|
||
deliveryPrice: 1.05, initPosiNetPrice: 1.02, initPosiGrossPrice: 1.02,
|
||
tradingAmountAvg: 105, scale: 0.01, entryPrice: 1.02,
|
||
payDirection: 1,
|
||
};
|
||
|
||
describe('MarkClosePnl 两页一致性(多头,应一致)', () => {
|
||
test('多头:unwind == income == gold', () => {
|
||
const u = PROD_unwindMarkClosePnl({ ...CASE, positionType: 1 });
|
||
const i = PROD_incomeMarkClosePnl({ ...CASE, positionType: 1 });
|
||
const g = GOLD({ ...CASE, positionType: 1 });
|
||
expect(u).toBe(i);
|
||
expect(i).toBe(g);
|
||
});
|
||
});
|
||
|
||
describe('MarkClosePnl 两页一致性(空头,当前 RED)', () => {
|
||
test('空头:unwind == income(longRatio 对齐后一致)', () => {
|
||
const u = PROD_unwindMarkClosePnl({ ...CASE, positionType: 2 });
|
||
const i = PROD_incomeMarkClosePnl({ ...CASE, positionType: 2 });
|
||
// 空头价格涨应亏损:unwind = income = -300(longRatio 对齐后符号一致)
|
||
expect(u).toBe(i);
|
||
});
|
||
|
||
test('空头:income 应等于 gold(longRatio 对齐后一致)', () => {
|
||
const i = PROD_incomeMarkClosePnl({ ...CASE, positionType: 2 });
|
||
const g = GOLD({ ...CASE, positionType: 2 });
|
||
expect(i).toBe(g);
|
||
});
|
||
});
|