using Qdp.ComputeService.Data.CommonModels.ValuationParams.Equity; using Qdp.ComputeService.Data.CommonModels.ValuationParams.FixedIncome; using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos; using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos.Options; using Qdp.Foundation.Implementations; using Qdp.Pricing.Base.Enums; using Qdp.Pricing.Base.Implementations; using Qdp.Pricing.Base.Interfaces; using Qdp.Pricing.Library.Common.Base; using Qdp.Pricing.Library.Common.Interfaces; using Qdp.Pricing.Library.Common.Products.Rates.Engines; using Qdp.Pricing.Library.Options.Products.Accumulator.Engines.Analytical; using Qdp.Pricing.Library.Options.Products.Accumulator.Engines.Numerical; using Qdp.Pricing.Library.Options.Products.Airbag.Engines.Analytical; using Qdp.Pricing.Library.Options.Products.Asian; using Qdp.Pricing.Library.Options.Products.AsianSyntheticSpread.Engines.Analytical; using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix; using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix.Numerical; using Qdp.Pricing.Library.Options.Products.Autocall.Snowball; using Qdp.Pricing.Library.Options.Products.Barrier; using Qdp.Pricing.Library.Options.Products.Binary; using Qdp.Pricing.Library.Options.Products.DoubleSharkFin; using Qdp.Pricing.Library.Options.Products.PayoffEnhance.Engines.Analytical; using Qdp.Pricing.Library.Options.Products.Rainbow; using Qdp.Pricing.Library.Options.Products.RangeAccrual; using Qdp.Pricing.Library.Options.Products.Spread; using Qdp.Pricing.Library.Options.Products.Vanilla; using YLErp.BLL.Calculation.Engine; using YLErp.BLL.Calculation.V2.Parameter; using YLErp.Commons; using YLErp.Configuration; using YLErp.Enums; using YLErp.Modules; using YLErp.Modules.CalculationLogModule; using YLErp.Modules.CalculationModule; using YLErp.QdpModule; using YLErp.QdpModule.Constants; namespace YLErp.BLL.Calculation.V2 { public class ValueCalculator { /// /// 计算金融产品及衍生品的价值 /// /// QdpMarketId /// 交易 /// 标的资产 /// 计算参数 /// public static TradeValueResult CalculateTradeValue( string userId, trade trade, underlying_manager underlying, ParameterBase parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default, bool quadratureFastMode = false) { underlying = underlying.Clone(); underlying.UnderlyingInstrumentType = ConsGlobal.InstrumentType.ConvertCalcType(underlying.UnderlyingInstrumentType,true); switch (trade.TradeType) { case "香草期权": case "场内期权": return CalculateVanillaOption(userId, trade, underlying, parameter as VanillaOptionParameter, pricingRequest, calcScenario); case "Risky期权": return CalculateRiskyOption(userId, trade, underlying, parameter as VanillaOptionParameter, pricingRequest, calcScenario); case "障碍期权": return CalculateBarrierOption(userId, trade, underlying, parameter as BarrierOptionParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode); case "亚式期权": return CalculateAsianOption(userId, trade, underlying, parameter as AsianOptionParameter, pricingRequest, calcScenario); case "二元期权": return CalculateBinaryOption(userId, trade, underlying, parameter as BinaryOptionParameter, pricingRequest, calcScenario); case "彩虹期权": return CalculateRainbowOption(userId, trade, underlying, parameter as RainbowOptionParameter, pricingRequest, calcScenario); case "价差期权": return CalculateSpreadOption(userId, trade, underlying, parameter as SpreadOptionParameter, pricingRequest, calcScenario); case "合成价差期权": return CalculateSyntheticNormalSpreadOption(userId, trade, underlying, parameter as SyntheticNormalSpreadOptionParameter, pricingRequest, calcScenario); case "亚式合成价差期权": return CalculateAsianSyntheticNormalSpreadOption(userId, trade, underlying, parameter as AsianSyntheticNormalSpreadOptionParameter, pricingRequest, calcScenario); case "双鲨期权": return CalculateDoubleSharkFinOption(userId, trade, underlying, parameter as DoubleSharkFinOptionParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode); case "凤凰期权": return CalculateAutocall(userId, trade, underlying, parameter as AutocallParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode); case "雪球期权": if (trade.IsSnowballSpecialist()) { SnowballSpecialistOptionCalculator snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator(); if (parameter?.SpotPrices == null || !parameter.SpotPrices.TryGetValue(underlying.UnderlyingCode, out var spotPrice)) { throw new Exception("[计算雪球期权交易]缺少SpotPrice"); } if (trade.trade_snowball.PrepaymentRatio > 0) { var specialSnowballTrade = snowballSpecialistOptionCalculator.GetSpecialTrade(trade); var specialSnowballResult = snowballSpecialistOptionCalculator.CalcOptionValue(parameter.ValueDate, spotPrice, (parameter as SnowballParameter).Volatility ?? 0, calcScenario, specialSnowballTrade); var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(trade); parameter.RiskFreeRate = breakevenSnowballTrade.NoRiskRate; var breakevenSnowballResult = CalculateSnowball(userId, breakevenSnowballTrade, underlying, parameter as SnowballParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode); return snowballSpecialistOptionCalculator.MergeTradeValueResult(specialSnowballResult, breakevenSnowballResult); } else { return snowballSpecialistOptionCalculator.CalcOptionValue(parameter.ValueDate, spotPrice, (parameter as SnowballParameter).Volatility ?? 0, calcScenario, trade); } } return CalculateSnowball(userId, trade, underlying, parameter as SnowballParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode); case "区间累积期权": return CalculateRangeAccrual(userId, trade, underlying, parameter as RangeAccrualParameter, pricingRequest, calcScenario); case "气囊结构": return CalculateAirbag(userId, trade, underlying, parameter as AirbagParameter, pricingRequest, calcScenario); case "收益增强结构": return CalculateUnderlyingEnhance(userId, trade, underlying, parameter as UnderlyingEnhanceParameter, pricingRequest, calcScenario); case "累计期权": return CalculateAccumulatorOption(userId, trade, underlying, parameter as AccumulatorOptionParameter, pricingRequest, calcScenario); case "现金流交易": return CalculateCashFlow(userId, trade, underlying, parameter as CashFlowTradeParameter, pricingRequest, calcScenario); case "远期": { if (parameter?.SpotPrices == null || !parameter.SpotPrices.TryGetValue(underlying.UnderlyingCode, out var spotPrice)) { throw new Exception("[计算远期交易]缺少SpotPrice"); } return CalculateForward(trade.Strike ?? 0.0, spotPrice, trade.Notional, trade.CallPut); } case "信用债": case "商品期货": case "商品现货": case "股票": { if (parameter?.SpotPrices == null || !parameter.SpotPrices.TryGetValue(underlying.UnderlyingCode, out var spotPrice)) { throw new Exception($"[计算{trade.TradeType}交易]缺少SpotPrice"); } var pv = spotPrice * trade.Notional; return new TradeValueResult { Pv = pv, RoundedPv = pv, Delta = trade.Notional, DeltaCash = pv }; } case "结构化产品": var para = parameter as VanillaOptionParameter; var isPreciseTimeMode = underlying.QuotationDate.Value.Date == valuedateBLL.ValueDate.Date; var request = new OptionValueCalcRequest(valuedateBLL.SysRiskFreeRate()) { vols = new[] { (double)para.Volatility }, spotPrices = new[] { parameter.SpotPrices[trade.UnderlyingCode] }, engineName = para.EngineName, preciseTimeMode = isPreciseTimeMode, pricingRequest = QdpPricingRequest.PRICE_GREEKS, quadratureFastMode = true, calcScenario = CalcScenarioEnum.Pricing, fixings = null, timeToMaturityDays = trade.TTMDays }; return OptionCalculatorV2.GetOptionValueResult(para.ValueDate, trade, request, out _); default: break; } throw new Exception($"不支持的交易结构{trade.TradeType}的计算"); } /// /// 原OptionCalculator中的GetOptionValueResult,仅用于定价计算 /// public static TradeValueResult GetOptionValueResultV2( string userId, underlying_manager underlying, trade trade, double[] vols, double[] spotPrices = null, string fixing = null, bool preciseTimeMode = false, double[] correlations = null, string discountCurveName = null, int maturityShift = 0, double timeToMaturityDays = double.NaN, string engineName = null, PricingRequest request = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default, bool quadratureFastMode = false) { if (spotPrices is null) { throw new ArgumentNullException(nameof(spotPrices)); } if (trade.TTMDays != null && timeToMaturityDays.Equals(double.NaN)) { timeToMaturityDays = trade.TTMDays.Value; } //股票默认到期日为行权日 if (underlying.UnderlyingInstrumentType == "Stock" && trade.ExerciseDate != null) { trade.MaturityDate = trade.ExerciseDate; } var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId); var parameter = ParameterHelper.CreatePricingParameter( underlying: underlying, variety: variety, trade: trade, vols: vols, spotPrices: spotPrices, fixing: fixing, preciseTimeMode: preciseTimeMode, correlations: correlations ?? new double[0], discountCurveName: discountCurveName, timeToMaturityDays: timeToMaturityDays, engineName: engineName); parameter.maturityShift = maturityShift; try { var result = CalculateTradeValue(userId, trade, underlying, parameter, request, calcScenario, quadratureFastMode: quadratureFastMode); if (result != null) { result.Strike = trade.Strike ?? 0; } return result; } catch (Exception ex) { throw new ServiceException($"计算交易{trade.TradeNumber}估值报错,{ex.Message}", ex); } } /// /// 计算保本雪球的年化期权费率 /// public static double CalculateSnowballAnnualPremium( string userId, underlying_manager underlying, trade trade, double[] vols, double[] spotPrices = null, bool preciseTimeMode = false, string discountCurveName = null, int maturityShift = 0, double timeToMaturityDays = double.NaN, string engineName = null, bool quadratureFastMode = false) { if (spotPrices is null) { throw new ArgumentNullException(nameof(spotPrices)); } if (trade.TTMDays != null && timeToMaturityDays.Equals(double.NaN)) { timeToMaturityDays = trade.TTMDays.Value; } //股票默认到期日为行权日 if (underlying.UnderlyingInstrumentType == "Stock" && trade.ExerciseDate != null) { trade.MaturityDate = trade.ExerciseDate; } var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId); var parameter = ParameterHelper.CreatePricingParameter( underlying, variety, trade, vols, spotPrices, null, preciseTimeMode, null, discountCurveName, timeToMaturityDays, engineName); parameter.maturityShift = maturityShift; return CalculateSnowballAnnualPremium(userId, trade, underlying, parameter as SnowballParameter, quadratureFastMode); } private static double CalculateSnowballAnnualPremium(string userId, trade trade, underlying_manager underlying, SnowballParameter parameter, bool quadratureFastMode) { var snowball = trade.trade_snowball; snowball.AnnualizedPremiumRate = null; var buySidePv = CalculateSnowball(userId, trade, underlying, parameter, PricingRequest.Pv, quadratureFastMode: quadratureFastMode).Pv; buySidePv /= trade.Notional; buySidePv = Math.Abs(buySidePv); var sellSideProduct = QdpTradeBuilder.GetSnowballOptionTrade(new SnowballOptionTradeParam { tradeId = null, volSurfaceNames = null, tradeDate = parameter.ValueDate, underlyingTickers = new[] { underlying.UnderlyingCode }, underlyingInstrumentType = underlying.UnderlyingInstrumentType, startDate = trade.StartDate.Value, endDate = trade.ExerciseDate.Value, initialSpotPrice = trade.SpotPrice ?? 0.0, isMoneynessOption = trade.IsMoneynessOptionData, koBarrier = snowball.KOBarrier, kiBarrier = snowball.KIBarrier, koObservationDateStr = snowball.KOObservationDates, couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null, observationDateStr = snowball.ObservationDates, coupon = snowball.Coupon, participationRate = trade.ParticipationRate ?? 1.0, principalRate = trade.PrincipalRate ?? 0.0, notional = 1.0, buysell = "Buy", exerciseDate = trade.ExerciseDate.Value, annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0, callput = QdpConverter.ConvertOptionType(trade.CallPut), strike = trade.Strike ?? 0.0, spreadStrikeAtKO = -1, spreadStrikeAtMaturity = -1, isFixedCoupon = false, koRebate = double.NaN, koBarrierAdjustStep = snowball.KOBarrierAdjustStep, //敲出转期权 useOptionPayoffAtKO = false, //未敲出转期权 useOptionPayoffAtMaturity = false, annualizedOptionPayoff = snowball.IsAnnualized2, couponDayCount = snowball.CouponDayCount, timeToMaturityDays = parameter.OverrideTTM ?? double.NaN, riskFreeRate = parameter.RiskFreeRate ?? double.NaN, dividendRate = parameter.DividendRate ?? double.NaN, }, SnowBallOptionMode.SimpleSnowball); var option = sellSideProduct.Instrument as SimpleSnowball; var engine = new QuadAutoCallEngine(); return engine.CalcSnowballCouponRate(option, parameter.SpotPrices.Values.First(), parameter.RiskFreeRate.Value, parameter.DividendRate.Value, parameter.Volatility.Value, hasNkoNkiRebate: true, targetPv: buySidePv); } /// /// 反算雪球票息 /// public static double CalculateSnowballKORebateV2( OtcTradeBase trade, trade_snowball snowball, DateTime valueDate, double[] vols, double[] spotPrices = null, bool preciseTimeMode = false, double timeToMaturityDays = double.NaN, double initialMarginRate = 0.0) { if (spotPrices is null || !spotPrices.Any()) { throw new ArgumentException("不能为空", nameof(spotPrices)); } if (vols is null || !vols.Any()) { throw new ArgumentException("不能为空", nameof(vols)); } //股票默认到期日为行权日 if (ConsGlobal.InstrumentType.CalcTypeIsStock(trade.UnderlyingInstrumentType) && trade.ExerciseDate != null) { trade.MaturityDate = trade.ExerciseDate; } var targetPv = trade.TradeSinglePrice ?? 0.0; snowball.AnnualizedPremiumRate = null; var para = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())); para.tradeId = null; para.volSurfaceNames = null; para.annualizedPremiumRate = null; para.preciseTimeMode = preciseTimeMode; para.tradeDate = valueDate; para.notional = 1.0; para.buysell = "Buy"; para.spreadStrikeAtKO = -1; para.koRebate = double.NaN; //敲出转期权 para.useOptionPayoffAtKO = false; //未敲出转期权 para.timeToMaturityDays = timeToMaturityDays; para.barrierStatus = null; var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(para, SnowBallOptionMode.SimpleSnowball); var option = optionTrade.Instrument as SimpleSnowball; var engine = new QuadAutoCallEngine(); if (initialMarginRate <= 0.0) //无预付预付金,按一笔正常雪球反算票息 { return engine.CalcSnowballCouponRate( option, spotPrices.First(), para.riskFreeRate, para.dividendRate, vols[0], hasNkoNkiRebate: snowball.Coupon > 0, targetPv: targetPv); } else //有预付预付金 { //计算方法: //将有预付预付金的雪球看作两笔雪球A和B的组合,A是没有预付预付金的原雪球,B是与A期限、敲出观察、敲出障碍都一样的一笔保本雪球 //1. 如果预付预付金比例为100%,则B的票息就是无风险利率,直接计算pv //2. 如果预付预付金比例小于100%(假设30%),则应该计算名义本金为30、票息为无风险利率的保本雪球pv // //最后用A和B的pv之和反算A的票息,即为所得结果 var paraB = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())); paraB.notional = initialMarginRate; //部分预付金 paraB.useOptionPayoffAtMaturity = false; //保本雪球 paraB.koRebate = para.riskFreeRate; paraB.coupon = para.riskFreeRate; var optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball); var optionB = optionTradeB.Instrument as SimpleSnowball; var pvB = engine.PriceSnowball(optionB, new Date(valueDate), spotPrices.First(), vols[0], paraB.dividendRate, paraB.riskFreeRate); //因为A的pv为0,用B的pv反算A的票息 var koRebateA = engine.CalcSnowballCouponRate( option, spotPrices.First(), para.riskFreeRate, para.dividendRate, vols[0], hasNkoNkiRebate: snowball.Coupon > 0, targetPv: pvB); return koRebateA; } } /// /// 先保留这个方法,不一定适用于客户真实情况 /// 这个方法是假设带初始预付金的雪球的初始pv也为0 /// private static double CalculateSnowballCouponRateWithInitialMargin( SnowballOptionTradeParam para, OtcTradeBase trade, trade_snowball snowball, SimpleSnowball option, DateTime valueDate, double[] vols, double[] spotPrices = null, double initialMarginRate = 0.0) { var engine = new QuadAutoCallEngine(); double koRebateB = 0.0, pvB = 0.0; if (initialMarginRate == 1.0) { koRebateB = para.riskFreeRate; var paraB = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())); paraB.notional = 1.0; paraB.useOptionPayoffAtMaturity = false; //保本雪球 paraB.koRebate = koRebateB; paraB.coupon = koRebateB; var optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball); var optionB = optionTradeB.Instrument as SimpleSnowball; pvB = engine.PriceSnowball(optionB, new Date(valueDate), spotPrices.First(), vols[0], paraB.dividendRate, paraB.riskFreeRate); } else { var paraB = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())); paraB.notional = initialMarginRate; //部分预付金 paraB.useOptionPayoffAtMaturity = false; //保本雪球 paraB.koRebate = para.riskFreeRate; paraB.coupon = para.riskFreeRate; var optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball); var optionB = optionTradeB.Instrument as SimpleSnowball; pvB = engine.PriceSnowball(optionB, new Date(valueDate), spotPrices.First(), vols[0], paraB.dividendRate, paraB.riskFreeRate); //再按全名义本金计算 paraB.notional = 1.0; optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball); optionB = optionTradeB.Instrument as SimpleSnowball; koRebateB = engine.CalcSnowballCouponRate( optionB, spotPrices.First(), para.riskFreeRate, para.dividendRate, vols[0], hasNkoNkiRebate: snowball.Coupon > 0, targetPv: pvB); } //使A+B为0,用-pvB反算A的票息,再相加 var koRebateA = engine.CalcSnowballCouponRate( option, spotPrices.First(), para.riskFreeRate, para.dividendRate, vols[0], hasNkoNkiRebate: snowball.Coupon > 0, targetPv: -pvB); return koRebateA + koRebateB; } public static double CalculatePhoenixCouponRate( underlying_manager underlying, trade trade, double[] vols, double[] spotPrices = null, bool preciseTimeMode = false, string discountCurveName = null, int maturityShift = 0, double timeToMaturityDays = double.NaN, string engineName = null) { if (spotPrices is null) { throw new ArgumentNullException(nameof(spotPrices)); } if (trade.TTMDays != null && timeToMaturityDays.Equals(double.NaN)) { timeToMaturityDays = trade.TTMDays.Value; } //股票默认到期日为行权日 if (underlying.UnderlyingInstrumentType == "Stock" && trade.ExerciseDate != null) { trade.MaturityDate = trade.ExerciseDate; } var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId); var parameter = ParameterHelper.CreatePricingParameter( underlying, variety, trade, vols, spotPrices, null, preciseTimeMode, null, discountCurveName, timeToMaturityDays, engineName); parameter.maturityShift = maturityShift; return CalculatePhoenixCouponRate(trade, underlying, parameter as AutocallParameter); } private static double CalculatePhoenixCouponRate(trade trade, underlying_manager underlying, AutocallParameter parameter) { var targetPv = trade.TradeSinglePrice ?? 0.0; var phoenix = trade.trade_autocall; var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(new AutocallOptionTradeParam { tradeId = null, volSurfaceNames = null, tradeDate = parameter.ValueDate, underlyingTickers = new[] { underlying.UnderlyingCode }, underlyingInstrumentType = underlying.UnderlyingInstrumentType, startDate = trade.StartDate.Value, endDate = trade.ExerciseDate.Value, initialSpotPrice = trade.SpotPrice ?? 0.0, isMoneynessOption = trade.IsMoneynessOptionData, couponBarrier = phoenix.CouponBarrier, koBarrier = phoenix.KOBarrier, kiBarrier = phoenix.KIBarrier, koObservationDateStr = phoenix.KOObservationDates, observationDateStr = phoenix.ObservationDates, coupon = phoenix.Coupon > 0 ? phoenix.Coupon : double.NaN, participationRate = trade.ParticipationRate ?? 1.0, principalRate = trade.PrincipalRate ?? 0.0, notional = 1.0, buysell = "Buy", exerciseDate = trade.ExerciseDate.Value, annualizedFactor = phoenix.AnnualizeFactor2 ?? 1.0, callput = QdpConverter.ConvertCallPut(trade.CallPut), kiOptionType = QdpConverter.ConvertOptionType(phoenix.KIPayoffType).ToString(), strike = trade.Strike ?? 0.0, spreadStrike = phoenix.SpreadStrike ?? -1, isFixedCoupon = phoenix.IsFixedCoupon, couponPayAtMaturity = phoenix.CouponPayType != CouponPayTypeEnum.AtCreated, includeCouponAfterKI = phoenix.IncludeCouponAfterKI, annualizedOptionPayoff = phoenix.IsAnnualized2, couponDayCount = phoenix.CouponDayCount, timeToMaturityDays = parameter.OverrideTTM ?? double.NaN, riskFreeRate = parameter.RiskFreeRate ?? double.NaN, dividendRate = parameter.DividendRate ?? double.NaN, }); var option = optionTrade.Instrument as AutoCall; var engine = new QuadAutoCallEngine(); return engine.CalcPhoenixCouponRate( option, parameter.SpotPrices.Values.First(), parameter.RiskFreeRate.Value, parameter.DividendRate.Value, parameter.Volatility.Value, targetPv: targetPv); } private static OptionTradeParamRequest GetOptionTradeParamRequest(ParameterBase parameter, params string[] volSurfaceNames) { return new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()) { volSurfaceNames = volSurfaceNames, dividends = parameter.Dividends, hasNightMarket = parameter.HasNightMarket, preciseTimeMode = parameter.PreciseTimeMode, timeToMaturityDays = parameter.OverrideTTM ?? double.NaN, maturityShift = parameter.maturityShift, ParamOverride = p => { //这段逻辑应该不需要,先注释掉,OTC-13391 //p.tradeDate = valueDateStr; //p.startDate = valueDateStr; p.riskFreeRate = parameter.RiskFreeRate ?? double.NaN; }, tradeId = null, fixings = null, }; } /// /// 计算香草期权 /// private static TradeValueResult CalculateVanillaOption( string userId, trade trade, underlying_manager underlying, VanillaOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode, parameter.IsForwardTrade); var optionTrade = QdpTradeBuilder.GetVanillaOptionTrade(trade, GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName), false); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as VanillaOption; var factory = OptionEngineFactory.GetEngineFactory("VanillaOption"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算Risky期权 /// private static TradeValueResult CalculateRiskyOption( string userId, trade trade, underlying_manager underlying, VanillaOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { TradeValueResult result = new TradeValueResult(); var tradeclone = trade.Clone(); tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(trade, trade.TradeAmount, 1); tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(trade, trade.Notional, underlying.CountRatio); var td1 = tradeclone.Clone(); var td2 = tradeclone.Clone(); if (trade.trade_risky_option.ParticipationRate2 != 0) { td2.Strike = trade.trade_risky_option.Strike2; td2.ParticipationRate = trade.trade_risky_option.ParticipationRate2; td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1); td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, underlying.CountRatio); result = CalculateVanillaOption(userId, td2, underlying, parameter, pricingRequest, calcScenario); } if (trade.trade_risky_option.ParticipationRate1 != 0) { td1.Strike = trade.trade_risky_option.Strike1; td1.ParticipationRate = trade.trade_risky_option.ParticipationRate1; td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1); td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, underlying.CountRatio); td1.OptionType = "看跌"; var singleresult1 = CalculateVanillaOption(userId, td1, underlying, parameter, pricingRequest, calcScenario); 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.Vol = singleresult1.Vol; } var td3 = tradeclone.Clone(); td3.Strike = trade.trade_risky_option.Strike3; //decimal 为了解决精度问题: 0.2-0.3=0.0999999999 var participationRate3 = (decimal)trade.trade_risky_option.ParticipationRate3 - (decimal)trade.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, underlying.CountRatio); var singleresult3 = CalculateVanillaOption(userId, td3, underlying, parameter, pricingRequest, calcScenario); 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.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.Vol = singleresult3.Vol; } } return result; } /// /// 计算障碍期权 /// private static TradeValueResult CalculateBarrierOption( string userId, trade trade, underlying_manager underlying, BarrierOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default, bool quadratureFastMode = false) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters(underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); //如果用户自定义的观察日就是每天观察,则忽略自定义观察日 var observationDateStr = trade.trade_barrier_option.ObservationDates; var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray(); var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr); if (barrierObservationDates != null && (barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length)) { observationDateStr = null; } var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var tParam = QdpTradeBuilder.GetBarrierOptionTradeParam(trade, trade.trade_barrier_option, tpReq); tParam.observationDateStr = observationDateStr; var optionTrade = QdpTradeBuilder.GetBarrierOptionTrade(tParam, valueDateStr); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as BarrierOption; var factory = OptionEngineFactory.GetEngineFactory("BarrierOption"); var engine = factory.GetEngine( parameter.EngineName, option.Exercise, option, !string.IsNullOrWhiteSpace(observationDateStr), quadratureFastMode); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算亚式期权 /// private static TradeValueResult CalculateAsianOption( string userId, trade trade, underlying_manager underlying, AsianOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); trade.TradeDate = trade.StartDate = parameter.ValueDate; var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); tpReq.fixings = AsianOptionFixingService.CheckAsiaFixings(trade, trade.trade_asian_option, parameter.Fixings, parameter.SpotPrices[underlying.UnderlyingCode]); var optionTrade = QdpTradeBuilder.GetAsianOptionTrade(trade, trade.trade_asian_option, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as AsianOption; var factory = OptionEngineFactory.GetEngineFactory("AsianOption"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise, trade.trade_asian_option.PayoffType, option, trade.trade_asian_option.StrikeType); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算二元期权 /// private static TradeValueResult CalculateBinaryOption( string userId, trade trade, underlying_manager underlying, BinaryOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var optionTrade = QdpTradeBuilder.GetBinaryOptionTrade(trade, trade.trade_binary_option, tpReq); var option = optionTrade.Instrument as BinaryOption; // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); //如果是美式二元,则要判断是否有自定义观察日 //如果用户自定义的观察日就是每天观察,则忽略自定义观察日 string observationDateStr = null; if (option.Exercise == OptionExercise.American) { observationDateStr = trade.trade_binary_option.ObservationDates; var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray(); var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr); if (barrierObservationDates != null && (barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length)) { observationDateStr = null; } } var factory = OptionEngineFactory.GetEngineFactory("BinaryOption"); var engine = factory.GetEngine( parameter.EngineName, option.Exercise, (parameter.ReplicationShiftSize <= 0.0) ? option.Strike * 0.01 : parameter.ReplicationShiftSize, QdpConverter.ConvertReplicationStrategy(parameter.BinaryReplicationStrategy), !string.IsNullOrWhiteSpace(observationDateStr), option.RebateAnnualizedAtKO); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算彩虹期权 /// private static TradeValueResult CalculateRainbowOption( string userId, trade trade, underlying_manager underlying, RainbowOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareRainbowOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpRainbowOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, trade.trade_rainbow_option.UnderlyingCodes); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceNames); var optionTrade = QdpTradeBuilder.GetRainbowOptionTrade(trade, trade.trade_rainbow_option, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as RainbowOption; var factory = OptionEngineFactory.GetEngineFactory("RainbowOption"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupRainbowOptionMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertRainbowOptionValueResult(results, trade); } /// /// 计算价差期权 /// private static TradeValueResult CalculateSpreadOption( string userId, trade trade, underlying_manager underlying, SpreadOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareSpreadOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpSpreadOptionValuationParameters( trade.trade_spread_option.UnderlyingAssetCodes(), marketObjectName.DiscountCurveName, marketObjectName.VolSurfaceNames); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceNames); var optionTrade = QdpTradeBuilder.GetSpreadOptionTrade(trade, trade.trade_spread_option, tpReq, correlations: parameter.Correlations); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as SpreadOption; var factory = OptionEngineFactory.GetEngineFactory("SpreadOption"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupSpreadOptionMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertSpreadOptionValueResult(results, trade); } /// /// 计算合成价差期权 /// private static TradeValueResult CalculateSyntheticNormalSpreadOption( string userId, trade trade, underlying_manager underlying, SyntheticNormalSpreadOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode, parameter.IsForwardTrade); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var optionTrade = QdpTradeBuilder.GetSSpreadOptionTrade(trade, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as VanillaOption; var factory = OptionEngineFactory.GetEngineFactory("SynthticNormalSpreadOption"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算亚式合成价差期权 /// private static TradeValueResult CalculateAsianSyntheticNormalSpreadOption( string userId, trade trade, underlying_manager underlying, AsianSyntheticNormalSpreadOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); trade.TradeDate = trade.StartDate = parameter.ValueDate; var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); tpReq.fixings = parameter.Fixings; var optionTrade = QdpTradeBuilder.GetAsianSSpreadOptionTrade(trade, trade.trade_asian_option, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var engine = new AnalyticalAsianSyntheticNormalSpreadOptionEngine(); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算双鲨期权 /// private static TradeValueResult CalculateDoubleSharkFinOption( string userId, trade trade, underlying_manager underlying, DoubleSharkFinOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default, bool quadratureFastMode = false) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var observationDateStr = trade.trade_double_sharkfin_option.ObservationDates; //如果用户自定义的观察日就是每天观察,则忽略自定义观察日 var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray(); var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr); if (barrierObservationDates != null && (barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length)) { observationDateStr = null; } var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var tParam = QdpTradeBuilder.GetDoubleSharkFinOptionTradeParam(trade, trade.trade_double_sharkfin_option, tpReq); tParam.observationDateStr = observationDateStr; var optionTrade = QdpTradeBuilder.GetDoubleSharkFinOptionTrade(tParam); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as DoubleSharkFinOption; var factory = OptionEngineFactory.GetEngineFactory("DoubleSharkFinOption"); var engine = factory.GetEngine( parameter.EngineName, option.Exercise, option, false, !string.IsNullOrWhiteSpace(observationDateStr), quadratureFastMode); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算远期交易(买方角度) /// public static TradeValueResult CalculateForward(double strike, double spotPrice, double notional, string callPut) { var pv = 0.0; if (callPut == "Call") { pv = (spotPrice - strike) * notional; } else if (callPut == "Put") { pv = (strike - spotPrice) * notional; } return new TradeValueResult { Pv = pv, Delta = callPut == "Call" ? notional : -notional, DeltaCash = callPut == "Call" ? spotPrice * notional : -spotPrice * notional }; } private static TradeValueResult CalculateSnowball( string userId, trade trade, underlying_manager underlying, SnowballParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default, bool quadratureFastMode = false) { var snowball = trade.trade_snowball; // 年化权利金模式的雪球期权 if (snowball.AnnualizedPremiumRate.HasValue && snowball.AnnualizedPremiumRate != 0) { return CalculateAnnualPremiumSnowball(userId, trade, underlying, parameter, pricingRequest, quadratureFastMode: quadratureFastMode); } var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(new SnowballOptionTradeParam { preciseTimeMode = parameter.PreciseTimeMode, tradeId = null, volSurfaceNames = new[] { marketObjectName.VolSurfaceName }, tradeDate = trade.TradeDate.Value, underlyingTickers = new[] { underlying.UnderlyingCode }, underlyingInstrumentType = underlying.UnderlyingInstrumentType, startDate = trade.StartDate.Value, endDate = trade.ExerciseDate.Value, initialSpotPrice = trade.SpotPrice ?? 0.0, isMoneynessOption = trade.IsMoneynessOptionData, koBarrier = snowball.KOBarrier, kiBarrier = snowball.KIBarrier, koObservationDateStr = snowball.KOObservationDates, couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null, observationDateStr = snowball.ObservationDates, coupon = snowball.Coupon, participationRate = trade.ParticipationRate ?? 1.0, principalRate = trade.PrincipalRate ?? 0.0, notional = trade.Notional, buysell = trade.BuySell, exerciseDate = trade.ExerciseDate.Value, annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0, callput = QdpConverter.ConvertCallPut(trade.CallPut), strike = trade.Strike ?? 0.0, spreadStrikeAtKO = snowball.SpreadStrikeAtKO ?? -1, spreadStrikeAtMaturity = snowball.SpreadStrikeAtMaturity ?? -1, isFixedCoupon = snowball.IsFixedCoupon, koRebate = snowball.KORebate, koBarrierAdjustStep = snowball.KOBarrierAdjustStep, //敲出转期权 useOptionPayoffAtKO = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate, //未敲出转期权 useOptionPayoffAtMaturity = snowball.KIPayoffType != KIPayoffTypeEnum.None, annualizedOptionPayoff = snowball.IsAnnualized2, couponDayCount = snowball.CouponDayCount, timeToMaturityDays = parameter.OverrideTTM ?? double.NaN, riskFreeRate = parameter.RiskFreeRate ?? double.NaN, dividendRate = parameter.DividendRate ?? double.NaN, koStrike = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate ? snowball.SpreadStrikeAtKO1.Value : double.NaN, koOptionType = QdpConverter.ConvertAutocallKOOptionType(snowball.KOPayoffType, QdpConverter.ConvertCallPut(trade.CallPut)), kiOptionType = QdpConverter.ConvertOptionType(snowball.KIPayoffType), barrierStatus = snowball.KnockInOutStatus, hasNightMarket=parameter.HasNightMarket }, SnowBallOptionMode.SimpleSnowball); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as SimpleSnowball; var factory = OptionEngineFactory.GetEngineFactory("SnowBall"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise, quadratureFastMode); IPricingResult results; if (!(engine is QuadAutoCallEngine) || parameter.ValueDate == trade.ExerciseDate) { results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); } else { //普通雪球期权可以采用加速方法定价 var market = SnowballTrade.ConvertToMarketCondition(optionTrade.TradeId, marketProxy.GetQdpMarket(valueDateStr), valuationParameter); var gridSnapshot = new QuadSnowballGridSnapshot(option, market, (QuadAutoCallEngine)engine); results = gridSnapshot.Calculate( market.SpotPrices.Value.Values.First(), option.BarrierStatus == BarrierStatus.KnockedIn, pricingRequest, rebuildGrid: false); } CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } /// /// 计算年化权利金模式的保本雪球 /// private static TradeValueResult CalculateAnnualPremiumSnowball( string userId, trade trade, underlying_manager underlying, SnowballParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default, bool quadratureFastMode = false) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var snowball = trade.trade_snowball; var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(new SnowballOptionTradeParam { preciseTimeMode = parameter.PreciseTimeMode, tradeId = null, volSurfaceNames = new[] { marketObjectName.VolSurfaceName }, tradeDate = trade.TradeDate.Value, underlyingTickers = new[] { underlying.UnderlyingCode }, underlyingInstrumentType = underlying.UnderlyingInstrumentType, startDate = trade.StartDate.Value, endDate = trade.ExerciseDate.Value, initialSpotPrice = trade.SpotPrice ?? 0.0, isMoneynessOption = trade.IsMoneynessOptionData, koBarrier = snowball.KOBarrier, kiBarrier = double.NaN, barrierStatus = snowball.KnockInOutStatus, koObservationDateStr = snowball.KOObservationDates, couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null, observationDateStr = snowball.ObservationDates, coupon = snowball.Coupon, participationRate = trade.ParticipationRate ?? 1.0, principalRate = trade.PrincipalRate ?? 0.0, notional = trade.Notional, buysell = trade.BuySell, exerciseDate = trade.ExerciseDate.Value, annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0, callput = QdpConverter.ConvertCallPut(trade.CallPut), strike = 0, spreadStrikeAtKO = -1, spreadStrikeAtMaturity = -1, isFixedCoupon = snowball.IsFixedCoupon, koRebate = snowball.KORebate, koBarrierAdjustStep = snowball.KOBarrierAdjustStep, useOptionPayoffAtKO = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate, useOptionPayoffAtMaturity = false, annualizedOptionPayoff = snowball.IsAnnualized2, couponDayCount = snowball.CouponDayCount, timeToMaturityDays = parameter.OverrideTTM ?? double.NaN, riskFreeRate = parameter.RiskFreeRate ?? double.NaN, dividendRate = parameter.DividendRate ?? double.NaN, }, SnowBallOptionMode.AnnualizedPremiumSnowball); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as AnnualizedPremiumSnowball; option.AnnualizedPremium = snowball.AnnualizedPremiumRate.Value; var factory = OptionEngineFactory.GetEngineFactory("SnowBall"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise, quadratureFastMode) as QuadAutoCallEngine; var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } private static TradeValueResult CalculateRangeAccrual( string userId, trade trade, underlying_manager underlying, RangeAccrualParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); tpReq.fixings = parameter.Fixings; var optionTrade = QdpTradeBuilder.GetRangeAccrualTrade(trade, trade.trade_rangeaccrual, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as RangeAccrual; var factory = OptionEngineFactory.GetEngineFactory("RangeAccrual"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } private static TradeValueResult CalculateAirbag( string userId, trade trade, underlying_manager underlying, AirbagParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var optionTrade = QdpTradeBuilder.GetAirbagOptionTrade(trade, trade.trade_airbag, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var engine = new AnalyticalAirbagReplicationEngine(); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } private static TradeValueResult CalculateUnderlyingEnhance( string userId, trade trade, underlying_manager underlying, UnderlyingEnhanceParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var optionTrade = QdpTradeBuilder.GetUnderlyingEnhanceTrade(trade, trade.trade_underlying_enhance, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var engine = new AnalyticalUnderlyingPayoffEnhanceEngine(calcAsOptions: true); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } private static TradeValueResult CalculateAutocall( string userId, trade trade, underlying_manager underlying, AutocallParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default, bool quadratureFastMode = false) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters( underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(trade, trade.trade_autocall, tpReq); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); var option = optionTrade.Instrument as AutoCall; var factory = OptionEngineFactory.GetEngineFactory("AutoCall"); var engine = factory.GetEngine(parameter.EngineName, option.Exercise, quadratureFastMode); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } private static TradeValueResult CalculateAccumulatorOption( string userId, trade trade, underlying_manager underlying, AccumulatorOptionParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = CreateQdpOptionValuationParameters(underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode); //如果用户自定义的观察日就是每天观察,则忽略自定义观察日 var observationDateStr = trade.trade_accumulator_option.KOObservationDates; var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray(); var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr); if (barrierObservationDates != null && (barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length)) { observationDateStr = null; } var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var tParam = QdpTradeBuilder.GetAccumulatorOptionTradeParam(trade, trade.trade_accumulator_option, tpReq); tParam.KOObservationDates = observationDateStr; //当到期日和定价日一样,并且当前时间大于等于3点,ttm传0;qdp里面 ttm等于0和不等于0逻辑处理不一样。 if (trade.ExerciseDate == parameter.ValueDate && DateTime.Now.Hour >= 15) { tParam.timeToMaturityDays = 0; } var optionTrade = QdpTradeBuilder.GetAccumulatorOptionTrade(tParam); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); IEngine engine; if (optionTrade is SegmentedAccumulatorOptionTrade) { engine = new AnalyticalSegmentedAccumulatorOptionEngine(); } else { if (tParam.EarlyTerminate) { engine = new QuadAccumulatorOptionEngine(); } else { engine = new AnalyticalAccumulatorOptionEngine(); } } var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]); } private static TradeValueResult CalculateCashFlow( string userId, trade trade, underlying_manager underlying, CashFlowTradeParameter parameter, PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS, CalcScenarioEnum calcScenario = CalcScenarioEnum.Default) { var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = CreateMarketProxy(userId, valueDateStr); var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter); var valuationParameter = new SimpleCfValuationParameters( marketObjectName.DiscountCurveName, marketObjectName.DiscountCurveName, marketObjectName.DiscountCurveName); var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName); var tParam = QdpTradeBuilder.GetCashFlowTradeParam(trade, trade.trade_cashflow, tpReq); var optionTrade = QdpTradeBuilder.GetCashFlowTrade(tParam, trade.StockEqvNotional); // 添加计算日志 AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber); IEngine engine = new DepositEngine(); var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter); CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName); return ConvertValueResult(results, trade, underlying, 0); } /// /// 构造MarketProxy /// /// /// /// private static IQdpMarketProxy CreateMarketProxy(string userId, string valueDateStr) { var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(userId); marketProxy.CreateMarket(valueDateStr); return marketProxy; } /// /// 准备标的价格数据 /// /// /// /// /// private static bool PrepareSpotPrices(IQdpMarketProxy marketProxy, string valueDateStr, Dictionary prices) { if (prices == null || prices.Count == 0) { return false; } foreach (var kv in prices) { marketProxy.AddStockPrice(kv.Key, valueDateStr, kv.Value); } return true; } /// /// 准备波动率曲面 /// /// /// /// /// /// private static string PrepareVolSurface(IQdpMarketProxy marketProxy, string valueDateStr, string volSurfaceName, double? overrideVol) { if (overrideVol.HasValue) { volSurfaceName = CreateVolSurfaceFromVolValue(marketProxy, valueDateStr, overrideVol.Value); } return volSurfaceName; } /// /// 准备折现曲线 /// /// /// /// /// /// private static string PrepareDiscountCurve(IQdpMarketProxy marketProxy, string valueDateStr, string discountCurveName, double? riskFreeRate) { if (riskFreeRate.HasValue) { discountCurveName = Guid.NewGuid().ToString(); var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, riskFreeRate.Value); marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve); } return discountCurveName; } /// /// 准备分红曲线 /// /// /// /// /// private static string PrepareDividendCurve(IQdpMarketProxy marketProxy, string valueDateStr, double? dividendRate) { if (dividendRate.HasValue) { var dividendCurveName = Guid.NewGuid().ToString(); var dividendCurve = CalculatorHelper.CreateConstantRiskFreeCurve(dividendCurveName, dividendRate.Value); marketProxy.AddYieldCurve(dividendCurveName, valueDateStr, dividendCurve); return dividendCurveName; } else { return null; } } /// /// 为期权计算准备市场数据 /// /// /// /// /// /// /// private static OptionMarketObjectName PrepareOptionMarketObjects( IQdpMarketProxy marketProxy, string valueDateStr, trade trade, underlying_manager underlying, BaseOptionParameter parameter) { if (!PrepareSpotPrices(marketProxy, valueDateStr, parameter.SpotPrices) && trade.TradeType != "现金流交易") { throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}"); } var volSurfaceName = PrepareVolSurface(marketProxy, valueDateStr, parameter.VolSurfaceName, parameter.Volatility); if (string.IsNullOrWhiteSpace(volSurfaceName) && trade.TradeType != "现金流交易") { throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}"); } var discountCurveName = PrepareDiscountCurve(marketProxy, valueDateStr, parameter.DiscountCurveName, parameter.RiskFreeRate); if (string.IsNullOrWhiteSpace(discountCurveName)) { throw new Exception($"交易计算未设置无风险利率: {trade.id} {trade.TradeNumber}"); } string dividendCurveName = null; if (underlying.UnderlyingInstrumentType == "Stock") { dividendCurveName = PrepareDividendCurve(marketProxy, valueDateStr, parameter.DividendRate ?? 0); if (string.IsNullOrWhiteSpace(dividendCurveName)) { throw new Exception($"交易计算未设置分红率: {trade.id} {trade.TradeNumber}"); } } return new OptionMarketObjectName() { DiscountCurveName = discountCurveName, DividendCurveName = dividendCurveName, VolSurfaceName = volSurfaceName }; } /// /// 为彩虹期权准备市场数据 /// private static RainbowOptionMarketObjectName PrepareRainbowOptionMarketObjects( IQdpMarketProxy marketProxy, string valueDateStr, trade trade, underlying_manager underlying, RainbowOptionParameter parameter) { if (!PrepareSpotPrices(marketProxy, valueDateStr, parameter.SpotPrices)) { throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}"); } foreach (var code in trade.trade_rainbow_option.UnderlyingCodes) { if (!parameter.SpotPrices.ContainsKey(code)) { throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {code}"); } } var volSurfaceName1 = PrepareVolSurface(marketProxy, valueDateStr, parameter.VolSurfaceName, parameter.Volatility); if (string.IsNullOrWhiteSpace(volSurfaceName1)) { throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber} {trade.trade_rainbow_option.UnderlyingCodes[0]}"); } var volSurfaceName2 = PrepareVolSurface(marketProxy, valueDateStr, parameter.VolSurfaceName2, parameter.Volatility2); if (string.IsNullOrWhiteSpace(volSurfaceName2)) { throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber} {trade.trade_rainbow_option.UnderlyingCodes[1]}"); } var discountCurveName = PrepareDiscountCurve(marketProxy, valueDateStr, parameter.DiscountCurveName, parameter.RiskFreeRate); if (string.IsNullOrWhiteSpace(discountCurveName)) { throw new Exception($"交易计算未设置无风险利率: {trade.id} {trade.TradeNumber}"); } string dividendCurveName = null; if (underlying.UnderlyingInstrumentType == "Stock") { dividendCurveName = PrepareDividendCurve(marketProxy, valueDateStr, parameter.DividendRate ?? 0); if (string.IsNullOrWhiteSpace(dividendCurveName)) { throw new Exception($"交易计算未设置分红率: {trade.id} {trade.TradeNumber}"); } } marketProxy.SaveCorrelationSurface(valueDateStr, trade.trade_rainbow_option.UnderlyingCodes, parameter.Correlation ?? 0); return new RainbowOptionMarketObjectName() { DiscountCurveName = discountCurveName, DividendCurveName = dividendCurveName, VolSurfaceNames = new string[] { volSurfaceName1, volSurfaceName2 } }; } private static SpreadOptionMarketObjectName PrepareSpreadOptionMarketObjects( IQdpMarketProxy marketProxy, string valueDateStr, trade trade, underlying_manager underlying, SpreadOptionParameter parameter) { if (!PrepareSpotPrices(marketProxy, valueDateStr, parameter.SpotPrices)) { throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}"); } var underlyingCodes = trade.trade_spread_option.UnderlyingAssetCodes(); if (parameter.VolSurfaceNames == null && (parameter.Volatilities == null || parameter.Volatilities.Length != underlyingCodes.Length) || parameter.Volatilities == null && (parameter.VolSurfaceNames == null || parameter.VolSurfaceNames.Length != underlyingCodes.Length)) { throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber}"); } var volSurfaceNames = new List(); for (var i = 0; i < underlyingCodes.Length; ++i) { if (!parameter.SpotPrices.ContainsKey(underlyingCodes[i])) { throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlyingCodes[i]}"); } var volSurfaceName = PrepareVolSurface( marketProxy, valueDateStr, (parameter.VolSurfaceNames != null && parameter.VolSurfaceNames.Length > i) ? parameter.VolSurfaceNames[i] : null, (parameter.Volatilities != null && parameter.Volatilities.Length > i) ? parameter.Volatilities[i] : null); volSurfaceNames.Add(volSurfaceName); } var discountCurveName = PrepareDiscountCurve(marketProxy, valueDateStr, parameter.DiscountCurveName, parameter.RiskFreeRate); if (string.IsNullOrWhiteSpace(discountCurveName)) { throw new Exception($"交易计算未设置无风险利率: {trade.id} {trade.TradeNumber}"); } marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[0], underlyingCodes[1] }, parameter.Correlations[0]); if (underlyingCodes.Length >= 3) { marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[0], underlyingCodes[2] }, parameter.Correlations[1]); marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[1], underlyingCodes[2] }, parameter.Correlations[2]); if (underlyingCodes.Length >= 4) { marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[0], underlyingCodes[3] }, parameter.Correlations[3]); marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[2], underlyingCodes[3] }, parameter.Correlations[5]); } } return new SpreadOptionMarketObjectName() { DiscountCurveName = discountCurveName, VolSurfaceNames = volSurfaceNames.ToArray() }; } /// /// 构造Qdp估值参数 /// private static OptionValuationParameters CreateQdpOptionValuationParameters( string underlyingInstrumentType, OptionMarketObjectName marketObjectName, string underlyingCode, bool isForwardTrade = false) { return new OptionValuationParameters( isForwardTrade ? MarketProxy.ConstantZeroCurve : marketObjectName.DiscountCurveName, underlyingInstrumentType == "Stock" ? marketObjectName.DividendCurveName : MarketProxy.ConstantZeroCurve, marketObjectName.VolSurfaceName, underlyingCode); } /// /// 构造彩虹期权的Qdp估值参数 /// private static OptionValuationParameters CreateQdpRainbowOptionValuationParameters( string underlyingInstrumentType, RainbowOptionMarketObjectName marketObjectName, string[] underlyingCodes) { return new OptionValuationParameters( marketObjectName.DiscountCurveName, (underlyingInstrumentType == "Stock" ? new string[] { marketObjectName.DividendCurveName, marketObjectName.DividendCurveName } : new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve }), marketObjectName.VolSurfaceNames, new string[] { underlyingCodes[0] + "_" + underlyingCodes[1] }, null); } private static OptionValuationParameters CreateQdpSpreadOptionValuationParameters( string[] underlyingTickers, string discountCurveName, string[] volSurfaceNames) { var dividendCurveNames = new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve }; var corrSurfNames = new string[] { underlyingTickers[0] + "_" + underlyingTickers[1] }; // construct correlation surface names // see SpreadOptionTrade.GenerateMarketConditionFromPrebuilt for the order if (underlyingTickers.Length == 3) { dividendCurveNames = new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve }; corrSurfNames = new string[] { underlyingTickers[0] + "_" + underlyingTickers[1], underlyingTickers[0] + "_" + underlyingTickers[2], underlyingTickers[1] + "_" + underlyingTickers[2] }; } else if (underlyingTickers.Length == 4) { dividendCurveNames = new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve }; corrSurfNames = new string[] { underlyingTickers[0] + "_" + underlyingTickers[1], underlyingTickers[0] + "_" + underlyingTickers[2], underlyingTickers[1] + "_" + underlyingTickers[2], underlyingTickers[0] + "_" + underlyingTickers[3], underlyingTickers[1] + "_" + underlyingTickers[3], underlyingTickers[2] + "_" + underlyingTickers[3], }; } return new OptionValuationParameters( discountCurveName, dividendCurveNames, volSurfaceNames, corrSurfNames, null); } /// /// 释放QdpMarket中的数据 /// private static void CleanupMarketObjects( BaseOptionParameter parameter, IQdpMarketProxy marketProxy, string valueDateStr, OptionMarketObjectName marketObjectName) { if (parameter.Volatility.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceName)) { marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceName); } if (parameter.RiskFreeRate.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.DiscountCurveName) && marketObjectName.DiscountCurveName != MarketProxy.ConstantZeroCurve) { marketProxy.RemoveYieldCurve(marketObjectName.DiscountCurveName, valueDateStr); } if (!string.IsNullOrWhiteSpace(marketObjectName.DividendCurveName) && marketObjectName.DividendCurveName != MarketProxy.ConstantZeroCurve) { marketProxy.RemoveYieldCurve(marketObjectName.DividendCurveName, valueDateStr); } } /// /// 为彩虹期权释放QdpMarket中的数据 /// private static void CleanupRainbowOptionMarketObjects( RainbowOptionParameter parameter, IQdpMarketProxy marketProxy, string valueDateStr, RainbowOptionMarketObjectName marketObjectName) { if (parameter.Volatility.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceNames[0])) { marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceNames[0]); } if (parameter.Volatility2.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceNames[1])) { marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceNames[1]); } if (parameter.RiskFreeRate.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.DiscountCurveName) && marketObjectName.DiscountCurveName != MarketProxy.ConstantZeroCurve) { marketProxy.RemoveYieldCurve(marketObjectName.DiscountCurveName, valueDateStr); } if (!string.IsNullOrWhiteSpace(marketObjectName.DividendCurveName) && marketObjectName.DividendCurveName != MarketProxy.ConstantZeroCurve) { marketProxy.RemoveYieldCurve(marketObjectName.DividendCurveName, valueDateStr); } } /// /// 为价差期权释放QdpMarket中的数据 /// private static void CleanupSpreadOptionMarketObjects( SpreadOptionParameter parameter, IQdpMarketProxy marketProxy, string valueDateStr, SpreadOptionMarketObjectName marketObjectName) { for (var i = 0; i < parameter.Volatilities.Length; ++i) { if (!string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceNames[i])) { marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceNames[i]); } } if (parameter.RiskFreeRate.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.DiscountCurveName) && marketObjectName.DiscountCurveName != MarketProxy.ConstantZeroCurve) { marketProxy.RemoveYieldCurve(marketObjectName.DiscountCurveName, valueDateStr); } } private static TradeValueResult ConvertValueResult(IPricingResult results, trade trade, underlying_manager underlying, double spotPrice) { if (results.Succeeded) { var ret = new TradeValueResult { UnderlyingCode = underlying.UnderlyingCode, Pv = results.Pv, Delta = results.Delta, Gamma = results.Gamma, Vega = results.Vega, CalendarDayTheta = results.Theta, TradingDayTheta = results.ThetaPnL, Rho = results.Rho, DeltaInLots = TradeLotsCalc.CalcDeltaInLots(results.Delta, null, underlying), DeltaCash = results.DeltaCash, //目前这个算法等价于 results.GammaCash/2*0.01 之所以没有这样算,是因为下面的算法是天风提供的; GammaCash = PS.Config.Company == Configuration.CompanyEnum.天风 ? (0.5 * results.Gamma * Math.Pow(spotPrice * 0.01, 2)) : results.GammaCash, VegaCash = results.VegaCash, Vol = results.PricingVol, //RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional }; if (trade.TradeType == "现金流交易" && trade.trade_cashflow?.DepositType == CashflowDepositTypeEnum.资金收益) { ret.Pv += trade.StockEqvNotional * trade.trade_cashflow.PrepayRatio * TradeCalcHelper.GetBuySellSign(trade.BuySell); } ret.RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional; ConvertTradeValueResultOfCompany(ret, trade.TradeType); return ret; } else { return null; } } /// /// 按照各公司的配置调整估值结果 /// /// public static void ConvertTradeValueResultOfCompany(TradeValueResult ret, string tradeType) { switch (PS.Config.Company) { case Configuration.CompanyEnum.国元固收: ret.Delta /= 10000; ret.Delta2 /= 10000; ret.Delta3 /= 10000; ret.Delta4 /= 10000; ret.DeltaCash /= 10000; ret.DeltaCash2 /= 10000; ret.DeltaCash3 /= 10000; ret.DeltaCash4 /= 10000; ret.DeltaInLots /= 10000; ret.DeltaMax /= 10000; ret.DeltaT1 /= 10000; ret.DDeltaDt /= 10000; ret.DDeltaDVol /= 10000; ret.SA_Delta /= 10000; ret.Gamma /= 100000000; ret.Gamma2 /= 100000000; ret.Gamma3 /= 100000000; ret.Gamma4 /= 100000000; ret.GammaCash /= 100000000; ret.GammaCash2 /= 100000000; ret.GammaCash3 /= 100000000; ret.GammaCash4 /= 100000000; ret.CrossGamma /= 100000000; break; case Configuration.CompanyEnum.招证: if (tradeType == "现金流交易") { ret.Rho = 0; } break; case Configuration.CompanyEnum.默认: case Configuration.CompanyEnum.国泰君安: case Configuration.CompanyEnum.申万: case Configuration.CompanyEnum.渤海: case Configuration.CompanyEnum.安粮: case Configuration.CompanyEnum.上期资本: case Configuration.CompanyEnum.格林大华: case Configuration.CompanyEnum.方顿: case Configuration.CompanyEnum.东兴: case Configuration.CompanyEnum.光大光子: case Configuration.CompanyEnum.长江: case Configuration.CompanyEnum.海通: case Configuration.CompanyEnum.茂川资本: case Configuration.CompanyEnum.宏源: case Configuration.CompanyEnum.伴兴: case Configuration.CompanyEnum.中财资本: case Configuration.CompanyEnum.瑞达: case Configuration.CompanyEnum.兴证: case Configuration.CompanyEnum.弘业: case Configuration.CompanyEnum.东方财富: case Configuration.CompanyEnum.国海: case Configuration.CompanyEnum.山西: case Configuration.CompanyEnum.天风: case Configuration.CompanyEnum.广期资本: case Configuration.CompanyEnum.厦门象屿: case Configuration.CompanyEnum.润和: case Configuration.CompanyEnum.兴业商贸: case Configuration.CompanyEnum.天示: case Configuration.CompanyEnum.中金: case Configuration.CompanyEnum.中粮: case Configuration.CompanyEnum.浙期: case Configuration.CompanyEnum.中基石化: case Configuration.CompanyEnum.国投: case Configuration.CompanyEnum.华安: case Configuration.CompanyEnum.国信金阳: case Configuration.CompanyEnum.国贸启润: case Configuration.CompanyEnum.东吴: case Configuration.CompanyEnum.广发商贸: case Configuration.CompanyEnum.湘财: case Configuration.CompanyEnum.华西: default: break; } } private static TradeValueResult ConvertRainbowOptionValueResult(IPricingResult results, trade trade) { if (results.Succeeded) { //var vol = 0.0; var ret = new TradeValueResult { UnderlyingCode = trade.trade_rainbow_option.UnderlyingAssetCode, Pv = results.Pv, Delta = results.asset1Delta, Gamma = results.asset1Gamma, Vega = results.asset1Vega, CalendarDayTheta = results.Theta, TradingDayTheta = results.ThetaPnL, Rho = results.Rho, DeltaCash = results.DeltaCash, GammaCash = results.GammaCash, Vol = results.PricingVol, UnderlyingCode2 = trade.trade_rainbow_option.UnderlyingAssetCode2, Delta2 = results.asset2Delta, Gamma2 = results.asset2Gamma, DeltaCash2 = results.asset2DeltaCash, GammaCash2 = results.asset2GammaCash, Vega2 = results.asset2Vega, CrossGamma = results.crossGamma, CrossVogga = results.crossVomma, CorrVega = results.correlationVega, RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional }; ConvertTradeValueResultOfCompany(ret, trade.TradeType); return ret; } else { return null; } } private static TradeValueResult ConvertSpreadOptionValueResult(IPricingResult results, trade trade) { if (results.Succeeded) { var valueResult = new TradeValueResult { Pv = results.Pv, Delta = results.asset1Delta, Gamma = results.asset1Gamma, Vega = results.asset1Vega, CalendarDayTheta = results.Theta, TradingDayTheta = results.ThetaPnL, Rho = results.Rho, DeltaCash = results.DeltaCash, GammaCash = results.GammaCash, Vol = results.PricingVol, Delta2 = results.asset2Delta, Gamma2 = results.asset2Gamma, DeltaCash2 = results.asset2DeltaCash, GammaCash2 = results.asset2GammaCash, Vega2 = results.asset2Vega, Delta3 = results.asset3Delta, Gamma3 = results.asset3Gamma, DeltaCash3 = results.asset3DeltaCash, GammaCash3 = results.asset3GammaCash, Vega3 = results.asset3Vega, Delta4 = results.asset4Delta, Gamma4 = results.asset4Gamma, DeltaCash4 = results.asset4DeltaCash, GammaCash4 = results.asset4GammaCash, Vega4 = results.asset4Vega, CrossGamma = results.crossGamma, CrossVogga = results.crossVomma, CorrVega = results.correlationVega, RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional }; var underlyingCodes = trade.trade_spread_option.UnderlyingAssetCodes(); valueResult.UnderlyingCode = underlyingCodes[0]; valueResult.UnderlyingCode2 = underlyingCodes[1]; if (underlyingCodes.Length > 2) { valueResult.UnderlyingCode3 = underlyingCodes[2]; } if (underlyingCodes.Length > 3) { valueResult.UnderlyingCode4 = underlyingCodes[3]; } ConvertTradeValueResultOfCompany(valueResult, trade.TradeType); return valueResult; } else { return null; } } private static string CreateVolSurfaceFromVolValue(IQdpMarketProxy marketProxy, string valueDateStr, double vol) { var volPart = GetDefaultVol(vol); volPart.QuotationDate = DateTime.Parse(valueDateStr); return SaveVolSurfaceToMarketProxy(marketProxy, valueDateStr, volPart); } /// /// 获取默认波动率 /// private static VolatilityDefault GetDefaultVol(double vol) { vol = vol < 0 ? ConsVolInfos.defVol : vol; var result = new VolatilityDefault { InterpolationMethod = ConsVolInfos.defInterpolationMethod, VolSurfaceMode = ConsVolInfos.defVolMode, VolType = ConsVolInfos.defVolType, QuotationDate = DateTime.MinValue, UnderlyingId = null, ContractCode = null, OptId = 0, OptName = ConsVolInfos.defOptName, OptDate = DateTime.Now }; result.Data = QdpVolHelper.GenerateFlatSingleVols(vol).ToJson(); return result; } private static string SaveVolSurfaceToMarketProxy(IQdpMarketProxy marketProxy, string valueDateStr, volatility volatility) { var volSurfaceName = Guid.NewGuid().ToString(); var volSurfaceWrap = new VolSurfaceBuilder { volSurfaceName = volSurfaceName, volSurfaceType = "MoneynessVol", interpolation = "BiLinear" }.SetVectors(volatility.VolTable).Build(valueDateStr); marketProxy.SaveVolSurface(volSurfaceWrap); return volSurfaceName; } /// /// 计算一组相同标的的期权交易,使用同一个波动率曲面 /// public static Dictionary CalculateOptionsWithSharedVolSurface( string userId, IEnumerable trades, underlying_manager underlying, ParameterBase parameter, volatility volatility, DateTime exerciseDate) { if (PS.Config.Is润和) { parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(parameter.ValueDate, exerciseDate, underlying?.UnderlyingTypeId ?? 0, false); } var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd"); var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(userId); marketProxy.CreateMarket(valueDateStr); var volSurfaceName = SaveVolSurfaceToMarketProxy(marketProxy, valueDateStr, volatility); var optionParameter = parameter as BaseOptionParameter; optionParameter.VolSurfaceName = volSurfaceName; var results = new Dictionary(); foreach (var trade in trades) { var result = CalculateTradeValue(userId, trade, underlying, parameter); results[trade.id] = result; } return results; } private static void AddCalculationLog(TradeBase trade, CalcScenarioEnum scenarioEnum, string tradeNumber) { try { var option = trade.Instrument as OptionBase; // 添加计算日志 CalculationLog log = new CalculationLog() { CreateTime = DateTime.Now, Scenario = scenarioEnum, LogObject = trade, TradeNumber = tradeNumber, Notional = trade.Notional, Exercise = option?.Exercise.ToString(), OptionType = option?.OptionType.ToString(), TradeDate = trade.TradeDate.ToString(), MaturityDate = trade.MaturityDate?.ToString(), InitialSpotPrice = option?.InitialSpotPrice.ToString(), Strike = option?.Strike.ToString(), }; ICalculationLogService calcLogService = new CalculationLogService(); calcLogService.AddLog(log, scenarioEnum); } catch (Exception ex) { Console.WriteLine(ex.Message); } } #region Greeks 薄接入层(③ 重定价桥,加法性,不改现有定价输出) /// /// 把某风险因子扰动到指定值后重定价,返回 PV(decimal)。 /// 克隆参数后施加扰动,不污染入参;现有 CalculateTradeValue 输出一行不变。 /// /// /// 这是把 GreeksBumpCalculator(有限差分引擎)接上真实定价路径的唯一接入点: /// 业务侧拿到返回的 PV 后,配合 自带的标准步长, /// 用 GreeksBumpCalculator.DeltaR/Delta/GammaR/VegaR 等即可算统一的希腊字母。 /// public static decimal CalcPvAtBumpedRiskFactor( string userId, trade t, underlying_manager u, RiskFactor factor, decimal bumpedValue, ParameterBase baseParam) { if (factor == null) throw new ArgumentNullException(nameof(factor)); var p = baseParam.Clone(); factor.ApplyTo(p, bumpedValue); return (decimal)CalculateTradeValue(userId, t, u, p).Pv; } /// /// 构造"PV 作为某风险因子值的函数"委托,供 GreeksBumpCalculator 中心差分使用。 /// 内部用真实 CalculateTradeValue 重定价;因子类别自带 FRTB SBA 标准步长(见 RiskFactor 工厂方法)。 /// public static Func PvAsFunctionOf( string userId, trade t, underlying_manager u, RiskFactor factor, ParameterBase baseParam) => factor.BuildPvFunction(p => (decimal)CalculateTradeValue(userId, t, u, p).Pv, baseParam); #endregion } class OptionMarketObjectName { public string DiscountCurveName { get; set; } public string DividendCurveName { get; set; } public string VolSurfaceName { get; set; } } class RainbowOptionMarketObjectName : OptionMarketObjectName { public string[] VolSurfaceNames { get; set; } } class SpreadOptionMarketObjectName : OptionMarketObjectName { public string[] VolSurfaceNames { get; set; } public string[] CorrelationSurfaceNames { get; set; } } }