using YLErp.BLL; using YLErp.Enums; using YLErp.Modules.CalculationModule.Abstract; using YLErp.QdpModule; using YLErp.ThirdParty.CaculatePrice.DongZheng; using YLErp.ThirdParty.CaculatePrice.DongZheng.Dto; namespace YLErp.Modules.CalculationModule { /// /// 只用于计算期权PV /// public class OptionCalculatorV2 { private static readonly IYcLogger log = LogFactory.GetLogger(nameof(OptionCalculatorV2)); /// /// 只用于计算期权PV /// public static TradeValueResult GetOptionValueResult(DateTime valueDate, trade td, OptionValueCalcRequest request, out underlying_manager[] underlyings) { if (td is null) { throw new ArgumentNullException(nameof(td)); } if (request is null) { throw new ArgumentNullException(nameof(request)); } using var marketProxy = new MarketProxy(valueDate, request.sysRiskFreeRate); return GetOptionValueResult(marketProxy, td, request, out underlyings); } /// /// 只用于计算期权PV /// public static TradeValueResult GetOptionValueResult(MarketProxy marketProxy, trade td, OptionValueCalcRequest request, out underlying_manager[] underlyings) { if (marketProxy is null) { throw new ArgumentNullException(nameof(marketProxy)); } if (td is null) { throw new ArgumentNullException(nameof(td)); } if (request is null) { throw new ArgumentNullException(nameof(request)); } if (request.spotPrices == null || !request.spotPrices.Any()) { throw new Exception("期权计算缺少标的现价"); } if (string.IsNullOrEmpty(td.UnderlyingCode) && td.HasUnderlying()) { throw new Exception("缺少标的代码"); } TradeValueResult result; //象屿最后一个交易日实时计算时TTM需要和平仓时算法一致 if (PS.Config.Is厦门象屿 && (request.calcScenario == CalcScenarioEnum.RealtimePosition || request.calcScenario == CalcScenarioEnum.RealtimeRisk) && td.SettlementType == SettlementTypeEnum.ReferencePrice) { var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode); if (underlying != null) { request.timeToMaturityDays = TradeCalcHelper.CalculateTTMDaysForXiangYu(valuedateBLL.ValueDate, td.ExerciseDate.Value, underlying.UnderlyingTypeId, false); //20210706:支持厦门象屿参考价相关交易(这类交易不需要支持精确模式) request.preciseTimeMode = false; } } //东证润和是精确时间模式参与计算,收盘的话,刚好是整数天,所以不需要特殊处理 if (PS.Config.Is润和 && !request.isEodCalc && (request.timeToMaturityDays == null || double.IsNaN(request.timeToMaturityDays.Value))) { request.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, td.ExerciseDate.Value, 0, false); } var tpReq = PrepareCalc(marketProxy, td, request, out underlyings, out var getAsianFixings); switch (td.TradeType) { case "香草期权": { var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(td, tpReq, false); result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "Risky期权": { result = GetOptionRisky(td, request, underlyings[0], tpReq, marketProxy); } break; case "场内期权": { var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(td, tpReq, true); result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "合成价差期权": { var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(td, tpReq); result = TradeRiskCalcUtil.GetSSpreadOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "亚式期权": { if (!PS.Config.Is润和 && !request.isEodCalc && (request.timeToMaturityDays == null || double.IsNaN(request.timeToMaturityDays.Value))) { tpReq.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, td.ExerciseDate.Value, 0, false); } tpReq.fixings = getAsianFixings(); var tradeParam = QdpTradeBuilder.GetAsianOptionTradeParam(td, td.trade_asian_option, tpReq); result = TradeRiskCalcUtil.GetAsianOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "亚式合成价差期权": { tpReq.fixings = getAsianFixings(); var tradeParam = QdpTradeBuilder.GetAsianOptionTradeParam(td, td.trade_asian_option, tpReq); result = TradeRiskCalcUtil.GetAsianSSpreadOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "障碍期权": { var tradeParam = QdpTradeBuilder.GetBarrierOptionTradeParam(td, td.trade_barrier_option, tpReq); result = TradeRiskCalcUtil.GetBarrierOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "二元期权": { var tradeParam = QdpTradeBuilder.GetBinaryOptionTradeParam(td, td.trade_binary_option, tpReq); result = TradeRiskCalcUtil.GetBinaryOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "双鲨期权": { var tradeParam = QdpTradeBuilder.GetDoubleSharkFinOptionTradeParam(td, td.trade_double_sharkfin_option, tpReq); result = TradeRiskCalcUtil.GetDoubleSharkFinOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "凤凰期权": { if (td.trade_autocall == null) { throw new ServiceException("缺少奇异期权数据,交易编号:" + td.TradeNumber); } if (td.trade_autocall.HappenedObservations == null && td.id > 0) { td.trade_autocall.HappenedObservations = ObservationDataService.QueryDatas(td.id, marketProxy.ValueDate); } var tradeParam = QdpTradeBuilder.GetAutocallOptionTradeParam(td, td.trade_autocall, tpReq); try { result = TradeRiskCalcUtil.GetAutocallOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } catch (Exception ex) { if (string.IsNullOrEmpty(td.TradeNumber)) { throw; } throw new Exception($"凤凰期权'{td.TradeNumber}'计算出错:{ex.Message}"); } } break; case "雪球期权": { if (td.IsSnowballSpecialist()) { var snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator(); if (td.trade_snowball.PrepaymentRatio > 0) { var specialSnowballTrade = snowballSpecialistOptionCalculator.GetSpecialTrade(td); var specialSnowballResult = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, request.spotPrices[0], request.vols[0], request.calcScenario, specialSnowballTrade); var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(td); var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(breakevenSnowballTrade, breakevenSnowballTrade.trade_snowball, tpReq); tradeParam.riskFreeRate = breakevenSnowballTrade.NoRiskRate ?? 0; var breakevenSnowballResult = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); result = snowballSpecialistOptionCalculator.MergeTradeValueResult(specialSnowballResult, breakevenSnowballResult); } else { result = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, request.spotPrices[0], request.vols[0], request.calcScenario, td); } } else { // 普通雪球 var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(td, td.trade_snowball, tpReq); result = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } } break; case "区间累积期权": { if (td.trade_rangeaccrual == null) { throw new ServiceException("缺少奇异期权数据,交易编号:" + td.TradeNumber); } if (td.trade_rangeaccrual.HappenedObservations == null && td.id > 0) { td.trade_rangeaccrual.HappenedObservations = ObservationDataService.QueryDatas(td.id, marketProxy.ValueDate, includeValueDate: false); } tpReq.fixings = request.fixings; if (string.IsNullOrWhiteSpace(tpReq.fixings) && (td.trade_rangeaccrual.HappenedObservations == null || td.trade_rangeaccrual.HappenedObservations.Count == 0)) { //非日终时不将当天的价格加入fixing中 var valueDate = request.calcScenario == CalcScenarioEnum.EodMargin || request.calcScenario == CalcScenarioEnum.EodSettlement || request.calcScenario == CalcScenarioEnum.ScenarioCalc ? marketProxy.ValueDate : marketProxy.ValueDate.AddDays(-1); tpReq.fixings = FixingService.GetFixingString(valueDate: valueDate, otcTrade: td, startDate: td.StartDate ?? td.TradeDate.Value, observationDates: td.trade_rangeaccrual.ObservationDates); } var tradeParam = QdpTradeBuilder.GetRangeAccrualTradeParam(td, td.trade_rangeaccrual, tpReq); result = TradeRiskCalcUtil.GetRangeAccrualValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "气囊结构": { var tradeParam = QdpTradeBuilder.GetAirbagOptionTradeParam(td, td.trade_airbag, tpReq); result = TradeRiskCalcUtil.GetAirbagOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "收益增强结构": { var tradeParam = QdpTradeBuilder.GetUnderlyingEnhanceTradeParam(td, td.trade_underlying_enhance, tpReq); result = TradeRiskCalcUtil.GetUnderlyingEnhanceValue(marketProxy, GetOptionCalcParam(tradeParam, request)); } break; case "累计期权": { var tradeParam = QdpTradeBuilder.GetAccumulatorOptionTradeParam(td, td.trade_accumulator_option, tpReq); result = TradeRiskCalcUtil.GetAccumulatorOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request), td.Notional); } break; case "现金流交易": { var tradeParam = QdpTradeBuilder.GetCashFlowTradeParam(td, td.trade_cashflow, tpReq); result = TradeRiskCalcUtil.GetCashFlowValue(marketProxy, GetOptionCalcParam(tradeParam, request), td.StockEqvNotional); } break; case "结构化产品": var structProductParm = QdpTradeBuilder.GetStructProductTradeParam(request, marketProxy, td); StructureResult spResult; log.Info("定价请求信息:" + JsonHelper.Serialize(structProductParm.Request)); if (DongZhengPriceApi.StructureProduct(structProductParm.Request, out spResult, structProductParm.VolSurface)) { result = new TradeValueResult { Pv = spResult.pv, Delta = spResult.delta, Gamma = spResult.gamma, Vega = spResult.vegaPercentage, TradingDayTheta = spResult.thetaPerDay, Rho = spResult.rhoPercentage }; } else { throw new Exception("东证结构化产品定价计算失败" + spResult.message); } log.Info("定价请求结果:" + JsonHelper.Serialize(spResult)); break; default: throw new Exception("不支持这种类型交易的期权计算:" + td.TradeType); } if (result != null) { result.Strike = td.Strike ?? 0; result.SpotPrice = request.spotPrices[0]; result.VegaCash = result.VegaCash.IsNormalize() ? result.VegaCash : (result.Vega * result.SpotPrice).Normalize(); } return result; } private static TradeValueResult GetOptionRisky( trade td, OptionValueCalcRequest request, underlying_manager underlyings, OptionTradeParamRequest tpReq, MarketProxy marketProxy) { var result = new TradeValueResult(); var tradeclone = td.Clone(); tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(td, td.TradeAmount, 1); tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(td, td.Notional, underlyings.CountRatio); var td1 = tradeclone.Clone(); var td2 = tradeclone.Clone(); if (td.trade_risky_option.ParticipationRate2 != 0) { td2.Strike = td.trade_risky_option.Strike2; td2.ParticipationRate = td.trade_risky_option.ParticipationRate2; td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1); td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, underlyings.CountRatio); var tradeParam2 = QdpTradeBuilder.GetVanillaOptionTradeParam(td2, tpReq, false); result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam2, request)); } if (td.trade_risky_option.ParticipationRate1 != 0) { td1.Strike = td.trade_risky_option.Strike1; td1.ParticipationRate = td.trade_risky_option.ParticipationRate1; td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1); td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, underlyings.CountRatio); td1.OptionType = "看跌"; var tradeParam1 = QdpTradeBuilder.GetVanillaOptionTradeParam(td1, tpReq, false); var singleresult1 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam1, request)); result.Pv -= singleresult1.Pv; result.Delta -= singleresult1.Delta; result.Gamma -= singleresult1.Gamma; result.Vega -= singleresult1.Vega; result.CalendarDayTheta -= singleresult1.CalendarDayTheta; result.TradingDayTheta -= singleresult1.TradingDayTheta; result.Rho -= singleresult1.Rho; result.DeltaInLots -= singleresult1.DeltaInLots; result.DeltaCash -= singleresult1.DeltaCash; result.GammaCash -= singleresult1.GammaCash; result.VegaCash -= singleresult1.VegaCash; result.RoundedPv -= singleresult1.RoundedPv; result.DDeltaDVol -= singleresult1.DDeltaDVol; result.DDeltaDt -= singleresult1.DDeltaDt; result.DVegaDVol -= singleresult1.DVegaDVol; result.DVegaDt -= singleresult1.DVegaDt; result.DeltaT1 -= singleresult1.DeltaT1; result.ErrorMessage += singleresult1.ErrorMessage; result.Vol = singleresult1.Vol; result.Succeeded = singleresult1.Succeeded && result.Succeeded; } var td3 = tradeclone.Clone(); td3.Strike = td.trade_risky_option.Strike3; //decimal 为了解决精度问题: 0.2-0.3=0.0999999999 var participationRate3 = (decimal)td.trade_risky_option.ParticipationRate3 - (decimal)td.trade_risky_option.ParticipationRate2; if (participationRate3 != 0) { td3.ParticipationRate = (double?)Math.Abs(participationRate3); td3.TradeAmount = TradeCalcHelper.GetTradeAmount(td3, td3.TradeAmount, 1); td3.Notional = TradeCalcHelper.GetTradeAmount(td3, td3.Notional, underlyings.CountRatio); var tradeParam3 = QdpTradeBuilder.GetVanillaOptionTradeParam(td3, tpReq, false); var singleresult3 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam3, request)); if (participationRate3 < 0) { result.Pv -= singleresult3.Pv; result.Delta -= singleresult3.Delta; result.Gamma -= singleresult3.Gamma; result.Vega -= singleresult3.Vega; result.CalendarDayTheta -= singleresult3.CalendarDayTheta; result.TradingDayTheta -= singleresult3.TradingDayTheta; result.Rho -= singleresult3.Rho; result.DeltaInLots -= singleresult3.DeltaInLots; result.DeltaCash -= singleresult3.DeltaCash; result.GammaCash -= singleresult3.GammaCash; result.VegaCash -= singleresult3.VegaCash; result.RoundedPv -= singleresult3.RoundedPv; result.DDeltaDVol -= singleresult3.DDeltaDVol; result.DDeltaDt -= singleresult3.DDeltaDt; result.DVegaDVol -= singleresult3.DVegaDVol; result.DVegaDt -= singleresult3.DVegaDt; result.DeltaT1 -= singleresult3.DeltaT1; result.ErrorMessage += singleresult3.ErrorMessage; result.Vol = singleresult3.Vol; } else { result.Pv += singleresult3.Pv; result.Delta += singleresult3.Delta; result.Gamma += singleresult3.Gamma; result.Vega += singleresult3.Vega; result.CalendarDayTheta += singleresult3.CalendarDayTheta; result.TradingDayTheta += singleresult3.TradingDayTheta; result.Rho += singleresult3.Rho; result.DeltaInLots += singleresult3.DeltaInLots; result.DeltaCash += singleresult3.DeltaCash; result.GammaCash += singleresult3.GammaCash; result.VegaCash += singleresult3.VegaCash; result.RoundedPv += singleresult3.RoundedPv; result.DDeltaDVol += singleresult3.DDeltaDVol; result.DDeltaDt += singleresult3.DDeltaDt; result.DVegaDVol += singleresult3.DVegaDVol; result.DVegaDt += singleresult3.DVegaDt; result.DeltaT1 += singleresult3.DeltaT1; result.ErrorMessage += singleresult3.ErrorMessage; result.Vol = singleresult3.Vol; } result.Succeeded = singleresult3.Succeeded && result.Succeeded; } return result; } /// /// 获取期权计算参数 /// private static OptionCalcParam GetOptionCalcParam(T tradeParam, OptionValueCalcRequest request) where T : OptionTradeParamBase { return new OptionCalcParam(tradeParam) { engineName = request.engineName, pricingRequest = request.pricingRequest, spotPrices = request.spotPrices, calcScenario = request.calcScenario, quadratureFastMode = request.quadratureFastMode, CalcDeltaT1 = request.calcDeltaT1 }; } /// /// 准备计算 /// private static OptionTradeParamRequest PrepareCalc(MarketProxy marketProxy, trade td , OptionValueCalcRequest request, out underlying_manager[] underlyings, out Func getAsianFixings) { getAsianFixings = new Func(() => { if (!string.IsNullOrEmpty(request.fixings)) { return request.fixings; } var fixings = AsianOptionFixingService.GetFixingString(marketProxy.ValueDate, td); fixings = FixingService.AddOrReplaceLastDateSpotPrice(fixings, marketProxy.ValueDate, request.spotPrices[0]); if (PS.Config.Is润和 && DateTime.Now.Hour < 15) { var index = fixings.IndexOf(marketProxy.ValueDate.ToString("yyyy-MM-dd")); if (index >= 0) { fixings = fixings.Remove(index).TrimEnd(';'); } } return AsianOptionFixingService.CheckAsiaFixings(td, td.trade_asian_option, fixings, request.spotPrices[0]); }); var tpReq = new OptionTradeParamRequest(request.sysRiskFreeRate) { hasNightMarket = false, maturityShift = request.maturityShift, ParamOverride = request.ParamOverride, preciseTimeMode = request.preciseTimeMode, timeToMaturityDays = request.timeToMaturityDays ?? double.NaN, tradeId = marketProxy.NextRequestId() + "_", volSurfaceNames = null, fixings = null }; tradeBLL.SetFieldsByTradeType(td); switch (td.TradeType) { case "彩虹期权": //var underlyingCodes = new[] { trade.UnderlyingCode, trade.trade_rainbow_option.UnderlyingAssetCode2 }; //request.volSurfaceNames = PrepareVols(marketProxy, tradeId.ToString(), underlyingCodes, request.vols); throw new Exception("不支持彩虹期权计算"); case "价差期权": //var underlyingCodes = trade.trade_spread_option.UnderlyingAssetCodes(); //request.volSurfaceNames = PrepareVols(marketProxy, tradeId.ToString(), underlyingCodes, request.vols); throw new Exception("不支持价差期权计算"); case ConsGlobal.TradeType.CashFlow: break; default: tpReq.volSurfaceNames = PrepareVols(marketProxy, tpReq.tradeId, new string[] { td.UnderlyingCode }, request.vols); if (td.StructureType == "亚式熊市价差") { tpReq.fixings = getAsianFixings(); } break; } var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode); if (underlying == null) { if (td.HasUnderlying()) { throw new TradeCalcExpception(td.id, "没有找到标的数据:" + td.UnderlyingCode); } } else if (underlying.IsFutures()) { var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId); tpReq.hasNightMarket = variety != null && variety.HasNightMarket; } underlyings = new[] { underlying ?? new underlying_manager() }; tpReq.tradeId += td.id > 0 ? td.id.ToString() : string.IsNullOrWhiteSpace(td.ExchangeOptionCode) ? td.UnderlyingCode : td.ExchangeOptionCode; return tpReq; } /// /// 准备波动率 /// private static string[] PrepareVols(MarketProxy marketProxy, string tradeId, string[] underlyingCodes, double[] vols) { if (string.IsNullOrEmpty(tradeId)) { throw new ArgumentException($"“{nameof(tradeId)}”不能是 Null 或为空。", nameof(tradeId)); } if (underlyingCodes == null || !underlyingCodes.Any()) { throw new Exception("缺少标的代码"); } if (vols == null || !vols.Any() || vols.Length < underlyingCodes.Length) { throw new Exception("缺少波动率"); } var volSurfaceNames = new string[underlyingCodes.Length]; for (var i = 0; i < underlyingCodes.Length; i++) { var volatility = QdpVolHelper.GetDefaultVolatility(vols[i]); volSurfaceNames[i] = QdpVolHelper.GetVolSurfaceName(tradeId, i > 0 ? underlyingCodes[i] : null); marketProxy.SetVolSurface(volSurfaceNames[i], volatility); } return volSurfaceNames; } /// /// 计算交叉GAMMA /// public static double[] CalcSSpreadCrossGammas(DateTime valueDate, trade td, OptionValueCalcRequest request, double[] coefficients) { if (td is null) { throw new ArgumentNullException(nameof(td)); } if (request is null) { throw new ArgumentNullException(nameof(request)); } if (request.spotPrices == null || !request.spotPrices.Any()) { throw new Exception("期权计算缺少标的现价"); } if (string.IsNullOrEmpty(td.UnderlyingCode) && td.HasUnderlying()) { throw new Exception("缺少标的代码"); } using var marketProxy = new MarketProxy(valueDate, request.sysRiskFreeRate); var tpReq = PrepareCalc(marketProxy, td, request, out _, out _); var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(td, tpReq, coefficients); return TradeRiskCalcUtil.CalcSSpreadCrossGammas(marketProxy, GetOptionCalcParam(tradeParam, request), coefficients); } } }