diff --git a/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs b/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs index 36e8484b..ef5a8622 100644 --- a/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs +++ b/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs @@ -1,5 +1,7 @@ +using YLErp; using YLErp.DBModels; using YLErp.DBModels.Enums; +using YLErp.Modules.EodModule; namespace YLErp.Modules.SwapModule { @@ -17,9 +19,21 @@ namespace YLErp.Modules.SwapModule private const int SwapTradeId = 9200; private const long PositionId = 9201; private const decimal InitialQty = 1000m; - private const decimal DailyRatePerUnit = 0.01m; // 每单位每天 0.01,便于手算 + private const decimal RegPer100 = 1.0m; // 每 100 元面值票息 1.0 → qty(1000) 时单期分红 = 1.0×1000/100 = 10 private static readonly DateTime StartDate = new(2026, 1, 5); + #region 内存债券付息数据(reg_date 口径,真实生产 GetBondPayments 读取) + + private const string BondUnderlying = "210210.IB"; + private static List BondPayments() => new List + { + // 登记日 1/6、1/7 各一期;支付日滞后若干日(刻意与登记日不同,验证按 reg_date 而非 pay_date 计提) + new BondPayment { underlyingCode = BondUnderlying, reg_date = new DateTime(2026, 1, 6), payment_date_pl = new DateTime(2026, 1, 9), payment_date = new DateTime(2026, 1, 9), payment_interest = RegPer100 }, + new BondPayment { underlyingCode = BondUnderlying, reg_date = new DateTime(2026, 1, 7), payment_date_pl = new DateTime(2026, 1, 10), payment_date = new DateTime(2026, 1, 10), payment_interest = RegPer100 }, + }; + + #endregion + #region Stubs /// SwapDealService stub:暴露 GetPreEodDividendSum,注入 EOD 数据(不连库)。 @@ -37,14 +51,25 @@ namespace YLErp.Modules.SwapModule => _eodPositions.FirstOrDefault(x => x.SwapTradeId == tradeId && x.PositionId == positionId && x.ValueDate == valueDate); } - /// SwapEodPositionService stub:暴露 UpdateEodPosition/CopyEodPosition + 线性 CalcBondPayment。 + /// 真实 BondPaymentService(reg_date 口径)seam:仅注入内存 BondPayment 数据,票息计算走生产 GetBondPayments+CalcPayment。 + private sealed class RealBondPaymentService : BondPaymentService + { + private readonly List _data; + public RealBondPaymentService(List data, OptUserInfo userInfo) : base(userInfo) { _data = data; } + protected override IQueryable QueryBondPayments(string underlyingCode) + => _data.Where(x => x.underlyingCode == underlyingCode).AsQueryable(); + } + + /// SwapEodPositionService stub:暴露 UpdateEodPosition/CopyEodPosition;CalcBondPayment 桥接真实 BondPaymentService(reg_date 口径,不再用线性假公式)。 private sealed class EodSvcStub : TestableSwapEodPositionService { - public EodSvcStub() : base(nameof(DividendEodNoDoubleCountTest)) { } + private readonly List _bondPayments; + public EodSvcStub(List bondPayments) : base(nameof(DividendEodNoDoubleCountTest)) { _bondPayments = bondPayments; } protected override decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio) { - int days = Math.Max(0, (int)(toDate - fromDate).TotalDays); - return DailyRatePerUnit * days * qty * shortRatio * directionRatio; + // 桥接真实生产口径:GetBondPayments 按 reg_date 过滤 + CalcPayment 累加(替换原线性假公式 DailyRatePerUnit*days*qty) + var svc = new RealBondPaymentService(_bondPayments, OptUserInfo.UnitTestUser); + return svc.CalcPayment(underlyingCode, fromDate, toDate, qty, shortRatio, directionRatio); } protected override underlying_manager GetUnderlyingData(string underlyingCode) => new underlying_manager { ValueAddedTax = 0m }; @@ -122,15 +147,15 @@ namespace YLErp.Modules.SwapModule /// 盘中收益互换:DividendIn 由 GetPreEodDividendSum 真实算(读 T-1 EOD)→ 保存 → EOD。 /// 验证:不重复(EOD TdCloseDividend 扣 DividendIn)+ 不丢失(当日新计进 PosiDividendSum)+ 守恒。 /// - /// 序列(StartDate=1/5,每日 0.01×1000=10): - /// D1=1/6 无事件 Copy:PosiDividendSum = 0 + 10 = 10 - /// D2=1/7 盘中互换:GetPreEodDividendSum(读 D1) → DividendIn=10;保存 swap_event;EOD:新计 10 - 实现 10 → PosiDividendSum=10 + /// 序列(StartDate=1/5,reg_date 1/6、1/7 各一期,每期 = qty×per100/100 = 10): + /// D1=1/6 无事件 Copy:窗口(1/5,1/6] 命中 reg_date 1/6 → TdPosiDividend=10,PosiDividendSum=10 + /// D2=1/7 盘中互换:GetPreEodDividendSum(读 D1) → DividendIn=10;保存 swap_event;EOD 窗口(1/6,1/7] 命中 reg_date 1/7 → 新计 10 - 实现 10 → PosiDividendSum=10 /// 守恒:全程新计(10+10) - 全程实现(10) = 末尾 PosiDividendSum(10) /// [TestMethod] public void 盘中收益互换_DividendIn真实算_保存后EOD_不重复不丢失() { - var eodSvc = new EodSvcStub(); + var eodSvc = new EodSvcStub(BondPayments()); var td = CreateTrade(); var position = CreatePosition(); var initialEod = CreateInitialEod(); @@ -174,15 +199,15 @@ namespace YLErp.Modules.SwapModule /// 登记日当日全平(盘中平仓→收盘持仓 0):按各交易场所规定,不享有登记日当日的分红 /// (股权登记日以收盘在册为准;盘中全平→收盘不在册)。验证系统行为符合该规定。 /// - /// 系统行为:①盘中 DividendIn=GetPreEodDividendSum 读 T-1(=T日前待实现,正确不含登记日当日); - /// ②EOD 全平 PosiQuantity=0 → TdPosiDividend=0(不计提登记日当日)+ PosiDividendSum=0。 - /// 即登记日当日分红既不进 DividendIn、也不进 PosiDividendSum = 正确不享有。 - /// 应得 = T日前待实现累计(r1.PosiDividendSum);实拿 = DividendIn → 相等,无丢失(不享有当日是正确的)。 + /// 系统行为:①盘中 DividendIn=GetPreEodDividendSum 读 T-1(=T日前待实现,正确不含登记日当日 reg_date 1/7 的分红); + /// ②EOD 全平 PosiQuantity=0 → TdPosiDividend=0(不计提登记日当日 reg_date 1/7)+ PosiDividendSum=0。 + /// 即登记日当日分红(reg_date 1/7 的 10)既不进 DividendIn、也不进 PosiDividendSum = 正确不享有。 + /// 应得 = T日前待实现累计(r1.PosiDividendSum,仅含 1/6 那期 10);实拿 = DividendIn → 相等,无丢失(不享有当日是正确的)。 /// [TestMethod] public void 登记日全平_按交易场所规定不享有当日分红() { - var eodSvc = new EodSvcStub(); + var eodSvc = new EodSvcStub(BondPayments()); var td = CreateTrade(); var position = CreatePosition(); var initialEod = CreateInitialEod();