fix(swap): 解决互换平仓时保证金返息重复计算问题
- 添加保证金返息去重机制,防止平仓时返息被重复计入资金记录 - 新增 GetMainCashRealizedPnL 方法专门处理返息剔除逻辑 - 更新 DealUnwind 和 ApproveSwapTrade 方法使用统一的返息处理口径 - 修改 ReleaseMarginByFundTag 方法增强标签分流功能 - 添加多个单元测试验证返息处理的正确性 - 将 SaveAllChanges 设为可测试化接缝便于测试验证 - 调整 FundTag 分流逻辑支持授信和现金腿分别处理
This commit is contained in:
@@ -1550,6 +1550,8 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
/// <summary>
|
||||
/// 按标签分流并写授信释放记录(virtual,测试可 stub 为全现金,见 TestableSwapDealService)。
|
||||
/// 分流结果以逐腿结算额(settlements)为准,入参 marginAmount/marginRebate 仅在
|
||||
/// 结算事件缺失(异常数据)时兜底,保证不丢资金记录。
|
||||
/// </summary>
|
||||
protected virtual UnwindTagSplit ReleaseMarginByFundTag(trade td, DateTime valueDate, List<swap_flow_event> interestEvents,
|
||||
decimal marginAmount, decimal marginRebate)
|
||||
@@ -1564,6 +1566,26 @@ namespace YLErp.Modules.SwapModule
|
||||
return split;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 主客户现金记录(平仓费)应使用的已实现盈亏:从 SwapRealizedPnL 中剔除保证金返息 SwapMarginRebatePnl。
|
||||
/// <para>
|
||||
/// 口径背景:CalcCloseAmount 汇总时预付金腿的 InterestClosePnL 已计入 SwapRealizedPnL
|
||||
/// (即总盈亏"已包含"返息),而 RecordMarginCashFlow 又会为这部分返息单独分流记账——
|
||||
/// 现金腿写"预付金返息"资金流水、授信腿不产生任何资金(R4 §2.4)。
|
||||
/// 主记录若不剔除,返息会被计两次:一次混在平仓费总额里,一次在独立返息记录里。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 必须剔除"完整返息"而非仅现金部分:授信腿返息同样混在总盈亏里,只是它不落资金流水;
|
||||
/// 若只减现金部分,授信返息会残留在主记录里被当成真实现金支付给客户。
|
||||
/// SwapMarginRebatePnl=0(无预付金腿交易,或存量待复核事件反序列化的默认值)时本方法为无操作,
|
||||
/// 历史事件审批重放不会改变金额,向后兼容。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private static decimal GetMainCashRealizedPnL(UnwindData unwindData)
|
||||
{
|
||||
return unwindData.SwapRealizedPnL - unwindData.SwapMarginRebatePnl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化利息腿信息
|
||||
/// </summary>
|
||||
@@ -2018,7 +2040,12 @@ namespace YLErp.Modules.SwapModule
|
||||
bool cofirm = false;
|
||||
ExecuteInTransaction(() =>
|
||||
{
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
// 主客户现金(平仓费)须剔除保证金返息(完整口径见 GetMainCashRealizedPnL):
|
||||
// SwapRealizedPnL 已含返息,RecordMarginCashFlow 又会单独为返息记账,不剔除会重复计一次;
|
||||
// 且须剔除完整 SwapMarginRebatePnl 而非仅现金部分——Credit 返息同样混在总盈亏里,
|
||||
// 只是不落资金,若只减现金部分会把授信返息当真金白银付出去。
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-GetMainCashRealizedPnL(unwindData)), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
// 平仓了结交易:预付金本金(SwapMarginAmount)+返息(SwapMarginRebatePnl)都按腿 FundTag 原路返还。
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
DealFloatPosition(unwindData);
|
||||
var flowList = new List<swap_flow_event>(unwindData.FlowEvents);
|
||||
@@ -2192,10 +2219,19 @@ namespace YLErp.Modules.SwapModule
|
||||
return data.ValueAddedTax ?? 0;
|
||||
}
|
||||
|
||||
private void DealUnwind(UnwindData unwindData, trade td, string actionMsg = "系统操作_自动平仓")
|
||||
/// <summary>
|
||||
/// 自动平仓的资金结算与持仓扣减(自动平仓/到期自动处理共用)。
|
||||
/// private 改 protected virtual 仅为可测试化:生产无子类覆写、行为不变,
|
||||
/// 测试经 TestableSwapDealService.DealUnwindForTest 直接驱动本方法(见 UW_005C)。
|
||||
/// </summary>
|
||||
protected virtual void DealUnwind(UnwindData unwindData, trade td, string actionMsg = "系统操作_自动平仓")
|
||||
{
|
||||
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapRealizedPnL), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCashInCashOut);
|
||||
// 与手工 SwapUnwind 同口径(完整背景见 GetMainCashRealizedPnL):
|
||||
// 主客户现金剔除已单独记账的保证金返息,防止返息重复计一次。
|
||||
// AddClientCash 为可测试化接缝:生产等价于原 AddClientCashInCashOut(见类头 seam 区注释)。
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-GetMainCashRealizedPnL(unwindData)), ClientCashInCashOut.系统操作_平仓费, unwindData.ValueDate);
|
||||
// 平仓了结交易:预付金本金+返息按腿 FundTag 原路返还。
|
||||
RecordMarginCashFlow(td, unwindData.ValueDate, unwindData.FlowEvents, unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
var flowList = new List<swap_flow_event>(unwindData.FlowEvents);
|
||||
var eventId = SaveSwapDeal(unwindData, (int)SwapEventTypeEnum.平仓, clientCashId, actionMsg);
|
||||
if (unwindData.CloseMethod == (int)CloseMethodEnum.全部平仓)
|
||||
@@ -2218,7 +2254,7 @@ namespace YLErp.Modules.SwapModule
|
||||
td.OptDate = DateTime.Now;
|
||||
td.OptId = UserId;
|
||||
td.OptName = UserName;
|
||||
DbContext.SaveChanges();
|
||||
SaveAllChanges(); // 可测试化接缝:生产=DbContext.SaveChanges(),测试空操作(原直写 DbContext.SaveChanges)
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算平仓总额
|
||||
@@ -2338,10 +2374,14 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
}
|
||||
string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费;
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate);
|
||||
decimal mainCashPnl = eventType == (int)SwapEventTypeEnum.平仓
|
||||
? GetMainCashRealizedPnL(swapEvent.unwindData)
|
||||
: swapEvent.unwindData.SwapRealizedPnL;
|
||||
int clientCashId = AddClientCash(td, Convert.ToDouble(-mainCashPnl), action, swapEvent.unwindData.ValueDate);
|
||||
if (eventType == (int)SwapEventTypeEnum.平仓)
|
||||
{
|
||||
RecordMarginCashFlow(td, swapEvent.unwindData.ValueDate, swapEvent.unwindData.FlowEvents, swapEvent.unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
RecordMarginCashFlow(td, swapEvent.unwindData.ValueDate, swapEvent.unwindData.FlowEvents,
|
||||
swapEvent.unwindData.SwapMarginAmount, 0m, AddClientCash);
|
||||
}
|
||||
swapEvent.ClientCashId = clientCashId;
|
||||
td.UnWindDate = swapEvent.unwindData.UnwindDate;
|
||||
|
||||
Reference in New Issue
Block a user