refactor(web): 平仓页收付方向符号显式化unwindLegSign.js+4例单测——裸三元(x==1?±1:∓1)抽为命名函数并冻结业务事实:利息盈亏与保证金返还本金对同一InterestDirection枚举必须反号(利息=买方持有标的向交易商融资的成本,保证金返还=退客户自己交的钱,业务确认非笔误),不变量断言两符号之和恒0,顺手统一必挂;interestPnlSign对利息/保证金两腿通用(changeInterestAmount模板对两类行都绑定,已核实);另删死代码:this.ratio/this.shortRatio数据属性+赋值全库无读取(模板亦无绑定)及注释掉的历史三元;SwapUnwind.cshtml补script标签,两个执行型测试沙箱喂真实模块;行为等价(Number===1与==1对实际入参等价已验证),全套315例绿
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
<script src="~/Scripts/app/swaptrade/swapPricePrecisionHelper.js?v=@HtmlUtil.JsVersion"></script>
|
||||
<script src="~/Scripts/app/swaptrade/swapCalc.js?v=@HtmlUtil.JsVersion"></script>
|
||||
<script src="~/Scripts/app/swaptrade/unwindBondCalc.js?v=@HtmlUtil.JsVersion"></script>
|
||||
<script src="~/Scripts/app/swaptrade/unwindLegSign.js?v=@HtmlUtil.JsVersion"></script>
|
||||
<script src="~/Scripts/app/swaptrade/unwindSwapTrade.js?v=@HtmlUtil.JsVersion"></script>
|
||||
}
|
||||
<div class="pb-3" id="vueDiv">
|
||||
|
||||
@@ -155,6 +155,8 @@ function loadVueApp(model, mockPost) {
|
||||
roundExitYtm(v) { return v; },
|
||||
applyManualEdit(s) { return s; }
|
||||
},
|
||||
// 收付方向符号:纯函数零依赖,直接喂真实模块(映射冻结由 unwindLegSign.test.js 覆盖)
|
||||
UnwindLegSign: require('../wwwroot/Scripts/app/swaptrade/unwindLegSign.js'),
|
||||
_: {
|
||||
round(value, precision) {
|
||||
return Number(Number(value || 0).toFixed(precision || 0));
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* unwindLegSign.test.js — 平仓页收付方向符号映射(隐式约定显式化)
|
||||
* ============================================================================
|
||||
* 冻结的领域事实(2026-08-21 业务确认):
|
||||
* 利息腿(融资成本)与保证金腿(返息/返还本金)方向【必须相反】——
|
||||
* 利息是买方持有标的向交易商融资的成本(买方付出去的钱);
|
||||
* 保证金是客户自己交的抵押金,返息/返还是把客户自己的钱退回来。
|
||||
* 两者对同一 InterestDirection 枚举符号互为镜像,这是业务事实而非笔误,
|
||||
* 任何人"顺手统一"这两个符号都会翻转保证金返还方向(资损级 bug)。
|
||||
*
|
||||
* 另冻结:PayDirection==1 → +1、PositionType==1(多头) → +1 的浮动端/多空符号。
|
||||
*
|
||||
* 运行:cd YLErpWeb/fe-tests && npx jest unwindLegSign
|
||||
*/
|
||||
const UnwindLegSign = require('../wwwroot/Scripts/app/swaptrade/unwindLegSign.js');
|
||||
|
||||
describe('收付方向符号:各枚举映射', () => {
|
||||
test('利息盈亏(利息腿/保证金腿通用):收取(1)→+1,支付(其他)→-1', () => {
|
||||
expect(UnwindLegSign.interestPnlSign(1)).toBe(1);
|
||||
expect(UnwindLegSign.interestPnlSign(0)).toBe(-1);
|
||||
expect(UnwindLegSign.interestPnlSign(null)).toBe(-1);
|
||||
});
|
||||
|
||||
test('保证金腿返还本金:与利息腿同枚举反号(返的是客户自己的钱)', () => {
|
||||
expect(UnwindLegSign.marginRebatePrincipalSign(1)).toBe(-1);
|
||||
expect(UnwindLegSign.marginRebatePrincipalSign(0)).toBe(1);
|
||||
expect(UnwindLegSign.marginRebatePrincipalSign(null)).toBe(1);
|
||||
});
|
||||
|
||||
test('浮动端收付:PayDirection==1(收取)→+1;多空:PositionType==1(多头)→+1', () => {
|
||||
expect(UnwindLegSign.payDirectionSign(1)).toBe(1);
|
||||
expect(UnwindLegSign.payDirectionSign(0)).toBe(-1);
|
||||
expect(UnwindLegSign.positionTypeSign(1)).toBe(1);
|
||||
expect(UnwindLegSign.positionTypeSign(0)).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('不变量:利息腿与保证金腿符号互为镜像(业务事实,禁止统一)', () => {
|
||||
test('同一 InterestDirection 下两符号之和恒为 0', () => {
|
||||
[1, 0, null, undefined, 2].forEach(dir => {
|
||||
expect(
|
||||
UnwindLegSign.interestPnlSign(dir)
|
||||
+ UnwindLegSign.marginRebatePrincipalSign(dir)
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -66,6 +66,8 @@ function loadUnwindHelpers() {
|
||||
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));
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* unwindLegSign.js — 平仓页收付方向符号(纯函数)
|
||||
* ============================================================================
|
||||
* 把散落在 unwindSwapTrade.js 各计算式里的裸三元(xxx==1 ? ±1 : ∓1)显式化为命名函数。
|
||||
* 【为什么必须显式化】利息盈亏与保证金返还本金对同一 InterestDirection 枚举符号相反——
|
||||
* 这是业务事实而非笔误,任何"顺手统一"都会翻转保证金返还方向(资损级 bug):
|
||||
* 利息盈亏(利息腿/保证金腿通用,changeInterestAmount 对两类行都生效):
|
||||
* 收益互换中买方为持有标的向交易商融资,利息=融资成本(买方付出去的钱),
|
||||
* InterestDirection==1(收取) → 盈亏记 +1;
|
||||
* 保证金返还本金:保证金是客户自己交的抵押金,返息/返还本金是把客户自己的钱退回来,
|
||||
* 本金流方向与利息(成本)相反 → 同枚举符号取镜像 -1。
|
||||
*
|
||||
* 其余两个符号:PayDirection==1(浮动端收取) → +1;PositionType==1(多头) → +1。
|
||||
*
|
||||
* 加载:浏览器 script 标签(先于 unwindSwapTrade.js);Node:module.exports 供 jest。
|
||||
*/
|
||||
var UnwindLegSign = (function () {
|
||||
'use strict';
|
||||
|
||||
// Number()===1 与页面原 ==1 对 '1'/true/数字等实际入参等价,且对 null/undefined 同样落到 -1 分支
|
||||
function interestPnlSign(interestDirection) {
|
||||
return Number(interestDirection) === 1 ? 1 : -1;
|
||||
}
|
||||
|
||||
function marginRebatePrincipalSign(interestDirection) {
|
||||
return Number(interestDirection) === 1 ? -1 : 1;
|
||||
}
|
||||
|
||||
function payDirectionSign(payDirection) {
|
||||
return Number(payDirection) === 1 ? 1 : -1;
|
||||
}
|
||||
|
||||
function positionTypeSign(positionType) {
|
||||
return Number(positionType) === 1 ? 1 : -1;
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
interestPnlSign: interestPnlSign,
|
||||
marginRebatePrincipalSign: marginRebatePrincipalSign,
|
||||
payDirectionSign: payDirectionSign,
|
||||
positionTypeSign: positionTypeSign
|
||||
});
|
||||
}());
|
||||
|
||||
if (typeof module === 'object' && module.exports) module.exports = UnwindLegSign;
|
||||
@@ -69,8 +69,6 @@ const vue = new Vue({
|
||||
// 平仓比例展示/输入均为"占期初(original)"语义(A):默认与每次重开都基于原始名义本金。
|
||||
// oriClosePercent = 剩余名义本金/期初名义本金 = 最多可平比例(不能平超过剩余持仓)。
|
||||
oriClosePercent: 1,
|
||||
ratio: 1,
|
||||
shortRatio: 1,
|
||||
},
|
||||
computed: {
|
||||
maxUnwindDate() {
|
||||
@@ -125,8 +123,6 @@ const vue = new Vue({
|
||||
this.marginList = model.FlowEvents.filter((item) => {
|
||||
return item.InterestMode == 5 || item.InterestMode == 6;
|
||||
});
|
||||
this.ratio = this.floatPosition.PayDirection == 1 ? -1 : 1;
|
||||
this.shortRatio = this.floatPosition.PositionType == 1 ? 1 : -1;
|
||||
this.TradeStartDate = model.TradeStartDate;
|
||||
// 最多可平比例(占期初口径) = 剩余名义本金 / 期初名义本金;分母为 0 时兜底为 1
|
||||
this.oriClosePercent = (this.deal.NotionalValue && this.deal.PosiNotionalValue)
|
||||
@@ -424,8 +420,8 @@ const vue = new Vue({
|
||||
},
|
||||
calcFloatClosePnl() {//计算浮动端平仓盈亏
|
||||
var thisObj = this;
|
||||
let floatRatio = thisObj.floatPosition.PayDirection == 1 ? 1 : -1;
|
||||
let longRatio = thisObj.floatPosition.PositionType == 1 ? 1 : -1;
|
||||
let floatRatio = UnwindLegSign.payDirectionSign(thisObj.floatPosition.PayDirection);
|
||||
let longRatio = UnwindLegSign.positionTypeSign(thisObj.floatPosition.PositionType);
|
||||
let TradingFee = thisObj.floatPosition.TradingFee == "" ? 0 : parseFloat(thisObj.floatPosition.TradingFee);
|
||||
let TradingFeePending = thisObj.floatPosition.TradingFeePending == "" ? 0 : parseFloat(thisObj.floatPosition.TradingFeePending);
|
||||
let deliveryPrice = thisObj.getStorageDeliveryPrice();
|
||||
@@ -440,7 +436,7 @@ const vue = new Vue({
|
||||
this.calcFloatClosePnl();
|
||||
},
|
||||
changeInterestAmount(item) {//修改利息金额
|
||||
let interestRatio = item.InterestDirection == 1 ? 1 : -1;
|
||||
let interestRatio = UnwindLegSign.interestPnlSign(item.InterestDirection);
|
||||
item.InterestClosePnL = formatSwapAmount(parseFloat(item.InterestAmount) * interestRatio + parseFloat(item.InterestFee) * interestRatio);
|
||||
this.calcCloseAmount();
|
||||
},
|
||||
@@ -450,8 +446,8 @@ const vue = new Vue({
|
||||
// this.calcCloseAmount();
|
||||
//},
|
||||
calcCloseAmount() {//计算平仓总额=浮动收取+利息收取-浮动支付-利息支付
|
||||
let floatRatio = this.floatPosition.PayDirection == 1 ? 1 : -1;
|
||||
let ratio = this.floatPosition.PositionType == 1 ? 1 : -1;
|
||||
let floatRatio = UnwindLegSign.payDirectionSign(this.floatPosition.PayDirection);
|
||||
let ratio = UnwindLegSign.positionTypeSign(this.floatPosition.PositionType);
|
||||
let thisObj = this;
|
||||
let pnl = parseFloat(this.floatPosition.FloatPnlSum);
|
||||
let TradingFee = thisObj.floatPosition.TradingFee == "" ? 0 : parseFloat(thisObj.floatPosition.TradingFee);
|
||||
@@ -468,14 +464,14 @@ const vue = new Vue({
|
||||
thisObj.floatPosition.TradingAmountFeeAvg = deliveryPrice + (TradingFee / thisObj.deal.CloseQty) * ratio;
|
||||
}
|
||||
this.interestList.forEach(x => {
|
||||
/*let interestRatio = x.InterestDirection == 1 ? 1 : -1;*/
|
||||
let interestAmount = parseFloat(x.InterestClosePnL);
|
||||
thisObj.deal.SwapCloseAmount = parseFloat(thisObj.deal.SwapCloseAmount) + interestAmount;
|
||||
thisObj.deal.SwapRealizedPnL = parseFloat(thisObj.deal.SwapRealizedPnL) + interestAmount;
|
||||
});
|
||||
this.marginList.forEach(x => {
|
||||
let interestAmount = parseFloat(x.InterestClosePnL);
|
||||
let interestRatio = x.InterestDirection == 1 ? -1 : 1;
|
||||
// 保证金返还本金符号与利息盈亏同枚举反号(见 unwindLegSign.js 头注——业务事实勿统一)
|
||||
let interestRatio = UnwindLegSign.marginRebatePrincipalSign(x.InterestDirection);
|
||||
thisObj.deal.SwapCloseAmount = parseFloat(thisObj.deal.SwapCloseAmount) + interestAmount;
|
||||
thisObj.deal.SwapMarginRebatePnl = parseFloat(thisObj.deal.SwapMarginRebatePnl) + interestAmount;
|
||||
thisObj.deal.SwapRealizedPnL = parseFloat(thisObj.deal.SwapRealizedPnL) + interestAmount;
|
||||
|
||||
Reference in New Issue
Block a user