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..1917c1f44c2a
--- /dev/null
+++ b/Algorithm.CSharp/IndicatorBasedOptionPricingModelRegressionAlgorithm.cs
@@ -0,0 +1,184 @@
+/*
+ * 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();
+ _option.PriceModel = new IndicatorBasedOptionPriceModel();
+ }
+
+ 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))
+ {
+ if (_option.PriceModel is not IndicatorBasedOptionPriceModel)
+ {
+ throw new RegressionTestException("Option pricing model was not set to IndicatorBasedOptionPriceModel");
+ }
+
+ 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}");
+
+ // 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 result = _option.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/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/Common/Extensions.cs b/Common/Extensions.cs
index 0c45efb93aa1..3436e1445c5c 100644
--- a/Common/Extensions.cs
+++ b/Common/Extensions.cs
@@ -3552,6 +3552,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/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..a13ebfc9ebde
--- /dev/null
+++ b/Common/Securities/Option/IOptionPriceModelProvider.cs
@@ -0,0 +1,30 @@
+/*
+ * 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.
+*/
+
+namespace QuantConnect.Securities.Option
+{
+ ///
+ /// Provides option price models for option securities
+ ///
+ public interface IOptionPriceModelProvider
+ {
+ ///
+ /// Gets the option price model for the specified option symbol
+ ///
+ /// The symbol
+ /// The option price model for the given symbol
+ IOptionPriceModel GetOptionPriceModel(Symbol symbol);
+ }
+}
diff --git a/Common/Securities/Option/Option.cs b/Common/Securities/Option/Option.cs
index 75b74b13d828..761353df86b2 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 ?? new QLOptionPriceModelProvider()).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 417d0f6efc74..e42e8098578a 100644
--- a/Common/Securities/Option/OptionPriceModelResult.cs
+++ b/Common/Securities/Option/OptionPriceModelResult.cs
@@ -28,6 +28,7 @@ public class OptionPriceModelResult
///
public static OptionPriceModelResult None { get; } = new(0, NullGreeks.Instance);
+ private readonly Lazy _theoreticalPrice;
private readonly Lazy _greeks;
private readonly Lazy _impliedVolatility;
@@ -36,7 +37,10 @@ public class OptionPriceModelResult
///
public decimal TheoreticalPrice
{
- get; private set;
+ get
+ {
+ return _theoreticalPrice.Value;
+ }
}
///
@@ -67,10 +71,8 @@ public Greeks Greeks
/// 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);
}
///
@@ -80,8 +82,19 @@ public OptionPriceModelResult(decimal theoreticalPrice, Greeks greeks)
/// 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/QLOptionPriceModelProvider.cs b/Common/Securities/Option/QLOptionPriceModelProvider.cs
new file mode 100644
index 000000000000..95e84586888a
--- /dev/null
+++ b/Common/Securities/Option/QLOptionPriceModelProvider.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 System;
+
+namespace QuantConnect.Securities.Option
+{
+ ///
+ /// Provides option price models for option securities based on QuantLib implementations
+ ///
+ public class QLOptionPriceModelProvider : IOptionPriceModelProvider
+ {
+ ///
+ /// Gets the option price model for the specified option symbol
+ ///
+ /// The symbol
+ /// The option price model for the given symbol
+ public IOptionPriceModel GetOptionPriceModel(Symbol symbol)
+ {
+ 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 => OptionPriceModels.BinomialCoxRossRubinstein(),
+ OptionStyle.European => OptionPriceModels.BlackScholes(),
+ _ => throw new ArgumentException("Invalid OptionStyle")
+ };
+ }
+ }
+}
diff --git a/Common/Securities/SecurityService.cs b/Common/Securities/SecurityService.cs
index 1cfd4e77cc7a..b0ad767881e4 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,7 @@ public SecurityService(CashBook cashBook,
_cacheProvider = cacheProvider;
_primaryExchangeProvider = primaryExchangeProvider;
_algorithm = algorithm;
+ _optionPriceModelProvider = optionPriceModelProvider;
}
///
@@ -176,12 +180,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..9e9bfbdc820b 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,
+ new IndicatorBasedOptionPriceModelProvider());
algorithm.Securities.SetSecurityService(securityService);
diff --git a/Indicators/GreeksIndicators.cs b/Indicators/GreeksIndicators.cs
new file mode 100644
index 000000000000..ea2ba288c00f
--- /dev/null
+++ b/Indicators/GreeksIndicators.cs
@@ -0,0 +1,140 @@
+/*
+ * 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;
+
+namespace QuantConnect.Indicators
+{
+ ///
+ /// Helper class that holds and updates the greeks indicators
+ ///
+ public class GreeksIndicators
+ {
+ private readonly static IRiskFreeInterestRateModel _interestRateProvider = new InterestRateProvider();
+
+ 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 GreeksHolder(Delta, Gamma, Vega, Theta, Rho);
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ public GreeksIndicators(Symbol optionSymbol, Symbol mirrorOptionSymbol, OptionPricingModelType? optionModel = null,
+ OptionPricingModelType? ivModel = null)
+ {
+ _optionSymbol = optionSymbol;
+ _mirrorOptionSymbol = mirrorOptionSymbol;
+
+ IDividendYieldModel dividendYieldModel = optionSymbol.SecurityType != SecurityType.IndexOption
+ ? DividendYieldProvider.CreateForOption(_optionSymbol)
+ : new ConstantDividendYieldModel(0);
+
+ ImpliedVolatility = new ImpliedVolatility(_optionSymbol, _interestRateProvider, dividendYieldModel, _mirrorOptionSymbol, ivModel);
+ Delta = new Delta(_optionSymbol, _interestRateProvider, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel);
+ Gamma = new Gamma(_optionSymbol, _interestRateProvider, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel);
+ Vega = new Vega(_optionSymbol, _interestRateProvider, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel);
+ Theta = new Theta(_optionSymbol, _interestRateProvider, dividendYieldModel, _mirrorOptionSymbol, optionModel, ivModel);
+ Rho = new Rho(_optionSymbol, _interestRateProvider, 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);
+ }
+
+ private class GreeksHolder : Greeks
+ {
+ public override decimal Delta { get; }
+
+ public override decimal Gamma { get; }
+
+ public override decimal Vega { get; }
+
+ public override decimal Theta { get; }
+
+ public override decimal Rho { get; }
+
+ public override decimal Lambda { get; }
+
+ public GreeksHolder(decimal delta, decimal gamma, decimal vega, decimal theta, decimal rho)
+ {
+ Delta = delta;
+ Gamma = gamma;
+ Vega = vega;
+ Theta = theta;
+ Rho = rho;
+ }
+ }
+ }
+}
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..9b41d0a65495
--- /dev/null
+++ b/Indicators/IndicatorBasedOptionPriceModel.cs
@@ -0,0 +1,146 @@
+/*
+ * 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;
+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 : IOptionPriceModel
+ {
+ private Symbol _contractSymbol;
+ private Symbol _mirrorContractSymbol;
+
+ ///
+ /// Creates a new containing the theoretical price based on
+ /// QuantConnect indicators.
+ ///
+ /// The option security object
+ ///
+ /// The current data slice. This can be used to access other information
+ /// available to the algorithm
+ ///
+ /// The option contract to evaluate
+ ///
+ /// An instance of containing the theoretical
+ /// price of the specified option contract.
+ ///
+ public OptionPriceModelResult Evaluate(Security security, Slice slice, OptionContract contract)
+ {
+ // 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;
+ // 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;
+ _mirrorContractSymbol = contractSymbol.GetMirrorOptionSymbol();
+ }
+
+ var underlyingData = slice.AllData
+ // We use trades for the underlying (see how Greeks indicators are registered to algorithms)
+ .Where(x => x.Symbol == contractSymbol.Underlying && (x is TradeBar || (x is Tick tick && tick.TickType == TickType.Trade)))
+ // Order by resolution
+ .OrderBy(x => x.EndTime - x.Time)
+ // Let's use the lowest resolution available, trying to match our pre calculated daily greeks (using daily bars if possible).
+ // If ticks, use the last tick in the slice
+ .LastOrDefault();
+
+ var period = TimeSpan.Zero;
+ BaseData optionData = null;
+ if (underlyingData != null)
+ {
+ period = underlyingData.EndTime - underlyingData.Time;
+ optionData = slice.AllData
+ .Where(x => x.Symbol == contractSymbol &&
+ // Use the same resolution data
+ x.EndTime - x.Time == period &&
+ // We use quotes for the options (see how Greeks indicators are registered to algorithms)
+ (x is QuoteBar || (x is Tick tick && tick.TickType == TickType.Quote)))
+ .LastOrDefault();
+ }
+
+ if (underlyingData == null || optionData == null)
+ {
+ if (Log.DebuggingEnabled)
+ {
+ Log.Debug($"IndicatorBasedOptionPriceModel.Evaluate(). Missing data for {contractSymbol} or {contractSymbol.Underlying}.");
+ }
+ return OptionPriceModelResult.None;
+ }
+
+ var mirrorContractSymbol = _mirrorContractSymbol;
+ var mirrorOptionData = slice.AllData
+ .Where(x => x.Symbol == mirrorContractSymbol &&
+ // Use the same resolution data
+ x.EndTime - x.Time == period &&
+ (x is QuoteBar || (x is Tick tick && tick.TickType == TickType.Quote)))
+ .LastOrDefault();
+
+ 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;
+ }
+
+ var greeksIndicators = new Lazy(() =>
+ {
+ var indicators = new GreeksIndicators(contractSymbol, mirrorContractSymbol);
+
+ if (underlyingData != null)
+ {
+ indicators.Update(underlyingData);
+ }
+ if (optionData != null)
+ {
+ indicators.Update(optionData);
+ }
+ if (mirrorOptionData != null)
+ {
+ indicators.Update(mirrorOptionData);
+ }
+
+ return indicators;
+ }, isThreadSafe: false);
+
+ return new OptionPriceModelResult(
+ () => greeksIndicators.Value.ImpliedVolatility.TheoreticalPrice,
+ () => greeksIndicators.Value.ImpliedVolatility,
+ () => greeksIndicators.Value.Greeks);
+ }
+ }
+}
diff --git a/Indicators/IndicatorBasedOptionPriceModelProvider.cs b/Indicators/IndicatorBasedOptionPriceModelProvider.cs
new file mode 100644
index 000000000000..86552c5b9dee
--- /dev/null
+++ b/Indicators/IndicatorBasedOptionPriceModelProvider.cs
@@ -0,0 +1,35 @@
+/*
+ * 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.Securities.Option;
+
+namespace QuantConnect.Indicators
+{
+ ///
+ /// Provides option price models for option securities based on Lean's Greeks indicators
+ ///
+ public class IndicatorBasedOptionPriceModelProvider : IOptionPriceModelProvider
+ {
+ ///
+ /// Gets the option price model for the specified option symbol
+ ///
+ /// The symbol
+ /// The option price model for the given symbol
+ public IOptionPriceModel GetOptionPriceModel(Symbol symbol)
+ {
+ return new IndicatorBasedOptionPriceModel();
+ }
+ }
+}
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 df2aa38c36cd..cd400c3f857c 100644
--- a/Tests/Common/Securities/OptionPriceModelTests.cs
+++ b/Tests/Common/Securities/OptionPriceModelTests.cs
@@ -968,7 +968,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/Util/ExtensionsTests.cs b/Tests/Common/Util/ExtensionsTests.cs
index 6dce18d7b32d..4214854d21af 100644
--- a/Tests/Common/Util/ExtensionsTests.cs
+++ b/Tests/Common/Util/ExtensionsTests.cs
@@ -40,6 +40,7 @@
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Tests.Brokerages;
+using QuantConnect.Util;
namespace QuantConnect.Tests.Common.Util
{
@@ -2159,6 +2160,45 @@ public void JsonStringSerializationRoundTrip()
Assert.AreEqual(original.Amount, deserialized.Amount);
}
+ 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..07911612ca7c
--- /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, 0.0000902)]
+ [TestCase(false, 5.05413609164657, 0.1428964, 0.9574846, 0.0311305, 0.0205564, -0.4502054, 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));
+ }
+ }
+}