fix(risk): 修复限额试算服务中的空值检查和异常处理

- 添加了交易对象存在性检查,避免空引用异常
- 增加了TradeType、TradeDate等关键字段的空值验证
- 添加了标的对象为空时的处理逻辑
- 实现了ExJson反序列化的异常捕获和错误处理
- 增加了CheckFund方法中交易列表的空值检查
- 添加了ExerciseDate为空的验证逻辑
- 增加了详细的日志记录用于调试追踪
This commit is contained in:
hjhan
2026-03-23 18:00:46 +08:00
parent f182b880c1
commit 7d0a45a20d
@@ -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
/// <returns></returns>
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<trade>();
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<ClientRiskCheckItem> clientRiskCheckResps = new List<ClientRiskCheckItem>();
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<CheckQuotaMoitorModel> checkPoisiList = new List<CheckQuotaMoitorModel>();
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<UnderlyingBond>(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<UnderlyingBond>(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<trade> 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;