/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ using System; using QuantConnect.Data; using QuantConnect.Data.Market; using QLNet; namespace QuantConnect.Securities.Option { using Logging; using PricingEngineFunc = Func; using PricingEngineFuncEx = Func; /// /// Provides QuantLib(QL) implementation of to support major option pricing models, available in QL. /// class QLOptionPriceModel : IOptionPriceModel { private readonly IQLUnderlyingVolatilityEstimator _underlyingVolEstimator; private readonly IQLRiskFreeRateEstimator _riskFreeRateEstimator; private readonly IQLDividendYieldEstimator _dividendYieldEstimator; private readonly PricingEngineFuncEx _pricingEngineFunc; /// /// When enabled, approximates Greeks if corresponding pricing model didn't calculate exact numbers. /// The default value is true. /// public bool EnableGreekApproximation { get; set; } = true; /// /// Method constructs QuantLib option price model with necessary estimators of underlying volatility, risk free rate, and underlying dividend yield /// /// Function modeled stochastic process, and returns new pricing engine to run calculations for that option /// The underlying volatility estimator /// The risk free rate estimator /// The underlying dividend yield estimator public QLOptionPriceModel(PricingEngineFunc pricingEngineFunc, IQLUnderlyingVolatilityEstimator underlyingVolEstimator, IQLRiskFreeRateEstimator riskFreeRateEstimator, IQLDividendYieldEstimator dividendYieldEstimator) { _pricingEngineFunc = (option, process) => pricingEngineFunc(process); _underlyingVolEstimator = underlyingVolEstimator ?? new ConstantQLUnderlyingVolatilityEstimator(); _riskFreeRateEstimator = riskFreeRateEstimator ?? new ConstantQLRiskFreeRateEstimator(); _dividendYieldEstimator = dividendYieldEstimator ?? new ConstantQLDividendYieldEstimator(); } /// /// Method constructs QuantLib option price model with necessary estimators of underlying volatility, risk free rate, and underlying dividend yield /// /// Function takes option and modeled stochastic process, and returns new pricing engine to run calculations for that option /// The underlying volatility estimator /// The risk free rate estimator /// The underlying dividend yield estimator public QLOptionPriceModel(PricingEngineFuncEx pricingEngineFunc, IQLUnderlyingVolatilityEstimator underlyingVolEstimator, IQLRiskFreeRateEstimator riskFreeRateEstimator, IQLDividendYieldEstimator dividendYieldEstimator) { _pricingEngineFunc = pricingEngineFunc; _underlyingVolEstimator = underlyingVolEstimator ?? new ConstantQLUnderlyingVolatilityEstimator(); _riskFreeRateEstimator = riskFreeRateEstimator ?? new ConstantQLRiskFreeRateEstimator(); _dividendYieldEstimator = dividendYieldEstimator ?? new ConstantQLDividendYieldEstimator(); } /// /// Evaluates the specified option contract to compute a theoretical price, IV and greeks /// /// The option security object /// The current data slice. This can be used to access other information /// available to the algorithm /// The option contract to evaluate /// An instance of containing the theoretical /// price of the specified option contract public OptionPriceModelResult Evaluate(Security security, Slice slice, OptionContract contract) { try { // setting up option pricing parameters var calendar = new UnitedStates(); var dayCounter = new Actual365Fixed(); var optionSecurity = (Option)security; var settlementDate = contract.Time.Date.AddDays(Option.DefaultSettlementDays); var maturityDate = contract.Expiry.Date.AddDays(Option.DefaultSettlementDays); var underlyingQuoteValue = new SimpleQuote((double)optionSecurity.Underlying.Price); var dividendYieldValue = new SimpleQuote(_dividendYieldEstimator.Estimate(security, slice, contract)); var dividendYield = new Handle(new FlatForward(0, calendar, dividendYieldValue, dayCounter)); var riskFreeRateValue = new SimpleQuote(_riskFreeRateEstimator.Estimate(security, slice, contract)); var riskFreeRate = new Handle(new FlatForward(0, calendar, riskFreeRateValue, dayCounter)); var underlyingVolValue = new SimpleQuote(_underlyingVolEstimator.Estimate(security, slice, contract)); var underlyingVol = new Handle(new BlackConstantVol(0, calendar, new Handle(underlyingVolValue), dayCounter)); // preparing stochastic process and payoff functions var stochasticProcess = new BlackScholesMertonProcess(new Handle(underlyingQuoteValue), dividendYield, riskFreeRate, underlyingVol); var payoff = new PlainVanillaPayoff(contract.Right == OptionRight.Call ? QLNet.Option.Type.Call : QLNet.Option.Type.Put, (double)contract.Strike); // creating option QL object var option = contract.Symbol.ID.OptionStyle == OptionStyle.American ? new VanillaOption(payoff, new AmericanExercise(settlementDate, maturityDate)) : new VanillaOption(payoff, new EuropeanExercise(maturityDate)); Settings.setEvaluationDate(settlementDate); // preparing pricing engine QL object option.setPricingEngine(_pricingEngineFunc(contract.Symbol, stochasticProcess)); // running calculations var npv = EvaluateOption(option); // function extracts QL greeks catching exception if greek is not generated by the pricing engine and reevaluates option to get numerical estimate of the seisitivity Func, Func, decimal> tryGetGreekOrReevaluate = (greek, reevalFunc) => { try { return (decimal)greek(); } catch (Exception) { return EnableGreekApproximation ? (decimal)reevalFunc() : 0.0m; } }; // function extracts QL greeks catching exception if greek is not generated by the pricing engine Func, decimal> tryGetGreek = greek => tryGetGreekOrReevaluate(greek, () => 0.0); // function extracts QL IV catching exception if IV is not generated by the pricing engine Func tryGetImpliedVol = () => { try { return (decimal)option.impliedVolatility((double)optionSecurity.Price, stochasticProcess); } catch (Exception err) { Log.Debug("tryGetImpliedVol() error: " + err.Message); return 0m; } }; Func> evalDeltaGamma = () => { try { return Tuple.Create((decimal)option.delta(), (decimal)option.gamma()); } catch (Exception) { if (EnableGreekApproximation) { var step = 0.01; var initial = underlyingQuoteValue.value(); underlyingQuoteValue.setValue(initial - step); var npvMinus = EvaluateOption(option); underlyingQuoteValue.setValue(initial + step); var npvPlus = EvaluateOption(option); underlyingQuoteValue.setValue(initial); return Tuple.Create((decimal)((npvPlus - npvMinus) / (2 * step)), (decimal)((npvPlus - 2 * npv + npvMinus) / (step * step))); } else return Tuple.Create(0.0m, 0.0m); } }; Func reevalVega = () => { var step = 0.001; var initial = underlyingVolValue.value(); underlyingVolValue.setValue(initial + step); var npvPlus = EvaluateOption(option); underlyingVolValue.setValue(initial); return (npvPlus - npv) / step; }; Func reevalTheta = () => { var step = 1.0 / 365.0; Settings.setEvaluationDate(settlementDate.AddDays(-1)); var npvMinus = EvaluateOption(option); Settings.setEvaluationDate(settlementDate); return (npv - npvMinus) / step; }; Func reevalRho = () => { var step = 0.001; var initial = riskFreeRateValue.value(); riskFreeRateValue.setValue(initial + step); var npvPlus = EvaluateOption(option); riskFreeRateValue.setValue(initial); return (npvPlus - npv) / step; }; // producing output with lazy calculations of IV and greeks return new OptionPriceModelResult((decimal)npv, tryGetImpliedVol, () => new Greeks(evalDeltaGamma, () => tryGetGreekOrReevaluate(() => option.vega(), reevalVega), () => tryGetGreekOrReevaluate(() => option.theta(), reevalTheta), () => tryGetGreekOrReevaluate(() => option.rho(), reevalRho), () => tryGetGreek(() => option.elasticity()))); } catch(Exception err) { Log.Debug("QLOptionPriceModel.Evaluate() error: " + err.Message); return new OptionPriceModelResult(0m, new Greeks()); } } /// /// Runs option evaluation and logs exceptions /// /// /// private static double EvaluateOption(VanillaOption option) { try { var npv = option.NPV(); if (double.IsNaN(npv) || double.IsInfinity(npv)) npv = 0.0; return npv; } catch (Exception err) { Log.Debug("QLOptionPriceModel.EvaluateOption() error: " + err.Message); return 0.0; } } } }