69d2f5ae82
Report Generator Tests / build (push) Has been cancelled
Research Regression Tests / build (push) Has been cancelled
Python Virtual Environments / build (push) Has been cancelled
API Tests / build (push) Has been cancelled
Benchmarks / build (push) Has been cancelled
Build & Test Lean / build (push) Has been cancelled
Regression Tests / build (push) Has been cancelled
* Make FOPs selection universe file-based for backtesting * Make FOPs option chains universe file based * Make Future universe selection file-based like option universe * Make Future universe selection file-based like option universe * Abstraction cleanup * Add FuturesChains API to QC algorithm Also refactor future chain provider to use the new FutureUniverse instead of zip file names * Update regression algorithms stats * Refactor QuantBook option and future history to use new universes * Fix failing tests * Fix failing tests * Fix failing tests * Minor future chains unit test improvement * Add futures chains DataFrame property Also, remove IDerivativeSecurity interface from Future * Add DataFrame property to FuturesChains class * Add regression algorithms * Add regression algorithms * Replace QCAlgorithm.FutureChainProvider usages with new FuturesChain api * Minor fixes * Reduce number of universe files in repo * Minor data fixes * Regression algorithms updates * Add implicit conversion from FuturesContract to Symbol Modified algorithms to use futures contract objects directly instead of accessing their Symbol property. Removed unnecessary import statements and redundant lines in various files. * Improve resolution handling for history requests * Changed _auxiliaryData field to lazily-initialized AuxiliaryData property * Refactor data handling in BaseChain and TimeSliceFactory - Added `AddData` method to `BaseChain` for adding market data - Refactored `TimeSliceFactory` to use `BaseChain.AddData` method * Remove specific constructors and indexers from Chain classes Removed public indexers in `BaseChains` for getting or setting `BaseChain` instances by `ticker` or `Symbol`, which were used for Pythonnet compatibility. * Remove chain cache logic from FuturesChainUniverse * Refactor class and interface names for clarity Renamed `FileBasedUniverse` to `BaseChainUniverseData` and `IFileBasedUniverse` to `IChainUniverseData`. * Add base class for options and futures contracts - Introduced `BaseContract` as an abstract base class for contracts, consolidating common properties and methods. - Removed ISymbolInterface * Add minor fix for future options tickers parsing Added tests * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Clean chain provider classes up * Remove ZipEntryName other classes and unused code Removed ZipEntryName class and references across various files. Removed DataQueueFuturesChainUniverseDataCollectionEnumerator and DataQueueOptionChainUniverseDataCollectionEnumerator classes. Removed OptionChainUniverseSubscriptionEnumeratorFactory class. Removed unused code for handling OptionChainUniverse and FuturesChainUniverse in FileSystemDataFeed.cs and LiveTradingDataFeed.cs. Removed several test files related to enumerator factories and universe data collection. * Minor changes and cleanup * Trigger Build * Trigger Build * Refactor FuturesContract data handling Forward price data from bars and ticks stored in private fields for improved memory usage * Fix: use universe data for market data in FuturesContract * Update regression algorithms stats after rebase Added HSI futures universe files * Sort configs by internal flag Internals go first * Throw from option universe data filters for future options Future options IV, Open interest and greeks are not supported for future options * Minor changes * Improve some regression algorithms * Minor fix for failing unit tests * Update FOPs universe file header Removed greeks and IV columns. Updated FOPs universe files: removed outdated columns. * Minor unit test fix * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Minor fix * Add history provider as constructor argument for chain providers * Update new regression algorithms data points count * Minor fix for FakeDataQueue * Add initialize method to chain providers classes * Minor changes * Trigger Build * Trigger Build * Trigger Build * Minor fix * Minor fix * Trigger Build * Trigger Build * Trigger Build * Trigger Build * Add logs to ProcessedDataProvider * Removed test logs * Minor fix * Support downloading options and futures universe files from api data provider
269 lines
13 KiB
C#
269 lines
13 KiB
C#
/*
|
|
* 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
|
|
{
|
|
/// <summary>
|
|
/// An instance of the <see cref="IDataProvider"/> that will download and update data files as needed via QC's Api.
|
|
/// </summary>
|
|
public class ApiDataProvider : BaseDownloaderDataProvider
|
|
{
|
|
private decimal _purchaseLimit = Config.GetValue("data-purchase-limit", decimal.MaxValue); //QCC
|
|
|
|
private readonly HashSet<SecurityType> _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;
|
|
|
|
/// <summary>
|
|
/// Initialize a new instance of the <see cref="ApiDataProvider"/>
|
|
/// </summary>
|
|
public ApiDataProvider()
|
|
{
|
|
_unsupportedSecurityType = new HashSet<SecurityType> { SecurityType.Future, SecurityType.FutureOption, SecurityType.Index, SecurityType.IndexOption };
|
|
|
|
_api = Composer.Instance.GetPart<IApi>();
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves data to be used in an algorithm.
|
|
/// If file does not exist, an attempt is made to download them from the api
|
|
/// </summary>
|
|
/// <param name="key">File path representing where the data requested</param>
|
|
/// <returns>A <see cref="Stream"/> of the data requested</returns>
|
|
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;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main filter to determine if this file needs to be downloaded
|
|
/// </summary>
|
|
/// <param name="filePath">File we are looking at</param>
|
|
/// <returns>True if should download</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempt to download data using the Api for and return a FileStream of that data.
|
|
/// </summary>
|
|
/// <param name="filePath">The path to store the file</param>
|
|
/// <returns>A FileStream of the data</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to determine if this filepath is auxiliary data
|
|
/// </summary>
|
|
/// <param name="filepath">The target file path</param>
|
|
/// <returns>True if this file is of auxiliary data</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to determine if this file path if for a universe file
|
|
/// </summary>
|
|
private static bool IsUniverseData(SecurityType securityType, string filepath)
|
|
{
|
|
return (securityType.IsOption() || securityType == SecurityType.Future) &&
|
|
filepath.Contains("universes", StringComparison.InvariantCulture);
|
|
}
|
|
}
|
|
}
|