Files
zszq-trs/UnitTestProject/Modules/SwapModule/GLMS20260105_0006_RegisterDateDividendTest.cs
T
张名锐 ac7b04e79d fix(bond): #JTZG-136 国联民生-有期间付息,自动互换的的时候互换日期不正确
- 将 BondPaymentService.GetBondPayments 方法的过滤条件从 reg_date 改为 payment_date
- 更新相关日志信息显示 [分红-付息日口径] 替代 [分红-登记日口径]
- 修正单元测试中的注释和变量名,统一使用 payment_date 口径描述
- 修改测试数据构造,验证按实际付息日进行票息计提的逻辑
- 更新相关文档注释,明确日期口径变更的具体实现方式
2026-08-26 13:40:59 +08:00

226 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using YLErp.Modules.EodModule;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 债券 TRS 付息日(payment_date)分红测试。
/// 验证:
/// A. BondPaymentService.GetBondPayments 用实际付息日(payment_date)判定付息归属
/// B. SwapDealService.GetPreEodDividendSum 读 EOD 快照
/// </summary>
[TestClass]
public class GLMS20260105_0006_RegisterDateDividendTest
{
private const string BondCode = "230004.IB";
private const int TradeId = 6006;
private const long PositionId = 60061;
private const decimal Qty = 20_000_000m;
private const decimal PaymentPer100 = 0.1808m;
private const decimal ExpectedDividend = 36_160m; // 20,000,000 × 0.1808 / 100
// 付息日历:登记日 4/3,支付日 4/6
private static readonly DateTime RegDate = new(2026, 4, 3);
private static readonly DateTime PayDate = new(2026, 4, 6);
private static readonly DateTime PrePayDate = new(2026, 4, 5);
// 多次付息日历:债券 230004.IB,每期票息 0.1808,共 5 次付息日
private static readonly DateTime[] RegDates = {
new(2026, 2, 28), new(2026, 4, 3), new(2026, 4, 29),
new(2026, 5, 29), new(2026, 6, 29)
};
private static readonly DateTime[] PayDates = {
new(2026, 3, 2), new(2026, 4, 6), new(2026, 4, 30),
new(2026, 6, 1), new(2026, 6, 30)
};
#region 成因 A:日期口径 seam
private sealed class TestableBondPaymentService : BondPaymentService
{
private readonly List<BondPayment> _data;
public TestableBondPaymentService(List<BondPayment> data) : base(OptUserInfo.UnitTestUser) { _data = data; }
protected override IQueryable<BondPayment> QueryBondPayments(string underlyingCode)
=> _data.Where(x => x.underlyingCode == underlyingCode).AsQueryable();
}
[TestMethod]
public void CauseA_付息日当日EOD_应按付息日口径选中付息记录()
{
var record = new BondPayment
{
underlyingCode = BondCode,
reg_date = RegDate, // 债权登记日 4/3
payment_date_pl = PayDate, // 理论付息日 4/6
payment_date = PayDate, // 实际付息日 4/6
payment_interest = PaymentPer100
};
var svc = new TestableBondPaymentService(new List<BondPayment> { record });
// 付息日当日的 EOD 计提区间 (4/5, 4/6]
var payments = svc.GetBondPayments(BondCode, PrePayDate, PayDate);
// 按支付日(payment_date=4/6)过滤 -> 4/6 落在区间 -> 1 条
Assert.AreEqual(1, payments.Count,
"付息日(4/6)当日 EOD 应按实际付息日(payment_date)选中该笔付息。");
}
[TestMethod]
public void CauseA_MultiRegDate_跨付息日区间命中正确子集()
{
var records = Enumerable.Range(0, 5).Select(i => new BondPayment
{
underlyingCode = BondCode,
reg_date = RegDates[i],
payment_date_pl = PayDates[i],
payment_date = PayDates[i],
payment_interest = PaymentPer100
}).ToList();
var svc = new TestableBondPaymentService(records);
// 单次窗口:每个付息日各自命中 1 条(验证按 payment_date 过滤)
for (int i = 0; i < 5; i++)
{
var prev = i == 0 ? PayDates[i].AddDays(-1) : PayDates[i - 1];
var hit = svc.GetBondPayments(BondCode, prev, PayDates[i]);
Assert.AreEqual(1, hit.Count, $"窗口({prev:yyyy-MM-dd},{PayDates[i]:yyyy-MM-dd}] 应仅命中付息日 {PayDates[i]:yyyy-MM-dd} 那条");
Assert.AreEqual(PayDates[i], hit[0].payment_date, "命中的应是该付息日记录");
}
// 长区间应命中全部 5 条,不漏不混
var all = svc.GetBondPayments(BondCode, PayDates[0].AddDays(-1), PayDates[4]);
Assert.AreEqual(5, all.Count, "长区间(付息日1前,付息日5] 应命中全部 5 次付息");
// 跨付息日中间区间:(4/5, 4/30] 应命中 4/6 与 4/30 两条(不含 3/2、6/1、6/30
var mid = svc.GetBondPayments(BondCode, new DateTime(2026, 4, 5), new DateTime(2026, 4, 30));
Assert.AreEqual(2, mid.Count, "(4/5,4/30] 应命中 4/6+4/30 两条");
CollectionAssert.AreEquivalent(
new[] { new DateTime(2026, 4, 6), new DateTime(2026, 4, 30) },
mid.Select(x => x.payment_date!.Value).ToArray());
}
[TestMethod]
public void CauseA_MultiRegDate_CalcPayment累加五期票息()
{
var records = Enumerable.Range(0, 5).Select(i => new BondPayment
{
underlyingCode = BondCode,
reg_date = RegDates[i],
payment_date_pl = PayDates[i],
payment_date = PayDates[i],
payment_interest = PaymentPer100
}).ToList();
var svc = new TestableBondPaymentService(records);
// 长区间取全部 5 期,CalcPayment 应累加 = 5 × 36160 = 180,800
var payments = svc.GetBondPayments(BondCode, PayDates[0].AddDays(-1), PayDates[4]);
var total = svc.CalcPayment(payments, Qty, 1, 1);
Assert.AreEqual(5 * ExpectedDividend, total, 0.01m,
"5 期票息累加应为 5 × 36,160 = 180,800;单期口径会漏计其余 4 期");
}
#endregion
#region 成因 BT-1 快照 seam
private sealed class TestableSwapDealService : SwapDealService
{
private readonly List<eod_swap> _eodSwaps;
private readonly List<eod_swap_position> _eodPositions;
public TestableSwapDealService(List<eod_swap> eodSwaps, List<eod_swap_position> eodPositions)
: base(OptUserInfo.UnitTestUser) { _eodSwaps = eodSwaps; _eodPositions = eodPositions; }
public decimal ExposeGetPreEodDividendSum(int tradeId, long positionId, DateTime dealDate)
=> GetPreEodDividendSum(tradeId, positionId, dealDate);
protected override IQueryable<eod_swap> QueryPreEodSwaps(int tradeId)
=> _eodSwaps.Where(x => x.SwapTradeId == tradeId).AsQueryable();
protected override eod_swap_position QueryPreEodPosition(int tradeId, long positionId, DateTime valueDate)
=> _eodPositions.FirstOrDefault(x => x.SwapTradeId == tradeId && x.PositionId == positionId && x.ValueDate == valueDate);
}
[TestMethod]
public void CauseB_付息日当天手动平仓_应读到当日EOD分红36160()
{
// 4/5 EOD:累计分红 0;4/6 EOD(付息日):累计分红 36160
var eodSwaps = new List<eod_swap>
{
new eod_swap { SwapTradeId = TradeId, ValueDate = PrePayDate },
new eod_swap { SwapTradeId = TradeId, ValueDate = PayDate }
};
var eodPositions = new List<eod_swap_position>
{
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = PrePayDate, PosiDividendSum = 0m, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = PayDate, PosiDividendSum = ExpectedDividend, PosiQuantity = Qty }
};
var svc = new TestableSwapDealService(eodSwaps, eodPositions);
// 付息日(4/6)当天手动平仓
var dividend = svc.ExposeGetPreEodDividendSum(TradeId, PositionId, PayDate);
Assert.AreEqual(ExpectedDividend, dividend, 0.01m,
"付息日(4/6)当天手动平仓应读到当日 EOD 累计分红 36,160。");
}
[TestMethod]
public void CauseB_MultiRegDate_Auto实现归0后下次付息日重新累加()
{
// 模拟:付息日1(3/2)计提 36160 → auto互换实现归0(3/3) → 付息日2(4/6)再计提 36160
var eodSwaps = new List<eod_swap>
{
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,3,1) },
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,3,2) },
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,3,3) },
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,4,5) },
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,4,6) },
};
var eodPositions = new List<eod_swap_position>
{
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,3,1), PosiDividendSum = 0m, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,3,2), PosiDividendSum = ExpectedDividend, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,3,3), PosiDividendSum = 0m, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,4,5), PosiDividendSum = 0m, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,4,6), PosiDividendSum = ExpectedDividend, PosiQuantity = Qty },
};
var svc = new TestableSwapDealService(eodSwaps, eodPositions);
// 付息日2(4/6)当天手动互换:应读 4/6 EOD = 36160(第二次,非第一次已实现的、非 0)
var dividend = svc.ExposeGetPreEodDividendSum(TradeId, PositionId, new DateTime(2026, 4, 6));
Assert.AreEqual(ExpectedDividend, dividend, 0.01m,
"付息日2(4/6)手动互换应读当日EOD=第二次分红36160。");
}
[TestMethod]
public void CauseB_MultiRegDate_手动互换期间分红挂账累计四期()
{
// 模拟:多次付息日之间未 auto 实现,分红挂账累加
// 4/6=36160, 4/30=72320, 6/1=108480, 6/30=1446404期累计)
var eodSwaps = new List<eod_swap>
{
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,4,6) },
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,4,30) },
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,6,1) },
new eod_swap { SwapTradeId = TradeId, ValueDate = new DateTime(2026,6,30) },
};
var eodPositions = new List<eod_swap_position>
{
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,4,6), PosiDividendSum = 1 * ExpectedDividend, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,4,30), PosiDividendSum = 2 * ExpectedDividend, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,6,1), PosiDividendSum = 3 * ExpectedDividend, PosiQuantity = Qty },
new eod_swap_position { SwapTradeId = TradeId, PositionId = PositionId, ValueDate = new DateTime(2026,6,30), PosiDividendSum = 4 * ExpectedDividend, PosiQuantity = Qty },
};
var svc = new TestableSwapDealService(eodSwaps, eodPositions);
// 每次付息日当天手动互换应读到该日累计值(验证多次付息累计被正确读取)
Assert.AreEqual(1 * ExpectedDividend, svc.ExposeGetPreEodDividendSum(TradeId, PositionId, new DateTime(2026, 4, 6)), 0.01m, "4/6 应读 36160");
Assert.AreEqual(2 * ExpectedDividend, svc.ExposeGetPreEodDividendSum(TradeId, PositionId, new DateTime(2026, 4, 30)), 0.01m, "4/30 应读 723202期累计)");
Assert.AreEqual(3 * ExpectedDividend, svc.ExposeGetPreEodDividendSum(TradeId, PositionId, new DateTime(2026, 6, 1)), 0.01m, "6/1 应读 1084803期累计)");
Assert.AreEqual(4 * ExpectedDividend, svc.ExposeGetPreEodDividendSum(TradeId, PositionId, new DateTime(2026, 6, 30)), 0.01m,
"6/30 应读 1446404期累计)。");
}
#endregion
}
}