/* * 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.IO; using System.Linq; using QuantConnect.Logging; using QuantConnect.Data.Market; using System.Collections.Generic; namespace QuantConnect.Data.UniverseSelection { /// /// Provides extension methods for the class /// public static class UniverseExtensions { /// /// Creates a new universe that logically is the result of wiring the two universes together such that /// the first will produce subscriptions for the second and the second will only select on data that has /// passed the first. /// /// NOTE: The and universe instances provided /// to this method should not be manually added to the algorithm. /// /// The first universe in this 'chain' /// The second universe in this 'chain' /// True if each symbol as its own configuration, false otherwise /// A new universe that can be added to the algorithm that represents invoking the first universe /// and then the second universe using the outputs of the first. public static Universe ChainedTo(this Universe first, Universe second, bool configurationPerSymbol) { var prefilteredSecond = second.PrefilterUsing(first); return new GetSubscriptionRequestsUniverseDecorator(first, (security, currentTimeUtc, maximumEndTimeUtc) => { return first.GetSubscriptionRequests(security, currentTimeUtc, maximumEndTimeUtc).Select(request => new SubscriptionRequest( template: request, isUniverseSubscription: true, universe: prefilteredSecond, security: security, configuration: configurationPerSymbol ? new SubscriptionDataConfig(prefilteredSecond.Configuration, symbol: security.Symbol) : prefilteredSecond.Configuration, startTimeUtc: currentTimeUtc - prefilteredSecond.Configuration.Resolution.ToTimeSpan(), endTimeUtc: currentTimeUtc.AddSeconds(-1) )); }); } /// /// Creates a new universe that restricts the universe selection data to symbols that passed the /// first universe's selection critera /// /// NOTE: The universe instance provided to this method should not be manually /// added to the algorithm. The should still be manually (assuming no other changes). /// /// The universe to be filtere /// The universe providing the set of symbols used for filtered /// A new universe that can be added to the algorithm that represents invoking the second /// using the selections from the first as a filter. public static Universe PrefilterUsing(this Universe second, Universe first) { return new SelectSymbolsUniverseDecorator(second, (utcTime, data) => { var clone = (BaseDataCollection)data.Clone(); clone.Data = clone.Data.Where(d => first.ContainsMember(d.Symbol)).ToList(); return second.SelectSymbols(utcTime, clone); }); } /// /// Creates a universe symbol /// /// The security /// The market /// The Universe ticker /// A symbol for user defined universe of the specified security type and market public static Symbol CreateSymbol(SecurityType securityType, string market, string ticker) { // TODO looks like we can just replace this for Symbol.Create? SecurityIdentifier sid; switch (securityType) { case SecurityType.Base: sid = SecurityIdentifier.GenerateBase(null, ticker, market); break; case SecurityType.Equity: sid = SecurityIdentifier.GenerateEquity(SecurityIdentifier.DefaultDate, ticker, market); break; case SecurityType.Option: var underlying = SecurityIdentifier.GenerateEquity(SecurityIdentifier.DefaultDate, ticker, market); sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying, market, 0, 0, 0); break; case SecurityType.FutureOption: var underlyingFuture = SecurityIdentifier.GenerateFuture(SecurityIdentifier.DefaultDate, ticker, market); sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingFuture, market, 0, 0, 0); break; case SecurityType.IndexOption: var underlyingIndex = SecurityIdentifier.GenerateIndex(ticker, market); sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingIndex, market, 0, 0, OptionStyle.European); break; case SecurityType.Forex: sid = SecurityIdentifier.GenerateForex(ticker, market); break; case SecurityType.Cfd: sid = SecurityIdentifier.GenerateCfd(ticker, market); break; case SecurityType.Index: sid = SecurityIdentifier.GenerateIndex(ticker, market); break; case SecurityType.Future: sid = SecurityIdentifier.GenerateFuture(SecurityIdentifier.DefaultDate, ticker, market); break; case SecurityType.Crypto: sid = SecurityIdentifier.GenerateCrypto(ticker, market); break; case SecurityType.CryptoFuture: sid = SecurityIdentifier.GenerateCryptoFuture(SecurityIdentifier.DefaultDate, ticker, market); break; case SecurityType.Commodity: default: throw new NotImplementedException($"The specified security type is not implemented yet: {securityType}"); } return new Symbol(sid, ticker); } /// /// Processes the universe download based on parameters. /// /// The data downloader instance. /// The parameters for universe downloading. public static void RunUniverseDownloader(IDataDownloader dataDownloader, DataUniverseDownloaderGetParameters universeDownloadParameters) { var universeDataBySymbol = new Dictionary(); foreach (var (processingDate, universeDownloaderParameters) in universeDownloadParameters.CreateDataDownloaderGetParameters()) { universeDataBySymbol.Clear(); foreach (var downloaderParameters in universeDownloaderParameters) { Log.Debug($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}:Generating universe for {downloaderParameters.Symbol} on {processingDate:yyyy/MM/dd}"); var historyData = dataDownloader.Get(downloaderParameters); if (historyData == null) { Log.Debug($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}: No data available for the following parameters: {universeDownloadParameters}"); continue; } foreach (var baseData in historyData) { switch (baseData) { case TradeBar tradeBar: if (!universeDataBySymbol.TryAdd(tradeBar.Symbol, new(tradeBar))) { universeDataBySymbol[tradeBar.Symbol].UpdateByTradeBar(tradeBar); } break; case OpenInterest openInterest: if (!universeDataBySymbol.TryAdd(openInterest.Symbol, new(openInterest))) { universeDataBySymbol[openInterest.Symbol].UpdateByOpenInterest(openInterest); } break; case QuoteBar quoteBar: if (!universeDataBySymbol.TryAdd(quoteBar.Symbol, new(quoteBar))) { universeDataBySymbol[quoteBar.Symbol].UpdateByQuoteBar(quoteBar); } break; default: throw new InvalidOperationException($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}: Unexpected data type encountered."); } } } if (universeDataBySymbol.Count == 0) { continue; } using var writer = new StreamWriter(universeDownloadParameters.GetUniverseFileName(processingDate)); writer.WriteLine($"#{OptionUniverse.CsvHeader}"); // Write option data, sorted by contract type (Call/Put), strike price, expiration date, and then by full ID foreach (var universeData in universeDataBySymbol .OrderBy(x => x.Key.Underlying != null) .ThenBy(d => d.Key.SecurityType.IsOption() ? d.Key.ID.OptionRight : 0) .ThenBy(d => d.Key.SecurityType.IsOption() ? d.Key.ID.StrikePrice : 0) .ThenBy(d => d.Key.ID.Date) .ThenBy(d => d.Key.ID)) { writer.WriteLine(universeData.Value.ToCsv()); } Log.Trace($"{nameof(UniverseExtensions)}.{nameof(RunUniverseDownloader)}:Generated for {universeDownloadParameters.Symbol} on {processingDate:yyyy/MM/dd} with {universeDataBySymbol.Count} entries"); } } } }