Files
zszq-trs/YLErpDAL/Modules/CalculationModule/SimpleOtcTradeRiskCalc.cs
T

605 lines
26 KiB
C#

using Org.BouncyCastle.Ocsp;
using Qdp.Pricing.Base.Implementations;
using YLErp.Abstract;
using YLErp.Abstract.DataProviders;
using YLErp.BLL;
using YLErp.Configuration;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
using YLErp.Enums;
using YLErp.Model;
using YLErp.Modules.ApiModule;
using YLErp.Modules.DataProviderModule;
using YLErp.Modules.VolatilityModule;
using YLErp.QdpModule;
namespace YLErp.Modules.CalculationModule
{
//这个类的主要目标是代替CalculatorHelper.CalculateRisksForTrades
//并且不再支持日终结算的计算,不再支持场内期权
/// <summary>
/// 简单场外衍生品交易风险计算
/// </summary>
class SimpleOtcTradeRiskCalc
{
/// <summary>
/// 波动率类型,默认:交易
/// </summary>
public string VolType { get; set; } = "交易";
public SettlementTypeEnum SettlementType { get; set; } = SettlementTypeEnum.ClosePrice;
/// <summary>
/// 覆盖交易波动率
/// </summary>
public Dictionary<int, double> OverrideVolsForTrade { get; set; }
/// <summary>
/// 是否使用交易波动率,默认false
/// </summary>
public bool IsUseTradeVol { get; set; }
/// <summary>
/// 精确时间模式,默认true
/// </summary>
public bool PreciseTimeMode { get; set; } = true;
/// <summary>
/// 是否增加波动率为百分比格式
/// </summary>
public bool IsAddVolPercent { get; set; } = true;
/// <summary>
/// 增加波动率
/// </summary>
public Dictionary<int, double> AddVolRateDic { get; set; }
/// <summary>
/// 计算指标枚举,默认只计算PV
/// </summary>
public PricingRequest PricingRequest { get; set; } = PricingRequest.Pv;
/// <summary>
/// 是否在进行预付金计算
/// </summary>
public bool IsMarginCalc { get; set; } = false;
/// <summary>
/// 计算场景
/// </summary>
public CalcScenarioEnum CalcScenario { get; set; }
/// <summary>
/// 是否使用手动维护的风险值
/// </summary>
public bool CanUseManual { get; set; } = false;
DateTime _valueDate;
IPriceProvider _priceProvider;
List<string> _errorList;
readonly UnderlyingDataProvider _umProvider;
public SimpleOtcTradeRiskCalc()
{
_umProvider = new UnderlyingDataProvider();
}
bool IsEodSettle => CalcScenario == CalcScenarioEnum.EodSettlement;
bool IsInitialMargin => CalcScenario == CalcScenarioEnum.InitialMargin;
private void AppendError(trade td, string errorMsg, Exception ex = null)
{
if (ex != null)
{
errorMsg = string.IsNullOrEmpty(errorMsg) ? ex.Messages() : errorMsg + "," + ex.Messages();
}
errorMsg = $"[{td.TradeType},交易编号:{td.TradeNumber}]计算出错:{errorMsg}";
if (IsEodSettle)
{
throw new Exception(errorMsg, ex);
}
if (_errorList == null)
{
_errorList = new List<string>();
}
_errorList.Add(errorMsg);
}
public TradeRiskResult CalculateRisksForTrades(DateTime valueDate, IEnumerable<trade> tradeList, IPriceProvider priceProvider, CalcScenarioEnum calcScenario)
{
var results = new List<TradeRiskResultRecord>();
var trResult = new TradeRiskResult { Results = results };
if (tradeList == null || !tradeList.Any())
{
return trResult;
}
if (IsEodSettle)
{
PreciseTimeMode = false;
}
_valueDate = valueDate;
_priceProvider = priceProvider ?? throw new ArgumentNullException(nameof(priceProvider));
var sysRiskFreeRate = (VolType == "光证" ? valuedateBLL.RiskFreeRateExtend : valuedateBLL.RiskFreeRate) / 100;
var recordlist = DbContextFactory.GetYLDbContext().dividendrate_record.Where(x => valueDate >= x.ValueDate).ToList();
using (var marketProxy = new MarketProxy(_valueDate, sysRiskFreeRate))
{
using var db = DbContextFactory.GetYLDbContext();
var tradeIds = tradeList.Select(x => x.id).ToArray();
var manualDicByTypes = db.eod_trade_risk_manual.Where(x => x.ValueDate == _valueDate && tradeIds.Contains(x.TradeId) && x.VolType == VolType && x.SettlementType == SettlementType).ToDictionary(x => x.TradeId);
var manualDic = db.eod_trade_risk_manual.Where(x => x.ValueDate == _valueDate && tradeIds.Contains(x.TradeId) && string.IsNullOrEmpty(x.VolType)).ToDictionary(x => x.TradeId);
foreach (var td in tradeList)
{
if (VolType == "光证")
{
td.NoRiskRate = sysRiskFreeRate;
}
//国元固收默认从全局配置里取分红率,如果TradeHisData里有分红率,则根据生效时间取最优
if (recordlist != null && recordlist.Any())
{
var _record = recordlist.Where(x => x.UnderlyingCode.Split(',').Any(code => code == td.UnderlyingCode) && x.TradeType.Contains(td.TradeType) && (x.OptionType == td.OptionType || x.OptionType == "全部")).OrderByDescending(x => x.OptDate).OrderByDescending(x => x.ValueDate);
if (_record.Any())
{
td.DividendRate = _record.FirstOrDefault()?.DividendRate;
}
}
td.DividendRate = td.DividendRate ?? td.NoRiskRate ?? sysRiskFreeRate;
TradeRiskResultRecord record;
if (IsMarginCalc && td.TradeType == "自定义交易")
{
record = new TradeRiskResultRecord
{
Trade = td,
Underlyings = new[] { DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode) },
ValueResult = new TradeValueResult() //自定义交易的预付金不需要在这里处理
};
if (PS.Config.ErpElement.ExternalAPIForCustomCalcEnable)
{
td.StartDate = _valueDate;
var tradeVol = IsUseTradeVol;
IsUseTradeVol = false;
var price = _priceProvider.GetPrice(td.UnderlyingCode);
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
var consVol = GetConstVol(td, price, IsEodSettle, um?.UnderlyingTypeId ?? 0);
//从风险对冲那边抄来的逻辑,波动率精度会导致计算结果和风险对冲有差异;
var volValue = Commons.OtcFormatHelper.FormatValue(consVol, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4);
var apiValue = TradeCalcApiHelper.CalculateCustomizedTrade(_valueDate, td, price, volValue, CalcScenario == CalcScenarioEnum.EodSettlement, calcScenario, TradeCalcApiHelper.PV, TradeCalcApiHelper.DELTA, TradeCalcApiHelper.GAMMA, TradeCalcApiHelper.VEGA, TradeCalcApiHelper.THETA, TradeCalcApiHelper.RHO);
if (apiValue.Success)
{
record.ValueResult = apiValue.Content;
}
//异常
else
{
throw new Exception(apiValue.Msg);
}
}
}
else if (td.TradeType != "场内期权" && ConsTrade.TradeTypesForHedge.Contains(td.TradeType))
{
record = new TradeRiskResultRecord
{
Trade = td,
Underlyings = new[] { DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode) },
ValueResult = new TradeValueResult()
{
Pv = td.Notional * _priceProvider.GetPrice(td.UnderlyingCode),
Delta = td.Notional
}
};
}
else
{
//分类维护的风险数据优先级高于普通的风险数据
manualDicByTypes.TryGetValue(td.id, out var manual);
if (manual == null)
{
manualDic.TryGetValue(td.id, out manual);
}
else
{
//如果分类维护的数据不全,通过单一数据补充
manualDic.TryGetValue(td.id, out var singleManual);
if (singleManual != null)
{
manual.Pv = manual.Pv ?? singleManual.Pv;
manual.Delta = manual.Delta ?? singleManual.Delta;
manual.DeltaCash = manual.DeltaCash ?? singleManual.DeltaCash;
manual.Gamma = manual.Gamma ?? singleManual.Gamma;
manual.GammaCash = manual.GammaCash ?? singleManual.GammaCash;
manual.Theta = manual.Theta ?? singleManual.Theta;
manual.Vega = manual.Vega ?? singleManual.Vega;
manual.VegaCash = manual.VegaCash ?? singleManual.VegaCash;
manual.Rho = manual.Rho ?? singleManual.Rho;
}
}
record = CalcOtcTrade(marketProxy, td, CanUseManual ? manual : null);
}
if (record != null)
{
if (PS.Config.ErpElement.IsPVIncludePrincipal)
{
record.ValueResult.Pv = record.ValueResult.Pv + td.PrincipalSum() * (td.BuySell == "卖出" ? -1 : 1);
record.ValueResult.RoundedPv = record.ValueResult.RoundedPv + td.PrincipalSum() * (td.BuySell == "卖出" ? -1 : 1);
}
results.Add(record);
}
}
}
return trResult;
}
//计算衍生品交易价值
private TradeRiskResultRecord CalcOtcTrade(MarketProxy mp, trade td, eod_trade_risk_manual manual)
{
var record = new TradeRiskResultRecord { Trade = td, ValueResult = null, Underlyings = null };
try
{
var um = _umProvider.GetUnderlying(td.UnderlyingCode);
record.Underlyings = new[] { um };
if (um == null && td.HasUnderlying())
{
AppendError(td, "未找到标的信息:" + td.UnderlyingCode);
record.ValueResult = GetTradeValueResult(td, "未找到标的信息");
return record;
}
if (ConsTrade.TradeCompleteStatus.Contains(td.TradeStatus))
{
record.ValueResult = GetTradeValueResult(td);
return record;
}
TradeValueResult result = null;
if (manual != null)
{
var spotPrice = GetSpotPrice(td);
manual.DeltaCash = manual.Delta * spotPrice;
manual.GammaCash = manual.Gamma * Math.Pow(spotPrice, 2) / 100;
manual.VegaCash = manual.Vega * spotPrice;
result = new TradeValueResult
{
Pv = manual.Pv ?? 0,
RoundedPv = manual.Pv ?? 0,
Delta = manual.Delta ?? 0,
Gamma = manual.Gamma ?? 0,
Vega = manual.Vega ?? 0,
TradingDayTheta = manual.Theta ?? 0,
CalendarDayTheta = manual.Theta ?? 0,
Rho = manual.Rho ?? 0,
DeltaCash = manual.DeltaCash ?? 0,
GammaCash = manual.GammaCash ?? 0
};
}
//没有自定义风险维护值 或者 自定风险维护没有涵盖PV和所有希腊值(部分维护场景)
//部分维护场景下,需要系统计算出未赋值的属性,进行合并返还
if (manual == null || manual != null && !manual.IsPVAndAllGreek)
{
switch (td.TradeType)
{
case "彩虹期权":
case "价差期权":
case "结构化交易":
AppendError(td, "不支持此交易类型的计算");
result = GetTradeValueResult(td, "不支持此交易类型的计算");
break;
case ConsGlobal.TradeType.Custom:
result = CalcCustom(td, IsEodSettle, um?.UnderlyingTypeId ?? 0);
break;
case ConsGlobal.TradeType.PayoffSwap:
result = PayoffSwapCalcService.CalcValue(td, _valueDate, IsMarginCalc ? null : _priceProvider, IsEodSettle);
break;
case ConsGlobal.TradeType.Forward:
{
var spotPrice = GetSpotPrice(td);
result = ForwardradeCalcService.CalcValue(td, spotPrice);
}
break;
case "信用债":
case "商品期货":
_priceProvider.TryGetPrice(td.UnderlyingCode, out var spotPriceGet);
result = new TradeValueResult()
{
Pv = td.Notional * spotPriceGet,
};
break;
case "场内期权":
default:
result = CalcOption(mp, td, IsEodSettle, IsInitialMargin, um?.UnderlyingTypeId ?? 0); //计算场外期权
break;
}
//系统计算结果和手动维护值合并
TradeRiskCalcUtil.GetOptionValueWithManual(manual, result);
}
else
{
if (td.TradeType != ConsGlobal.TradeType.CashFlow && result.Vol == 0)
{
result.Vol = GetConstVol(td, GetSpotPrice(td), IsEodSettle, um?.UnderlyingTypeId ?? 0);
}
}
record.ValueResult = result;
}
catch (Exception ex)
{
AppendError(td, null, ex);
record.ValueResult = GetTradeValueResult(td, ex.Message);
}
return record;
}
private TradeValueResult GetTradeValueResult(trade td, string errmsg = null)
{
return new TradeValueResult
{
TradeId = td.id,
UnderlyingCode = td.UnderlyingCode,
Strike = td.Strike,
UnderlyingId = td.UnderlyingId,
ErrorMessage = errmsg,
Succeeded = string.IsNullOrEmpty(errmsg)
};
}
//获取标的现价
private double GetSpotPrice(trade td)
{
if (string.IsNullOrWhiteSpace(td.UnderlyingCode))
{
return 0;
}
//20210706:支持参考价处理 -- 远期没有参考价
if (td.SettlementType == SettlementTypeEnum.ReferencePrice
&& _priceProvider.TryGetPrice(ConsGlobal.RefPricePrefix + td.UnderlyingCode, out var price))
{
return price;
}
double BasiseodPrice = 0;
if (PS.Config.ErpElement.ForwardTradePriceModel == Configuration.Enums.ForwardTradePriceModel.STANDARD && !string.IsNullOrWhiteSpace(td.BasisUnderlyingCode) && td.TradeType == ConsGlobal.TradeType.Forward)
{
_priceProvider.TryGetPrice(td.BasisUnderlyingCode, out BasiseodPrice);
}
if (_priceProvider.TryGetPrice(td.UnderlyingCode, out price))
{
return price -= BasiseodPrice;
}
else
{
throw new Exception("未找到标的价格:" + td.UnderlyingCode);
}
}
//计算自定义交易
private TradeValueResult CalcCustom(trade td, bool isEodCalc, int underlyingTypeId)
{
var spotPrice = GetSpotPrice(td);
var volValue = GetConstVol(td, spotPrice, isEodCalc, underlyingTypeId);
//从风险对冲那边抄来的逻辑,波动率精度会导致计算结果和风险对冲有差异;
volValue = Commons.OtcFormatHelper.FormatValue(volValue, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4);
var (manual, optionValue) = TradeRiskCalcUtil.GetManualOptionValue(_valueDate, td, spotPrice, volValue, td.TradeType == "自定义交易", CalcScenario, VolType, SettlementType, isSettle: CalcScenario == CalcScenarioEnum.EodSettlement);
//收盘时如果自定义交易还活着且没有维护当日风险,并且收的时系统日期当日的盘,抛出exception
if (IsEodSettle && !ConsTrade.TradeCompleteStatus.Contains(td.TradeStatus) && _valueDate == valuedateBLL.ValueDate && !PS.Config.IsMustRiskManual)
{
if ((manual == null || manual.ValueDate != _valueDate) && !PS.Config.Is润和)
{
var error = $"[{td.TradeType}] 交易'{td.TradeNumber}'在{_valueDate:yyyy-MM-dd}需先进行交易风险维护";
throw new Exception(error);
}
}
return optionValue;
}
//计算场外期权
private TradeValueResult CalcOption(MarketProxy mp, trade td, bool isEodCalc, bool isInitialMargin, int underlyingTypeId)
{
var spotPrice = GetSpotPrice(td);
var req = new OptionValueCalcRequest(mp.RiskFreeRate)
{
correlations = null, //因为没有处理多标的,所以这里为null
engineName = null,
maturityShift = 0,
preciseTimeMode = PreciseTimeMode,
ParamOverride = null,
pricingRequest = PricingRequest,
spotPrices = new[] { spotPrice },
vols = new[] { 0d },
calcScenario = CalcScenario
};
req.timeToMaturityDays = double.NaN;
//象屿最后一个交易日实时计算时TTM需要和平仓时算法一致
if (PS.Config.Is厦门象屿
&& (CalcScenario == CalcScenarioEnum.RealtimePosition || CalcScenario == CalcScenarioEnum.RealtimeRisk)
&& td.SettlementType == SettlementTypeEnum.ReferencePrice)
{
req.timeToMaturityDays = TradeCalcHelper.CalculateTTMDaysForXiangYu(valuedateBLL.ValueDate, td.ExerciseDate.Value, underlyingTypeId, false);
//20210706:支持厦门象屿参考价相关交易(这类交易不需要支持精确模式)
req.preciseTimeMode = false;
}
if (PS.Config.Is润和 && IsInitialMargin)
{
req.timeToMaturityDays = td.TTMDays;
}
if (td.TradeType != ConsGlobal.TradeType.CashFlow)
{
var consVol = GetConstVol(td, spotPrice, isEodCalc, underlyingTypeId);
//从风险对冲那边抄来的逻辑,波动率精度会导致计算结果和风险对冲有差异;
consVol = Commons.OtcFormatHelper.FormatValue(consVol, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4);
req.vols = new[] { consVol };
if (IsMarginCalc && CalcScenario == CalcScenarioEnum.EodSettlement)
{
LogFactory.GetLogger("日终预付金计算").Info($"{td.TradeType}'{td.TradeNumber}',波动率:{consVol},spotPrice:{spotPrice}");
}
}
req.isEodCalc = IsEodSettle;
return OptionCalculatorV2.GetOptionValueResult(mp, td, req, out _);
}
#region----获取波动率----
/// <summary>
///
/// </summary>
/// <param name="td"></param>
/// <param name="spotPrice"></param>
/// <param name="isEodCalc"></param>
/// <param name="underlyingTypeId">只适用于光证波动率</param>
/// <returns></returns>
private double GetConstVol(trade td, double spotPrice, bool isEodCalc, int underlyingTypeId)
{
var vol = GetConstVolRaw(td, spotPrice, isEodCalc, underlyingTypeId);
if (AddVolRateDic != null && AddVolRateDic.TryGetValue(td.id, out var addVolRate))
{
vol += IsAddVolPercent ? addVolRate * vol : addVolRate;
}
return vol;
}
private double GetConstVolRaw(trade td, double spotPrice, bool isEodCalc, int underlyingTypeId)
{
if (td.TradeType == "场内期权")
{
var vol = new ExOptionSavedVolProvider(_valueDate).GetSavedVol(td.ExchangeOptionCode, _valueDate);
if (vol != null)
{
return vol.Value;
}
}
else
{
if (OverrideVolsForTrade != null && OverrideVolsForTrade.TryGetValue(td.id, out var overrideVol))
{
return overrideVol;
}
if (IsUseTradeVol)
{
if (VolType == "交易" || VolType == "持仓")
{
return VolatilityHelper.GetTradeVol(td, _valueDate, IsEodSettle);
}
if (VolType == "对冲")
{
return TradeHedgeVolService.GetTradeHedgeVol(td, _valueDate);
}
}
}
if (VolType == "光证")
{
var va = DataCacheProvider.GetVarietyDataSource().GetData(underlyingTypeId)
?? throw new Exception("未找到光证波动率:" + td.UnderlyingCode);
return VarietyVolService.GetVarietyVol(_valueDate, va.id)
?? throw new Exception("未找到光证波动率:" + td.UnderlyingCode);
}
if (VolType == "开仓")
{
return td.TradeOpenVolatility ?? 0;
}
var userGroup = string.Empty;
if (ConsUserGroup.HasGroup)
{
userGroup = DataCacheProvider.GetAssetUnitDataSource().GetData(td.AssetId)?.UserGroup;
if (string.IsNullOrEmpty(userGroup))
{
throw new Exception($"取波动率时未能获取UserGroup");
}
}
if (VolType == "BidAskVol")
{
//结算时应以平仓的交易方向来选择曲面
//如果交易是买入,平仓时是卖出,则应该用Ask曲面
var tempVolType = td.BuySell == "买入" ? "报价Ask" : "报价Bid";
var bidAskVol = VolatilityHelper.GetVol(_valueDate, tempVolType, td.UnderlyingCode, userGroup)
?? throw new Exception($"未找到'{tempVolType}'波动率:{td.UnderlyingCode}");
return GetInterpolatedVol(td, bidAskVol, spotPrice, isEodCalc);
}
//当需要去波动率曲面中查询时,所有公司的VolType都应该是交易;
//上面这句描述应该是错误,当曲面波动率模式时,应该用配置的结算波动率,todo
string tempVolType2 = VolType;
if (VolType == "对冲")
{
tempVolType2 = "交易";
}
else if (VolType == "持仓" || string.IsNullOrEmpty(tempVolType2))
{
tempVolType2 = IsUseTradeVol ? "交易" : valuedateBLL.SystemDate.EodSettleVolMode.TrimToNull() ?? "交易";
}
var underlyingVols = VolatilityHelper.GetVol(_valueDate, tempVolType2, td.UnderlyingCode, userGroup)
?? throw new Exception($"未找到'{tempVolType2}'波动率:{td.UnderlyingCode}");
return GetInterpolatedVol(td, underlyingVols, spotPrice, isEodCalc);
}
private double GetInterpolatedVol(trade td, IVolatility vols, double spotPrice, bool isEodCalc)
{
return VolatilityHelper.GetInterpolatedVol(
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
volSurface: vols,
valueDate: _valueDate,
underlyingCode: td.UnderlyingCode,
exerciseDate: td.ExerciseDate.Value,
strike: td.Strike ?? 0,
isBuy: td.BuySell == "买入",
isCall: td.CallPut == "Call",
spotPrice: spotPrice,
isMoneynessOption: td.IsMoneynessOption == "是",
isEodCalc: isEodCalc);
}
#endregion
}
}