fix: 统一互换部分平仓交易费用与待结算费用的分摊舍入
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
/// <summary>
|
||||
/// 校验上日是否收盘
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user