feat(bond): 支持股票和基金现金分红纳入债券支付计算 - 收紧过度开发行为

- 实现股票/基金现金分红数据从 ex_dividend_info 同步到 BondPayment
- 新增公司行为去重机制,避免镜像任务完成后重复计息
- 统一现金分红存储口径为"每 10 份派现金额",保持与同步任务一致性
- 修改 CalcPayment 方法,股票/基金分红需除以 10 转换实际现金金额
- 添加单元测试验证债券票息和股票/基金分红的不同计算方式
- 更新文档注释说明"每 10 份派现金额"存储规范
- 修复公司行为生效日处理逻辑,确保正确应用除权系数
- 扩展测试覆盖股票类证券的公司行为处理场景
This commit is contained in:
张名锐
2026-08-20 13:30:36 +08:00
parent aa3548e77f
commit b2f4e16782
13 changed files with 384 additions and 368 deletions
@@ -97,19 +97,10 @@ namespace YLErp.Modules.SwapModule
public void ExecuteFundCorporateActions(
IReadOnlyCollection<eod_swap_position> positions,
IReadOnlyCollection<eod_swap_position> previousEodPositions,
IReadOnlyCollection<swap_flow_event> flowEvents,
IReadOnlyCollection<ex_dividend_info> dividendInfos)
{
ApplyFundCorporateActions(
ApplyCorporateActions(
positions,
previousEodPositions,
flowEvents,
dividendInfos.ToDictionary(x => x.UnderlyingCode, StringComparer.OrdinalIgnoreCase),
SettleDate);
ApplyFundCashDividends(
positions,
previousEodPositions,
dividendInfos.ToDictionary(x => x.UnderlyingCode, StringComparer.OrdinalIgnoreCase),
SettleDate);
}
@@ -315,8 +306,6 @@ namespace YLErp.Modules.SwapModule
service.ExecuteFundCorporateActions(
new[] { actual },
new[] { previousEod },
Array.Empty<swap_flow_event>(),
service.ExDividendInfos);
Assert.AreEqual(2000m, actual.PosiQuantity);
@@ -381,25 +370,24 @@ namespace YLErp.Modules.SwapModule
new List<swap_flow_event>(),
price: 100m);
service.ExDividendInfos.Add(CreateFundCorporateAction(shareAmount: 10m));
var todayEod = previousEod.Clone();
todayEod.ValueDate = SettleDate;
todayEod.UnderlyingPrice = 100m;
// 生产重收盘每次都会从上一日 EOD clone 出新的当日基线,再应用一次公司行为;
// 底层 ApplyCorporateActions 只负责处理调用方提供的未调整基线,不再承担恢复旧基线的测试兼容职责。
var firstRunEod = previousEod.Clone();
firstRunEod.ValueDate = SettleDate;
firstRunEod.UnderlyingPrice = 100m;
service.ExecuteFundCorporateActions(new[] { firstRunEod }, service.ExDividendInfos);
service.ExecuteFundCorporateActions(
new[] { todayEod },
new[] { previousEod },
Array.Empty<swap_flow_event>(),
service.ExDividendInfos);
service.ExecuteFundCorporateActions(
new[] { todayEod },
new[] { previousEod },
Array.Empty<swap_flow_event>(),
service.ExDividendInfos);
var rerunEod = previousEod.Clone();
rerunEod.ValueDate = SettleDate;
rerunEod.UnderlyingPrice = 100m;
service.ExecuteFundCorporateActions(new[] { rerunEod }, service.ExDividendInfos);
Assert.AreEqual(2000m, todayEod.PosiQuantity);
Assert.AreEqual(1000m, todayEod.TdChangedQty);
Assert.AreEqual(50m, todayEod.PosiGrossPrice);
Assert.AreEqual(100000m, todayEod.PosiNotionalValue);
Assert.AreEqual(2000m, firstRunEod.PosiQuantity);
Assert.AreEqual(1000m, firstRunEod.TdChangedQty);
Assert.AreEqual(50m, firstRunEod.PosiGrossPrice);
Assert.AreEqual(100000m, firstRunEod.PosiNotionalValue);
Assert.AreEqual(firstRunEod.PosiQuantity, rerunEod.PosiQuantity);
Assert.AreEqual(firstRunEod.PosiGrossPrice, rerunEod.PosiGrossPrice);
}
[TestMethod]
@@ -427,8 +415,6 @@ namespace YLErp.Modules.SwapModule
service.ExecuteFundCorporateActions(
new[] { actual },
new[] { previousEod },
Array.Empty<swap_flow_event>(),
service.ExDividendInfos);
Assert.AreEqual(1000m, actual.PosiQuantity);
@@ -559,8 +545,6 @@ namespace YLErp.Modules.SwapModule
service.ExecuteFundCorporateActions(
new[] { actual },
new[] { previousEod },
Array.Empty<swap_flow_event>(),
service.ExDividendInfos);
Assert.AreEqual(10m, actual.PosiQuantity,