From 8c4f452482abb4d4a218dd9cf1bb6d2e3abef3e2 Mon Sep 17 00:00:00 2001 From: tengyufan <1532636164@qq.com> Date: Wed, 29 Jul 2026 13:47:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=9F=E4=B8=80=E4=BA=92=E6=8D=A2?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=B9=B3=E4=BB=93=E4=BA=A4=E6=98=93=E8=B4=B9?= =?UTF-8?q?=E7=94=A8=E4=B8=8E=E5=BE=85=E7=BB=93=E7=AE=97=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E5=88=86=E6=91=8A=E8=88=8D=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SwapModule/InitUnwindTradingFeeTest.cs | 32 +++++++++++++++- .../Modules/SwapModule/SwapDealService.cs | 22 +++++------ YLErpWeb/fe-tests/unwindSwapTrade.test.js | 10 +++++ .../Scripts/app/swaptrade/unwindSwapTrade.js | 37 ++++++++++++++----- 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs b/UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs index f9edd583..0b9f7b37 100644 --- a/UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs +++ b/UnitTestProject/Modules/SwapModule/InitUnwindTradingFeeTest.cs @@ -34,10 +34,12 @@ namespace YLErp.Modules.SwapModule var position = new swap_position { PosiFeeType = 0, - PosiTradingFeeUnit = 0.1234m + PosiTradingFeeUnit = 0.1234m, + PosiTradingFeePending = 1234.00m }; var unwindData = new UnwindData { + NotionalValue = 1_000_000m, CloseNotionalValue = 1_000_000m, CloseQty = 8888m }; @@ -53,10 +55,12 @@ namespace YLErp.Modules.SwapModule var position = new swap_position { PosiFeeType = 1, - PosiTradingFeeUnit = 1.235m + PosiTradingFeeUnit = 1.235m, + PosiTradingFeePending = 12.35m }; var unwindData = new UnwindData { + NotionalQty = 10m, CloseNotionalValue = 1_000_000m, CloseQty = 10m }; @@ -106,6 +110,30 @@ namespace YLErp.Modules.SwapModule Assert.AreEqual(450m, fee); } + [TestMethod] + public void PartialCloseTradingFeeAndPendingFeeUseTheSameRoundedOriginalFeeAllocation() + { + var oriPosition = new swap_position + { + PosiFeeType = 0, + PosiTradingFeeUnit = 1.1234m, + PosiTradingFeePending = 113.46m + }; + var unwindData = new UnwindData + { + NotionalValue = 10098m, + CloseNotionalValue = 4039.2m, + NotionalQty = 10000m, + CloseQty = 4000m + }; + + var tradingFee = InvokeCalcInitTradingFee(oriPosition, unwindData); + var pendingFee = InvokeCalcInitTradingFeePending(oriPosition, new swap_position(), unwindData); + + Assert.AreEqual(45.38m, tradingFee); + Assert.AreEqual(45.38m, pendingFee); + } + [TestMethod] public void LegacyPendingFeeKeepsCurrentPositionValueWhenNoBaseRateIsConfigured() { diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 875533e1..26b7e58c 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -323,12 +323,19 @@ namespace YLErp.Modules.SwapModule return 0; } - if (oriPosition.PosiFeeType == 1) + if (oriPosition.PosiTradingFeeUnit == 0) { - return Math.Round(oriPosition.PosiTradingFeeUnit * unwindData.CloseQty, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); + return 0; } - return Math.Round(oriPosition.PosiTradingFeeUnit / 100m * unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); + var closeBase = oriPosition.PosiFeeType == 1 ? unwindData.CloseQty : unwindData.CloseNotionalValue; + var originalBase = oriPosition.PosiFeeType == 1 ? unwindData.NotionalQty : unwindData.NotionalValue; + if (originalBase <= 0) + { + return 0; + } + + return Math.Round(oriPosition.PosiTradingFeePending * closeBase / originalBase, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); } private static decimal CalcInitTradingFeePending(swap_position oriPosition, swap_position position, UnwindData unwindData) @@ -338,14 +345,7 @@ namespace YLErp.Modules.SwapModule return position?.PosiTradingFeePending ?? 0; } - var closeBase = oriPosition.PosiFeeType == 1 ? unwindData.CloseQty : unwindData.CloseNotionalValue; - var originalBase = oriPosition.PosiFeeType == 1 ? unwindData.NotionalQty : unwindData.NotionalValue; - if (originalBase <= 0) - { - return position?.PosiTradingFeePending ?? 0; - } - - return Math.Round(oriPosition.PosiTradingFeePending * closeBase / originalBase, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); + return CalcInitTradingFee(oriPosition, unwindData); } /// /// 校验上日是否收盘 diff --git a/YLErpWeb/fe-tests/unwindSwapTrade.test.js b/YLErpWeb/fe-tests/unwindSwapTrade.test.js index 2cd6472e..53c8197d 100644 --- a/YLErpWeb/fe-tests/unwindSwapTrade.test.js +++ b/YLErpWeb/fe-tests/unwindSwapTrade.test.js @@ -112,6 +112,16 @@ describe('base-rate pending trading fee', () => { 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); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js index b547887e..ed14af8f 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js @@ -23,15 +23,26 @@ const swapPosiFeeCalc = { : normalizedFeeUnit / 100 * normalizedCloseNotionalValue; return otcformat.trading.StockEqvNotional(_.round(tradingFee, 2)); }, - calcTradingFeePending(beforeCloseFee, feeType, feeUnit, closeNotionalValue, closeQty, notionalValue, notionalQty, closePercent) { + calcAllocatedTradingFee(totalFee, feeType, feeUnit, closeNotionalValue, closeQty, notionalValue, notionalQty) { const normalizedFeeUnit = Number(feeUnit) || 0; - if (normalizedFeeUnit !== 0) { - const normalizedFeeType = this.normalizeFeeType(feeType); - const closeBase = normalizedFeeType === consPosiFeeType.Unit ? Number(closeQty) || 0 : Number(closeNotionalValue) || 0; - const originalBase = normalizedFeeType === consPosiFeeType.Unit ? Number(notionalQty) || 0 : Number(notionalValue) || 0; - if (originalBase > 0) { - return otcformat.trading.StockEqvNotional(_.round((Number(beforeCloseFee) || 0) * closeBase / originalBase, 2)); - } + if (normalizedFeeUnit === 0) { + return null; + } + + const normalizedFeeType = this.normalizeFeeType(feeType); + const closeBase = normalizedFeeType === consPosiFeeType.Unit ? Number(closeQty) || 0 : Number(closeNotionalValue) || 0; + const originalBase = normalizedFeeType === consPosiFeeType.Unit ? Number(notionalQty) || 0 : Number(notionalValue) || 0; + if (originalBase <= 0) { + return null; + } + + return otcformat.trading.StockEqvNotional(_.round((Number(totalFee) || 0) * closeBase / originalBase, 2)); + }, + calcTradingFeePending(beforeCloseFee, feeType, feeUnit, closeNotionalValue, closeQty, notionalValue, notionalQty, closePercent) { + const allocatedFee = this.calcAllocatedTradingFee( + beforeCloseFee, feeType, feeUnit, closeNotionalValue, closeQty, notionalValue, notionalQty); + if (allocatedFee !== null) { + return allocatedFee; } return (Number(beforeCloseFee) || 0) * (Number(closePercent) || 0); @@ -192,7 +203,15 @@ const vue = new Vue({ this.deal.ClosePercent); }, refreshTradingFeeByUnit() { - this.floatPosition.TradingFee = swapPosiFeeCalc.calcTradingFee( + const allocatedFee = swapPosiFeeCalc.calcAllocatedTradingFee( + this.floatPosition.BeforeCloseFee, + this.floatPosition.PosiFeeType, + this.floatPosition.PosiTradingFeeUnit, + this.deal.CloseNotionalValue, + this.deal.CloseQty, + this.deal.NotionalValue, + this.deal.NotionalQty); + this.floatPosition.TradingFee = allocatedFee !== null ? allocatedFee : swapPosiFeeCalc.calcTradingFee( this.floatPosition.PosiFeeType, this.floatPosition.PosiTradingFeeUnit, this.deal.CloseNotionalValue,