using YLErp.Derivatives.Interest; using YLErp.Modules.DataProviderModule; namespace YLErp.Modules.EodModule.SettlementModule { /// /// 收盘 FR007 定盘入库检查(独立检查服务,由 EodCheckSettlePrice 在 /// "检查标的结算价格缺失"步骤内编排调用——报错前缀沿用该步骤名,保持既有报错口径)。 /// /// 职责边界:只回答"计息会读的那个取价日,FR007 定盘行是否已入库"。 /// 日期推导与计息路径共用同一套日历函数(IndexFixerBase.GetFixingDate), /// 保证检查口径 ≡ 取数口径。第二步完整版(按各浮动腿重置日推导所需日期集合 + /// 批量校验 + Fr007FixingCache 复用)也落在本类,不回填 EodCheckSettlePrice。 /// public class EodCheckFr007Price : EodSettleServiceBaseV2 { /// 报错里最多列出的交易编号数,超出以"等N笔"收尾,防刷屏。 internal const int MaxTradesInMessage = 20; public EodCheckFr007Price(EodSettlementContextV2 context) : base(context) { ResetDbContext(); } /// /// 检查结算日(回拨后的取价日)FR007 定盘是否入库;缺价抛 EodSettleException 终止收盘。 /// step 由调用方传入:本检查隶属于"检查标的结算价格缺失"步骤,报错前缀与其保持一致。 /// public void Execute(string step) { var checkDate = GetCheckDate(_context.SettleDate); if (!EodPriceQueryService.CheckFR007Price(checkDate)) { var tradeNumbers = GetFloatTradeNumbers(); // 仅缺价时才查,正常收盘零开销 _context.RaiseError(step, BuildMissingMessage(_context.SettleDate, checkDate, tradeNumbers)); } } /// /// 收盘 FR007 检查日:与计息取价共用同一套日历函数(IndexFixerBase.GetFixingDate), /// 保证"检查的日期"与"计息实际读的日期"始终一致。 /// /// 回拨发生在日历层(GetNonHolidayDefore),与 interest_rule 取值无关: /// 交易日 → 当日自身(工作日收盘行为零变更); /// 周末/节假日 → 上一交易日(非交易日无定盘,按裸收盘日检查会形成 /// "非交易日收盘必报缺价"的死锁——2026-08-28 缺陷:0523 周六收盘 /// 报"0523 FR007未入库",且检查只看收盘日不看任何交易,改交易要素无法绕过)。 /// 抽成纯函数便于无数据库单测。 /// internal static DateTime GetCheckDate(DateTime settleDate) => IndexFixerBase.GetFixingDate(settleDate.Date, 0); /// /// 组装 FR007 缺价报错文案(纯函数便于单测): /// 显示实际检查日(= 计息会读的取价日);收盘日为非交易日时注明已回拨; /// 附涉及 FR007 浮动的交易编号供定位——注意这是"涉及"而非"精确需要该日定盘" /// (精确归因需按各腿重置日推导,属第二步完整版改造,此处宁多报不漏报)。 /// internal static string BuildMissingMessage(DateTime settleDate, DateTime checkDate, IReadOnlyList fr007TradeNumbers) { var rollbackNote = checkDate != settleDate.Date ? $"(收盘日{settleDate:yyyy年MM月dd日}为非交易日,已回拨至上一交易日检查)" : string.Empty; string tradeNote; if (fr007TradeNumbers == null || fr007TradeNumbers.Count == 0) { tradeNote = string.Empty; } else { var listed = string.Join("、", fr007TradeNumbers.Take(MaxTradesInMessage)); tradeNote = fr007TradeNumbers.Count > MaxTradesInMessage ? $";涉及FR007浮动的交易: {listed} 等{fr007TradeNumbers.Count}笔" : $";涉及FR007浮动的交易: {listed}"; } return $"{checkDate:yyyy年MM月dd日}FR007价格未入库{rollbackNote}{tradeNote}"; } /// /// 结算范围内挂 FR007 浮动利率的交易编号。仅在缺价报错时调用(正常收盘零开销)。 /// 口径是"结算范围内 FR007 浮动腿所属交易",不含重置日归因——非重置日的交易当日 /// 不取价、也一并列出,定位时宁多报不漏报。 /// private string[] GetFloatTradeNumbers() { var tradePredicate = _context.PredicateBuilder.GetOtcSwapTradePredicate(); return (from t in DbContext.trade.Where(tradePredicate) join p in DbContext.swap_position on t.id equals p.SwapTradeId where !p.Invalid && p.FloatRateUnderlyingCode == "FR007" select t.TradeNumber) .Distinct() .OrderBy(n => n) .ToArray(); } } }