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