fix(swap): 分红前部分平仓过的交易,后续平仓/互换分红收益改由 EOD 单一可信源提供

问题:GLMS-20260105-0004 在 2/28 部分平仓 40%、3/2 登记日互换后,3/3 继续平仓/互换时
"浮动端平仓盈亏·分红"误显 -36,160(应为 0)。根因:前端 getDivindIn 用期初全额持仓
×totalInterest 重算分红(-90,400),扣除 consumedDividend(-54,240) 后余 -36,160,
恰为已平仓 40%(登记日前已不持有、不享有该笔分红)部分被重复计入。

方案C:分红收益改由后端读上一收盘日 eod_swap_position.PosiDividendSum 提供(单一可信源),
前端不再自算。EOD 的 PosiDividendSum 已按"实际持仓递推 + 当日实现扣除"准确算出待实现分红,
3/2 互换后为 0。因手动平仓/互换后端不重算 DividendIn、直接落库前端值,后端 Init 阶段给出
正确值可同时修正展示与落库。

改动:
- SwapDealService 新增 GetPreEodDividendSum(复用 GetUnwindInterests:624 的上一 EOD 日期推导
  + SwapEodPositionService.GetPreEodPositions),protected virtual 便于测试覆写、无 EOD 返回 0
- InitUnwind/InitIncome 的 floatEvent.DividendIn 从硬编码 0 改为 GetPreEodDividendSum 返回值
- unwindSwapTrade.js/incomeSwapTrade.js getDivindIn 删除 GetBondPayMentInterest 调用与全部自算,
  保留后端 DividendIn 值、DividendPending=0
- GetBondPayMentInterest 加废弃文档注释(保留接口供历史调用,consumedDividend 无日期过滤隐患随废弃消解)

测试:GLMS20260105PartialCloseDividendBugTest 连真实库验收——3/3 平仓/互换 DividendIn=0(修复前
-36,160)、无 EOD 防御返回 0。全量构建 0 错误。
This commit is contained in:
hjhan
2026-08-06 19:09:45 +08:00
parent 68c3e63c16
commit e3c473bafd
5 changed files with 210 additions and 35 deletions
+35 -2
View File
@@ -350,7 +350,9 @@ namespace YLErp.Modules.SwapModule
floatEvent.PositionId = position.PositionId;
floatEvent.EventType = (int)SwapEventTypeEnum.;
floatEvent.EventReason = "交易";
floatEvent.DividendIn = 0;
// 方案C:分红收益改由上一收盘日 EOD PosiDividendSum 提供(单一可信源),
// 前端 getDivindIn 不再覆盖;消除"期初持仓×totalInterest"对已平仓部分的重复计入。
floatEvent.DividendIn = GetPreEodDividendSum(tradeId, position.PositionId, dealDate);
floatEvent.UnderlyingCode = position.UnderlyingCode;
floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
floatEvent.CloseFee = 0;
@@ -561,7 +563,9 @@ namespace YLErp.Modules.SwapModule
if (position != null)
{
floatEvent.PositionId = position.PositionId;
floatEvent.DividendIn = 0;
// 方案C:分红收益改由上一收盘日 EOD PosiDividendSum 提供(单一可信源),
// 前端 getDivindIn 不再覆盖;消除"期初持仓×totalInterest"对已平仓部分的重复计入。
floatEvent.DividendIn = GetPreEodDividendSum(tradeId, position.PositionId, dealDate);
floatEvent.UnderlyingCode = position.UnderlyingCode;
floatEvent.UnderlyingInstrumentType = position.UnderlyingInstrumentType;
floatEvent.CloseFee = 0;
@@ -949,6 +953,35 @@ namespace YLErp.Modules.SwapModule
return notionalValue > 0 ? posiNotionalValue / notionalValue : 1;
}
/// <summary>
/// 读取"上一收盘日"浮动腿的待实现分红(eod_swap_position.PosiDividendSum),
/// 用于平仓/互换预览页展示"浮动端平仓盈亏·分红(DividendIn)"。
/// <para>方案C:替代前端 totalInterest × 期初持仓 的重算——后者会把登记日前已平仓、
/// 不享有该笔分红的部分重复计入(GLMS-20260105-0004 误显 -36,160)。
/// EOD 的 PosiDividendSum 已按"实际持仓递推 + 当日实现扣除"算出待实现分红,
/// 是单一可信源。</para>
/// <para>复用 GetUnwindInterests(cs:624-626) 的"上一 EOD 日期"推导:取 eod_swap 中
/// ValueDate &lt; dealDate 的最大日期,无则 dealDate.AddDays(-1);再经
/// SwapEodPositionService.GetPreEodPositions 取该日持仓,匹配 PositionId。</para>
/// <para>抽为 protected virtual:与 GetMaxIncomeValueDate 一致,便于测试替身覆写、
/// 也兼容无 EOD 的边界(返回 0,与历史 DividendIn=0 行为一致)。</para>
/// </summary>
/// <param name="tradeId">互换交易ID</param>
/// <param name="positionId">浮动腿持仓IDswap_position.PositionId</param>
/// <param name="dealDate">预览/平仓业务日期</param>
/// <returns>上一收盘日该浮动腿的待实现分红;无 EOD 记录返回 0</returns>
protected virtual decimal GetPreEodDividendSum(int tradeId, long positionId, DateTime dealDate)
{
var lastEod = DbContext.eod_swap
.Where(x => x.ValueDate < dealDate && x.SwapTradeId == tradeId)
.OrderByDescending(o => o.ValueDate).FirstOrDefault();
var preEodDate = lastEod == null ? dealDate.AddDays(-1) : lastEod.ValueDate;
var preEod = new SwapEodPositionService(this)
.GetPreEodPositions(tradeId, preEodDate)
.FirstOrDefault(x => x.PositionId == positionId);
return preEod == null ? 0m : preEod.PosiDividendSum;
}
/// <summary>
/// 获取固定利率
/// </summary>