diff --git a/Algorithm.CSharp/BasicTemplateOptionsHistoryAlgorithm.cs b/Algorithm.CSharp/BasicTemplateOptionsHistoryAlgorithm.cs index 058bb0072c20..c280801f03aa 100644 --- a/Algorithm.CSharp/BasicTemplateOptionsHistoryAlgorithm.cs +++ b/Algorithm.CSharp/BasicTemplateOptionsHistoryAlgorithm.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -47,7 +47,7 @@ public override void Initialize() // set the pricing model for Greeks and volatility // find more pricing models https://www.quantconnect.com/lean/documentation/topic27704.html - option.PriceModel = OptionPriceModels.CrankNicolsonFD(); + option.PriceModel = OptionPriceModels.BlackScholes(); // set the warm-up period for the pricing model SetWarmup(TimeSpan.FromDays(4)); // set the benchmark to be the initial cash diff --git a/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs index 12a2775e3bbb..0e680bf9f80d 100644 --- a/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs +++ b/Algorithm.CSharp/FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs @@ -61,7 +61,7 @@ public override void Initialize() .Take(1) .Single(), Resolution.Minute); - _esOption.PriceModel = OptionPriceModels.BjerksundStensland(); + _esOption.PriceModel = OptionPriceModels.QuantLib.BjerksundStensland(); _expectedOptionContract = QuantConnect.Symbol.CreateOption(_es19m20.Symbol, Market.CME, OptionStyle.American, OptionRight.Call, 3200m, new DateTime(2020, 6, 19)); if (_esOption.Symbol != _expectedOptionContract) diff --git a/Algorithm.CSharp/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.cs b/Algorithm.CSharp/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.cs index 77a743213691..680cc1108da2 100644 --- a/Algorithm.CSharp/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.cs @@ -84,7 +84,6 @@ public override void OnData(Slice slice) var deltas = slice.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Delta).ToList(); var gammas = slice.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Gamma).ToList(); - var lambda = slice.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Lambda).ToList(); var rho = slice.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Rho).ToList(); var theta = slice.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Theta).ToList(); var impliedVol = slice.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.ImpliedVolatility).ToList(); @@ -102,10 +101,6 @@ public override void OnData(Slice slice) { throw new AggregateException("Option contract Gamma was equal to zero"); } - if (lambda.Any(l => l == 0)) - { - throw new AggregateException("Option contract Lambda was equal to zero"); - } if (rho.Any(r => r == 0)) { throw new AggregateException("Option contract Rho was equal to zero"); diff --git a/Algorithm.CSharp/IndicatorBasedOptionPricingModelIndexOptionRegressionAlgorithm.cs b/Algorithm.CSharp/IndicatorBasedOptionPricingModelIndexOptionRegressionAlgorithm.cs new file mode 100644 index 000000000000..e52f33dc8e97 --- /dev/null +++ b/Algorithm.CSharp/IndicatorBasedOptionPricingModelIndexOptionRegressionAlgorithm.cs @@ -0,0 +1,46 @@ +/* + * 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 QuantConnect.Indicators; +using QuantConnect.Securities.Option; +using System; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// This example demonstrates how to override the option pricing model with the + /// for a given index option security. + /// + public class IndicatorBasedOptionPricingModelIndexOptionRegressionAlgorithm : IndicatorBasedOptionPricingModelRegressionAlgorithm + { + protected override DateTime TestStartDate => new(2021, 1, 4); + + protected override DateTime TestEndDate => new(2021, 1, 4); + + protected override Option GetOption() + { + var index = AddIndex("SPX"); + var indexOption = AddIndexOption(index.Symbol); + indexOption.SetFilter(u => u.CallsOnly()); + return indexOption; + } + + /// + /// Data Points count of all timeslices of algorithm + /// + public override long DataPoints => 4806; + } +} diff --git a/Algorithm.CSharp/IndicatorBasedOptionPricingModelRegressionAlgorithm.cs b/Algorithm.CSharp/IndicatorBasedOptionPricingModelRegressionAlgorithm.cs new file mode 100644 index 000000000000..14b32fc53f17 --- /dev/null +++ b/Algorithm.CSharp/IndicatorBasedOptionPricingModelRegressionAlgorithm.cs @@ -0,0 +1,190 @@ +/* + * 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.Collections.Generic; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Indicators; +using QuantConnect.Securities.Option; +using System; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// This example demonstrates how to override the option pricing model with the + /// for a given option security. + /// + public class IndicatorBasedOptionPricingModelRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private bool _checked; + + private Option _option; + + protected virtual DateTime TestStartDate => new(2015, 12, 24); + + protected virtual DateTime TestEndDate => new(2015, 12, 24); + + public override void Initialize() + { + SetStartDate(TestStartDate); + SetEndDate(TestEndDate); + SetCash(100000); + + _option = GetOption(); + + if (_option.PriceModel is not IndicatorBasedOptionPriceModel) + { + throw new RegressionTestException("Option pricing model was not set to IndicatorBasedOptionPriceModel, which should be the default"); + } + } + + protected virtual Option GetOption() + { + var equity = AddEquity("GOOG"); + var option = AddOption(equity.Symbol); + option.SetFilter(u => u.Strikes(-2, +2).Expiration(0, 180)); + return option; + } + + public override void OnData(Slice slice) + { + if (!_checked && slice.OptionChains.TryGetValue(_option.Symbol, out var chain)) + { + foreach (var contract in chain) + { + var contractSecurity = Securities[contract.Symbol] as Option; + if (contractSecurity.PriceModel is not IndicatorBasedOptionPriceModel) + { + throw new RegressionTestException("Contract security pricing model was not set to IndicatorBasedOptionPriceModel"); + } + + var theoreticalPrice = contract.TheoreticalPrice; + var iv = contract.ImpliedVolatility; + var greeks = contract.Greeks; + + Log($"{contract.Symbol}:: Theoretical Price: {theoreticalPrice}, IV: {iv}, " + + $"Delta: {greeks.Delta}, Gamma: {greeks.Gamma}, Vega: {greeks.Vega}, " + + $"Theta: {greeks.Theta}, Rho: {greeks.Rho}, Lambda: {greeks.Lambda}"); + + // Sanity check values + + var theoreticalPriceChecked = false; + // If IV is zero (model could not converge) we skip the theoretical price check, as it will be zero too + if (iv != 0) + { + if (theoreticalPrice <= 0) + { + throw new RegressionTestException($"Invalid theoretical price for {contract.Symbol}: {theoreticalPrice}"); + } + theoreticalPriceChecked = true; + } + // We check for all greeks and IV together. e.g. IV could be zero if the model can't converge, say for instance if a contract is iliquid or deep ITM/OTM + if (greeks == null || + (iv == 0 && greeks.Delta == 0 && greeks.Gamma == 0 && greeks.Vega== 0 && greeks.Theta == 0 && greeks.Rho == 0)) + { + throw new RegressionTestException($"Invalid Greeks for {contract.Symbol}"); + } + + // Manually evaluate the price model, just in case + var security = Securities[contract.Symbol] as Option; + var result = security.EvaluatePriceModel(slice, contract); + + if (result == null || + result.TheoreticalPrice != theoreticalPrice || + result.ImpliedVolatility != iv || + result.Greeks.Delta != greeks.Delta || + result.Greeks.Gamma != greeks.Gamma || + result.Greeks.Vega != greeks.Vega || + result.Greeks.Theta != greeks.Theta || + result.Greeks.Rho != greeks.Rho) + { + throw new RegressionTestException($"EvaluatePriceModel returned different results for {contract.Symbol}"); + } + + _checked |= theoreticalPriceChecked; + } + } + } + + public override void OnEndOfAlgorithm() + { + if (!_checked) + { + throw new RegressionTestException("Option chain was never received."); + } + } + + /// + /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. + /// + public bool CanRunLocally { get; } = true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public List Languages { get; } = new() { Language.CSharp }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public virtual long DataPoints => 37131; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 0; + + /// + /// Final status of the algorithm + /// + public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "0"}, + {"Average Win", "0%"}, + {"Average Loss", "0%"}, + {"Compounding Annual Return", "0%"}, + {"Drawdown", "0%"}, + {"Expectancy", "0"}, + {"Start Equity", "100000"}, + {"End Equity", "100000"}, + {"Net Profit", "0%"}, + {"Sharpe Ratio", "0"}, + {"Sortino Ratio", "0"}, + {"Probabilistic Sharpe Ratio", "0%"}, + {"Loss Rate", "0%"}, + {"Win Rate", "0%"}, + {"Profit-Loss Ratio", "0"}, + {"Alpha", "0"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0"}, + {"Annual Variance", "0"}, + {"Information Ratio", "0"}, + {"Tracking Error", "0"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$0.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", ""}, + {"Portfolio Turnover", "0%"}, + {"Drawdown Recovery", "0"}, + {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"} + }; + } +} diff --git a/Algorithm.CSharp/OptionExpiryDateTodayRegressionAlgorithm.cs b/Algorithm.CSharp/OptionExpiryDateTodayRegressionAlgorithm.cs index 2e8087514d1f..9ec7229e15f9 100644 --- a/Algorithm.CSharp/OptionExpiryDateTodayRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionExpiryDateTodayRegressionAlgorithm.cs @@ -42,7 +42,7 @@ public override void Initialize() { return universeFilter.IncludeWeeklys().Strikes(-2, +2).Expiration(0, 10); }); - option.PriceModel = OptionPriceModels.BaroneAdesiWhaley(); + option.PriceModel = OptionPriceModels.BlackScholes(); _optionSymbol = option.Symbol; SetWarmUp(TimeSpan.FromDays(3)); diff --git a/Algorithm.CSharp/OptionGreeksRegressionAlgorithm.cs b/Algorithm.CSharp/OptionGreeksRegressionAlgorithm.cs index c0b5108fdc87..5441e873cea7 100644 --- a/Algorithm.CSharp/OptionGreeksRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionGreeksRegressionAlgorithm.cs @@ -27,7 +27,7 @@ namespace QuantConnect.Algorithm.CSharp public class OptionGreeksRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition { private Symbol _itmCallSymbol, _otmCallSymbol, _itmPutSymbol, _otmPutSymbol; - private const decimal error = 0.05m; + private const decimal error = 0.1m; public override void Initialize() { diff --git a/Algorithm.CSharp/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.cs b/Algorithm.CSharp/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.cs index dbf8825d462f..7ab9ead8912e 100644 --- a/Algorithm.CSharp/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.cs @@ -33,7 +33,7 @@ public override void Initialize() option.SetFilter(u => u.StandardsOnly().Strikes(-1, 1).Expiration(0, 35)); // BaroneAdesiWhaley model supports American style options - option.PriceModel = OptionPriceModels.BaroneAdesiWhaley(); + option.PriceModel = OptionPriceModels.QuantLib.BaroneAdesiWhaley(); SetWarmup(2, Resolution.Daily); diff --git a/Algorithm.CSharp/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.cs b/Algorithm.CSharp/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.cs index ca1764623e9c..d07222b471bd 100644 --- a/Algorithm.CSharp/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using QuantConnect.Indicators; using QuantConnect.Securities.Option; namespace QuantConnect.Algorithm.CSharp @@ -31,8 +32,8 @@ public override void Initialize() var option = AddOption("AAPL", Resolution.Minute); option.SetFilter(u => u.StandardsOnly().Strikes(-1, 1).Expiration(0, 35)); - // BlackSholes model does not support American style options - option.PriceModel = OptionPriceModels.BlackScholes(); + // QL BlackSholes model does not support American style options + option.PriceModel = OptionPriceModels.QuantLib.BlackScholes(); SetWarmup(2, Resolution.Daily); diff --git a/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs b/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs index 34d461372234..64aebae96899 100644 --- a/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.cs @@ -32,7 +32,7 @@ public override void Initialize() var option = AddIndexOption("SPX", Resolution.Hour); // BaroneAdesiWhaley model does not support European style options - option.PriceModel = OptionPriceModels.BaroneAdesiWhaley(); + option.PriceModel = OptionPriceModels.QuantLib.BaroneAdesiWhaley(); SetWarmup(7, Resolution.Daily); diff --git a/Algorithm.CSharp/OptionUniverseFilterGreeksRegressionAlgorithm.cs b/Algorithm.CSharp/OptionUniverseFilterGreeksRegressionAlgorithm.cs index 269db4120b82..b328b16a21de 100644 --- a/Algorithm.CSharp/OptionUniverseFilterGreeksRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionUniverseFilterGreeksRegressionAlgorithm.cs @@ -62,8 +62,8 @@ public override void Initialize() MaxGamma = 0.0006m; MinVega = 0.01m; MaxVega = 1.5m; - MinTheta = -2.0m; - MaxTheta = -0.5m; + MinTheta = -730m; + MaxTheta = -182.5m; MinRho = 0.5m; MaxRho = 3.0m; MinIv = 1.0m; diff --git a/Algorithm.CSharp/QLOptionPricingModelRegressionAlgorithm.cs b/Algorithm.CSharp/QLOptionPricingModelRegressionAlgorithm.cs new file mode 100644 index 000000000000..cb6924cb3588 --- /dev/null +++ b/Algorithm.CSharp/QLOptionPricingModelRegressionAlgorithm.cs @@ -0,0 +1,145 @@ +/* + * 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.Collections.Generic; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using QuantConnect.Securities.Option; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// This example demonstrates how to override the option pricing model with the + /// for a given option security. + /// + public class QLOptionPricingModelRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private bool _checked; + + private Option _option; + + public override void Initialize() + { + SetStartDate(2015, 12, 24); + SetEndDate(2015, 12, 24); + SetCash(100000); + + var equity = AddEquity("GOOG"); + _option = AddOption(equity.Symbol); + _option.SetFilter(u => u.Strikes(-2, +2).Expiration(0, 180)); + + // Set the option price model to the default QL model + _option.SetPriceModel(QLOptionPriceModelProvider.Instance.GetOptionPriceModel(_option.Symbol)); + + if (_option.PriceModel is not QLOptionPriceModel) + { + throw new RegressionTestException("Option pricing model was not set to QLOptionPriceModel, which should be the default"); + } + } + + public override void OnData(Slice slice) + { + if (!_checked && slice.OptionChains.TryGetValue(_option.Symbol, out var chain)) + { + if (_option.PriceModel is not QLOptionPriceModel) + { + throw new RegressionTestException("Option pricing model was not set to QLOptionPriceModel"); + } + + foreach (var contract in chain) + { + var theoreticalPrice = contract.TheoreticalPrice; + var iv = contract.ImpliedVolatility; + var greeks = contract.Greeks; + + Log($"{contract.Symbol}:: Theoretical Price: {theoreticalPrice}, IV: {iv}, " + + $"Delta: {greeks.Delta}, Gamma: {greeks.Gamma}, Vega: {greeks.Vega}, " + + $"Theta: {greeks.Theta}, Rho: {greeks.Rho}, Lambda: {greeks.Lambda}"); + + _checked |= true; + } + } + } + + public override void OnEndOfAlgorithm() + { + if (!_checked) + { + throw new RegressionTestException("Option chain was never received."); + } + } + + /// + /// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm. + /// + public bool CanRunLocally { get; } = true; + + /// + /// This is used by the regression test system to indicate which languages this algorithm is written in. + /// + public List Languages { get; } = new() { Language.CSharp, Language.Python }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public virtual long DataPoints => 37131; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 0; + + /// + /// Final status of the algorithm + /// + public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed; + + /// + /// This is used by the regression test system to indicate what the expected statistics are from running the algorithm + /// + public Dictionary ExpectedStatistics => new Dictionary + { + {"Total Orders", "0"}, + {"Average Win", "0%"}, + {"Average Loss", "0%"}, + {"Compounding Annual Return", "0%"}, + {"Drawdown", "0%"}, + {"Expectancy", "0"}, + {"Start Equity", "100000"}, + {"End Equity", "100000"}, + {"Net Profit", "0%"}, + {"Sharpe Ratio", "0"}, + {"Sortino Ratio", "0"}, + {"Probabilistic Sharpe Ratio", "0%"}, + {"Loss Rate", "0%"}, + {"Win Rate", "0%"}, + {"Profit-Loss Ratio", "0"}, + {"Alpha", "0"}, + {"Beta", "0"}, + {"Annual Standard Deviation", "0"}, + {"Annual Variance", "0"}, + {"Information Ratio", "0"}, + {"Tracking Error", "0"}, + {"Treynor Ratio", "0"}, + {"Total Fees", "$0.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", ""}, + {"Portfolio Turnover", "0%"}, + {"Drawdown Recovery", "0"}, + {"OrderListHash", "d41d8cd98f00b204e9800998ecf8427e"} + }; + } +} diff --git a/Algorithm.Python/BasicTemplateOptionsHistoryAlgorithm.py b/Algorithm.Python/BasicTemplateOptionsHistoryAlgorithm.py index ee711d9ef110..6968b1338779 100644 --- a/Algorithm.Python/BasicTemplateOptionsHistoryAlgorithm.py +++ b/Algorithm.Python/BasicTemplateOptionsHistoryAlgorithm.py @@ -38,7 +38,7 @@ def initialize(self): # set the pricing model for Greeks and volatility # find more pricing models https://www.quantconnect.com/lean/documentation/topic27704.html - option.price_model = OptionPriceModels.crank_nicolson_fd() + option.price_model = OptionPriceModels.black_scholes() # set the warm-up period for the pricing model self.set_warm_up(TimeSpan.from_days(4)) # set the benchmark to be the initial cash diff --git a/Algorithm.Python/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.py b/Algorithm.Python/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.py index c4d51c689dd9..c6e03c478896 100644 --- a/Algorithm.Python/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.py +++ b/Algorithm.Python/IndexOptionCallITMGreeksExpiryRegressionAlgorithm.py @@ -61,7 +61,6 @@ def on_data(self, data: Slice): deltas = [i.greeks.delta for i in self.sort_by_max_volume(data)] gammas = [i.greeks.gamma for i in self.sort_by_max_volume(data)] #data.option_chains.values().order_by_descending(y => y.contracts.values().sum(x => x.volume)).first().contracts.values().select(x => x.greeks.gamma).to_list() - lambda_ = [i.greeks.lambda_ for i in self.sort_by_max_volume(data)] #data.option_chains.values().order_by_descending(y => y.contracts.values().sum(x => x.volume)).first().contracts.values().select(x => x.greeks.lambda).to_list() rho = [i.greeks.rho for i in self.sort_by_max_volume(data)] #data.option_chains.values().order_by_descending(y => y.contracts.values().sum(x => x.volume)).first().contracts.values().select(x => x.greeks.rho).to_list() theta = [i.greeks.theta for i in self.sort_by_max_volume(data)] #data.option_chains.values().order_by_descending(y => y.contracts.values().sum(x => x.volume)).first().contracts.values().select(x => x.greeks.theta).to_list() vega = [i.greeks.vega for i in self.sort_by_max_volume(data)] #data.option_chains.values().order_by_descending(y => y.contracts.values().sum(x => x.volume)).first().contracts.values().select(x => x.greeks.vega).to_list() @@ -76,9 +75,6 @@ def on_data(self, data: Slice): if any([i for i in gammas if i == 0]): raise AssertionError("Option contract Gamma was equal to zero") - if any([i for i in lambda_ if lambda_ == 0]): - raise AssertionError("Option contract Lambda was equal to zero") - if any([i for i in rho if i == 0]): raise AssertionError("Option contract Rho was equal to zero") diff --git a/Algorithm.Python/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.py b/Algorithm.Python/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.py index a87ff4cbe450..f65beb8801c3 100644 --- a/Algorithm.Python/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.py +++ b/Algorithm.Python/OptionPriceModelForSupportedAmericanOptionRegressionAlgorithm.py @@ -27,7 +27,7 @@ def initialize(self): option.set_filter(lambda u: u.standards_only().strikes(-1, 1).expiration(0, 35)) # BaroneAdesiWhaley model supports American style options - option.price_model = OptionPriceModels.barone_adesi_whaley() + option.price_model = OptionPriceModels.QuantLib.barone_adesi_whaley() self.set_warmup(2, Resolution.DAILY) diff --git a/Algorithm.Python/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.py b/Algorithm.Python/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.py index 072af5b2496b..2c6e8dccdfde 100644 --- a/Algorithm.Python/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.py +++ b/Algorithm.Python/OptionPriceModelForUnsupportedAmericanOptionRegressionAlgorithm.py @@ -27,7 +27,7 @@ def initialize(self): option.set_filter(lambda u: u.standards_only().strikes(-1, 1).expiration(0, 35)) # BlackSholes model does not support American style options - option.price_model = OptionPriceModels.black_scholes() + option.price_model = OptionPriceModels.QuantLib.black_scholes() self.set_warmup(2, Resolution.DAILY) diff --git a/Algorithm.Python/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.py b/Algorithm.Python/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.py index ea2210f6bc13..d363b7972f06 100644 --- a/Algorithm.Python/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.py +++ b/Algorithm.Python/OptionPriceModelForUnsupportedEuropeanOptionRegressionAlgorithm.py @@ -25,7 +25,7 @@ def initialize(self): option = self.add_index_option("SPX", Resolution.HOUR) # BaroneAdesiWhaley model does not support European style options - option.price_model = OptionPriceModels.barone_adesi_whaley() + option.price_model = OptionPriceModels.QuantLib.barone_adesi_whaley() self.set_warmup(7, Resolution.DAILY) diff --git a/Algorithm.Python/OptionUniverseFilterGreeksRegressionAlgorithm.py b/Algorithm.Python/OptionUniverseFilterGreeksRegressionAlgorithm.py index a70ba54f355c..b48f7138b339 100644 --- a/Algorithm.Python/OptionUniverseFilterGreeksRegressionAlgorithm.py +++ b/Algorithm.Python/OptionUniverseFilterGreeksRegressionAlgorithm.py @@ -34,8 +34,8 @@ def initialize(self): self._max_gamma = 0.0006 self._min_vega = 0.01 self._max_vega = 1.5 - self._min_theta = -2.0 - self._max_theta = -0.5 + self._min_theta = -730.0 + self._max_theta = -182.5 self._min_rho = 0.5 self._max_rho = 3.0 self._min_iv = 1.0 diff --git a/Algorithm.Python/QLOptionPricingModelRegressionAlgorithm.py b/Algorithm.Python/QLOptionPricingModelRegressionAlgorithm.py new file mode 100644 index 000000000000..6c2854f4d69e --- /dev/null +++ b/Algorithm.Python/QLOptionPricingModelRegressionAlgorithm.py @@ -0,0 +1,59 @@ +# 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. + +from AlgorithmImports import * + +### +### This example demonstrates how to override the option pricing model with the +### for a given option security. +### +class QLOptionPricingModelRegressionAlgorithm(QCAlgorithm): + + def initialize(self): + self.set_start_date(2015, 12, 24) + self.set_end_date(2015, 12, 24) + self.set_cash(100000) + + equity = self.add_equity("GOOG") + self._option = self.add_option(equity.symbol) + self._option.set_filter(lambda u: u.strikes(-2, +2).expiration(0, 180)) + + # Set the option price model to the default QL model + self._option.set_price_model(QLOptionPriceModelProvider.INSTANCE.get_option_price_model(self._option.symbol)) + + if not isinstance(self._option.price_model, QLOptionPriceModel): + raise Exception("Option pricing model was not set to QLOptionPriceModel, which should be the default") + + self._checked = False + + def on_data(self, slice): + if self._checked: + return; + + chain = slice.option_chains.get(self._option.symbol) + if chain is not None: + if not isinstance(self._option.price_model, QLOptionPriceModel): + raise Exception("Option pricing model was not set to QLOptionPriceModel"); + + for contract in chain: + theoretical_price = contract.theoretical_price + iv = contract.implied_volatility + greeks = contract.greeks + self.log(f"{contract.symbol}:: Theoretical Price: {theoretical_price}, IV: {iv}, " + + f"Delta: {greeks.delta}, Gamma: {greeks.gamma}, Vega: {greeks.vega}, " + + f"Theta: {greeks.theta}, Rho: {greeks.rho}, Lambda: {greeks.lambda_}") + self._checked = True + + def on_end_of_algorithm(self): + if not self._checked: + raise Exception("Option chain was never received.") diff --git a/Common/Data/UniverseSelection/OptionUniverse.cs b/Common/Data/UniverseSelection/OptionUniverse.cs index 566af6b1a448..b2c30b7412dd 100644 --- a/Common/Data/UniverseSelection/OptionUniverse.cs +++ b/Common/Data/UniverseSelection/OptionUniverse.cs @@ -206,7 +206,7 @@ public static string ToCsv(Symbol symbol, decimal open, decimal high, decimal lo } return $"{GetOptionSymbolCsv(symbol)},{open},{high},{low},{close},{volume}," - + $"{openInterest},{impliedVolatility},{greeks?.Delta},{greeks?.Gamma},{greeks?.Vega},{greeks?.Theta},{greeks?.Rho}"; + + $"{openInterest},{impliedVolatility},{greeks?.Delta},{greeks?.Gamma},{greeks?.Vega},{greeks?.ThetaPerDay},{greeks?.Rho}"; } /// @@ -257,7 +257,7 @@ private class PreCalculatedGreeks : Greeks public override decimal Vega => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 2); - public override decimal Theta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 3); + public override decimal Theta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 3) * 365m; public override decimal Rho => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 4); diff --git a/Common/Extensions.cs b/Common/Extensions.cs index f34acb4b53c6..88343d1d5a27 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -3612,6 +3612,27 @@ public static Symbol AdjustSymbolByOffset(this Symbol symbol, uint offset) return symbol; } + /// + /// Helper method to get the mirror option symbol for a given option symbol + /// + /// The original option contract symbol + /// The mirror option contract symbol + public static Symbol GetMirrorOptionSymbol(this Symbol contractSymbol) + { + if (!contractSymbol.SecurityType.IsOption() || contractSymbol.IsCanonical()) + { + throw new ArgumentException(Messages.Extensions.NotAValidOptionSymbolForMirror); + } + + return Symbol.CreateOption(contractSymbol.Underlying, + contractSymbol.ID.Symbol, + contractSymbol.ID.Market, + contractSymbol.ID.OptionStyle, + contractSymbol.ID.OptionRight.Invert(), + contractSymbol.ID.StrikePrice, + contractSymbol.ID.Date); + } + /// /// Helper method to unsubscribe a given configuration, handling any required mapping /// diff --git a/Indicators/OptionPricingModelType.cs b/Common/Indicators/OptionPricingModelType.cs similarity index 100% rename from Indicators/OptionPricingModelType.cs rename to Common/Indicators/OptionPricingModelType.cs diff --git a/Common/Messages/Messages.QuantConnect.cs b/Common/Messages/Messages.QuantConnect.cs index d0aaf4fecfb3..9dec38803b95 100644 --- a/Common/Messages/Messages.QuantConnect.cs +++ b/Common/Messages/Messages.QuantConnect.cs @@ -256,6 +256,11 @@ public static class Extensions /// public static string GreatestCommonDivisorEmptyList = "The list of values cannot be empty"; + /// + /// Returns a string message saying that the symbol for which a mirror contract is being created is not a valid option symbol + /// + public static string NotAValidOptionSymbolForMirror = "Cannot create mirror contract for non-option symbol or canonical option symbol"; + /// /// Returns a string message saying the process of downloading data from the given url failed /// diff --git a/Common/Securities/FutureOption/FutureOption.cs b/Common/Securities/FutureOption/FutureOption.cs index b08c9e66c8bc..e09598c7e82d 100644 --- a/Common/Securities/FutureOption/FutureOption.cs +++ b/Common/Securities/FutureOption/FutureOption.cs @@ -60,7 +60,8 @@ public FutureOption(Symbol symbol, new SecurityPriceVariationModel(), currencyConverter, registeredTypes, - underlying + underlying, + null ) { BuyingPowerModel = new FuturesOptionsMarginModel(0, this); diff --git a/Common/Securities/IndexOption/IndexOption.cs b/Common/Securities/IndexOption/IndexOption.cs index 3347018f04a5..e23f91ba7246 100644 --- a/Common/Securities/IndexOption/IndexOption.cs +++ b/Common/Securities/IndexOption/IndexOption.cs @@ -38,6 +38,7 @@ public class IndexOption : Option.Option /// Cache of security objects /// Future underlying security /// Settlement type for the index option. Most index options are cash-settled. + /// The option price model provider public IndexOption(Symbol symbol, SecurityExchangeHours exchangeHours, Cash quoteCurrency, @@ -46,7 +47,8 @@ public IndexOption(Symbol symbol, IRegisteredSecurityDataTypesProvider registeredTypes, SecurityCache securityCache, Security underlying, - SettlementType settlementType = SettlementType.Cash) + SettlementType settlementType = SettlementType.Cash, + IOptionPriceModelProvider priceModelProvider = null) : base(symbol, quoteCurrency, symbolProperties, @@ -63,7 +65,8 @@ public IndexOption(Symbol symbol, new IndexOptionPriceVariationModel(), currencyConverter, registeredTypes, - underlying + underlying, + priceModelProvider ) { ExerciseSettlement = settlementType; diff --git a/Common/Securities/Option/IOptionPriceModelProvider.cs b/Common/Securities/Option/IOptionPriceModelProvider.cs new file mode 100644 index 000000000000..bc0b016503df --- /dev/null +++ b/Common/Securities/Option/IOptionPriceModelProvider.cs @@ -0,0 +1,34 @@ +/* + * 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 QuantConnect.Indicators; + +namespace QuantConnect.Securities.Option +{ + /// + /// Provides option price models for option securities + /// + public interface IOptionPriceModelProvider + { + /// + /// Gets the option price model for the specified option symbol. + /// If no pricing model is specified, the default option price model for the symbol security type will be returned. + /// + /// The symbol + /// The option pricing model type to use + /// The option price model for the given symbol + IOptionPriceModel GetOptionPriceModel(Symbol symbol, OptionPricingModelType? pricingModelType = null); + } +} diff --git a/Common/Securities/Option/Option.cs b/Common/Securities/Option/Option.cs index 81ea3aaf7634..38504b6e0e51 100644 --- a/Common/Securities/Option/Option.cs +++ b/Common/Securities/Option/Option.cs @@ -57,13 +57,15 @@ public class Option : Security, IDerivativeSecurity, IOptionPrice /// Currency converter used to convert /// instances into units of the account currency /// Provides all data types registered in the algorithm + /// The option price model provider /// Used in testing public Option(SecurityExchangeHours exchangeHours, SubscriptionDataConfig config, Cash quoteCurrency, OptionSymbolProperties symbolProperties, ICurrencyConverter currencyConverter, - IRegisteredSecurityDataTypesProvider registeredTypes) + IRegisteredSecurityDataTypesProvider registeredTypes, + IOptionPriceModelProvider priceModelProvider = null) : this(config.Symbol, quoteCurrency, symbolProperties, @@ -80,7 +82,8 @@ public Option(SecurityExchangeHours exchangeHours, new SecurityPriceVariationModel(), currencyConverter, registeredTypes, - null) + null, + priceModelProvider) { AddData(config); SetDataNormalizationMode(DataNormalizationMode.Raw); @@ -98,6 +101,7 @@ public Option(SecurityExchangeHours exchangeHours, /// Provides all data types registered in the algorithm /// Cache to store security information /// Future underlying security + /// The option price model provider public Option(Symbol symbol, SecurityExchangeHours exchangeHours, Cash quoteCurrency, @@ -105,8 +109,9 @@ public Option(Symbol symbol, ICurrencyConverter currencyConverter, IRegisteredSecurityDataTypesProvider registeredTypes, SecurityCache securityCache, - Security underlying) - : this(symbol, + Security underlying, + IOptionPriceModelProvider priceModelProvider = null) + : this (symbol, quoteCurrency, symbolProperties, new OptionExchange(exchangeHours), @@ -122,7 +127,8 @@ public Option(Symbol symbol, new SecurityPriceVariationModel(), currencyConverter, registeredTypes, - underlying) + underlying, + priceModelProvider) { } @@ -149,7 +155,8 @@ protected Option(Symbol symbol, IPriceVariationModel priceVariationModel, ICurrencyConverter currencyConverter, IRegisteredSecurityDataTypesProvider registeredTypesProvider, - Security underlying + Security underlying, + IOptionPriceModelProvider priceModelProvider ) : base( symbol, quoteCurrency, @@ -173,18 +180,7 @@ Security underlying ExerciseSettlement = SettlementType.PhysicalDelivery; SetDataNormalizationMode(DataNormalizationMode.Raw); OptionExerciseModel = new DefaultExerciseModel(); - PriceModel = symbol.ID.OptionStyle switch - { - // CRR model has the best accuracy and speed suggested by - // Branka, Zdravka & Tea (2014). Numerical Methods versus Bjerksund and Stensland Approximations for American Options Pricing. - // International Journal of Economics and Management Engineering. 8:4. - // Available via: https://downloads.dxfeed.com/specifications/dxLibOptions/Numerical-Methods-versus-Bjerksund-and-Stensland-Approximations-for-American-Options-Pricing-.pdf - // Also refer to OptionPriceModelTests.MatchesIBGreeksBulk() test, - // we select the most accurate and computational efficient model - OptionStyle.American => OptionPriceModels.BinomialCoxRossRubinstein(), - OptionStyle.European => OptionPriceModels.BlackScholes(), - _ => throw new ArgumentException("Invalid OptionStyle") - }; + PriceModel = (priceModelProvider ?? QLOptionPriceModelProvider.Instance).GetOptionPriceModel(symbol); Holdings = new OptionHolding(this, currencyConverter); _symbolProperties = (OptionSymbolProperties)symbolProperties; SetFilter(-1, 1, TimeSpan.Zero, TimeSpan.FromDays(35)); diff --git a/Common/Securities/Option/OptionPriceModelResult.cs b/Common/Securities/Option/OptionPriceModelResult.cs index 46e9eaa0c70a..eb4ecc5f18e5 100644 --- a/Common/Securities/Option/OptionPriceModelResult.cs +++ b/Common/Securities/Option/OptionPriceModelResult.cs @@ -29,13 +29,24 @@ public class OptionPriceModelResult /// public static OptionPriceModelResult None { get; } = new(0, NullGreeks.Instance); + private Lazy _theoreticalPrice; private Lazy _greeks; private Lazy _impliedVolatility; /// /// Gets the theoretical price as computed by the /// - public decimal TheoreticalPrice { get; set; } + public decimal TheoreticalPrice + { + get + { + return _theoreticalPrice.Value; + } + set + { + _theoreticalPrice = new Lazy(() => value, isThreadSafe: false); + } + } /// /// Gets the implied volatility of the option contract @@ -80,10 +91,8 @@ public OptionPriceModelResult() /// The theoretical price computed by the price model /// The sensitivities (greeks) computed by the price model public OptionPriceModelResult(decimal theoreticalPrice, Greeks greeks) + : this(() => theoreticalPrice, () => decimal.Zero, () => greeks) { - TheoreticalPrice = theoreticalPrice; - _impliedVolatility = new Lazy(() => 0m, isThreadSafe: false); - _greeks = new Lazy(() => greeks, isThreadSafe: false); } /// @@ -104,8 +113,19 @@ public OptionPriceModelResult(decimal theoreticalPrice, decimal impliedVolatilit /// The calculated implied volatility /// The sensitivities (greeks) computed by the price model public OptionPriceModelResult(decimal theoreticalPrice, Func impliedVolatility, Func greeks) + : this(() => theoreticalPrice, impliedVolatility, greeks) + { + } + + /// + /// Initializes a new instance of the class with lazy calculations of implied volatility and greeks + /// + /// The theoretical price computed by the price model + /// The calculated implied volatility + /// The sensitivities (greeks) computed by the price model + public OptionPriceModelResult(Func theoreticalPrice, Func impliedVolatility, Func greeks) { - TheoreticalPrice = theoreticalPrice; + _theoreticalPrice = new Lazy(theoreticalPrice, isThreadSafe: false); _impliedVolatility = new Lazy(impliedVolatility, isThreadSafe: false); _greeks = new Lazy(greeks, isThreadSafe: false); } diff --git a/Common/Securities/Option/OptionPriceModels.QuantLib.cs b/Common/Securities/Option/OptionPriceModels.QuantLib.cs new file mode 100644 index 000000000000..c074bf2baaa2 --- /dev/null +++ b/Common/Securities/Option/OptionPriceModels.QuantLib.cs @@ -0,0 +1,191 @@ +/* + * 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 QLNet; +using System; +using System.Linq; +using Fasterflect; + +namespace QuantConnect.Securities.Option +{ + using PricingEngineFuncEx = Func; + + public static partial class OptionPriceModels + { + /// + /// Static class contains definitions of major option pricing models that can be used in LEAN, + /// based on QuantLib implementations. + /// + /// + /// To introduce particular model into algorithm add the following line to the algorithm's Initialize() method: + /// + /// option.PriceModel = OptionPriceModels.QuantLib.BjerksundStensland(); // Option pricing model of choice + /// + /// + public static class QuantLib + { + private const int _timeStepsFD = 100; + + /// + /// Creates pricing engine by engine type name. + /// + /// QL price engine name + /// The risk free rate + /// List of option styles supported by the pricing model. It defaults to both American and European option styles + /// New option price model instance of specific engine + public static IOptionPriceModel Create(string priceEngineName, decimal riskFree, OptionStyle[] allowedOptionStyles = null) + { + var type = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic) + .SelectMany(a => a.GetTypes()) + .Where(s => s.Implements(typeof(IPricingEngine))) + .FirstOrDefault(t => t.FullName?.EndsWith(priceEngineName, StringComparison.InvariantCulture) == true); + + return new QLOptionPriceModel(process => (IPricingEngine)Activator.CreateInstance(type, process), + riskFreeRateEstimator: new ConstantQLRiskFreeRateEstimator(riskFree), + allowedOptionStyles: allowedOptionStyles); + } + + /// + /// Pricing engine for European vanilla options using analytical formula. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_analytic_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BlackScholes() + { + return QLOptionPriceModelProvider.Instance.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BlackScholes); + } + + /// + /// Barone-Adesi and Whaley pricing engine for American options (1987) + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_barone_adesi_whaley_approximation_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BaroneAdesiWhaley() + { + return new QLOptionPriceModel(process => new BaroneAdesiWhaleyApproximationEngine(process), + allowedOptionStyles: new[] { OptionStyle.American }); + } + + /// + /// Bjerksund and Stensland pricing engine for American options (1993) + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_bjerksund_stensland_approximation_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BjerksundStensland() + { + return new QLOptionPriceModel(process => new BjerksundStenslandApproximationEngine(process), + allowedOptionStyles: new[] { OptionStyle.American }); + } + + /// + /// Pricing engine for European vanilla options using integral approach. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_integral_engine.html + /// + /// New option price model instance + public static IOptionPriceModel Integral() + { + return new QLOptionPriceModel(process => new IntegralEngine(process), + allowedOptionStyles: new[] { OptionStyle.European }); + } + + /// + /// Pricing engine for European and American options using finite-differences. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel CrankNicolsonFD() + { + PricingEngineFuncEx pricingEngineFunc = (symbol, process) => + symbol.ID.OptionStyle == OptionStyle.American + ? new FDAmericanEngine(process, _timeStepsFD, _timeStepsFD - 1) + : new FDEuropeanEngine(process, _timeStepsFD, _timeStepsFD - 1); + + return new QLOptionPriceModel(pricingEngineFunc); + } + + /// + /// Pricing engine for European and American vanilla options using binomial trees. Jarrow-Rudd model. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BinomialJarrowRudd() + { + return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, QLOptionPriceModelProvider.TimeStepsBinomial)); + } + + + /// + /// Pricing engine for European and American vanilla options using binomial trees. Cox-Ross-Rubinstein(CRR) model. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BinomialCoxRossRubinstein() + { + return QLOptionPriceModelProvider.Instance.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BinomialCoxRossRubinstein); + } + + /// + /// Pricing engine for European and American vanilla options using binomial trees. Additive Equiprobabilities model. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel AdditiveEquiprobabilities() + { + return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, QLOptionPriceModelProvider.TimeStepsBinomial)); + } + + /// + /// Pricing engine for European and American vanilla options using binomial trees. Trigeorgis model. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BinomialTrigeorgis() + { + return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, QLOptionPriceModelProvider.TimeStepsBinomial)); + } + + /// + /// Pricing engine for European and American vanilla options using binomial trees. Tian model. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BinomialTian() + { + return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, QLOptionPriceModelProvider.TimeStepsBinomial)); + } + + /// + /// Pricing engine for European and American vanilla options using binomial trees. Leisen-Reimer model. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BinomialLeisenReimer() + { + return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, QLOptionPriceModelProvider.TimeStepsBinomial)); + } + + /// + /// Pricing engine for European and American vanilla options using binomial trees. Joshi model. + /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// + /// New option price model instance + public static IOptionPriceModel BinomialJoshi() + { + return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, QLOptionPriceModelProvider.TimeStepsBinomial)); + } + } + } +} diff --git a/Common/Securities/Option/OptionPriceModels.cs b/Common/Securities/Option/OptionPriceModels.cs index 6132ccdcbcdd..2001ac884a57 100644 --- a/Common/Securities/Option/OptionPriceModels.cs +++ b/Common/Securities/Option/OptionPriceModels.cs @@ -13,178 +13,60 @@ * limitations under the License. */ -using QLNet; using System; -using System.Linq; -using Fasterflect; namespace QuantConnect.Securities.Option { - using PricingEngineFuncEx = Func; - /// /// Static class contains definitions of major option pricing models that can be used in LEAN /// /// /// To introduce particular model into algorithm add the following line to the algorithm's Initialize() method: /// - /// option.PriceModel = OptionPriceModels.BjerksundStensland(); // Option pricing model of choice + /// option.PriceModel = OptionPriceModels.BlackScholes(); // Option pricing model of choice /// /// - public static class OptionPriceModels + public static partial class OptionPriceModels { - private const int _timeStepsBinomial = 100; - private const int _timeStepsFD = 100; - - /// - /// Creates pricing engine by engine type name. - /// - /// QL price engine name - /// The risk free rate - /// List of option styles supported by the pricing model. It defaults to both American and European option styles - /// New option price model instance of specific engine - public static IOptionPriceModel Create(string priceEngineName, decimal riskFree, OptionStyle[] allowedOptionStyles = null) - { - var type = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.IsDynamic) - .SelectMany(a => a.GetTypes()) - .Where(s => s.Implements(typeof(IPricingEngine))) - .FirstOrDefault(t => t.FullName?.EndsWith(priceEngineName, StringComparison.InvariantCulture) == true); - - return new QLOptionPriceModel(process => (IPricingEngine)Activator.CreateInstance(type, process), - riskFreeRateEstimator: new ConstantQLRiskFreeRateEstimator(riskFree), - allowedOptionStyles: allowedOptionStyles); - } - - /// - /// Pricing engine for European vanilla options using analytical formula. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_analytic_european_engine.html - /// - /// New option price model instance - public static IOptionPriceModel BlackScholes() - { - return new QLOptionPriceModel(process => new AnalyticEuropeanEngine(process), - allowedOptionStyles: new[] { OptionStyle.European }); - } - /// - /// Barone-Adesi and Whaley pricing engine for American options (1987) - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_barone_adesi_whaley_approximation_engine.html + /// Default option price model provider used by LEAN when creating price models. /// - /// New option price model instance - public static IOptionPriceModel BaroneAdesiWhaley() - { - return new QLOptionPriceModel(process => new BaroneAdesiWhaleyApproximationEngine(process), - allowedOptionStyles: new[] { OptionStyle.American }); - } + internal static IOptionPriceModelProvider DefaultPriceModelProvider { get; set; } /// - /// Bjerksund and Stensland pricing engine for American options (1993) - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_bjerksund_stensland_approximation_engine.html + /// Null pricing engine that returns the current price as the option theoretical price. + /// It will also set the option Greeks and implied volatility to zero, effectively disabling the pricing. /// - /// New option price model instance - public static IOptionPriceModel BjerksundStensland() + public static IOptionPriceModel Null() { - return new QLOptionPriceModel(process => new BjerksundStenslandApproximationEngine(process), - allowedOptionStyles: new[] { OptionStyle.American }); + return new CurrentPriceOptionPriceModel(); } /// - /// Pricing engine for European vanilla options using integral approach. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_integral_engine.html + /// Pricing engine for Black-Scholes model. /// /// New option price model instance - public static IOptionPriceModel Integral() - { - return new QLOptionPriceModel(process => new IntegralEngine(process), - allowedOptionStyles: new[] { OptionStyle.European }); - } - - /// - /// Pricing engine for European and American options using finite-differences. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html - /// - /// New option price model instance - public static IOptionPriceModel CrankNicolsonFD() - { - PricingEngineFuncEx pricingEngineFunc = (symbol, process) => - symbol.ID.OptionStyle == OptionStyle.American - ? new FDAmericanEngine(process, _timeStepsFD, _timeStepsFD - 1) - : new FDEuropeanEngine(process, _timeStepsFD, _timeStepsFD - 1); - - return new QLOptionPriceModel(pricingEngineFunc); - } - - /// - /// Pricing engine for European and American vanilla options using binomial trees. Jarrow-Rudd model. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html - /// - /// New option price model instance - public static IOptionPriceModel BinomialJarrowRudd() + public static IOptionPriceModel BlackScholes() { - return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, _timeStepsBinomial)); + return DefaultPriceModelProvider.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BlackScholes); } - /// - /// Pricing engine for European and American vanilla options using binomial trees. Cox-Ross-Rubinstein(CRR) model. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// Pricing engine for Cox-Ross-Rubinstein (CRR) model. /// /// New option price model instance public static IOptionPriceModel BinomialCoxRossRubinstein() { - return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, _timeStepsBinomial)); + return DefaultPriceModelProvider.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.BinomialCoxRossRubinstein); } /// - /// Pricing engine for European and American vanilla options using binomial trees. Additive Equiprobabilities model. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html + /// Pricing engine for forward binomial tree model. /// /// New option price model instance - public static IOptionPriceModel AdditiveEquiprobabilities() + public static IOptionPriceModel ForwardTree() { - return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, _timeStepsBinomial)); + return DefaultPriceModelProvider.GetOptionPriceModel(Symbol.Empty, Indicators.OptionPricingModelType.ForwardTree); } - - /// - /// Pricing engine for European and American vanilla options using binomial trees. Trigeorgis model. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html - /// - /// New option price model instance - public static IOptionPriceModel BinomialTrigeorgis() - { - return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, _timeStepsBinomial)); - } - - /// - /// Pricing engine for European and American vanilla options using binomial trees. Tian model. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html - /// - /// New option price model instance - public static IOptionPriceModel BinomialTian() - { - return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, _timeStepsBinomial)); - } - - /// - /// Pricing engine for European and American vanilla options using binomial trees. Leisen-Reimer model. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html - /// - /// New option price model instance - public static IOptionPriceModel BinomialLeisenReimer() - { - return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, _timeStepsBinomial)); - } - - /// - /// Pricing engine for European and American vanilla options using binomial trees. Joshi model. - /// QuantLib reference: http://quantlib.org/reference/class_quant_lib_1_1_f_d_european_engine.html - /// - /// New option price model instance - public static IOptionPriceModel BinomialJoshi() - { - return new QLOptionPriceModel(process => new BinomialVanillaEngine(process, _timeStepsBinomial)); - } - } } diff --git a/Common/Securities/Option/QLOptionPriceModelProvider.cs b/Common/Securities/Option/QLOptionPriceModelProvider.cs new file mode 100644 index 000000000000..0ae53a331e4a --- /dev/null +++ b/Common/Securities/Option/QLOptionPriceModelProvider.cs @@ -0,0 +1,76 @@ +/* + * 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 QLNet; +using QuantConnect.Indicators; +using System; + +namespace QuantConnect.Securities.Option +{ + /// + /// Provides option price models for option securities based on QuantLib implementations + /// + public class QLOptionPriceModelProvider : IOptionPriceModelProvider + { + internal const int TimeStepsBinomial = 100; + + /// + /// Singleton instance of the + /// + public static QLOptionPriceModelProvider Instance { get; } = new(); + + private QLOptionPriceModelProvider() + { + } + + /// + /// Gets the option price model for the specified option symbol + /// + /// The symbol + /// The option pricing model type to use + /// The option price model for the given symbol + public IOptionPriceModel GetOptionPriceModel(Symbol symbol, OptionPricingModelType? pricingModelType = null) + { + if (pricingModelType.HasValue) + { + return GetOptionPriceModel(pricingModelType.Value); + } + + return symbol.ID.OptionStyle switch + { + // CRR model has the best accuracy and speed suggested by + // Branka, Zdravka & Tea (2014). Numerical Methods versus Bjerksund and Stensland Approximations for American Options Pricing. + // International Journal of Economics and Management Engineering. 8:4. + // Available via: https://downloads.dxfeed.com/specifications/dxLibOptions/Numerical-Methods-versus-Bjerksund-and-Stensland-Approximations-for-American-Options-Pricing-.pdf + // Also refer to OptionPriceModelTests.MatchesIBGreeksBulk() test, + // we select the most accurate and computational efficient model + OptionStyle.American => GetOptionPriceModel(OptionPricingModelType.BinomialCoxRossRubinstein), + OptionStyle.European => GetOptionPriceModel(OptionPricingModelType.BlackScholes), + _ => throw new ArgumentException("Invalid OptionStyle") + }; + } + + private static QLOptionPriceModel GetOptionPriceModel(OptionPricingModelType pricingModelType) + { + return pricingModelType switch + { + OptionPricingModelType.BlackScholes => new QLOptionPriceModel(process => new AnalyticEuropeanEngine(process), + allowedOptionStyles: [OptionStyle.European]), + OptionPricingModelType.BinomialCoxRossRubinstein => new QLOptionPriceModel(process => new BinomialVanillaEngine(process, TimeStepsBinomial)), + _ => throw new ArgumentException($"Unsupported pricing model type: {pricingModelType}") + }; + } + } +} diff --git a/Common/Securities/SecurityService.cs b/Common/Securities/SecurityService.cs index 1cfd4e77cc7a..c6d820f895f5 100644 --- a/Common/Securities/SecurityService.cs +++ b/Common/Securities/SecurityService.cs @@ -21,6 +21,7 @@ using QuantConnect.Interfaces; using System.Collections.Generic; using QuantConnect.Data.Market; +using QuantConnect.Securities.Option; namespace QuantConnect.Securities { @@ -36,6 +37,7 @@ public class SecurityService : ISecurityService private readonly ISecurityInitializerProvider _securityInitializerProvider; private readonly SecurityCacheProvider _cacheProvider; private readonly IPrimaryExchangeProvider _primaryExchangeProvider; + private readonly IOptionPriceModelProvider _optionPriceModelProvider; private readonly IAlgorithm _algorithm; private bool _isLiveMode; private bool _modelsMismatchWarningSent; @@ -50,7 +52,8 @@ public SecurityService(CashBook cashBook, IRegisteredSecurityDataTypesProvider registeredTypes, SecurityCacheProvider cacheProvider, IPrimaryExchangeProvider primaryExchangeProvider = null, - IAlgorithm algorithm = null) + IAlgorithm algorithm = null, + IOptionPriceModelProvider optionPriceModelProvider = null) { _cashBook = cashBook; _registeredTypes = registeredTypes; @@ -60,6 +63,8 @@ public SecurityService(CashBook cashBook, _cacheProvider = cacheProvider; _primaryExchangeProvider = primaryExchangeProvider; _algorithm = algorithm; + _optionPriceModelProvider = optionPriceModelProvider; + OptionPriceModels.DefaultPriceModelProvider = _optionPriceModelProvider; } /// @@ -176,12 +181,12 @@ private Security CreateSecurity(Symbol symbol, case SecurityType.Option: if (addToSymbolCache) SymbolCache.Set(symbol.Underlying.Value, symbol.Underlying); - security = new Option.Option(symbol, exchangeHours, quoteCash, new Option.OptionSymbolProperties(symbolProperties), _cashBook, _registeredTypes, cache, underlying); + security = new Option.Option(symbol, exchangeHours, quoteCash, new Option.OptionSymbolProperties(symbolProperties), _cashBook, _registeredTypes, cache, underlying, _optionPriceModelProvider); break; case SecurityType.IndexOption: if (addToSymbolCache) SymbolCache.Set(symbol.Underlying.Value, symbol.Underlying); - security = new IndexOption.IndexOption(symbol, exchangeHours, quoteCash, new IndexOption.IndexOptionSymbolProperties(symbolProperties), _cashBook, _registeredTypes, cache, underlying); + security = new IndexOption.IndexOption(symbol, exchangeHours, quoteCash, new IndexOption.IndexOptionSymbolProperties(symbolProperties), _cashBook, _registeredTypes, cache, underlying, priceModelProvider: _optionPriceModelProvider); break; case SecurityType.FutureOption: diff --git a/Engine/Engine.cs b/Engine/Engine.cs index 5bd6209d0afc..97f7f465b20f 100644 --- a/Engine/Engine.cs +++ b/Engine/Engine.cs @@ -26,6 +26,7 @@ using QuantConnect.Data; using QuantConnect.Data.Auxiliary; using QuantConnect.Exceptions; +using QuantConnect.Indicators; using QuantConnect.Interfaces; using QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.Lean.Engine.HistoricalData; @@ -151,7 +152,8 @@ public void Run(AlgorithmNodePacket job, AlgorithmManager manager, string assemb registeredTypesProvider, new SecurityCacheProvider(algorithm.Portfolio), mapFilePrimaryExchangeProvider, - algorithm); + algorithm, + IndicatorBasedOptionPriceModelProvider.Instance); algorithm.Securities.SetSecurityService(securityService); diff --git a/Indicators/GreeksIndicators.cs b/Indicators/GreeksIndicators.cs new file mode 100644 index 000000000000..93d42a36e069 --- /dev/null +++ b/Indicators/GreeksIndicators.cs @@ -0,0 +1,151 @@ +/* + * 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 QuantConnect.Data; +using QuantConnect.Data.Market; +using QuantConnect.Securities.Option; + +namespace QuantConnect.Indicators +{ + /// + /// Helper class that holds and updates the greeks indicators + /// + public class GreeksIndicators + { + private readonly static IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider(); + private readonly static IDividendYieldModel _constantDividendYieldModel = new ConstantDividendYieldModel(0); + + private readonly Symbol _optionSymbol; + private readonly Symbol _mirrorOptionSymbol; + + /// + /// Gets the implied volatility indicator + /// + public ImpliedVolatility ImpliedVolatility { get; } + + /// + /// Gets the delta indicator + /// + public Delta Delta { get; } + + /// + /// Gets the gamma indicator + /// + public Gamma Gamma { get; } + + /// + /// Gets the vega indicator + /// + public Vega Vega { get; } + + /// + /// Gets the theta indicator + /// + public Theta Theta { get; } + + /// + /// Gets the rho indicator + /// + public Rho Rho { get; } + + /// + /// Gets the interest rate used in the calculations + /// + public decimal InterestRate => Delta.RiskFreeRate; + + /// + /// Gets the dividend yield used in the calculations + /// + public decimal DividendYield => Delta.DividendYield; + + /// + /// Gets the current greeks values + /// + public Greeks Greeks => new Greeks(Delta, Gamma, Vega, Theta * 365m, Rho, 0m); + + /// + /// Whether the mirror option is set and will be used in the calculations. + /// + public bool UseMirrorOption => _mirrorOptionSymbol != null; + + /// + /// Gets the current result of the greeks indicators, including the implied volatility, theoretical price and greeks values + /// + public OptionPriceModelResult CurrentResult => new OptionPriceModelResult(ImpliedVolatility.TheoreticalPrice, ImpliedVolatility, Greeks); + + /// + /// Gets the dividend yield model to be used in the calculations for the specified option symbol. + /// + public static IDividendYieldModel GetDividendYieldModel(Symbol optionSymbol) + { + return optionSymbol.SecurityType != SecurityType.IndexOption + ? DividendYieldProvider.CreateForOption(optionSymbol) + : _constantDividendYieldModel; + } + + /// + /// Creates a new instance of the class + /// + public GreeksIndicators(Symbol optionSymbol, Symbol mirrorOptionSymbol, OptionPricingModelType? optionModel = null, + OptionPricingModelType? ivModel = null, IDividendYieldModel dividendYieldModel = null, + IRiskFreeInterestRateModel riskFreeInterestRateModel = null) + { + _optionSymbol = optionSymbol; + _mirrorOptionSymbol = mirrorOptionSymbol; + + dividendYieldModel ??= GetDividendYieldModel(optionSymbol); + riskFreeInterestRateModel ??= _interestRateProvider; + + ImpliedVolatility = new ImpliedVolatility(_optionSymbol, riskFreeInterestRateModel, dividendYieldModel, _mirrorOptionSymbol, ivModel); + Delta = new Delta(_optionSymbol, riskFreeInterestRateModel, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel); + Gamma = new Gamma(_optionSymbol, riskFreeInterestRateModel, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel); + Vega = new Vega(_optionSymbol, riskFreeInterestRateModel, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel); + Theta = new Theta(_optionSymbol, riskFreeInterestRateModel, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel); + Rho = new Rho(_optionSymbol, riskFreeInterestRateModel, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel); + + Delta.ImpliedVolatility = ImpliedVolatility; + Gamma.ImpliedVolatility = ImpliedVolatility; + Vega.ImpliedVolatility = ImpliedVolatility; + Theta.ImpliedVolatility = ImpliedVolatility; + Rho.ImpliedVolatility = ImpliedVolatility; + } + + /// + /// Feeds the specified data into the indicators + /// + public void Update(IBaseData data) + { + ImpliedVolatility.Update(data); + Delta.Update(data); + Gamma.Update(data); + Vega.Update(data); + Theta.Update(data); + Rho.Update(data); + } + + /// + /// Resets the indicators to their default state + /// + public void Reset() + { + ImpliedVolatility.Reset(); + Delta.Reset(); + Gamma.Reset(); + Vega.Reset(); + Theta.Reset(); + Rho.Reset(); + } + } +} diff --git a/Indicators/ImpliedVolatility.cs b/Indicators/ImpliedVolatility.cs index 6b3b656f1d4f..d18b0e7bdd13 100644 --- a/Indicators/ImpliedVolatility.cs +++ b/Indicators/ImpliedVolatility.cs @@ -13,13 +13,13 @@ * limitations under the License. */ -using System; using MathNet.Numerics.RootFinding; using Python.Runtime; using QuantConnect.Data; using QuantConnect.Logging; using QuantConnect.Python; using QuantConnect.Util; +using System; namespace QuantConnect.Indicators { @@ -31,6 +31,11 @@ public class ImpliedVolatility : OptionIndicatorBase private decimal _impliedVolatility; private Func SmoothingFunction; + /// + /// Gets the theoretical option price + /// + public IndicatorBase TheoreticalPrice { get; } + /// /// Initializes a new instance of the ImpliedVolatility class /// @@ -63,6 +68,31 @@ public ImpliedVolatility(string name, Symbol option, IRiskFreeInterestRateModel return impliedVol; }; } + + TheoreticalPrice = new FunctionalIndicator($"{name}_TheoreticalPrice", + (iv) => + { + // Volatility is zero, price is not changing, can return current theoretical price. + // This also allows us avoid errors in calculation when IV is zero. + if (iv.Value == 0m) + { + return TheoreticalPrice.Current.Value; + } + + var theoreticalPrice = CalculateTheoreticalPrice((double)iv.Value, (double)UnderlyingPrice.Current.Value, (double)Strike, + OptionGreekIndicatorsHelper.TimeTillExpiry(Expiry, iv.EndTime), (double)RiskFreeRate.Current.Value, (double)DividendYield.Current.Value, + Right, optionModel); + try + { + return Convert.ToDecimal(theoreticalPrice); + } + catch (OverflowException) + { + return TheoreticalPrice.Current.Value; + } + }, + _ => IsReady) + .Of(this); } /// @@ -237,8 +267,17 @@ protected override decimal ComputeIndicator() return _impliedVolatility; } + /// + /// Resets this indicator and all sub-indicators + /// + public override void Reset() + { + TheoreticalPrice.Reset(); + base.Reset(); + } + // Calculate the theoretical option price - private static double TheoreticalPrice(double volatility, double spotPrice, double strikePrice, double timeTillExpiry, double riskFreeRate, + private static double CalculateTheoreticalPrice(double volatility, double spotPrice, double strikePrice, double timeTillExpiry, double riskFreeRate, double dividendYield, OptionRight optionType, OptionPricingModelType? optionModel = null) { if (timeTillExpiry <= 0) @@ -302,7 +341,7 @@ protected virtual decimal CalculateIV(decimal timeTillExpiry) decimal? impliedVol = null; try { - Func f = (vol) => TheoreticalPrice(vol, underlyingPrice, strike, timeTillExpiry, riskFreeRate, dividendYield, right, optionModel) - optionPrice; + Func f = (vol) => CalculateTheoreticalPrice(vol, underlyingPrice, strike, timeTillExpiry, riskFreeRate, dividendYield, right, optionModel) - optionPrice; impliedVol = Convert.ToDecimal(Brent.FindRoot(f, lowerBound, upperBound, accuracy, 100)); } catch diff --git a/Indicators/IndicatorBasedOptionPriceModel.cs b/Indicators/IndicatorBasedOptionPriceModel.cs new file mode 100644 index 000000000000..b82957134358 --- /dev/null +++ b/Indicators/IndicatorBasedOptionPriceModel.cs @@ -0,0 +1,204 @@ +/* + * 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 QuantConnect.Data; +using QuantConnect.Data.Market; +using QuantConnect.Logging; +using QuantConnect.Securities.Option; +using System; +using System.Linq; + +namespace QuantConnect.Indicators +{ + /// + /// Provides an implementation of that uses QuantConnect indicators + /// to provide a theoretical price for the option contract. + /// + public class IndicatorBasedOptionPriceModel : OptionPriceModel + { + private Symbol _contractSymbol; + private Symbol _mirrorContractSymbol; + private readonly OptionPricingModelType? _optionPricingModelType; + private readonly OptionPricingModelType? _ivModelType; + private IDividendYieldModel _dividendYieldModel; + private readonly IRiskFreeInterestRateModel _riskFreeInterestRateModel; + private readonly bool _userSpecifiedDividendYieldModel; + private readonly bool _useMirrorContract; + private GreeksIndicators _indicators; + + /// + /// Creates a new instance of the class + /// + /// The option pricing model type to be used by the indicators + /// The option pricing model type to be used by the implied volatility indicator + /// The dividend yield model to be used by the indicators + /// The risk free interest rate model to be used by the indicators + /// Whether to use the mirror contract when possible + public IndicatorBasedOptionPriceModel(OptionPricingModelType? optionModel = null, + OptionPricingModelType? ivModel = null, IDividendYieldModel dividendYieldModel = null, + IRiskFreeInterestRateModel riskFreeInterestRateModel = null, bool useMirrorContract = true) + { + _optionPricingModelType = optionModel; + _ivModelType = ivModel; + _dividendYieldModel = dividendYieldModel; + _riskFreeInterestRateModel = riskFreeInterestRateModel; + _useMirrorContract = useMirrorContract; + _userSpecifiedDividendYieldModel = dividendYieldModel != null; + } + + /// + /// Creates a new containing the theoretical price based on + /// QuantConnect indicators. + /// + /// The evaluation parameters + /// + /// An instance of containing the theoretical + /// price of the specified option contract. + /// + public override OptionPriceModelResult Evaluate(OptionPriceModelParameters parameters) + { + var contract = parameters.Contract; + var slice = parameters.Slice; + + // expired options have no price + if (contract.Time.Date > contract.Expiry.Date) + { + if (Log.DebuggingEnabled) + { + Log.Debug($"IndicatorBasedOptionPriceModel.Evaluate(). Expired {contract.Symbol}. Time > Expiry: {contract.Time.Date} > {contract.Expiry.Date}"); + } + return OptionPriceModelResult.None; + } + + var contractSymbol = _contractSymbol; + var mirrorContractSymbol = _mirrorContractSymbol; + var symbolsChanged = false; + // These models are supposed to be one per contract (security instance), so we cache the symbols to avoid calling + // GetMirrorOptionSymbol multiple times. If the contract changes by any reason, we just update the cached symbols. + if (contractSymbol != contract.Symbol) + { + contractSymbol = _contractSymbol = contract.Symbol; + + if (_useMirrorContract) + { + mirrorContractSymbol = _mirrorContractSymbol = contractSymbol.GetMirrorOptionSymbol(); + } + + if (!_userSpecifiedDividendYieldModel) + { + _dividendYieldModel = GreeksIndicators.GetDividendYieldModel(contractSymbol); + } + + symbolsChanged = true; + } + + BaseData underlyingData = null; + BaseData optionData = null; + BaseData mirrorOptionData = null; + + foreach (var useBars in new[] { true, false }) + { + if (useBars) + { + TradeBar underlyingTradeBar = null; + QuoteBar underlyingQuoteBar = null; + if ((slice.Bars.TryGetValue(contractSymbol.Underlying, out underlyingTradeBar) || + slice.QuoteBars.TryGetValue(contractSymbol.Underlying, out underlyingQuoteBar)) && + slice.QuoteBars.TryGetValue(contractSymbol, out var optionQuoteBar)) + { + underlyingData = (BaseData)underlyingTradeBar ?? underlyingQuoteBar; + optionData = optionQuoteBar; + + if (_useMirrorContract && slice.QuoteBars.TryGetValue(mirrorContractSymbol, out var mirrorOptionQuoteBar)) + { + mirrorOptionData = mirrorOptionQuoteBar; + } + + break; + } + } + else + { + if (slice.Ticks.TryGetValue(contractSymbol.Underlying, out var underlyingTicks) && + slice.Ticks.TryGetValue(contractSymbol, out var optionTicks)) + { + // Get last underlying trade tick + underlyingData = underlyingTicks.LastOrDefault(x => x.TickType == TickType.Trade); + if (underlyingData == null) + { + continue; + } + + // Get last option quote tick + optionData = optionTicks.LastOrDefault(x => x.TickType == TickType.Quote); + if (optionData == null) + { + underlyingData = null; + continue; + } + + // Try to get last mirror option quote tick + if (_useMirrorContract && slice.Ticks.TryGetValue(_mirrorContractSymbol, out var mirrorOptionTicks)) + { + mirrorOptionData = mirrorOptionTicks.LastOrDefault(x => x.TickType == TickType.Quote); + } + + break; + } + } + } + + if (underlyingData == null || optionData == null) + { + if (Log.DebuggingEnabled) + { + Log.Debug($"IndicatorBasedOptionPriceModel.Evaluate(). Missing data for {contractSymbol} or {contractSymbol.Underlying}."); + } + return OptionPriceModelResult.None; + } + + if (mirrorOptionData == null) + { + if (Log.DebuggingEnabled) + { + Log.Debug($"IndicatorBasedOptionPriceModel.Evaluate(). Missing data for mirror option {mirrorContractSymbol}. Using contract symbol only."); + } + // Null so that the indicators don't consider the mirror option and don't expect data for it + mirrorContractSymbol = null; + } + + if (_indicators == null || symbolsChanged || + // The mirror contract can go from null to non-null and vice versa, so we need to check if the symbol has changed in that case as well + (_indicators.UseMirrorOption && mirrorContractSymbol == null) || (!_indicators.UseMirrorOption && mirrorContractSymbol != null)) + { + // We'll try to reuse the indicators instance whenever possible + _indicators = new GreeksIndicators(contractSymbol, mirrorContractSymbol, _optionPricingModelType, _ivModelType, + _dividendYieldModel, _riskFreeInterestRateModel); + } + + _indicators.Update(underlyingData); + _indicators.Update(optionData); + if (mirrorOptionData != null) + { + _indicators.Update(mirrorOptionData); + } + + var result = _indicators.CurrentResult; + _indicators.Reset(); + + return result; + } + } +} diff --git a/Indicators/IndicatorBasedOptionPriceModelProvider.cs b/Indicators/IndicatorBasedOptionPriceModelProvider.cs new file mode 100644 index 000000000000..3e8186109e18 --- /dev/null +++ b/Indicators/IndicatorBasedOptionPriceModelProvider.cs @@ -0,0 +1,63 @@ +/* + * 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 QuantConnect.Data; +using QuantConnect.Securities.Option; + +namespace QuantConnect.Indicators +{ + /// + /// Provides option price models for option securities based on Lean's Greeks indicators + /// + public class IndicatorBasedOptionPriceModelProvider : IOptionPriceModelProvider + { + /// + /// Singleton instance of the + /// + public static IndicatorBasedOptionPriceModelProvider Instance { get; } = new(); + + private IndicatorBasedOptionPriceModelProvider() + { + } + + /// + /// Gets the option price model for the specified option symbol + /// + /// The symbol + /// The option pricing model type to use + /// The option price model for the given symbol + public IOptionPriceModel GetOptionPriceModel(Symbol symbol, OptionPricingModelType? pricingModelType = null) + { + return new IndicatorBasedOptionPriceModel(pricingModelType, pricingModelType); + } + + /// + /// Gets the option price model with the specified configuration + /// + /// The option pricing model type to be used by the indicators + /// The option pricing model type to be used by the implied volatility indicator + /// The dividend yield model to be used by the indicators + /// The risk free interest rate model to be used by the indicatorsv + /// Whether to use the mirror contract when possible + /// The option price model for the given symbol + public IOptionPriceModel GetOptionPriceModel(OptionPricingModelType? optionModel = null, + OptionPricingModelType? ivModel = null, IDividendYieldModel dividendYieldModel = null, + IRiskFreeInterestRateModel riskFreeInterestRateModel = null, + bool useMirrorContract = true) + { + return new IndicatorBasedOptionPriceModel(optionModel, ivModel, dividendYieldModel, riskFreeInterestRateModel, useMirrorContract); + } + } +} diff --git a/Tests/Algorithm/AlgorithmIndicatorsTests.cs b/Tests/Algorithm/AlgorithmIndicatorsTests.cs index e5636f9e34e7..dd0764671c4d 100644 --- a/Tests/Algorithm/AlgorithmIndicatorsTests.cs +++ b/Tests/Algorithm/AlgorithmIndicatorsTests.cs @@ -524,7 +524,7 @@ public void IndicatorHistoryIsSupportedInPythonForOptionsIndicators([Range(1, 4) Assert.AreEqual(390, dataFrame.GetAttr("shape")[0].GetAndDispose()); // Assert dataframe column names are current, price, oppositeprice and underlyingprice var columns = dataFrame.GetAttr("columns").InvokeMethod>("tolist"); - var expectedColumns = new[] { "current", "price", "oppositeprice", "underlyingprice" }; + var expectedColumns = new[] { "current", "price", "oppositeprice", "underlyingprice", "theoreticalprice" }; CollectionAssert.AreEquivalent(expectedColumns, columns); } diff --git a/Tests/Common/Securities/OptionPriceModelTests.cs b/Tests/Common/Securities/OptionPriceModelTests.cs index 9098c927c0bf..e02d4e42b042 100644 --- a/Tests/Common/Securities/OptionPriceModelTests.cs +++ b/Tests/Common/Securities/OptionPriceModelTests.cs @@ -36,6 +36,11 @@ namespace QuantConnect.Tests.Common [TestFixture] public class OptionPriceModelTests { + [OneTimeSetUp] + public void OneTimeSetUp() + { + OptionPriceModels.DefaultPriceModelProvider = QLOptionPriceModelProvider.Instance; + } [Test] public void PutCallParityTest() @@ -216,7 +221,7 @@ public void BaroneAdesiWhaleyPortfolioTest() var optionCall = GetOption(SPY_C_192_Feb19_2016E, equity, tz); optionCall.SetMarketPrice(new Tick { Value = price }); - var priceModel = OptionPriceModels.BaroneAdesiWhaley(); + var priceModel = OptionPriceModels.QuantLib.BaroneAdesiWhaley(); var results = priceModel.Evaluate(new OptionPriceModelParameters(optionCall, null, contract)); var callPrice = results.TheoreticalPrice; @@ -249,7 +254,7 @@ public void EvaluationDateWorksInPortfolioTest() var optionCall = GetOption(SPY_C_192_Feb19_2016E, equity, tz); optionCall.SetMarketPrice(new Tick { Value = price }); - var priceModel = OptionPriceModels.BaroneAdesiWhaley(); + var priceModel = OptionPriceModels.QuantLib.BaroneAdesiWhaley(); var results = priceModel.Evaluate(new OptionPriceModelParameters(optionCall, null, contract)); var callPrice1 = results.TheoreticalPrice; @@ -268,7 +273,7 @@ public void CreatesOptionPriceModelByName(string priceEngineName) IOptionPriceModel priceModel = null; Assert.DoesNotThrow(() => { - priceModel = OptionPriceModels.Create(priceEngineName, 0.01m); + priceModel = OptionPriceModels.QuantLib.Create(priceEngineName, 0.01m); }); Assert.NotNull(priceModel); @@ -292,7 +297,7 @@ public void GreekApproximationTest() var optionPut = GetOption(Symbols.SPY_P_192_Feb19_2016, equity, tz); optionPut.SetMarketPrice(new Tick { Value = price }); - var priceModel = (QLOptionPriceModel)OptionPriceModels.CrankNicolsonFD(); + var priceModel = (QLOptionPriceModel)OptionPriceModels.QuantLib.CrankNicolsonFD(); priceModel.EnableGreekApproximation = false; var results = priceModel.Evaluate(optionPut, null, contract); @@ -302,7 +307,7 @@ public void GreekApproximationTest() Assert.AreEqual(greeks.Rho, 0); Assert.AreEqual(greeks.Vega, 0); - priceModel = (QLOptionPriceModel)OptionPriceModels.CrankNicolsonFD(); + priceModel = (QLOptionPriceModel)OptionPriceModels.QuantLib.CrankNicolsonFD(); priceModel.EnableGreekApproximation = true; results = priceModel.Evaluate(optionPut, null, contract); @@ -396,7 +401,7 @@ public void ThrowsIfOptionStyleIsNotSupportedByQLPricingModel(string qlModelName optionPut.SetMarketPrice(new Tick { Value = 7m }); // dummy non-zero price // running evaluation - var priceModel = (IOptionPriceModel)typeof(OptionPriceModels).GetMethod(qlModelName).Invoke(null, new object[] { }); + var priceModel = (IOptionPriceModel)typeof(OptionPriceModels.QuantLib).GetMethod(qlModelName).Invoke(null, new object[] { }); TestDelegate call = () => priceModel.Evaluate(new OptionPriceModelParameters(optionCall, null, contractCall)); TestDelegate put = () => priceModel.Evaluate(new OptionPriceModelParameters(optionPut, null, contractPut)); @@ -824,7 +829,7 @@ private void MatchesIBGreeksTest(Symbol symbol, Symbol optionSymbol, string file // setting up option var contract = GetOptionContract(optionSymbol, symbol, evaluationDate); var option = GetOption(optionSymbol, equity, tz); - var priceModel = (IOptionPriceModel)typeof(OptionPriceModels).GetMethod(qlModelName).Invoke(null, new object[] { }); + var priceModel = (IOptionPriceModel)typeof(OptionPriceModels.QuantLib).GetMethod(qlModelName).Invoke(null, new object[] { }); // Get test data var data = File.ReadAllLines($"TestData/greeks/{filename}.csv") @@ -997,7 +1002,7 @@ public static Equity GetEquity(Symbol symbol, decimal underlyingPrice, decimal u return equity; } - public OptionContract GetOptionContract(Symbol symbol, Symbol underlying, DateTime evaluationDate) + public static OptionContract GetOptionContract(Symbol symbol, Symbol underlying, DateTime evaluationDate) { var option = CreateOption(symbol); return new OptionContract(option) { Time = evaluationDate }; diff --git a/Tests/Common/Securities/Options/OptionFilterUniverseTests.cs b/Tests/Common/Securities/Options/OptionFilterUniverseTests.cs index 0803e2f6157b..fa2eaebeb0dd 100644 --- a/Tests/Common/Securities/Options/OptionFilterUniverseTests.cs +++ b/Tests/Common/Securities/Options/OptionFilterUniverseTests.cs @@ -141,7 +141,7 @@ public void FiltersContractsByOpenInterest() [TestCase("Delta", 0.63, 0.64, 4)] [TestCase("Gamma", 0.0008, 0.0011, 4)] [TestCase("Vega", 7.5, 11.3, 5)] - [TestCase("Theta", -1.10, -0.50, 8)] + [TestCase("Theta", -401.50, -182.50, 8)] [TestCase("Rho", 4, 10, 10)] public void FiltersContractsByIndividualGreek(string greekName, decimal greekMinValue, decimal greekMaxValue, int expectedContracts) { @@ -170,8 +170,8 @@ public void FiltersContractsByMultipleGreeks() var deltaMax = 0.68m; var gammaMin = 0.00024m; var gammaMax = 0.0028m; - var thetaMin = -1.40m; - var thetaMax = -0.40m; + var thetaMin = -511m; + var thetaMax = -146m; var expectedContracts = 11; // Set up diff --git a/Tests/Common/Util/ExtensionsTests.cs b/Tests/Common/Util/ExtensionsTests.cs index 83088bcc0479..696511e1bf21 100644 --- a/Tests/Common/Util/ExtensionsTests.cs +++ b/Tests/Common/Util/ExtensionsTests.cs @@ -46,6 +46,7 @@ using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Tests.Brokerages; +using QuantConnect.Util; namespace QuantConnect.Tests.Common.Util { @@ -2234,6 +2235,45 @@ private static HttpClient MockClient(string content, HttpStatusCode code) return new HttpClient(handlerMock.Object); } + private static TestCaseData[] MirrorOptionTestCases + { + get + { + var spy = Symbol.Create("SPY", SecurityType.Equity, Market.USA); + var spx = Symbol.Create("SPX", SecurityType.Index, Market.USA); + + var strike = 100m; + var expiry = new DateTime(2021, 1, 1); + + var spyCall = Symbol.CreateOption(spy, Market.USA, OptionStyle.American, OptionRight.Call, strike, expiry); + var spyPut = Symbol.CreateOption(spy, Market.USA, OptionStyle.American, OptionRight.Put, strike, expiry); + + var spxCall = Symbol.CreateOption(spx, Market.USA, OptionStyle.European, OptionRight.Call, strike, expiry); + var spxPut = Symbol.CreateOption(spx, Market.USA, OptionStyle.European, OptionRight.Put, strike, expiry); + + var spxwCall = Symbol.CreateOption(spx, "SPXW", Market.USA, OptionStyle.European, OptionRight.Call, strike, expiry); + var spxwPut = Symbol.CreateOption(spx, "SPXW", Market.USA, OptionStyle.European, OptionRight.Put, strike, expiry); + + return new[] + { + new TestCaseData(spyCall).Returns(spyPut), + new TestCaseData(spyPut).Returns(spyCall), + + new TestCaseData(spxCall).Returns(spxPut), + new TestCaseData(spxPut).Returns(spxCall), + + new TestCaseData(spxwCall).Returns(spxwPut), + new TestCaseData(spxwPut).Returns(spxwCall), + }; + } + } + + [TestCaseSource(nameof(MirrorOptionTestCases))] + public Symbol GetsCorrectMirrorOption(Symbol optionSymbol) + { + return optionSymbol.GetMirrorOptionSymbol(); + } + private PyObject ConvertToPyObject(object value) { using (Py.GIL()) diff --git a/Tests/Indicators/IndicatorBasedOptionPriceModelTests.cs b/Tests/Indicators/IndicatorBasedOptionPriceModelTests.cs new file mode 100644 index 000000000000..c549ae37901a --- /dev/null +++ b/Tests/Indicators/IndicatorBasedOptionPriceModelTests.cs @@ -0,0 +1,128 @@ +/* + * 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 NUnit.Framework; +using QuantConnect.Data; +using QuantConnect.Data.Market; +using QuantConnect.Indicators; +using QuantConnect.Securities.Option; +using QuantConnect.Tests.Common; +using System; +using System.Collections.Generic; + +namespace QuantConnect.Tests.Indicators +{ + [TestFixture] + public class IndicatorBasedOptionPriceModelTests + { + [TestCase(true, 6.05391914652262, 0.3564563, 0.7560627, 0.0430897, 0.0662474, -4.3932945 * 365, 0.0000902)] + [TestCase(false, 5.05413609164657, 0.1428964, 0.9574846, 0.0311305, 0.0205564, -0.4502054 * 365, 0.0000057)] + public void WorksWithAndWithoutMirrorContract([Values] bool withMirrorContract, decimal expectedTheoreticalPrice, + decimal expectedIv, decimal expectedDelta, decimal expectedGamma, decimal expectedVega, + decimal expectedTheta, decimal expectedRho) + { + GetTestData(true, true, withMirrorContract, out var option, out var contract, out var slice); + + var model = new IndicatorBasedOptionPriceModel(); + + var result = model.Evaluate(option, slice, contract); + var theoreticalPrice = result.TheoreticalPrice; + var iv = result.ImpliedVolatility; + var greeks = result.Greeks; + + Assert.AreEqual(expectedTheoreticalPrice, theoreticalPrice); + Assert.AreEqual(expectedIv, iv); + Assert.AreEqual(expectedDelta, greeks.Delta); + Assert.AreEqual(expectedGamma, greeks.Gamma); + Assert.AreEqual(expectedVega, greeks.Vega); + Assert.AreEqual(expectedTheta, greeks.Theta); + Assert.AreEqual(expectedRho, greeks.Rho); + } + + [TestCase(false, false)] + [TestCase(true, false)] + [TestCase(false, true)] + public void WontCalculateIfMissindData(bool withUnderlyingData, bool withOptionData) + { + GetTestData(withUnderlyingData, withOptionData, true, out var option, out var contract, out var slice); + + var model = new IndicatorBasedOptionPriceModel(); + var result = model.Evaluate(option, slice, contract); + + Assert.AreEqual(OptionPriceModelResult.None, result); + } + + private static void GetTestData(bool withUnderlying, bool withOption, bool withMirrorOption, + out Option option, out OptionContract contract, out Slice slice) + { + var underlyingSymbol = Symbols.GOOG; + var date = new DateTime(2015, 11, 24); + var contractSymbol = Symbols.CreateOptionSymbol(underlyingSymbol.Value, OptionRight.Call, 745m, date); + + var tz = TimeZones.NewYork; + var underlyingPrice = 750m; + var underlyingVolume = 10000; + var contractPrice = 5m; + var underlying = OptionPriceModelTests.GetEquity(underlyingSymbol, underlyingPrice, underlyingVolume, tz); + option = OptionPriceModelTests.GetOption(contractSymbol, underlying, tz); + contract = OptionPriceModelTests.GetOptionContract(contractSymbol, underlyingSymbol, date); + + var time = date.Add(new TimeSpan(9, 31, 0)); + + var data = new List(); + + if (withUnderlying) + { + var underlyingData = new TradeBar(time, underlyingSymbol, underlyingPrice, underlyingPrice, underlyingPrice, underlyingPrice, underlyingVolume, TimeSpan.FromMinutes(1)); + data.Add(underlyingData); + underlying.SetMarketPrice(underlyingData); + } + + if (withOption) + { + var contractData = new QuoteBar(time, + contractSymbol, + new Bar(contractPrice, contractPrice, contractPrice, contractPrice), + 10, + new Bar(contractPrice + 0.1m, contractPrice + 0.1m, contractPrice + 0.1m, contractPrice + 0.1m), + 10, + TimeSpan.FromMinutes(1)); + data.Add(contractData); + option.SetMarketPrice(contractData); + } + + if (withMirrorOption) + { + var mirrorContractSymbol = Symbol.CreateOption(contractSymbol.Underlying, + contractSymbol.ID.Symbol, + contractSymbol.ID.Market, + contractSymbol.ID.OptionStyle, + contractSymbol.ID.OptionRight == OptionRight.Call ? OptionRight.Put : OptionRight.Call, + contractSymbol.ID.StrikePrice, + contractSymbol.ID.Date); + var mirrorContractPrice = 1m; + data.Add(new QuoteBar(time, + mirrorContractSymbol, + new Bar(mirrorContractPrice, mirrorContractPrice, mirrorContractPrice, mirrorContractPrice), + 10, + new Bar(mirrorContractPrice + 0.1m, mirrorContractPrice + 0.1m, mirrorContractPrice + 0.1m, mirrorContractPrice + 0.1m), + 10, + TimeSpan.FromMinutes(1))); + } + + slice = new Slice(time, data, time.ConvertToUtc(tz)); + } + } +} diff --git a/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs b/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs index 3cb3559c0a1e..9371d2cb4892 100644 --- a/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs +++ b/Tests/ToolBox/RandomDataGenerator/RandomDataGeneratorTests.cs @@ -242,7 +242,7 @@ private static QuantConnect.ToolBox.RandomDataGenerator.RandomDataGenerator GetG // from settings if (security is Option option) { - option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName, + option.PriceModel = OptionPriceModels.QuantLib.Create(settings.OptionPriceEngineName, _interestRateProvider.GetRiskFreeRate(settings.Start, settings.End)); } })), @@ -281,7 +281,7 @@ private static SecurityService GetSecurityService(RandomDataGeneratorSettings se // from settings if (security is Option option) { - option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName, + option.PriceModel = OptionPriceModels.QuantLib.Create(settings.OptionPriceEngineName, _interestRateProvider.GetRiskFreeRate(settings.Start, settings.End)); } })), diff --git a/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs b/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs index 91f2937f01b6..4adbb6049d91 100644 --- a/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs +++ b/ToolBox/RandomDataGenerator/RandomDataGeneratorProgram.cs @@ -102,7 +102,7 @@ List tickers // from settings if (security is Option option) { - option.PriceModel = OptionPriceModels.Create(settings.OptionPriceEngineName, + option.PriceModel = OptionPriceModels.QuantLib.Create(settings.OptionPriceEngineName, _interestRateProvider.GetRiskFreeRate(settings.Start, settings.End)); } })),