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));
}
})),