fix(swap): 解决互换交易全额平仓计算和判定问题
- 在前端计算函数中添加全额平仓时返回剩余数量的逻辑 - 添加针对 trade2308 问题的单元测试验证全额平仓场景 - 实现后端全额平仓请求标准化处理方法 NormalizeFullCloseRequest - 添加重新计算标准化平仓金额的方法 RecalculateNormalizedUnwindAmounts - 实现平仓后是否全额关闭的判定逻辑 IsFullCloseAfterDeduction - 在平仓流程中集成全额平仓标准化处理和金额重新计算 - 更新平仓后交易状态和剩余金额数量的处理逻辑 - 完善审批流程中的平仓数据标准化和流动事件处理 - 优化持仓扣减时全额平仓的处理逻辑 - 添加多个单元测试验证各种全额平仓场景的正确性
This commit is contained in:
@@ -197,6 +197,8 @@ namespace YLErp.Modules.SwapModule
|
||||
public void UW_007_SwapUnwind_占期初A转占剩余B_全平判定正确()
|
||||
{
|
||||
var td = SwapDealTestFactory.CreateTrade();
|
||||
td.StockEqvNotional = 600000;
|
||||
td.TradeAmount = 600000;
|
||||
var service = new TestableSwapDealService(td);
|
||||
var unwindData = SwapDealTestFactory.CreateUnwindData(
|
||||
swapRealizedPnL: 0m, closeMethod: (int)CloseMethodEnum.全部平仓, closePercent: 0.6m,
|
||||
@@ -259,6 +261,33 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.AreEqual(500000.01, td.StockEqvNotional, 0.000001, "trade 剩余名义本金应在扣减后舍入两位小数");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UW_013_SwapUnwind_合法零点零一剩余不应判定全平()
|
||||
{
|
||||
var td = SwapDealTestFactory.CreateTrade();
|
||||
td.StockEqvNotional = 1000000.01;
|
||||
td.TradeAmount = 10000.01;
|
||||
var service = new TestableSwapDealService(td);
|
||||
var unwindData = SwapDealTestFactory.CreateUnwindData(
|
||||
swapRealizedPnL: 0m,
|
||||
closeMethod: (int)CloseMethodEnum.部分平仓,
|
||||
closePercent: 1000000m / 1000000.01m,
|
||||
closeQty: 10000m,
|
||||
closeNotionalValue: 1000000m,
|
||||
positionQty: 10000.01m);
|
||||
unwindData.NotionalValue = 1000000.01m;
|
||||
unwindData.PosiNotionalValue = 1000000.01m;
|
||||
|
||||
service.SwapUnwind(unwindData);
|
||||
|
||||
Assert.AreEqual("确认成交", td.TradeStatus,
|
||||
"剩余名义本金和数量均为0.01时仍应保持部分平仓状态");
|
||||
Assert.AreEqual(1, td.HasPartialUnWind,
|
||||
"合法的0.01尾差不应被清零");
|
||||
Assert.AreEqual(0.01, td.StockEqvNotional, 0.000001);
|
||||
Assert.AreEqual(0.01, td.TradeAmount, 0.000001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UW_010_SwapUnwind_现金与两位利息事件保持一致()
|
||||
{
|
||||
@@ -289,5 +318,183 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.AreEqual(10m, unwindData.SwapRealizedPnL);
|
||||
Assert.AreEqual(-10d, service.ClientCashCalls[0].amount, 0.001d);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UW_011_trade2308_full_close_uses_remaining_values_and_zeroes_tail()
|
||||
{
|
||||
var td = SwapDealTestFactory.CreateTrade();
|
||||
td.StockEqvNotional = 4906156.15;
|
||||
td.TradeAmount = 5000000;
|
||||
td.Notional = 5000000;
|
||||
var service = new TestableSwapDealService(td);
|
||||
var unwindData = SwapDealTestFactory.CreateUnwindData(
|
||||
swapRealizedPnL: 0m,
|
||||
closeMethod: (int)CloseMethodEnum.部分平仓,
|
||||
closePercent: 0.5m,
|
||||
closeQty: 5000000.01m,
|
||||
closeNotionalValue: 4906156.15m,
|
||||
positionQty: 5000000m);
|
||||
unwindData.NotionalValue = 9812312.31m;
|
||||
unwindData.PosiNotionalValue = 4906156.15m;
|
||||
|
||||
service.SwapUnwind(unwindData);
|
||||
|
||||
var saved = service.SaveSwapDealCalls[0].data;
|
||||
Assert.AreEqual((int)CloseMethodEnum.部分平仓, saved.CloseMethod,
|
||||
"CloseMethod 保留本次部分平仓意图,终态由扣减后的持仓事实决定");
|
||||
Assert.AreEqual(5000000m, saved.CloseQty);
|
||||
Assert.AreEqual(4906156.15m, saved.CloseNotionalValue);
|
||||
Assert.AreEqual(0d, td.StockEqvNotional, 0.000001);
|
||||
Assert.AreEqual(0d, td.TradeAmount, 0.000001);
|
||||
Assert.AreEqual(0d, td.Notional, 0.000001);
|
||||
Assert.AreEqual("已平仓", td.TradeStatus);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UW_014_full_close_quantity_normalization_recalculates_pnl_and_cash()
|
||||
{
|
||||
var td = SwapDealTestFactory.CreateTrade();
|
||||
td.StockEqvNotional = 4906156.15;
|
||||
td.TradeAmount = 5000000;
|
||||
td.Notional = 5000000;
|
||||
var service = new TestableSwapDealService(td);
|
||||
var unwindData = SwapDealTestFactory.CreateUnwindData(
|
||||
swapRealizedPnL: 50000000.10m,
|
||||
closeMethod: (int)CloseMethodEnum.部分平仓,
|
||||
closePercent: 0.5m,
|
||||
closeQty: 5000000.01m,
|
||||
closeNotionalValue: 4906156.15m,
|
||||
positionQty: 5000000m);
|
||||
unwindData.NotionalValue = 9812312.31m;
|
||||
unwindData.PosiNotionalValue = 4906156.15m;
|
||||
unwindData.SwapCloseAmount = 50000000.10m;
|
||||
var floatEvent = new swap_flow_event
|
||||
{
|
||||
UnderlyingCode = "UT-FLOAT",
|
||||
PositionType = (int)PositionTypeFlag.Long,
|
||||
EventType = (int)SwapEventTypeEnum.平仓,
|
||||
PayDirection = 1,
|
||||
PosiGrossPrice = 1m,
|
||||
TradingAmountAvg = 11m,
|
||||
MarkClosePnl = 50000000.10m
|
||||
};
|
||||
unwindData.FlowEvents.Add(floatEvent);
|
||||
|
||||
service.SwapUnwind(unwindData);
|
||||
|
||||
Assert.AreEqual(5000000m, unwindData.CloseQty);
|
||||
Assert.AreEqual(50000000m, floatEvent.MarkClosePnl);
|
||||
Assert.AreEqual(50000000m, unwindData.SwapRealizedPnL);
|
||||
Assert.AreEqual(50000000m, unwindData.SwapCloseAmount);
|
||||
Assert.AreEqual(-50000000d, service.ClientCashCalls.Single().amount, 0.001d);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UW_012_approve_restores_A_to_B_and_normalizes_flow_for_full_close()
|
||||
{
|
||||
var td = SwapDealTestFactory.CreateTrade();
|
||||
td.StockEqvNotional = 4906156.15;
|
||||
td.TradeAmount = 5000000;
|
||||
var unwindData = SwapDealTestFactory.CreateUnwindData(
|
||||
swapRealizedPnL: 0m,
|
||||
closeMethod: (int)CloseMethodEnum.部分平仓,
|
||||
closePercent: 4906156.15m / 9812312.31m,
|
||||
closeQty: 5000000.01m,
|
||||
closeNotionalValue: 4906156.15m,
|
||||
positionQty: 5000000m);
|
||||
unwindData.NotionalValue = 9812312.31m;
|
||||
unwindData.PosiNotionalValue = 4906156.15m;
|
||||
var floatEvent = new swap_flow_event
|
||||
{
|
||||
EventId = 1,
|
||||
UnderlyingCode = "261031.IB",
|
||||
PositionType = (int)PositionTypeFlag.Long,
|
||||
Quantity = 5000000.01m,
|
||||
PositionQty = -0.01m
|
||||
};
|
||||
var swapEvent = new swap_event
|
||||
{
|
||||
id = 1,
|
||||
SwapTradeId = SwapDealTestFactory.SwapTradeId,
|
||||
EventType = (int)SwapEventTypeEnum.平仓,
|
||||
Invalid = false,
|
||||
EventData = JsonConvert.SerializeObject(unwindData)
|
||||
};
|
||||
var service = new TestableSwapDealService(td,
|
||||
swapEvents: new Dictionary<int, swap_event>
|
||||
{
|
||||
[(int)SwapEventTypeEnum.平仓] = swapEvent
|
||||
},
|
||||
flowEventsByEventId: new Dictionary<long, List<swap_flow_event>>
|
||||
{
|
||||
[1] = new List<swap_flow_event> { floatEvent }
|
||||
});
|
||||
|
||||
service.ApproveSwapTrade(td, (int)SwapEventTypeEnum.平仓);
|
||||
|
||||
Assert.AreEqual(1m, swapEvent.unwindData.ClosePercent);
|
||||
Assert.AreEqual((int)CloseMethodEnum.部分平仓, swapEvent.unwindData.CloseMethod,
|
||||
"审批不应把部分平仓事件改写为全平意图");
|
||||
Assert.AreEqual(5000000m, swapEvent.unwindData.CloseQty);
|
||||
Assert.AreEqual(4906156.15m, swapEvent.unwindData.CloseNotionalValue);
|
||||
Assert.AreEqual(5000000m, floatEvent.Quantity);
|
||||
Assert.AreEqual(0m, floatEvent.PositionQty);
|
||||
Assert.AreEqual("已平仓", td.TradeStatus);
|
||||
Assert.AreEqual(0d, td.StockEqvNotional, 0.000001);
|
||||
Assert.AreEqual(0d, td.TradeAmount, 0.000001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UW_015_approve_full_close_recalculates_normalized_pnl_before_cash()
|
||||
{
|
||||
var td = SwapDealTestFactory.CreateTrade();
|
||||
td.StockEqvNotional = 4906156.15;
|
||||
td.TradeAmount = 5000000;
|
||||
var unwindData = SwapDealTestFactory.CreateUnwindData(
|
||||
swapRealizedPnL: 50000000.10m,
|
||||
closeMethod: (int)CloseMethodEnum.部分平仓,
|
||||
closePercent: 4906156.15m / 9812312.31m,
|
||||
closeQty: 5000000.01m,
|
||||
closeNotionalValue: 4906156.15m,
|
||||
positionQty: 5000000m);
|
||||
unwindData.NotionalValue = 9812312.31m;
|
||||
unwindData.PosiNotionalValue = 4906156.15m;
|
||||
unwindData.SwapCloseAmount = 50000000.10m;
|
||||
var floatEvent = new swap_flow_event
|
||||
{
|
||||
EventId = 1,
|
||||
UnderlyingCode = "261031.IB",
|
||||
PositionType = (int)PositionTypeFlag.Long,
|
||||
PayDirection = 1,
|
||||
PosiGrossPrice = 1m,
|
||||
TradingAmountAvg = 11m,
|
||||
MarkClosePnl = 50000000.10m,
|
||||
Quantity = 5000000.01m,
|
||||
PositionQty = -0.01m
|
||||
};
|
||||
var swapEvent = new swap_event
|
||||
{
|
||||
id = 1,
|
||||
SwapTradeId = SwapDealTestFactory.SwapTradeId,
|
||||
EventType = (int)SwapEventTypeEnum.平仓,
|
||||
Invalid = false,
|
||||
EventData = JsonConvert.SerializeObject(unwindData)
|
||||
};
|
||||
var service = new TestableSwapDealService(td,
|
||||
swapEvents: new Dictionary<int, swap_event>
|
||||
{
|
||||
[(int)SwapEventTypeEnum.平仓] = swapEvent
|
||||
},
|
||||
flowEventsByEventId: new Dictionary<long, List<swap_flow_event>>
|
||||
{
|
||||
[1] = new List<swap_flow_event> { floatEvent }
|
||||
});
|
||||
|
||||
service.ApproveSwapTrade(td, (int)SwapEventTypeEnum.平仓);
|
||||
|
||||
Assert.AreEqual(5000000m, swapEvent.unwindData.CloseQty);
|
||||
Assert.AreEqual(50000000m, swapEvent.unwindData.SwapRealizedPnL);
|
||||
Assert.AreEqual(-50000000d, service.ClientCashCalls.Single().amount, 0.001d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user