diff --git a/Lean.DataSource.DerivativeUniverseGenerator/ChainSymbolProvider.cs b/Lean.DataSource.DerivativeUniverseGenerator/ChainSymbolProvider.cs index 927e627..2bc3e0c 100644 --- a/Lean.DataSource.DerivativeUniverseGenerator/ChainSymbolProvider.cs +++ b/Lean.DataSource.DerivativeUniverseGenerator/ChainSymbolProvider.cs @@ -27,7 +27,7 @@ namespace QuantConnect.DataSource.DerivativeUniverseGenerator /// /// File based symbol chain provider /// - public class ChainSymbolProvider + public abstract class ChainSymbolProvider { private readonly IDataCacheProvider _dataCacheProvider; protected readonly DateTime _processingDate; @@ -172,19 +172,23 @@ private List GetSymbolsFromZipEntryNames(string zipFileName, Symbol cano .Where(symbol => _processingDate.Date < symbol.ID.Date.Date) .Distinct(); - if (canonicalSymbol.SecurityType.IsOption()) + return OrderSymbols(symbols, canonicalSymbol.SecurityType).ToList(); + } + + /// + /// Orders the given chain of contracts. + /// + protected static IEnumerable OrderSymbols(IEnumerable symbols, SecurityType securityType) + { + if (securityType.IsOption()) { - symbols = symbols.OrderBy(symbol => symbol.ID.OptionRight) + return symbols.OrderBy(symbol => symbol.ID.OptionRight) .ThenBy(symbol => symbol.ID.Date) .ThenBy(symbol => symbol.ID.StrikePrice) .ThenBy(symbol => symbol.ID); } - else - { - symbols = symbols.OrderBy(symbol => symbol.ID.Date).ThenBy(symbol => symbol.ID); - } - return symbols.ToList(); + return symbols.OrderBy(symbol => symbol.ID.Date).ThenBy(symbol => symbol.ID); } } } diff --git a/Lean.DataSource.DerivativeUniverseGenerator/DerivativeUniverseGenerator.cs b/Lean.DataSource.DerivativeUniverseGenerator/DerivativeUniverseGenerator.cs index b458ac9..57bf51b 100644 --- a/Lean.DataSource.DerivativeUniverseGenerator/DerivativeUniverseGenerator.cs +++ b/Lean.DataSource.DerivativeUniverseGenerator/DerivativeUniverseGenerator.cs @@ -165,11 +165,7 @@ private Dictionary> GetSymbolsToProcess() /// /// Gets the available universe symbols grouped by their canonical symbol. /// - protected virtual Dictionary> GetSymbols() - { - var symbolChainProvider = new ChainSymbolProvider(_dataCacheProvider, _processingDate, _securityType, _market, _dataFolderRoot); - return symbolChainProvider.GetSymbols(); - } + protected abstract Dictionary> GetSymbols(); /// /// Filters the symbols to process based on the given list of symbols. diff --git a/Lean.DataSource.OptionsUniverseGenerator/OptionChainSymbolProvider.cs b/Lean.DataSource.OptionsUniverseGenerator/OptionChainSymbolProvider.cs new file mode 100644 index 0000000..63e5895 --- /dev/null +++ b/Lean.DataSource.OptionsUniverseGenerator/OptionChainSymbolProvider.cs @@ -0,0 +1,104 @@ +/* + * 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.Linq; +using QuantConnect.Util; +using QuantConnect.Interfaces; +using System.Collections.Generic; +using QuantConnect.Configuration; +using QuantConnect.DataSource.DerivativeUniverseGenerator; + +namespace QuantConnect.DataSource.OptionsUniverseGenerator +{ + /// + /// Options chain symbol provider used for fetching the option chains from data file names + /// + public class OptionChainSymbolProvider : ChainSymbolProvider + { + private readonly IOptionChainProvider _optionChainProvider; + private readonly string _market; + + /// + /// Initializes a new instance of the class + /// + public OptionChainSymbolProvider(IDataCacheProvider dataCacheProvider, DateTime processingDate, SecurityType securityType, + string market, string dataFolderRoot) + : base(dataCacheProvider, processingDate, securityType, market, dataFolderRoot) + { + _market = market; + + if (Config.TryGetValue("universe-option-chain-provider", out var optionChainProviderStr) && + !string.IsNullOrEmpty(optionChainProviderStr)) + { + _optionChainProvider = Composer.Instance.GetExportedValueByTypeName(optionChainProviderStr); + } + } + + /// + /// Gets all the available symbols keyed by the canonical symbol from the available price data in the data folder. + /// + public override Dictionary> GetSymbols() + { + if (_optionChainProvider == null) + { + return base.GetSymbols(); + } + + // A tickerless dummy symbol fetches the contracts of every canonical of the + // generator's security type and market the provider finds + var contracts = _optionChainProvider.GetOptionContractList(CreateChainsRequestSymbol(), _processingDate)?.ToList(); + if (contracts == null || contracts.Count == 0) + { + // The custom chain provider failed, fallback to the file-based chains + return base.GetSymbols(); + } + + return contracts + .Where(symbol => symbol.SecurityType == _securityType + && symbol.ID.Market == _market + // do not return expired contracts + && _processingDate.Date < symbol.ID.Date.Date) + .Distinct() + .GroupBy(symbol => symbol.Canonical) + .ToDictionary(group => group.Key, group => OrderSymbols(group, _securityType).ToList()); + } + + /// + /// Creates the tickerless dummy symbol used to request the chains of every canonical of the + /// generator's security type and market from the custom chain provider + /// + private Symbol CreateChainsRequestSymbol() + { + Symbol underlying; + switch (_securityType) + { + case SecurityType.Option: + // equity SID generation must skip mapping, which rejects empty tickers + underlying = new Symbol(SecurityIdentifier.GenerateEquity(string.Empty, _market, mapSymbol: false), string.Empty); + break; + case SecurityType.IndexOption: + underlying = Symbol.Create(string.Empty, SecurityType.Index, _market); + break; + default: + throw new NotSupportedException($"OptionChainSymbolProvider.CreateChainsRequestSymbol(): " + + $"unsupported security type {_securityType}"); + } + + return Symbol.CreateCanonicalOption(underlying); + } + } +} diff --git a/Lean.DataSource.OptionsUniverseGenerator/OptionsUniverseGenerator.cs b/Lean.DataSource.OptionsUniverseGenerator/OptionsUniverseGenerator.cs index 8d92bda..12b2d67 100644 --- a/Lean.DataSource.OptionsUniverseGenerator/OptionsUniverseGenerator.cs +++ b/Lean.DataSource.OptionsUniverseGenerator/OptionsUniverseGenerator.cs @@ -79,6 +79,12 @@ protected override IDerivativeUniverseFileEntry CreateUniverseEntry(Symbol symbo return new OptionUniverseEntry(symbol); } + protected override Dictionary> GetSymbols() + { + var symbolChainProvider = new OptionChainSymbolProvider(_dataCacheProvider, _processingDate, _securityType, _market, _dataFolderRoot); + return symbolChainProvider.GetSymbols(); + } + protected override bool NeedsUnderlyingData() { // We don't need underlying data for future options, since they don't have greeks, so no need for underlying data for calculation diff --git a/QuantConnect.DataSource.DerivativeUniverseGeneratorTests/DerivativeUniverseGeneratorTests.cs b/QuantConnect.DataSource.DerivativeUniverseGeneratorTests/DerivativeUniverseGeneratorTests.cs index 35bd8ca..ba4a1e2 100644 --- a/QuantConnect.DataSource.DerivativeUniverseGeneratorTests/DerivativeUniverseGeneratorTests.cs +++ b/QuantConnect.DataSource.DerivativeUniverseGeneratorTests/DerivativeUniverseGeneratorTests.cs @@ -159,6 +159,11 @@ protected override Dictionary> FilterSymbols(Dictionary> GetSymbols() + { + return new Dictionary>(); + } + protected override IDerivativeUniverseFileEntry CreateUniverseEntry(Symbol symbol) { return new BaseDerivativeUniverseFileEntry(symbol);