refactor(swap): 优化基金公司行为处理逻辑 - finished001
- 修改 FindFundCorporateActions 方法以支持查询跨日期范围的公司行为记录 - 实现按生效日期和ID顺序稳定排序的多条公司行为记录处理 - 更新平仓时基金基线恢复逻辑以正确处理跨多个生效日的场景 - 在测试类中重写 FindFundCorporateActions 方法以支持单元测试验证 - 添加完整的跨非交易日公司行为恢复功能测试用例
This commit is contained in:
@@ -198,6 +198,41 @@ namespace YLErp.Modules.SwapModule
|
||||
"Fund 份额缩小为 0.01 倍时,直接平仓期初价应为 100 / 0.01 = 10000");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FCA_UW_009_登记日跨非交易日到生效日按范围恢复基金基线()
|
||||
{
|
||||
var eodDate = new DateTime(2026, 7, 12);
|
||||
var effectiveDate = new DateTime(2026, 7, 13);
|
||||
var unwindDate = new DateTime(2026, 7, 17);
|
||||
var realtime = CreateRealtimeFundPosition();
|
||||
var eod = CreateEod(eodDate, 1000m, 100m);
|
||||
var service = CreateService(SwapDealTestFactory.CreateTrade(), realtime, eod, hasCompletedFlow: false);
|
||||
service.ExDividendInfos.Add(new ex_dividend_info
|
||||
{
|
||||
id = 1,
|
||||
UnderlyingCode = "FUND.TEST",
|
||||
// 7/10 登记,7/13 生效;7/11、7/12 虽无交易但仍可能存在未除权 EOD 快照。
|
||||
ExDividendDate = new DateTime(2026, 7, 10),
|
||||
EffectiveDate = effectiveDate,
|
||||
// 生产数据口径:1 拆 2 直接存 Split=2,GiveShareAmount 不参与该拆分。
|
||||
GiveShareAmount = 0m,
|
||||
Split = 2m,
|
||||
ValidStatus = true
|
||||
});
|
||||
var unwindData = CreateFullCloseUnwindData();
|
||||
unwindData.ValueDate = unwindDate;
|
||||
unwindData.UnwindDate = unwindDate;
|
||||
|
||||
Assert.IsTrue(service.RestoreEffectiveFundPositionForTest(unwindData, unwindDate));
|
||||
|
||||
Assert.AreEqual(2000m, realtime.PosiQuantity,
|
||||
"7 月 17 日平仓应补应用 7 月 13 日生效的 Split=2,公司行为不能只按平仓日命中");
|
||||
Assert.AreEqual(50m, realtime.PosiGrossPrice);
|
||||
Assert.AreEqual(2000m, unwindData.PositionQty);
|
||||
Assert.AreEqual(2000m, unwindData.CloseQty);
|
||||
Assert.AreEqual(50m, unwindData.FlowEvents.Single().PosiGrossPrice);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FCA_UW_003_正式平仓按FundEod基线重算PnL和现金()
|
||||
{
|
||||
|
||||
@@ -60,6 +60,20 @@ namespace YLErp.Modules.SwapModule
|
||||
&& x.UnderlyingCode == underlyingCode
|
||||
&& x.EffectiveDate == valueDate.Date);
|
||||
|
||||
protected override List<ex_dividend_info> FindFundCorporateActions(
|
||||
string underlyingCode,
|
||||
DateTime eodDate,
|
||||
DateTime valueDate)
|
||||
=> ExDividendInfos
|
||||
.Where(x => x.ValidStatus
|
||||
&& x.UnderlyingCode == underlyingCode
|
||||
&& x.EffectiveDate.HasValue
|
||||
&& x.EffectiveDate.Value.Date > eodDate.Date
|
||||
&& x.EffectiveDate.Value.Date <= valueDate.Date)
|
||||
.OrderBy(x => x.EffectiveDate)
|
||||
.ThenBy(x => x.id)
|
||||
.ToList();
|
||||
|
||||
protected override decimal GetFundCorporateActionClosePrice(
|
||||
ex_dividend_info dividendInfo,
|
||||
decimal fallbackPrice)
|
||||
|
||||
@@ -98,7 +98,8 @@ namespace YLErp.Modules.EodModule
|
||||
/// <returns></returns>
|
||||
public List<BondPayment> GetBondPayments(string underlyingCode, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var result = DbContext.bondPayment.Where(x => x.underlyingCode == underlyingCode && x.payment_date > startDate && x.payment_date <= endDate).AsNoTracking().ToList();
|
||||
var result = DbContext.bondPayment.Where(x => x.underlyingCode == underlyingCode
|
||||
&& x.payment_date > startDate && x.payment_date <= endDate).AsNoTracking().ToList();
|
||||
|
||||
// 让 Copy/Update EOD 始终只依赖 BondPaymentService,而不必在收盘链路直接累加 ex_dividend_info。
|
||||
// 口径约定:bond_payment_info.payment_interest 对 Stock/Fund 统一按“每 10 份派现金额”存储,
|
||||
|
||||
@@ -310,6 +310,29 @@ namespace YLErp.Modules.SwapModule
|
||||
&& x.EffectiveDate.Value == valueDate.Date);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询从最近 EOD 之后到平仓日已经生效的 Stock/Fund 公司行为。
|
||||
/// 平仓可能跨越登记日、生效日和多个非交易日,因此不能只按 valueDate 命中一条记录。
|
||||
/// 调用方已限定为 Stock/Fund 浮动腿;同日多条记录按生效日、主键稳定排序后逐条应用。
|
||||
/// </summary>
|
||||
protected virtual List<ex_dividend_info> FindFundCorporateActions(
|
||||
string underlyingCode,
|
||||
DateTime eodDate,
|
||||
DateTime valueDate)
|
||||
{
|
||||
var fromDate = eodDate.Date;
|
||||
var toDate = valueDate.Date;
|
||||
return DbContext.ex_dividend_info
|
||||
.Where(x => x.ValidStatus
|
||||
&& x.UnderlyingCode == underlyingCode
|
||||
&& x.EffectiveDate.HasValue
|
||||
&& x.EffectiveDate.Value.Date > fromDate
|
||||
&& x.EffectiveDate.Value.Date <= toDate)
|
||||
.OrderBy(x => x.EffectiveDate)
|
||||
.ThenBy(x => x.id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stock/Fund 公司行为系数仍使用登记日收盘价,而不是生效日盘中/收盘价。
|
||||
/// 测试可用 EOD 快照价格作为回退值;生产从登记日行情表取真实收盘价。
|
||||
@@ -387,13 +410,12 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
|
||||
// 最近 EOD 已经处于生效日或更晚时,说明该快照本身已经是除权后基线,
|
||||
// 不能再次套系数。只有“最近 EOD < EffectiveDate <= valueDate”时,
|
||||
// 才在盘中恢复后补一次公司行为。
|
||||
var corporateAction = FindFundCorporateAction(
|
||||
// 不能再次套系数。若平仓跨过多个生效日,则按生效日、id 顺序逐条补齐。
|
||||
var corporateActions = FindFundCorporateActions(
|
||||
position.UnderlyingCode,
|
||||
eodPosition.ValueDate,
|
||||
valueDate);
|
||||
if (corporateAction?.EffectiveDate > eodPosition.ValueDate.Date
|
||||
&& corporateAction.EffectiveDate.Value.Date <= valueDate.Date)
|
||||
foreach (var corporateAction in corporateActions ?? new List<ex_dividend_info>())
|
||||
{
|
||||
var closePrice = GetFundCorporateActionClosePrice(
|
||||
corporateAction,
|
||||
|
||||
Reference in New Issue
Block a user