diff --git a/YLErpDAL/Modules/RiskEngine/Helper/RiskMarketDeviationHelper.cs b/YLErpDAL/Modules/RiskEngine/Helper/RiskMarketDeviationHelper.cs deleted file mode 100644 index 0af2c96f..00000000 --- a/YLErpDAL/Modules/RiskEngine/Helper/RiskMarketDeviationHelper.cs +++ /dev/null @@ -1,340 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using YLErp.BLL; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 风控行情偏离类变量辅助方法,统一封装债券中债估值偏离和非债券行情价格偏离的取数、计算和命中明细生成逻辑。 - /// - public static class RiskMarketDeviationHelper - { - /// - /// 计算当前交易所有浮动支付端的债券类净价偏离值,返回最大偏离值用于规则比较。 - /// - /// 数据库上下文。 - /// 当前交易ID。 - /// 包含最大净价偏离值和逐笔偏离明细的变量返回值。 - public static RiskVariableValueDetail GetBondNetPriceDeviation(YLContext dbContext, int tradeId) - { - return GetBondValuationDeviation( - dbContext, - tradeId, - "债券类净价偏离", - "期初交割净价", - "中债估值净价", - p => p.PosiNetNoFeePrice, - v => v.net_price); - } - - /// - /// 计算当前交易所有浮动支付端的债券类收益率偏离值,返回最大偏离值用于规则比较。 - /// - /// 数据库上下文。 - /// 当前交易ID。 - /// 包含最大收益率偏离值和逐笔偏离明细的变量返回值。 - public static RiskVariableValueDetail GetBondYieldDeviation(YLContext dbContext, int tradeId) - { - return GetBondValuationDeviation( - dbContext, - tradeId, - "债券类收益率偏离", - "期初成交收益率", - "中债估值收益率", - p => p.InitYtm, - v => v.yield); - } - - /// - /// 计算当前交易所有浮动支付端的非债券类价格偏离值,返回最大偏离值用于规则比较。 - /// - /// 数据库上下文。 - /// 当前交易ID。 - /// 包含最大价格偏离值和逐笔偏离明细的变量返回值。 - public static RiskVariableValueDetail GetNonBondPriceDeviation(YLContext dbContext, int tradeId) - { - if (dbContext == null) - throw new ArgumentNullException(nameof(dbContext)); - - var tradeDate = GetTradeDate(dbContext, tradeId); - var floatingPositions = GetFloatingPaymentPositions(dbContext, tradeId) - .Select(p => new - { - p.id, - p.UnderlyingCode, - p.PosiGrossPrice - }) - .ToList(); - - if (!floatingPositions.Any()) - throw new Exception("浮动支付端记录不存在"); - - var underlyingCodes = floatingPositions - .Select(p => p.UnderlyingCode) - .Distinct() - .ToList(); - - // 非债券类取交易日前最近一条行情,不使用银行间日历,也不要求行情日等于上一银行间交易日。 - var eodRows = dbContext.eod_commodity_future_price - .Where(e => underlyingCodes.Contains(e.UnderlyingCode) - && e.ValueDate < tradeDate) - .Select(e => new - { - e.id, - e.UnderlyingCode, - e.ValueDate, - e.ClosePrice - }) - .ToList(); - - // 先按标的批量查出行情,再在内存中分组取最近日,避免每条浮动支付端单独访问数据库。 - var eodByUnderlyingCode = eodRows - .GroupBy(e => e.UnderlyingCode) - .ToDictionary( - g => g.Key, - g => g.OrderByDescending(e => e.ValueDate).ThenBy(e => e.id).First()); - - var valuationItems = floatingPositions - .Select(p => new - { - Position = p, - Eod = eodByUnderlyingCode.ContainsKey(p.UnderlyingCode) ? eodByUnderlyingCode[p.UnderlyingCode] : null - }) - .ToList(); - - var missingEodItems = valuationItems - .Where(x => x.Eod == null) - .Select(x => $"记录ID {x.Position.id},标的{x.Position.UnderlyingCode}") - .ToList(); - - if (missingEodItems.Any()) - throw new Exception($"未找到交易日前行情收盘价:" + string.Join(";", missingEodItems)); - - var diffItems = valuationItems - .Select(x => new - { - PositionId = x.Position.id, - UnderlyingCode = x.Position.UnderlyingCode, - PositionPrice = x.Position.PosiGrossPrice * 100m, - MarketDate = x.Eod.ValueDate, - MarketPrice = Convert.ToDecimal(x.Eod.ClosePrice), - DiffAbs = Math.Abs(x.Position.PosiGrossPrice * 100m - Convert.ToDecimal(x.Eod.ClosePrice)) - }) - .ToList(); - - return BuildDeviationDetail( - diffItems.Select(x => new DeviationItem - { - PositionId = x.PositionId, - UnderlyingCode = x.UnderlyingCode, - PositionValue = x.PositionPrice, - MarketDate = x.MarketDate, - MarketValue = x.MarketPrice, - DiffAbs = x.DiffAbs - }).ToList(), - "非债券类价格偏离", - "期初标的价格", - "上一行情收盘价"); - } - - /// - /// 债券类中债估值偏离的公共计算入口,净价偏离和收益率偏离仅通过字段选择器区分取值字段。 - /// - /// 数据库上下文。 - /// 当前交易ID。 - /// 偏离规则名称,用于生成命中明细。 - /// 交易侧取值名称,用于生成命中明细。 - /// 市场估值取值名称,用于生成命中明细。 - /// 交易侧字段选择器。 - /// 中债估值字段选择器。 - /// 包含最大偏离值和逐笔偏离明细的变量返回值。 - private static RiskVariableValueDetail GetBondValuationDeviation( - YLContext dbContext, - int tradeId, - string deviationName, - string positionValueName, - string marketValueName, - Func positionValueSelector, - Func valuationValueSelector) - { - if (dbContext == null) - throw new ArgumentNullException(nameof(dbContext)); - - var tradeDate = GetTradeDate(dbContext, tradeId); - var previousTradingDay = RiskCalendarHelper.GetPreviousInterbankTradingDay(dbContext, tradeDate); - var nextTradingDate = previousTradingDay.AddDays(1); - var floatingPositions = GetFloatingPaymentPositions(dbContext, tradeId).ToList(); - - if (!floatingPositions.Any()) - throw new Exception("浮动支付端记录不存在"); - - var positionItems = floatingPositions - .Select(p => new - { - p.id, - p.UnderlyingCode, - PositionValue = positionValueSelector(p) - }) - .ToList(); - - var missingPositionValueIds = positionItems - .Where(p => !p.PositionValue.HasValue) - .Select(p => p.id.ToString()) - .ToList(); - - if (missingPositionValueIds.Any()) - throw new Exception($"浮动支付端{positionValueName}为空,记录ID:" + string.Join("、", missingPositionValueIds)); - - var underlyingCodes = positionItems - .Select(p => p.UnderlyingCode) - .Distinct() - .ToList(); - - // 债券类必须严格匹配上一银行间交易日当天的中债估值,不能简单取交易日前最近估值日。 - var valuationRows = dbContext.china_bond_valuation - .Where(v => underlyingCodes.Contains(v.bond_id) - && v.valuation_date >= previousTradingDay - && v.valuation_date < nextTradingDate) - .ToList() - .Select(v => new - { - v.id, - v.bond_id, - v.valuation_date, - v.credibility, - ValuationValue = valuationValueSelector(v) - }) - .Where(v => v.ValuationValue.HasValue) - .ToList(); - - // 同一标的同一估值日可能有多条来源,按可信度优先,ID兜底稳定排序。 - var valuationByBondId = valuationRows - .GroupBy(v => v.bond_id) - .ToDictionary( - g => g.Key, - g => g.OrderBy(v => v.credibility).ThenBy(v => v.id).First()); - - var valuationItems = positionItems - .Select(p => new - { - Position = p, - Valuation = valuationByBondId.ContainsKey(p.UnderlyingCode) ? valuationByBondId[p.UnderlyingCode] : null - }) - .ToList(); - - var missingValuationItems = valuationItems - .Where(x => x.Valuation == null) - .Select(x => $"记录ID {x.Position.id},标的{x.Position.UnderlyingCode}") - .ToList(); - - if (missingValuationItems.Any()) - throw new Exception($"未找到上一银行间交易日{previousTradingDay:yyyy-MM-dd}的{marketValueName}:" + string.Join(";", missingValuationItems)); - - var diffItems = valuationItems - .Select(x => new DeviationItem - { - PositionId = x.Position.id, - UnderlyingCode = x.Position.UnderlyingCode, - PositionValue = x.Position.PositionValue.Value * 100m, - MarketDate = x.Valuation.valuation_date, - MarketValue = x.Valuation.ValuationValue.Value, - DiffAbs = Math.Abs(x.Position.PositionValue.Value * 100m - x.Valuation.ValuationValue.Value) - }) - .ToList(); - - return BuildDeviationDetail(diffItems, deviationName, positionValueName, marketValueName); - } - - /// - /// 获取当前交易的交易日,所有行情偏离规则都以交易日作为市场数据取数基准。 - /// - /// 数据库上下文。 - /// 当前交易ID。 - /// 交易日日期部分。 - private static DateTime GetTradeDate(YLContext dbContext, int tradeId) - { - var tradeDate = dbContext.trade - .Where(t => t.id == tradeId) - .Select(t => t.TradeDate) - .FirstOrDefault(); - - if (!tradeDate.HasValue) - throw new Exception("交易日为空"); - - return tradeDate.Value.Date; - } - - /// - /// 获取当前交易下全部浮动支付端记录,行情偏离类规则需要遍历同一TradeId下所有浮动支付端。 - /// - /// 数据库上下文。 - /// 当前交易ID。 - /// 浮动支付端记录查询对象。 - private static IQueryable GetFloatingPaymentPositions(YLContext dbContext, int tradeId) - { - return dbContext.swap_position - .Where(p => p.SwapTradeId == tradeId - && p.IsInitial - && !p.Invalid - && p.PosiDirection == 2 - && !string.IsNullOrEmpty(p.UnderlyingCode)); - } - - /// - /// 统一生成行情偏离类变量返回值,变量值取最大偏离值,命中说明保留逐笔偏离明细。 - /// - /// 逐笔偏离结果。 - /// 偏离规则名称。 - /// 交易侧取值名称。 - /// 市场侧取值名称。 - /// 包含最大偏离值和逐笔偏离明细的变量返回值。 - private static RiskVariableValueDetail BuildDeviationDetail( - List diffItems, - string deviationName, - string positionValueName, - string marketValueName) - { - var maxDiffItem = diffItems - .OrderByDescending(x => x.DiffAbs) - .ThenBy(x => x.PositionId) - .First(); - - var deviatedItems = diffItems - .Where(x => x.DiffAbs > 0m) - .OrderByDescending(x => x.DiffAbs) - .ThenBy(x => x.PositionId) - .Select(x => $"记录ID {x.PositionId},标的{x.UnderlyingCode}:{positionValueName}{FormatDecimal(x.PositionValue)},{x.MarketDate:yyyy-MM-dd}{marketValueName}{FormatDecimal(x.MarketValue)},偏离{FormatDecimal(x.DiffAbs)}") - .ToList(); - - string diffMessage = deviatedItems.Any() - ? $"存在{deviationName}的浮动支付端记录:" + string.Join(";", deviatedItems) - : $"未发现{deviationName}记录"; - - return new RiskVariableValueDetail(maxDiffItem.DiffAbs, diffMessage); - } - - /// - /// 格式化风控命中说明中的数值,避免展示过长小数。 - /// - /// 待格式化数值。 - /// 最多9位小数的展示文本。 - private static string FormatDecimal(decimal value) - { - return value.ToString("0.#########"); - } - - /// - /// 行情偏离计算的中间结果模型,用于把债券和非债券两类计算结果统一交给明细构建逻辑。 - /// - private class DeviationItem - { - public long PositionId { get; set; } - public string UnderlyingCode { get; set; } - public decimal PositionValue { get; set; } - public DateTime MarketDate { get; set; } - public decimal MarketValue { get; set; } - public decimal DiffAbs { get; set; } - } - } -}