diff --git a/Algorithm.CSharp/OptionChainsMultipleFullDataRegressionAlgorithm.cs b/Algorithm.CSharp/OptionChainsMultipleFullDataRegressionAlgorithm.cs new file mode 100644 index 000000000000..b457a6662eb4 --- /dev/null +++ b/Algorithm.CSharp/OptionChainsMultipleFullDataRegressionAlgorithm.cs @@ -0,0 +1,143 @@ +/* + * 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; +using System.Collections.Generic; +using System.Linq; +using QuantConnect.Data; +using QuantConnect.Data.Market; +using QuantConnect.Interfaces; +using QuantConnect.Securities; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// Regression algorithm illustrating the usage of the method + /// to get multiple option chains, which contains additional data besides the symbols, including prices, implied volatility and greeks. + /// It also shows how this data can be used to filter the contracts based on certain criteria. + /// + public class OptionChainsMultipleFullDataRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition + { + private Symbol _googOptionContract; + private Symbol _spxOptionContract; + + public override void Initialize() + { + SetStartDate(2015, 12, 24); + SetEndDate(2015, 12, 24); + SetCash(100000); + + var goog = AddEquity("GOOG").Symbol; + var spx = AddIndex("SPX").Symbol; + + var chains = OptionChains(new[] { goog, spx }); + + _googOptionContract = GetContract(chains, goog, TimeSpan.FromDays(10)); + _spxOptionContract = GetContract(chains, spx, TimeSpan.FromDays(60)); + + AddOptionContract(_googOptionContract); + AddIndexOptionContract(_spxOptionContract); + } + + private Symbol GetContract(OptionChains chains, Symbol underlying, TimeSpan expirySpan) + { + return chains + .Where(kvp => kvp.Key.Underlying == underlying) + .Select(kvp => kvp.Value) + .Single() + // Get contracts expiring within a given span, with an implied volatility greater than 0.5 and a delta less than 0.5 + .Where(contractData => contractData.Symbol.ID.Date - Time <= expirySpan && + contractData.ImpliedVolatility > 0.5m && + contractData.Delta < 0.5m) + // Get the contract with the latest expiration date + .OrderByDescending(x => x.Symbol.ID.Date) + .First() + .Symbol; + } + + public override void OnData(Slice slice) + { + // Do some trading with the selected contract for sample purposes + if (!Portfolio.Invested) + { + MarketOrder(_googOptionContract, 1); + } + else + { + Liquidate(); + } + } + + /// + /// 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 virtual List Languages { get; } = new() { Language.CSharp, Language.Python }; + + /// + /// Data Points count of all timeslices of algorithm + /// + public long DataPoints => 1059; + + /// + /// Data Points count of the algorithm history + /// + public int AlgorithmHistoryDataPoints => 2; + + /// + /// 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", "210"}, + {"Average Win", "0%"}, + {"Average Loss", "0%"}, + {"Compounding Annual Return", "0%"}, + {"Drawdown", "0%"}, + {"Expectancy", "0"}, + {"Start Equity", "100000"}, + {"End Equity", "96041"}, + {"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", "$209.00"}, + {"Estimated Strategy Capacity", "$0"}, + {"Lowest Capacity Asset", "GOOCV W6U7PD1F2WYU|GOOCV VP83T1ZUHROL"}, + {"Portfolio Turnover", "85.46%"}, + {"OrderListHash", "a7ab1a9e64fe9ba76ea33a40a78a4e3b"} + }; + } +} diff --git a/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs b/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs index 37852f18056a..2157c9a7316e 100644 --- a/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs +++ b/Algorithm.CSharp/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.cs @@ -110,12 +110,8 @@ public void CheckGreeks(OptionChain contracts) // Greeks should be valid if they were successfuly accessed for supported option style if (_optionStyleIsSupported) { - if (greeks == null) - { - greeks = new ModeledGreeks(); - } - - if (greeks.Delta == 0m && greeks.Gamma == 0m && greeks.Theta == 0m && greeks.Vega == 0m && greeks.Rho == 0m) + if (greeks == null || + (greeks.Delta == 0m && greeks.Gamma == 0m && greeks.Theta == 0m && greeks.Vega == 0m && greeks.Rho == 0m)) { throw new RegressionTestException($"Expected greeks to not be zero simultaneously for {contract.Symbol.Value}, an {_option.Style} style option, using {_option?.PriceModel.GetType().Name}, but they were"); } diff --git a/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py b/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py index d230e517f7dd..53c5fb1a310e 100644 --- a/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py +++ b/Algorithm.Python/OptionChainFullDataRegressionAlgorithm.py @@ -12,6 +12,7 @@ # limitations under the License. from AlgorithmImports import * +from datetime import timedelta ### ### Regression algorithm illustrating the usage of the method @@ -30,19 +31,13 @@ def initialize(self): option_chain = self.option_chain(goog) # Demonstration using data frame: + df = option_chain.data_frame # Get contracts expiring within 10 days, with an implied volatility greater than 0.5 and a delta less than 0.5 - contracts = [ - symbol - # Index is a tuple and the first element is the symbol - for (symbol,), contract_data in option_chain.data_frame.iterrows() - if symbol.id.date - self.time <= timedelta(days=10) and contract_data["impliedvolatility"] > 0.5 and contract_data["delta"] < 0.5 - ] - - # Get the contract with the latest expiration date - option_contract = sorted(contracts, key=lambda x: x.id.date, reverse=True)[0] - - # Can use the symbol instance to index the data frame - self.debug(f"Option contract data:\n{option_chain.data_frame.loc[(option_contract)]}") + contracts = df.loc[(df['expiry'] <= self.time + timedelta(days=10)) & (df['impliedvolatility'] > 0.5) & (df['delta'] < 0.5)] + + # Get the contract with the latest expiration date. + # Note: the result of df.loc[] is a series, and its name is a tuple with a single element (contract symbol) + option_contract = contracts.loc[contracts['expiry'].idxmax()].name[0] self._option_contract = self.add_option_contract(option_contract) diff --git a/Algorithm.Python/OptionChainsMultipleFullDataRegressionAlgorithm.py b/Algorithm.Python/OptionChainsMultipleFullDataRegressionAlgorithm.py new file mode 100644 index 000000000000..8cf04c969a91 --- /dev/null +++ b/Algorithm.Python/OptionChainsMultipleFullDataRegressionAlgorithm.py @@ -0,0 +1,62 @@ +# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. +# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from AlgorithmImports import * +from datetime import timedelta + +### +### Regression algorithm illustrating the usage of the method +### to get multiple option chains, which contains additional data besides the symbols, including prices, implied volatility and greeks. +### It also shows how this data can be used to filter the contracts based on certain criteria. +### +class OptionChainsMultipleFullDataRegressionAlgorithm(QCAlgorithm): + + def initialize(self): + self.set_start_date(2015, 12, 24) + self.set_end_date(2015, 12, 24) + self.set_cash(100000) + + goog = self.add_equity("GOOG").symbol + spx = self.add_index("SPX").symbol + + chains = self.option_chains([goog, spx]) + + self._goog_option_contract = self.get_contract(chains, goog, timedelta(days=10)) + self._spx_option_contract = self.get_contract(chains, spx, timedelta(days=60)) + + self.add_option_contract(self._goog_option_contract) + self.add_index_option_contract(self._spx_option_contract) + + def get_contract(self, chains: OptionChains, underlying: Symbol, expiry_span: timedelta) -> Symbol: + df = chains.data_frame + + # Index by the requested underlying, by getting all data with canonicals which underlying is the requested underlying symbol: + canonicals = df.index.get_level_values('canonical') + condition = [canonical for canonical in canonicals if getattr(canonical, 'underlying') == underlying] + df = df.loc[condition] + + # Get contracts expiring in the next 10 days with an implied volatility greater than 0.5 and a delta less than 0.5 + contracts = df.loc[(df['expiry'] <= self.time + expiry_span) & (df['impliedvolatility'] > 0.5) & (df['delta'] < 0.5)] + + # Select the contract with the latest expiry date + contracts.sort_values(by='expiry', ascending=False, inplace=True) + + # Get the symbol: the resulting series name is a tuple (canonical symbol, contract symbol) + return contracts.iloc[0].name[1] + + def on_data(self, data): + # Do some trading with the selected contract for sample purposes + if not self.portfolio.invested: + self.market_order(self._goog_option_contract, 1) + else: + self.liquidate() diff --git a/Algorithm/QCAlgorithm.Python.cs b/Algorithm/QCAlgorithm.Python.cs index dbca952c68b3..607f42ad8f0f 100644 --- a/Algorithm/QCAlgorithm.Python.cs +++ b/Algorithm/QCAlgorithm.Python.cs @@ -1638,6 +1638,21 @@ public void AddCommand(PyObject type) }; } + + /// + /// Get the option chains for the specified symbols at the current time () + /// + /// + /// The symbols for which the option chain is asked for. + /// It can be either the canonical options or the underlying symbols. + /// + /// The option chains + [DocumentationAttribute(AddingData)] + public OptionChains OptionChains(PyObject symbols) + { + return OptionChains(symbols.ConvertToSymbolEnumerable()); + } + /// /// Gets indicator base type /// diff --git a/Algorithm/QCAlgorithm.cs b/Algorithm/QCAlgorithm.cs index 31c1a8d12098..1741ea787858 100644 --- a/Algorithm/QCAlgorithm.cs +++ b/Algorithm/QCAlgorithm.cs @@ -3382,8 +3382,7 @@ public OptionChains OptionChains(IEnumerable symbols) var optionCanonicalSymbols = canonicalSymbols.Where(x => x.SecurityType != SecurityType.FutureOption); var futureOptionCanonicalSymbols = canonicalSymbols.Where(x => x.SecurityType == SecurityType.FutureOption); - // TODO: Resolution.Daily should not be necessary. Remove when GH#8343 is resolved - var optionChainsData = History(optionCanonicalSymbols, 1, Resolution.Daily).GetUniverseData() + var optionChainsData = History(optionCanonicalSymbols, 1).GetUniverseData() .Select(x => (x.Keys.Single(), x.Values.Single().Cast())); // TODO: For FOPs, we fall back to the option chain provider until OptionUniverse supports them @@ -3411,20 +3410,6 @@ public OptionChains OptionChains(IEnumerable symbols) return chains; } - /// - /// Get the option chains for the specified symbols at the current time () - /// - /// - /// The symbols for which the option chain is asked for. - /// It can be either the canonical options or the underlying symbols. - /// - /// The option chains - [DocumentationAttribute(AddingData)] - public OptionChains OptionChains(PyObject symbols) - { - return OptionChains(symbols.ConvertToSymbolEnumerable()); - } - /// /// Register a command type to be used ///