feat:删除标的利率函数库,通用性太低。
This commit is contained in:
@@ -1,340 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using YLErp.BLL;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控行情偏离类变量辅助方法,统一封装债券中债估值偏离和非债券行情价格偏离的取数、计算和命中明细生成逻辑。
|
||||
/// </summary>
|
||||
public static class RiskMarketDeviationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算当前交易所有浮动支付端的债券类净价偏离值,返回最大偏离值用于规则比较。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>包含最大净价偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
public static RiskVariableValueDetail GetBondNetPriceDeviation(YLContext dbContext, int tradeId)
|
||||
{
|
||||
return GetBondValuationDeviation(
|
||||
dbContext,
|
||||
tradeId,
|
||||
"债券类净价偏离",
|
||||
"期初交割净价",
|
||||
"中债估值净价",
|
||||
p => p.PosiNetNoFeePrice,
|
||||
v => v.net_price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前交易所有浮动支付端的债券类收益率偏离值,返回最大偏离值用于规则比较。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>包含最大收益率偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
public static RiskVariableValueDetail GetBondYieldDeviation(YLContext dbContext, int tradeId)
|
||||
{
|
||||
return GetBondValuationDeviation(
|
||||
dbContext,
|
||||
tradeId,
|
||||
"债券类收益率偏离",
|
||||
"期初成交收益率",
|
||||
"中债估值收益率",
|
||||
p => p.InitYtm,
|
||||
v => v.yield);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前交易所有浮动支付端的非债券类价格偏离值,返回最大偏离值用于规则比较。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>包含最大价格偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
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(),
|
||||
"非债券类价格偏离",
|
||||
"期初标的价格",
|
||||
"上一行情收盘价");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 债券类中债估值偏离的公共计算入口,净价偏离和收益率偏离仅通过字段选择器区分取值字段。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <param name="deviationName">偏离规则名称,用于生成命中明细。</param>
|
||||
/// <param name="positionValueName">交易侧取值名称,用于生成命中明细。</param>
|
||||
/// <param name="marketValueName">市场估值取值名称,用于生成命中明细。</param>
|
||||
/// <param name="positionValueSelector">交易侧字段选择器。</param>
|
||||
/// <param name="valuationValueSelector">中债估值字段选择器。</param>
|
||||
/// <returns>包含最大偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
private static RiskVariableValueDetail GetBondValuationDeviation(
|
||||
YLContext dbContext,
|
||||
int tradeId,
|
||||
string deviationName,
|
||||
string positionValueName,
|
||||
string marketValueName,
|
||||
Func<YLErp.DBModels.swap_position, decimal?> positionValueSelector,
|
||||
Func<YLErp.DBModels.ChinaBondValuation, decimal?> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前交易的交易日,所有行情偏离规则都以交易日作为市场数据取数基准。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>交易日日期部分。</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前交易下全部浮动支付端记录,行情偏离类规则需要遍历同一TradeId下所有浮动支付端。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>浮动支付端记录查询对象。</returns>
|
||||
private static IQueryable<YLErp.DBModels.swap_position> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一生成行情偏离类变量返回值,变量值取最大偏离值,命中说明保留逐笔偏离明细。
|
||||
/// </summary>
|
||||
/// <param name="diffItems">逐笔偏离结果。</param>
|
||||
/// <param name="deviationName">偏离规则名称。</param>
|
||||
/// <param name="positionValueName">交易侧取值名称。</param>
|
||||
/// <param name="marketValueName">市场侧取值名称。</param>
|
||||
/// <returns>包含最大偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
private static RiskVariableValueDetail BuildDeviationDetail(
|
||||
List<DeviationItem> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化风控命中说明中的数值,避免展示过长小数。
|
||||
/// </summary>
|
||||
/// <param name="value">待格式化数值。</param>
|
||||
/// <returns>最多9位小数的展示文本。</returns>
|
||||
private static string FormatDecimal(decimal value)
|
||||
{
|
||||
return value.ToString("0.#########");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 行情偏离计算的中间结果模型,用于把债券和非债券两类计算结果统一交给明细构建逻辑。
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user