fix(swap): 修复部分平仓计息计算中的尾差处理问题
- 修正了部分平仓后待实现利息收入的预期值从 0.006383561644 到 -0.010438356164 - 修正了预期待实现利息值从 0.820569301369 到 0.410474008219 - 在 SwapDealService 中添加了已完成平仓事件的查询逻辑,排除已平仓头寸的本金计算 - 重构了 SwapEodPositionService 中的待结算利息计算逻辑,区分自动互换和平仓场景 - 在测试类中添加了已完成流程事件的查找方法和相关测试数据 - 更新了测试用例以验证平仓事件对利息计算的影响
This commit is contained in:
@@ -713,7 +713,7 @@ namespace YLErp.Modules.SwapModule
|
||||
"部分平仓计算必须带入自动互换遗留的待实现尾差");
|
||||
Assert.AreEqual(position.id, service.LastInterestCalculationEodPosition.PositionId,
|
||||
"部分平仓计息必须按腿标识匹配上一日日终");
|
||||
AssertDecimal(0.006383561644m, firstCloseResult.InterestIncomeSum,
|
||||
AssertDecimal(-0.010438356164m, firstCloseResult.InterestIncomeSum,
|
||||
"部分平仓后待实现应延续历史尾差");
|
||||
AssertDecimal(0.02m, firstCloseResult.RealizedInterest,
|
||||
"部分平仓后累计已实现应包含此前自动互换和本次平仓");
|
||||
@@ -744,7 +744,7 @@ namespace YLErp.Modules.SwapModule
|
||||
const decimal rate = 0.0299m;
|
||||
const decimal pendingInterest = 0.820379534246m;
|
||||
const decimal settledInterest = 0.82m;
|
||||
const decimal expectedPendingInterest = 0.820569301369m;
|
||||
const decimal expectedPendingInterest = 0.410474008219m;
|
||||
var service = new StubEodPositionService();
|
||||
var td = CreateTrade();
|
||||
td.trade_extend.ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace YLErp.Modules.SwapModule
|
||||
protected override List<trade_extend> FindTradeExtends(List<int> tradeIds) => _extends;
|
||||
protected override List<eod_swap> FindEodSwapsByDate(DateTime valueDate) => _eodSwaps;
|
||||
protected override List<swap_flow_event> FindFlowEvents(int swapTradeId, DateTime settleDate) => _flowEvents;
|
||||
protected override List<swap_flow_event> FindCompletedFlowEvents(List<int> tradeIds) => _flowEvents;
|
||||
protected override List<eod_swap_position> FindEodSwapPositions(int swapTradeId, DateTime preSettleDate)
|
||||
=> _eodPositions.Where(x => x.SwapTradeId == swapTradeId && x.ValueDate >= preSettleDate).ToList();
|
||||
protected override List<swap_position> FindSwapPositions(int swapTradeId)
|
||||
@@ -296,7 +297,18 @@ namespace YLErp.Modules.SwapModule
|
||||
new List<swap_position> { CreateFloatPosition(1, 1000), initialPrepay, realPrepay },
|
||||
new List<eod_swap_position> { CreateFloatEodPosition(1, 1000, 1.0020m), prepayEod },
|
||||
new List<eod_swap> { new eod_swap { SwapTradeId = SwapTradeId, ValueDate = PreSettleDate } },
|
||||
new List<trade_extend> { CreateExtend() }, new List<swap_flow_event>());
|
||||
new List<trade_extend> { CreateExtend() },
|
||||
new List<swap_flow_event>
|
||||
{
|
||||
CreateCloseFlowEvent(1, 300),
|
||||
new swap_flow_event
|
||||
{
|
||||
SwapTradeId = SwapTradeId, PositionId = initialPrepayId,
|
||||
EventType = (int)SwapEventTypeEnum.平仓,
|
||||
EventDate = SettleDate,
|
||||
DataState = (int)SwapFlowDateStateEnum.完成
|
||||
}
|
||||
});
|
||||
|
||||
service.ExecuteSwapPositionCompose(SettleDate, PreSettleDate);
|
||||
|
||||
@@ -338,13 +350,21 @@ namespace YLErp.Modules.SwapModule
|
||||
};
|
||||
var closeFlow = CreateCloseFlowEvent(1, 300);
|
||||
closeFlow.InterestRate = 0.01m;
|
||||
var prepayCloseFlow = new swap_flow_event
|
||||
{
|
||||
SwapTradeId = SwapTradeId, PositionId = initialPrepayId,
|
||||
EventType = (int)SwapEventTypeEnum.平仓,
|
||||
EventDate = SettleDate, DataState = (int)SwapFlowDateStateEnum.完成,
|
||||
InterestMode = (int)InterestModeEnum.初始预付金,
|
||||
InterestPrincipal = 300m
|
||||
};
|
||||
var service = new TestableSwapEodService(
|
||||
new List<trade> { td },
|
||||
new List<swap_position> { CreateFloatPosition(1, 1000), initialPrepay, realPrepay },
|
||||
new List<eod_swap_position> { CreateFloatEodPosition(1, 1000, 1.0020m), prepayEod },
|
||||
new List<eod_swap> { new eod_swap { SwapTradeId = SwapTradeId, ValueDate = PreSettleDate } },
|
||||
new List<trade_extend> { CreateExtend() },
|
||||
new List<swap_flow_event> { closeFlow });
|
||||
new List<swap_flow_event> { closeFlow, prepayCloseFlow });
|
||||
|
||||
service.ExecuteSwapPositionCompose(SettleDate, PreSettleDate);
|
||||
|
||||
|
||||
@@ -700,6 +700,9 @@ namespace YLErp.Modules.SwapModule
|
||||
|| x.InterestMode == (int)InterestModeEnum.追加预付金)
|
||||
.GroupBy(x => x.PositionId)
|
||||
.ToDictionary(x => x.Key, x => x.Sum(v => v.InterestPrincipal));
|
||||
var priorClosePositionIds = new HashSet<long>((completedFlowEvents ?? Enumerable.Empty<swap_flow_event>())
|
||||
.Where(x => x.EventType == (int)SwapEventTypeEnum.平仓 && x.EventDate <= settleDate)
|
||||
.Select(x => x.PositionId));
|
||||
|
||||
return origPositions.Where(x => x.PosiDirection == 0).Select(p =>
|
||||
{
|
||||
@@ -709,6 +712,10 @@ namespace YLErp.Modules.SwapModule
|
||||
var realLeg = realPositions.FirstOrDefault(r => r.PositionId == p.id);
|
||||
if (realLeg != null)
|
||||
{
|
||||
if (!priorClosePositionIds.Contains(p.id))
|
||||
{
|
||||
return p;
|
||||
}
|
||||
var futurePrincipal = hasNotionalFlows
|
||||
? p.InterestPrincipalFix * futureCloseNotional / originalNotional
|
||||
: futureClosePrincipal.TryGetValue(p.id, out var flowPrincipal) ? flowPrincipal : 0m;
|
||||
|
||||
@@ -1412,12 +1412,9 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
else
|
||||
{
|
||||
var useAccrualBalance = !autoSwap
|
||||
&& (position.InterestMode == (int)InterestModeEnum.初始预付金
|
||||
|| position.InterestMode == (int)InterestModeEnum.追加预付金);
|
||||
var pendingInterestBeforeSettlement = useAccrualBalance
|
||||
? lastInterestIncomeSum + newEodPayPosition.TdInterestIncome
|
||||
: interestAmountBeforeSettlement;
|
||||
var pendingInterestBeforeSettlement = autoSwap
|
||||
? interestAmountBeforeSettlement
|
||||
: lastInterestIncomeSum + newEodPayPosition.TdInterestIncome;
|
||||
newEodPayPosition.InterestIncomeSum = RoundEodInterest(
|
||||
pendingInterestBeforeSettlement - newEodPayPosition.TdCloseInterest);
|
||||
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
|
||||
|
||||
Reference in New Issue
Block a user