/* * 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 NodaTime; using QuantConnect.Util; using QuantConnect.Data; using QuantConnect.Logging; using QuantConnect.Interfaces; using QuantConnect.Securities; using QuantConnect.Configuration; using QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.Data.UniverseSelection; using DataFeeds = QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.DownloaderDataProvider.Launcher.Models; using QuantConnect.DownloaderDataProvider.Launcher.Models.Constants; using QuantConnect.Lean.Engine.HistoricalData; namespace QuantConnect.DownloaderDataProvider.Launcher; public static class Program { /// /// Synchronizer in charge of guaranteeing a single operation per file path /// private readonly static KeyStringSynchronizer DiskSynchronizer = new(); /// /// The provider used to cache history data files /// private static readonly IDataCacheProvider _dataCacheProvider = new DiskDataCacheProvider(DiskSynchronizer); /// /// Represents the time interval of 5 seconds. /// private static TimeSpan _logDisplayInterval = TimeSpan.FromSeconds(5); /// /// Provides access to exchange hours and raw data times zones in various markets /// private static readonly MarketHoursDatabase _marketHoursDatabase = MarketHoursDatabase.FromDataFolder(); /// /// The main entry point for the application. /// /// Command-line arguments passed to the application. public static void Main(string[] args) { // Parse report arguments and merge with config to use in the optimizer if (args.Length > 0) { Config.MergeCommandLineArgumentsWithConfiguration(DownloaderDataProviderArgumentParser.ParseArguments(args)); } InitializeConfigurations(); var dataDownloader = Composer.Instance.GetExportedValueByTypeName(Config.Get(DownloaderCommandArguments.CommandDownloaderDataDownloader)); var commandDataType = Config.Get(DownloaderCommandArguments.CommandDataType).ToUpperInvariant(); switch (commandDataType) { case "UNIVERSE": RunUniverseDownloader(dataDownloader, new DataUniverseDownloadConfig()); break; case "TRADE": case "QUOTE": case "OPENINTEREST": RunDownload(dataDownloader, new DataDownloadConfig(), Globals.DataFolder, _dataCacheProvider); break; default: Log.Error($"QuantConnect.DownloaderDataProvider.Launcher: Unsupported command data type '{commandDataType}'. Valid options: UNIVERSE, TRADE, QUOTE, OPENINTEREST."); break; } if (dataDownloader is BrokerageDataDownloader brokerageDataDownloader) { brokerageDataDownloader.DisposeSafely(); } } /// /// Executes a data download operation using the specified data downloader. /// /// An instance of an object implementing the interface, responsible for downloading data. /// Configuration settings for the data download operation. /// The directory where the downloaded data will be stored. /// The provider used to cache history data files /// True if the symbol should be mapped while writing the data /// Thrown when is null. public static void RunDownload(IDataDownloader dataDownloader, DataDownloadConfig dataDownloadConfig, string dataDirectory, IDataCacheProvider dataCacheProvider, bool mapSymbol = true) { if (dataDownloader == null) { throw new ArgumentNullException(nameof(dataDownloader), "The data downloader instance cannot be null. Please ensure that a valid instance of data downloader is provided."); } var totalDownloadSymbols = dataDownloadConfig.Symbols.Count; var completeSymbolCount = 0; var startDownloadUtcTime = DateTime.UtcNow; foreach (var symbol in dataDownloadConfig.Symbols) { var downloadParameters = new DataDownloaderGetParameters(symbol, dataDownloadConfig.Resolution, dataDownloadConfig.StartDate, dataDownloadConfig.EndDate, dataDownloadConfig.TickType); Log.Trace($"DownloaderDataProvider.Main(): Starting download {downloadParameters}"); var downloadedData = dataDownloader.Get(downloadParameters); if (downloadedData == null) { completeSymbolCount++; Log.Trace($"DownloaderDataProvider.Main(): No data available for the following parameters: {downloadParameters}"); continue; } var (dataTimeZone, exchangeTimeZone) = GetDataAndExchangeTimeZoneBySymbol(symbol); var writer = new LeanDataWriter(dataDownloadConfig.Resolution, symbol, dataDirectory, dataDownloadConfig.TickType, dataCacheProvider, mapSymbol: mapSymbol); var groupedData = DataFeeds.DownloaderDataProvider.FilterAndGroupDownloadDataBySymbol( downloadedData, symbol, dataDownloadConfig.DataType, exchangeTimeZone, dataTimeZone, downloadParameters.StartUtc, downloadParameters.EndUtc); var lastLogStatusTime = DateTime.UtcNow; foreach (var data in groupedData) { writer.Write(data.Select(data => { var utcNow = DateTime.UtcNow; if (utcNow - lastLogStatusTime >= _logDisplayInterval) { lastLogStatusTime = utcNow; Log.Trace($"Downloading data for {downloadParameters.Symbol}. Please hold on..."); } return data; })); } completeSymbolCount++; var symbolPercentComplete = (double)completeSymbolCount / totalDownloadSymbols * 100; Log.Trace($"DownloaderDataProvider.RunDownload(): {symbolPercentComplete:F2}% complete ({completeSymbolCount} out of {totalDownloadSymbols} symbols)"); Log.Trace($"DownloaderDataProvider.RunDownload(): Download completed for {downloadParameters.Symbol} at {downloadParameters.Resolution} resolution, " + $"covering the period from {dataDownloadConfig.StartDate} to {dataDownloadConfig.EndDate}."); } Log.Trace($"All downloads completed in {(DateTime.UtcNow - startDownloadUtcTime).TotalSeconds:F2} seconds."); } /// /// Initiates the universe downloader using the provided configuration. /// /// The data downloader instance. /// The universe download configuration. private static void RunUniverseDownloader(IDataDownloader dataDownloader, DataUniverseDownloadConfig dataUniverseDownloadConfig) { foreach (var symbol in dataUniverseDownloadConfig.Symbols) { var universeDownloadParameters = new DataUniverseDownloaderGetParameters(symbol, dataUniverseDownloadConfig.StartDate, dataUniverseDownloadConfig.EndDate); UniverseExtensions.RunUniverseDownloader(dataDownloader, universeDownloadParameters); } } /// /// Retrieves the data time zone and exchange time zone associated with the specified symbol. /// /// The symbol for which to retrieve time zones. /// /// A tuple containing the data time zone and exchange time zone. /// The data time zone represents the time zone for data related to the symbol. /// The exchange time zone represents the time zone for trading activities related to the symbol. /// private static (DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone) GetDataAndExchangeTimeZoneBySymbol(Symbol symbol) { var entry = _marketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.SecurityType); return (entry.DataTimeZone, entry.ExchangeHours.TimeZone); } /// /// Initializes various configurations for the application. /// This method sets up logging, data providers, map file providers, and factor file providers. /// /// /// The method reads configuration values to determine whether debugging is enabled, /// which log handler to use, and which data, map file, and factor file providers to initialize. /// /// /// /// /// /// /// /// public static void InitializeConfigurations() { Log.DebuggingEnabled = Config.GetBool("debug-mode", false); Log.LogHandler = Composer.Instance.GetExportedValueByTypeName(Config.Get("log-handler", "CompositeLogHandler")); var dataProvider = Composer.Instance.GetExportedValueByTypeName("DefaultDataProvider"); var mapFileProvider = Composer.Instance.GetExportedValueByTypeName(Config.Get("map-file-provider", "LocalDiskMapFileProvider")); var factorFileProvider = Composer.Instance.GetExportedValueByTypeName(Config.Get("factor-file-provider", "LocalDiskFactorFileProvider")); var optionChainProvider = Composer.Instance.GetPart(); if (optionChainProvider == null) { var historyManager = Composer.Instance.GetExportedValueByTypeName(nameof(HistoryProviderManager)); historyManager.Initialize(new HistoryProviderInitializeParameters(null, null, dataProvider, _dataCacheProvider, mapFileProvider, factorFileProvider, _ => { }, false, new DataPermissionManager(), null, new AlgorithmSettings())); var baseOptionChainProvider = new LiveOptionChainProvider(); baseOptionChainProvider.Initialize(new(mapFileProvider, historyManager)); optionChainProvider = new CachingOptionChainProvider(baseOptionChainProvider); Composer.Instance.AddPart(optionChainProvider); } mapFileProvider.Initialize(dataProvider); factorFileProvider.Initialize(mapFileProvider, dataProvider); } }