/* * 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 DataFeeds = QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.DownloaderDataProvider.Launcher.Models.Constants; 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 dataDownloadConfig = new DataDownloadConfig(); RunDownload(dataDownloader, dataDownloadConfig, Globals.DataFolder, _dataCacheProvider); } /// /// 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."); } // Calculate the total number of seconds between the EndDate and StartDate var totalDataPerSymbolInSeconds = (dataDownloadConfig.EndDate - dataDownloadConfig.StartDate).TotalSeconds; var totalDataInSeconds = totalDataPerSymbolInSeconds * 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, LeanData.GetDataType(downloadParameters.Resolution, downloadParameters.TickType), 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; var progressSoFar = (data.EndTime - dataDownloadConfig.StartDate).TotalSeconds + totalDataPerSymbolInSeconds * completeSymbolCount; var eta = CalculateETA(utcNow, startDownloadUtcTime, totalDataInSeconds, progressSoFar); var progress = CalculateProgress(data.EndTime, dataDownloadConfig.StartDate, dataDownloadConfig.EndDate); Log.Trace($"DownloaderDataProvider.RunDownload(): Downloading {downloadParameters.Symbol} data: {progress:F2}%. ETA: {eta}"); } return data; })); } completeSymbolCount++; 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."); } /// /// 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); } /// /// Calculates the Estimated Time of Arrival (ETA) based on the current progress of a download. /// /// The current UTC DateTime. /// The DateTime when the download started. /// /// /// A TimeSpan representing the Estimated Time of Arrival (ETA). /// /// The method calculates the time elapsed since the start of downloading and estimates /// the remaining time based on the current progress. It uses the difference between /// the end time and the end date to calculate missing data time, and the difference /// between the end time and the start date to calculate the progress so far. /// The ETA is then calculated based on these values. /// public static TimeSpan CalculateETA(DateTime utcNow, DateTime startUtcTime, double totalDataInSeconds, double currentProgressSecond) { // Calculate how much time has passed since the start of downloading TimeSpan howMuchItTookSoFar = utcNow - startUtcTime; var missingDataSecond = totalDataInSeconds - currentProgressSecond; // Calculate ETA in seconds var etaSeconds = (missingDataSecond / currentProgressSecond) * howMuchItTookSoFar.TotalSeconds; // Convert ETA from seconds to TimeSpan TimeSpan etaTimeSpan = TimeSpan.FromSeconds(etaSeconds); return etaTimeSpan; } /// /// Calculates the progress as a percentage based on the elapsed time between the start and end dates. /// /// The current date and time. /// The start date and time. /// The end date and time. /// A double representing the progress as a percentage. private static double CalculateProgress(DateTime currentTime, DateTime start, DateTime end) { double totalDays = (end - start).TotalDays; if (totalDays == 0) { return 0; } double elapsedDays = (currentTime - start).TotalDays; return (elapsedDays / totalDays) * 100; } /// /// 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")); mapFileProvider.Initialize(dataProvider); factorFileProvider.Initialize(mapFileProvider, dataProvider); } }