/* * 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 System.Threading; using QuantConnect.Api; using QuantConnect.Util; using QuantConnect.Logging; using QuantConnect.Interfaces; using System.Collections.Generic; using QuantConnect.Configuration; namespace QuantConnect.Lean.Engine.DataFeeds { /// /// An instance of the that will download and update data files as needed via QC's Api. /// public class ApiDataProvider : BaseDownloaderDataProvider { private decimal _purchaseLimit = Config.GetValue("data-purchase-limit", decimal.MaxValue); //QCC private readonly HashSet _unsupportedSecurityType; private readonly DataPricesList _dataPrices; private readonly IApi _api; private readonly bool _subscribedToIndiaEquityMapAndFactorFiles; private readonly bool _subscribedToUsaEquityMapAndFactorFiles; private readonly bool _subscribedToFutureMapAndFactorFiles; private volatile bool _invalidSecurityTypeLog; /// /// Initialize a new instance of the /// public ApiDataProvider() { _unsupportedSecurityType = new HashSet { SecurityType.Future, SecurityType.FutureOption, SecurityType.Index, SecurityType.IndexOption }; _api = Composer.Instance.GetPart(); // If we have no value for organization get account preferred if (string.IsNullOrEmpty(Globals.OrganizationID)) { var account = _api.ReadAccount(); Globals.OrganizationID = account?.OrganizationId; Log.Trace($"ApiDataProvider(): Will use organization Id '{Globals.OrganizationID}'."); } // Read in data prices and organization details _dataPrices = _api.ReadDataPrices(Globals.OrganizationID); var organization = _api.ReadOrganization(Globals.OrganizationID); foreach (var productItem in organization.Products.Where(x => x.Type == ProductType.Data).SelectMany(product => product.Items)) { if (productItem.Id == 37) { // Determine if the user is subscribed to Equity map and factor files (Data product Id 37) _subscribedToUsaEquityMapAndFactorFiles = true; } else if (productItem.Id == 137) { // Determine if the user is subscribed to Future map and factor files (Data product Id 137) _subscribedToFutureMapAndFactorFiles = true; } else if (productItem.Id == 172) { // Determine if the user is subscribed to India map and factor files (Data product Id 172) _subscribedToIndiaEquityMapAndFactorFiles = true; } } // Verify user has agreed to data provider agreements if (organization.DataAgreement.Signed) { //Log Agreement Highlights Log.Trace("ApiDataProvider(): Data Terms of Use has been signed. \r\n" + $" Find full agreement at: {_dataPrices.AgreementUrl} \r\n" + "==========================================================================\r\n" + $"CLI API Access Agreement: On {organization.DataAgreement.SignedTime:d} You Agreed:\r\n" + " - Display or distribution of data obtained through CLI API Access is not permitted. \r\n" + " - Data and Third Party Data obtained via CLI API Access can only be used for individual or internal employee's use.\r\n" + " - Data is provided in LEAN format can not be manipulated for transmission or use in other applications. \r\n" + " - QuantConnect is not liable for the quality of data received and is not responsible for trading losses. \r\n" + "=========================================================================="); Thread.Sleep(TimeSpan.FromSeconds(3)); } else { // Log URL to go accept terms throw new InvalidOperationException($"ApiDataProvider(): Must agree to terms at {_dataPrices.AgreementUrl}, before using the ApiDataProvider"); } // Verify we have the balance to maintain our purchase limit, if not adjust it to meet our balance var balance = organization.Credit.Balance; if (balance < _purchaseLimit) { if (_purchaseLimit != decimal.MaxValue) { Log.Error("ApiDataProvider(): Purchase limit is greater than balance." + $" Setting purchase limit to balance : {balance}"); } _purchaseLimit = balance; } } /// /// Retrieves data to be used in an algorithm. /// If file does not exist, an attempt is made to download them from the api /// /// File path representing where the data requested /// A of the data requested public override Stream Fetch(string key) { return DownloadOnce(key, s => { // Verify we have enough credit to handle this var pricePath = Api.Api.FormatPathForDataRequest(key); var price = _dataPrices.GetPrice(pricePath); // No price found if (price == -1) { throw new ArgumentException($"ApiDataProvider.Fetch(): No price found for {pricePath}"); } if (_purchaseLimit < price) { throw new ArgumentException($"ApiDataProvider.Fetch(): Cost {price} for {pricePath} data exceeds remaining purchase limit: {_purchaseLimit}"); } if (DownloadData(key)) { // Update our purchase limit. _purchaseLimit -= price; } }); } /// /// Main filter to determine if this file needs to be downloaded /// /// File we are looking at /// True if should download protected override bool NeedToDownload(string filePath) { // Ignore null if (filePath == null) { return false; } // Some security types can't be downloaded, lets attempt to extract that information if (LeanData.TryParseSecurityType(filePath, out SecurityType securityType, out var market) && _unsupportedSecurityType.Contains(securityType) && // We do support universe data for some security types (options and futures) !IsUniverseData(securityType, filePath)) { // we do support future auxiliary data (map and factor files) if (securityType != SecurityType.Future || !IsAuxiliaryData(filePath)) { if (!_invalidSecurityTypeLog) { // let's log this once. Will still use any existing data on disk _invalidSecurityTypeLog = true; Log.Error($"ApiDataProvider(): does not support security types: {string.Join(", ", _unsupportedSecurityType)}"); } return false; } } if (securityType == SecurityType.Equity && filePath.Contains("fine", StringComparison.InvariantCultureIgnoreCase) && filePath.Contains("fundamental", StringComparison.InvariantCultureIgnoreCase)) { // Ignore fine fundamental data requests return false; } // Only download if it doesn't exist or is out of date. // Files are only "out of date" for non date based files (hour, daily, margins, etc.) because this data is stored all in one file var shouldDownload = !File.Exists(filePath) || filePath.IsOutOfDate(); if (shouldDownload) { if (securityType == SecurityType.Future) { if (!_subscribedToFutureMapAndFactorFiles) { throw new ArgumentException("ApiDataProvider(): Must be subscribed to map and factor files to use the ApiDataProvider " + "to download Future auxiliary data from QuantConnect. " + "Please visit https://www.quantconnect.com/datasets/quantconnect-us-futures-security-master for details."); } } // Final check; If we want to download and the request requires equity data we need to be sure they are subscribed to map and factor files else if (!_subscribedToUsaEquityMapAndFactorFiles && market.Equals(Market.USA, StringComparison.InvariantCultureIgnoreCase) && (securityType == SecurityType.Equity || securityType == SecurityType.Option || IsAuxiliaryData(filePath))) { throw new ArgumentException("ApiDataProvider(): Must be subscribed to map and factor files to use the ApiDataProvider " + "to download Equity data from QuantConnect. " + "Please visit https://www.quantconnect.com/datasets/quantconnect-security-master for details."); } else if (!_subscribedToIndiaEquityMapAndFactorFiles && market.Equals(Market.India, StringComparison.InvariantCultureIgnoreCase) && (securityType == SecurityType.Equity || securityType == SecurityType.Option || IsAuxiliaryData(filePath))) { throw new ArgumentException("ApiDataProvider(): Must be subscribed to map and factor files to use the ApiDataProvider " + "to download India data from QuantConnect. " + "Please visit https://www.quantconnect.com/datasets/truedata-india-equity-security-master for details."); } } return shouldDownload; } /// /// Attempt to download data using the Api for and return a FileStream of that data. /// /// The path to store the file /// A FileStream of the data protected virtual bool DownloadData(string filePath) { if (Log.DebuggingEnabled) { Log.Debug($"ApiDataProvider.Fetch(): Attempting to get data from QuantConnect.com's data library for {filePath}."); } if (_api.DownloadData(filePath, Globals.OrganizationID)) { Log.Trace($"ApiDataProvider.Fetch(): Successfully retrieved data for {filePath}."); return true; } // Failed to download; _api.DownloadData() will post error return false; } /// /// Helper method to determine if this filepath is auxiliary data /// /// The target file path /// True if this file is of auxiliary data private static bool IsAuxiliaryData(string filepath) { return filepath.Contains("map_files", StringComparison.InvariantCulture) || filepath.Contains("factor_files", StringComparison.InvariantCulture) || filepath.Contains("fundamental", StringComparison.InvariantCulture) || filepath.Contains("shortable", StringComparison.InvariantCulture); } /// /// Helper method to determine if this file path if for a universe file /// private static bool IsUniverseData(SecurityType securityType, string filepath) { return (securityType.IsOption() || securityType == SecurityType.Future) && filepath.Contains("universes", StringComparison.InvariantCulture); } } }