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; }
- }
- }
-}