using YLErp.BLL; using YLErp.Configuration.Enums; using YLErp.Modules.DataProviderModule; namespace YLErp.Modules.EodModule.SettlementModule { /// /// 检查当日结算价,是否全部进系统。 /// public class EodCheckSettlePrice : EodSettleServiceBaseV2 { public const string Step = "检查标的结算价格缺失"; public EodCheckSettlePrice(EodSettlementContextV2 context) : base(context) { ResetDbContext(); //DbContext.SetDebugLog(); } /// /// 检查收盘价 /// public void Execute() { if (!_context.OtcTrades.Any() && !hasSwaptrade()) { return; } var request = _context.Request; var settleDate = _context.SettleDate; var clienIds = _context.Request.ClientIds; IQueryable allQuery = null; // FR007 定盘检查委托给独立检查服务(职责分离):检查日=计息实际读取的取价日, // 交易日=当日、周末/节假日=上一交易日,报错沿用本步骤名保持既有口径。 new EodCheckFr007Price(_context).Execute(Step); //判断当日结算价是否已经入库 if (!EodPriceQueryService.CheckDbExists(_context.SettleDate)) { _context.RaiseError(Step, "结算价格未入库"); } //检查是否所有待结算交易标的价格都存在 //获取所有现存(抵押状态)抵押品信息 增加抵押品价格检查 if (request.IsSettleOtcTrades) { var tradePredicate = _context.PredicateBuilder.GetOtcTradePredicate() .And(t => t.TradeType != "自定义交易" && t.UnderlyingCode != null); //新增客户筛选 tw if (clienIds != null) { tradePredicate = tradePredicate.And(l => clienIds.Contains(l.ClientId)); } allQuery = DbContext.trade.Where(tradePredicate).Select(n => n.UnderlyingCode).Distinct(); if (PS.Config.ErpElement.ForwardTradePriceModel == ForwardTradePriceModel.STANDARD) { allQuery = allQuery.Union(DbContext.trade.Where(tradePredicate).Where(x => x.BasisUnderlyingCode != null && x.BasisUnderlyingCode != "").Select(n => n.BasisUnderlyingCode).Distinct()); } var clientProductPredicate = _context.PredicateBuilder.GetClientProductPredicate(); //新增客户筛选 tw if (clienIds != null) { clientProductPredicate = clientProductPredicate.And(l => clienIds.Contains(l.ClientId)); } var clientProductQuery = from t in DbContext.clientcashincashout_product.Where(clientProductPredicate) join um in DbContext.underlying_manager on t.UnderlyingId equals um.id select um.UnderlyingCode; allQuery = allQuery.Union(clientProductQuery.Distinct()); } //检查场内标的 if (request.IsSettleExchangeTrades) { var exTradePredicate = _context.PredicateBuilder.GetExchangeTradePredicate(); var exTradeQuery = DbContext.ExchangeTrade.Where(exTradePredicate).Select(n => n.UnderlyingCode); allQuery = allQuery == null ? exTradeQuery.Distinct() : allQuery.Union(exTradeQuery.Distinct()); var preSettleDate = _context.PreSettleDate; // 持仓分支传入 clienIds:使上一交易日持仓标的按当前收盘客户作用域收敛(防御点详见 QueryPositionUnderlyingCodes)。 var positionQuery = QueryPositionUnderlyingCodes(DbContext, preSettleDate, settleDate, clienIds); allQuery = allQuery.Union(positionQuery.Distinct()); } if (allQuery == null) { return; } //检查是否所有标的都在结算日有结算价格 var umCodeArr = allQuery.ToArray(); var eodPriceProvider = _context.GetEodPriceProvider().Initialize(umCodeArr); var umCodes = umCodeArr.Where(n => !string.IsNullOrEmpty(n) && !eodPriceProvider.HasValue(n)).ToHashSet(StringComparer.OrdinalIgnoreCase); //排除掉节假日不需要结算的交易 foreach (var t in _context.HolidayTrades) { if (!string.IsNullOrEmpty(t.UnderlyingCode)) { umCodes.Remove(t.UnderlyingCode); } } if (umCodes.Any()) { var codes = string.Join(", ", umCodes); _context.RaiseError(Step, "标的代码:" + codes); } } /// /// 取上一交易日持仓中需结算价的标的(持仓分支)。 /// 抽成 static 以便单测直接覆盖 ClientId 收敛语义。 /// /// ── 防御行为(EQD-6967)───────────────────────────────────────────── /// 收盘缺失价检查在“按客户作用域结算(ClientIds 非 null)”时,本应只校验 /// 当前收盘客户自己的标的,而不应把“其他客户”或“系统级(ClientId=0)”的 /// 上一交易日持仓标的误报为缺失价。历史上持仓分支完全未引用 ClientId, /// 导致客户 A 收盘时被其他客户/系统级持仓的标的噪声干扰(见缺陷现象)。 /// /// 本方法通过 clienIds 过滤做收敛,与 OTC 分支、客户产品分支已有的 /// ClientId 过滤语义保持一致: /// · clienIds == null → 系统级全量结算,短路为 true(不收窄,零回归); /// · clienIds != null → 仅返回属于指定客户的持仓标的(Contains(t.ClientId)), /// 过滤掉其他客户及系统级(ClientId=0)持仓噪声。 /// 注意:场外交易结算模式(IsSettleExchangeTrades=false)下持仓分支整块跳过, /// 此时本方法不会被调用,属操作层面的规避而非修复。 /// ─────────────────────────────────────────────────────────────────── /// public static IQueryable QueryPositionUnderlyingCodes(YLContext db, DateTime preSettleDate, DateTime settleDate, IEnumerable clienIds) { var futureTypes = ConsGlobal.InstrumentType.GetFutureTypes(); return from t in db.eod_trade_position join um in db.underlying_manager on t.UnderlyingCode equals um.UnderlyingCode where t.ValueDate == preSettleDate && t.Amount != 0 && ConsTrade.TradeTypesForHedge.Contains(t.TradeType) && (!futureTypes.Contains(um.UnderlyingInstrumentType) || um.MaturityDate >= settleDate) // 【防御点·EQD-6967】按客户作用域收敛持仓标的: // clienIds==null → 系统级全量结算,不收窄(零回归); // clienIds!=null → 仅保留指定客户持仓,过滤掉其他客户/系统级(ClientId=0)持仓噪声。 && (clienIds == null || clienIds.Contains(t.ClientId)) select t.UnderlyingCode; } /// /// 获取互换标的 /// /// private string[] GetSwapUnderlyingCodes() { var tradePredicate = _context.PredicateBuilder.GetOtcSwapTradePredicate(); var query = from t in DbContext.trade.Where(tradePredicate) join p in DbContext.swap_position on t.id equals p.SwapTradeId where p.IsInitial && !string.IsNullOrEmpty(p.UnderlyingCode) && !p.Invalid select p.UnderlyingCode; return query.Distinct().ToArray(); } private bool hasSwaptrade() { var tradePredicate = _context.PredicateBuilder.GetOtcSwapTradePredicate(); return DbContext.trade.Where(tradePredicate).Any(); } } }