From 7d0a45a20da69f8163bae2e43966e091e33a4f19 Mon Sep 17 00:00:00 2001 From: hjhan Date: Mon, 23 Mar 2026 18:00:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(risk):=20=E4=BF=AE=E5=A4=8D=E9=99=90?= =?UTF-8?q?=E9=A2=9D=E8=AF=95=E7=AE=97=E6=9C=8D=E5=8A=A1=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E7=A9=BA=E5=80=BC=E6=A3=80=E6=9F=A5=E5=92=8C=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E5=A4=84=E7=90=86=20-=20=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E5=AF=B9=E8=B1=A1=E5=AD=98=E5=9C=A8=E6=80=A7=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=EF=BC=8C=E9=81=BF=E5=85=8D=E7=A9=BA=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=20-=20=E5=A2=9E=E5=8A=A0=E4=BA=86TradeType?= =?UTF-8?q?=E3=80=81TradeDate=E7=AD=89=E5=85=B3=E9=94=AE=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=9A=84=E7=A9=BA=E5=80=BC=E9=AA=8C=E8=AF=81=20-=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=86=E6=A0=87=E7=9A=84=E5=AF=B9=E8=B1=A1=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA=E6=97=B6=E7=9A=84=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= =?UTF-8?q?=20-=20=E5=AE=9E=E7=8E=B0=E4=BA=86ExJson=E5=8F=8D=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E7=9A=84=E5=BC=82=E5=B8=B8=E6=8D=95=E8=8E=B7?= =?UTF-8?q?=E5=92=8C=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=20-=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BA=86CheckFund=E6=96=B9=E6=B3=95=E4=B8=AD=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E5=88=97=E8=A1=A8=E7=9A=84=E7=A9=BA=E5=80=BC=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=20-=20=E6=B7=BB=E5=8A=A0=E4=BA=86ExerciseDate?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E7=9A=84=E9=AA=8C=E8=AF=81=E9=80=BB=E8=BE=91?= =?UTF-8?q?=20-=20=E5=A2=9E=E5=8A=A0=E4=BA=86=E8=AF=A6=E7=BB=86=E7=9A=84?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=E7=94=A8=E4=BA=8E=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E8=BF=BD=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/RiskModule/QuotaMonitorService.cs | 94 ++++++++++++++++++- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs index 897cee20..706a5423 100644 --- a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs +++ b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs @@ -1,4 +1,4 @@ -using BaseOUDAL; +using BaseOUDAL; using ClosedXML.Report.Utils; using Confluent.Kafka; using CsvHelper; @@ -4721,8 +4721,26 @@ namespace YLErp.Modules.RiskModule /// public QuotaTrial QuotaCheck(int tradeId, int trialSource) { + _logger.Info($"[限额试算] 开始执行 - tradeId: {tradeId}, trialSource: {trialSource}"); var result = new QuotaTrial(); var tradeObj = DbContext.trade.AsNoTracking().Where(O => O.id == tradeId).FirstOrDefault(); + + // 检查交易对象是否存在 + if (tradeObj == null) + { + _logger.Error($"[限额试算] 错误: 找不到交易记录 - tradeId: {tradeId}"); + throw new ArgumentNullException(nameof(tradeObj), $"找不到ID为 {tradeId} 的交易记录"); + } + + _logger.Info($"[限额试算] 交易信息 - tradeId: {tradeObj.id}, TradeNumber: {tradeObj.TradeNumber}, TradeType: {tradeObj.TradeType}, ClientId: {tradeObj.ClientId}"); + + // 检查关键字段是否为null + if (string.IsNullOrEmpty(tradeObj.TradeType)) + { + _logger.Error($"[限额试算] 错误: TradeType 为 null 或空 - tradeId: {tradeId}"); + throw new ArgumentNullException(nameof(tradeObj.TradeType), "交易类型不能为空"); + } + var tradeList = new List(); if (tradeObj.TradeType == "结构化交易") { @@ -4732,6 +4750,15 @@ namespace YLErp.Modules.RiskModule { tradeList.Add(tradeObj); } + // 检查关键字段 + if (!tradeObj.TradeDate.HasValue) + { + _logger.Error($"[限额试算] 错误: TradeDate 为 null - tradeId: {tradeId}"); + throw new ArgumentNullException(nameof(tradeObj.TradeDate), "交易日期不能为空"); + } + + _logger.Info($"[限额试算] 检查持仓 - positions.Count: {positions?.Count ?? 0}"); + var fundStatus = CheckFund(tradeList, tradeObj.StockEqvNotional, tradeObj.ClientId, out var fundMsg, out var availableMsg); //var riskWarningStatus = CheckRiskWarning(tradeList, out var riskWarningMsg); string riskWarningMsg = string.Empty; @@ -4763,6 +4790,8 @@ namespace YLErp.Modules.RiskModule } List clientRiskCheckResps = new List(); var floatPosi = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && x.PosiQuantity > 0 && x.IsInitial && !x.Invalid).FirstOrDefault(); + _logger.Info($"[限额试算] floatPosi: {(floatPosi == null ? "null" : $"id={floatPosi.id}, UnderlyingCode={floatPosi.UnderlyingCode}")}"); + if (floatPosi != null) { using var bondDb = new BondOmsDBContext(); @@ -4781,15 +4810,28 @@ namespace YLErp.Modules.RiskModule var umCodes = clientPositions.Select(s => s.security_id).Distinct().ToList(); umCodes.Add(floatPosi.UnderlyingCode); umCodes = umCodes.Distinct().ToList(); + _logger.Info($"[限额试算] umCodes.Count: {umCodes?.Count ?? 0}"); + var ums = DataCacheProvider.GetUnderlyingDataSource().AsQueryable().Where(x => umCodes.Contains(x.UnderlyingCode)); + _logger.Info($"[限额试算] ums.Count: {ums?.Count() ?? 0}"); + List checkPoisiList = new List(); var posiList = GetPosiQuotaMoitors(); + _logger.Info($"[限额试算] posiList.Count: {posiList?.Count ?? 0}"); foreach (var item in clientPositions) { var dealDate = tradeObj.TradeDate.Value; decimal vobp = 0; double lastPrice = 0; var um = ums.FirstOrDefault(x => x.UnderlyingCode == item.security_id); + + _logger.Info($"[限额试算] 处理持仓 - security_id: {item.security_id}, um: {(um == null ? "null" : $"id={um.id}, UnderlyingCode={um.UnderlyingCode}")}"); + + if (um == null) + { + _logger.Error($"[限额试算] 错误: 找不到标的 - security_id: {item.security_id}"); + continue; + } CheckQuotaMoitorModel checkQuotaMoitorModel = new CheckQuotaMoitorModel(); checkQuotaMoitorModel.Qty = (item.position_qty ?? 0) * 10000; checkQuotaMoitorModel.id = item.id; @@ -4818,8 +4860,34 @@ namespace YLErp.Modules.RiskModule var bondPrice = EodPriceQueryService.GetBondPrice(dealDate, item.security_id); lastPrice = bondPrice != null ? bondPrice.ClosePrice : (um.Price ?? 0) * Convert.ToDouble(ConsGlobal.bondPriceMultiple); vobp = bondPrice != null ? bondPrice.Vobp ?? 0 : 0; - var bond = JsonHelper.Deserialize(um.ExJson); - checkQuotaMoitorModel.Circulation = (bond.IssueSize * 100000000m) ?? 0; + + // 检查 ExJson 是否为 null 或空 + if (string.IsNullOrEmpty(um.ExJson)) + { + _logger.Error($"[限额试算] 错误: um.ExJson 为 null 或空 - um.id: {um.id}, UnderlyingCode: {um.UnderlyingCode}"); + checkQuotaMoitorModel.Circulation = 0; + } + else + { + try + { + var bond = JsonHelper.Deserialize(um.ExJson); + if (bond == null) + { + _logger.Error($"[限额试算] 错误: 反序列化 bond 失败 - um.id: {um.id}, ExJson: {um.ExJson.Substring(0, Math.Min(100, um.ExJson.Length))}"); + checkQuotaMoitorModel.Circulation = 0; + } + else + { + checkQuotaMoitorModel.Circulation = (bond.IssueSize * 100000000m) ?? 0; + } + } + catch (Exception ex) + { + _logger.Error($"[限额试算] 错误: 反序列化 bond 异常 - um.id: {um.id}", ex); + checkQuotaMoitorModel.Circulation = 0; + } + } } } var ratio = checkQuotaMoitorModel.Side == 0 ? 1 : -1; @@ -4859,6 +4927,7 @@ namespace YLErp.Modules.RiskModule result.QuotaWarningDetails = quotaWarningMsg; result.AvailableForClient = availableMsg; result.TrialSource = trialSource; + _logger.Info($"[限额试算] 试算完成 - tradeId: {tradeId}, TrialStatus: {result.TrialStatus}, FundCheck: {fundStatus}, QuotaCheck: {quotaStatus}"); SaveQuotaTrial(result); return result; } @@ -4960,11 +5029,20 @@ namespace YLErp.Modules.RiskModule private bool CheckFund(List trades, double stockEqvNotional, int clientId, out string msg, out string avmsg) { - + _logger.Info($"[CheckFund] 开始资金检查 - clientId: {clientId}, stockEqvNotional: {stockEqvNotional}, trades.Count: {trades?.Count ?? 0}"); + msg = ""; avmsg = ""; var totalCredit = 0.0; var availableStockEqvNotional = 0.0; + + if (trades == null || trades.Count == 0) + { + _logger.Error("[CheckFund] 错误: trades 为 null 或空列表"); + msg = "交易列表为空"; + return false; + } + var client = DataCacheProvider.GetClientDataSource().GetData(clientId); if (client == null) { @@ -5020,6 +5098,14 @@ namespace YLErp.Modules.RiskModule var clientBalance = clientBalances[0]; if (tradePrice <= 0) { + // 检查 ExerciseDate 是否为 null + if (!trades[0].ExerciseDate.HasValue) + { + _logger.Error($"[CheckFund] 错误: trades[0].ExerciseDate 为 null - tradeId: {trades[0].id}"); + msg = "交易行权日期不能为空"; + return false; + } + if (trades[0].ExerciseDate.Value.Date >= valuedateBLL.ValueDate.Date) { var margins = margin + tradePrice;