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..24c495dbcd32
--- /dev/null
+++ b/Algorithm.CSharp/IndicatorBasedOptionPricingModelRegressionAlgorithm.cs
@@ -0,0 +1,177 @@
+/*
+ * 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
+ if (theoreticalPrice <= 0)
+ {
+ throw new RegressionTestException($"Invalid theoretical price for {contract.Symbol}: {theoreticalPrice}");
+ }
+ // 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 |= 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 };
+
+ ///
+ /// 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/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/Indicators/GreeksIndicators.cs b/Indicators/GreeksIndicators.cs
new file mode 100644
index 000000000000..c751f81c8d30
--- /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..651c10f2c7fe
--- /dev/null
+++ b/Indicators/IndicatorBasedOptionPriceModel.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;
+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
+ {
+ ///
+ /// 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 = contract.Symbol;
+ 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 = 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 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.");
+ }
+ 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/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/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));
+ }
+ }
+}