generated from QuantConnect/Lean.DataSource.SDK
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate CoinApi Integration to Standalone Project (#1)
* feat: remove template files * feat: prepare solution's structure feat: github issue & PR templates feat: prepare GH workflow file * feat: paste coinApi files from ToolBox * feat: paste coinApi.Converter files from ToolBox * feat: paste coinApi.test files from ToolBox feat: TestSetup initializer * feat: divide HistoryProvider in file * remove: does not support exchanges in SymbolMapper * refactor: downgrade pckg Microsoft.NET.Test.Sdk * feat: handle JsonSerializationException feat: simplify code feat: remove one warning msg * feat: DQH tests with different param feat: GetBrokerage CryptoFuture Symbol Test feat: init with wrong api key test feat: helper class for tests * feat: forceTypeNameOnExisting set up to false for GetExportedValueByTypeName * feat: add sync bash script in Converter project * remove: unsupported markets from Converter * feat: ValidateSubscription * fix: productId in ValidateSubscription * refactor: make static of TestHelper class fix: deprecated GDAX to Coinbase Market in Symbol test * feat: CoinAPIDataDownloader fea: test of CoinAPIDataDownloader fix: reset config in wrong api key test * fix: handle exception when parsing response in History * feat: update Readme * Create LICENSE * fix: reset config in test where we change config * refactor: create RestClient at once time * refactor: create RestRequest only once * rename: Converter to DataProcessing * refactor: test OneTimeSetUp to testing class * refactor: increase delay in DQH tests * fix: change delay and init DQH class tests * refactor: GlobalSetup make static * refactor: ProcessFeed in DQH tests * feat: add some explicit and log trace * refactor: validation on null tick remove: thread sleep * feat: add delay in ProcessFeed by cancellationToken refactor: future test * fix: tick symbol in CryptoFuture test * remove: Explicit attribute in tests
- Loading branch information
Showing
56 changed files
with
3,236 additions
and
1,373 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<!--- Provide a general summary of the issue in the Title above --> | ||
|
||
## Expected Behavior | ||
<!--- If you're describing a bug, tell us what should happen --> | ||
<!--- If you're suggesting a change/improvement, tell us how it should work --> | ||
|
||
## Current Behavior | ||
<!--- If describing a bug, tell us what happens instead of the expected behavior --> | ||
<!--- If suggesting a change/improvement, explain the difference from current behavior --> | ||
|
||
## Possible Solution | ||
<!--- Not obligatory, but suggest a fix/reason for the bug, --> | ||
<!--- or ideas how to implement the addition or change --> | ||
|
||
## Steps to Reproduce (for bugs) | ||
<!--- Provide a link to a live example, or an unambiguous set of steps to --> | ||
<!--- reproduce this bug. Include code to reproduce, if relevant --> | ||
1. | ||
2. | ||
3. | ||
4. | ||
|
||
## Context | ||
<!--- How has this issue affected you? What are you trying to accomplish? --> | ||
<!--- Providing context helps us come up with a solution that is most useful in the real world --> | ||
|
||
## Your Environment | ||
<!--- Include as many relevant details about the environment you experienced the bug in --> | ||
* Version used: | ||
* Environment name and version (e.g. PHP 5.4 on nginx 1.9.1): | ||
* Server type and version: | ||
* Operating System and version: | ||
* Link to your project: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<!--- Provide a general summary of your changes in the Title above --> | ||
|
||
## Description | ||
<!--- Describe your changes in detail --> | ||
|
||
## Motivation and Context | ||
<!--- Why is this change required? What problem does it solve? --> | ||
<!--- If it fixes an open issue, please link to the issue here. --> | ||
|
||
## How Has This Been Tested? | ||
<!--- Please describe in detail how you tested your changes. --> | ||
<!--- Include details of your testing environment, and the tests you ran to --> | ||
<!--- see how your change affects other areas of the code, etc. --> | ||
|
||
## Screenshots (if appropriate): | ||
|
||
## Types of changes | ||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> | ||
- [ ] Bug fix (non-breaking change which fixes an issue) | ||
- [ ] New feature (non-breaking change which adds functionality) | ||
- [ ] Breaking change (fix or feature that would cause existing functionality to change) | ||
|
||
## Checklist: | ||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. --> | ||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> | ||
- [ ] My code follows the code style of this project. | ||
- [ ] My change requires a change to the documentation. | ||
- [ ] I have updated the documentation accordingly. | ||
- [ ] I have read the **CONTRIBUTING** document. | ||
- [ ] I have added tests to cover my changes. | ||
- [ ] All new and existing tests passed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
/* | ||
* 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 QuantConnect.Data; | ||
using QuantConnect.Util; | ||
using System.Diagnostics; | ||
using QuantConnect.Logging; | ||
using QuantConnect.ToolBox; | ||
using QuantConnect.CoinAPI; | ||
|
||
namespace QuantConnect.DataProcessing | ||
{ | ||
/// <summary> | ||
/// Console application for converting CoinApi raw data into Lean data format for high resolutions (tick, second and minute) | ||
/// </summary> | ||
public class CoinApiDataConverter | ||
{ | ||
/// <summary> | ||
/// List of supported exchanges | ||
/// </summary> | ||
private static readonly HashSet<string> SupportedMarkets = new[] | ||
{ | ||
Market.Coinbase, | ||
Market.Bitfinex, | ||
Market.Binance, | ||
Market.Kraken, | ||
Market.BinanceUS | ||
}.ToHashSet(); | ||
|
||
private readonly DirectoryInfo _rawDataFolder; | ||
private readonly DirectoryInfo _destinationFolder; | ||
private readonly SecurityType _securityType; | ||
private readonly DateTime _processingDate; | ||
private readonly string _market; | ||
|
||
/// <summary> | ||
/// CoinAPI data converter. | ||
/// </summary> | ||
/// <param name="date">the processing date.</param> | ||
/// <param name="rawDataFolder">path to the raw data folder.</param> | ||
/// <param name="destinationFolder">destination of the newly generated files.</param> | ||
/// <param name="securityType">The security type to process</param> | ||
/// <param name="market">The market to process (optional). Defaults to processing all markets in parallel.</param> | ||
public CoinApiDataConverter(DateTime date, string rawDataFolder, string destinationFolder, string market = null, SecurityType securityType = SecurityType.Crypto) | ||
{ | ||
_market = string.IsNullOrWhiteSpace(market) | ||
? null | ||
: market.ToLowerInvariant(); | ||
|
||
_processingDate = date; | ||
_securityType = securityType; | ||
_rawDataFolder = new DirectoryInfo(Path.Combine(rawDataFolder, "crypto", "coinapi")); | ||
if (!_rawDataFolder.Exists) | ||
{ | ||
throw new ArgumentException($"CoinApiDataConverter(): Source folder not found: {_rawDataFolder.FullName}"); | ||
} | ||
|
||
_destinationFolder = new DirectoryInfo(destinationFolder); | ||
_destinationFolder.Create(); | ||
} | ||
|
||
/// <summary> | ||
/// Runs this instance. | ||
/// </summary> | ||
/// <returns></returns> | ||
public bool Run() | ||
{ | ||
var stopwatch = Stopwatch.StartNew(); | ||
|
||
var symbolMapper = new CoinApiSymbolMapper(); | ||
var success = true; | ||
|
||
// There were cases of files with with an extra suffix, following pattern: | ||
// <TickType>-<ID>-<Exchange>_SPOT_<BaseCurrency>_<QuoteCurrency>_<ExtraSuffix>.csv.gz | ||
// Those cases should be ignored for SPOT prices. | ||
var tradesFolder = new DirectoryInfo( | ||
Path.Combine( | ||
_rawDataFolder.FullName, | ||
"trades", | ||
_processingDate.ToStringInvariant(DateFormat.EightCharacter))); | ||
|
||
var quotesFolder = new DirectoryInfo( | ||
Path.Combine( | ||
_rawDataFolder.FullName, | ||
"quotes", | ||
_processingDate.ToStringInvariant(DateFormat.EightCharacter))); | ||
|
||
var rawMarket = _market != null && | ||
CoinApiSymbolMapper.MapMarketsToExchangeIds.TryGetValue(_market, out var rawMarketValue) | ||
? rawMarketValue | ||
: null; | ||
|
||
var securityTypeFilter = (string name) => name.Contains("_SPOT_"); | ||
if (_securityType == SecurityType.CryptoFuture) | ||
{ | ||
securityTypeFilter = (string name) => name.Contains("_FTS_") || name.Contains("_PERP_"); | ||
} | ||
|
||
// Distinct by tick type and first two parts of the raw file name, separated by '-'. | ||
// This prevents us from double processing the same ticker twice, in case we're given | ||
// two raw data files for the same symbol. Related: https://github.com/QuantConnect/Lean/pull/3262 | ||
var apiDataReader = new CoinApiDataReader(symbolMapper); | ||
var filesToProcessCandidates = tradesFolder.EnumerateFiles("*.gz") | ||
.Concat(quotesFolder.EnumerateFiles("*.gz")) | ||
.Where(f => securityTypeFilter(f.Name) && (rawMarket == null || f.Name.Contains(rawMarket))) | ||
.Where(f => f.Name.Split('_').Length == 4) | ||
.ToList(); | ||
|
||
var filesToProcessKeys = new HashSet<string>(); | ||
var filesToProcess = new List<FileInfo>(); | ||
|
||
foreach (var candidate in filesToProcessCandidates) | ||
{ | ||
try | ||
{ | ||
var entryData = apiDataReader.GetCoinApiEntryData(candidate, _processingDate, _securityType); | ||
CurrencyPairUtil.DecomposeCurrencyPair(entryData.Symbol, out var baseCurrency, out var quoteCurrency); | ||
|
||
if (!candidate.FullName.Contains(baseCurrency) && !candidate.FullName.Contains(quoteCurrency)) | ||
{ | ||
throw new Exception($"Skipping {candidate.FullName} we have the wrong symbol {entryData.Symbol}!"); | ||
} | ||
|
||
var key = candidate.Directory.Parent.Name + entryData.Symbol.ID; | ||
if (filesToProcessKeys.Add(key)) | ||
{ | ||
// Separate list from HashSet to preserve ordering of viable candidates | ||
filesToProcess.Add(candidate); | ||
} | ||
} | ||
catch (Exception err) | ||
{ | ||
// Most likely the exchange isn't supported. Log exception message to avoid excessive stack trace spamming in console output | ||
Log.Error(err.Message); | ||
} | ||
} | ||
|
||
Parallel.ForEach(filesToProcess, (file, loopState) => | ||
{ | ||
Log.Trace($"CoinApiDataConverter(): Starting data conversion from source file: {file.Name}..."); | ||
try | ||
{ | ||
ProcessEntry(apiDataReader, file); | ||
} | ||
catch (Exception e) | ||
{ | ||
Log.Error(e, $"CoinApiDataConverter(): Error processing entry: {file.Name}"); | ||
success = false; | ||
loopState.Break(); | ||
} | ||
} | ||
); | ||
|
||
Log.Trace($"CoinApiDataConverter(): Finished in {stopwatch.Elapsed}"); | ||
return success; | ||
} | ||
|
||
/// <summary> | ||
/// Processes the entry. | ||
/// </summary> | ||
/// <param name="coinapiDataReader">The coinapi data reader.</param> | ||
/// <param name="file">The file.</param> | ||
private void ProcessEntry(CoinApiDataReader coinapiDataReader, FileInfo file) | ||
{ | ||
var entryData = coinapiDataReader.GetCoinApiEntryData(file, _processingDate, _securityType); | ||
|
||
if (!SupportedMarkets.Contains(entryData.Symbol.ID.Market)) | ||
{ | ||
// only convert data for supported exchanges | ||
return; | ||
} | ||
|
||
var tickData = coinapiDataReader.ProcessCoinApiEntry(entryData, file); | ||
|
||
// in some cases the first data points from '_processingDate' get's included in the previous date file | ||
// so we will ready previous date data and drop most of it just to save these midnight ticks | ||
var yesterdayDate = _processingDate.AddDays(-1); | ||
var yesterdaysFile = new FileInfo(file.FullName.Replace( | ||
_processingDate.ToStringInvariant(DateFormat.EightCharacter), | ||
yesterdayDate.ToStringInvariant(DateFormat.EightCharacter))); | ||
if (yesterdaysFile.Exists) | ||
{ | ||
var yesterdaysEntryData = coinapiDataReader.GetCoinApiEntryData(yesterdaysFile, yesterdayDate, _securityType); | ||
tickData = tickData.Concat(coinapiDataReader.ProcessCoinApiEntry(yesterdaysEntryData, yesterdaysFile)); | ||
} | ||
else | ||
{ | ||
Log.Error($"CoinApiDataConverter(): yesterdays data file not found '{yesterdaysFile.FullName}'"); | ||
} | ||
|
||
// materialize the enumerable into a list, since we need to enumerate over it twice | ||
var ticks = tickData.Where(tick => tick.Time.Date == _processingDate) | ||
.OrderBy(t => t.Time) | ||
.ToList(); | ||
|
||
var writer = new LeanDataWriter(Resolution.Tick, entryData.Symbol, _destinationFolder.FullName, entryData.TickType); | ||
writer.Write(ticks); | ||
|
||
Log.Trace($"CoinApiDataConverter(): Starting consolidation for {entryData.Symbol.Value} {entryData.TickType}"); | ||
var consolidators = new List<TickAggregator>(); | ||
|
||
if (entryData.TickType == TickType.Trade) | ||
{ | ||
consolidators.AddRange(new[] | ||
{ | ||
new TradeTickAggregator(Resolution.Second), | ||
new TradeTickAggregator(Resolution.Minute) | ||
}); | ||
} | ||
else | ||
{ | ||
consolidators.AddRange(new[] | ||
{ | ||
new QuoteTickAggregator(Resolution.Second), | ||
new QuoteTickAggregator(Resolution.Minute) | ||
}); | ||
} | ||
|
||
foreach (var tick in ticks) | ||
{ | ||
if (tick.Suspicious) | ||
{ | ||
// When CoinAPI loses connectivity to the exchange, they indicate | ||
// it in the data by providing a value of `-1` for bid/ask price. | ||
// We will keep it in tick data, but will remove it from consolidated data. | ||
continue; | ||
} | ||
|
||
foreach (var consolidator in consolidators) | ||
{ | ||
consolidator.Update(tick); | ||
} | ||
} | ||
|
||
foreach (var consolidator in consolidators) | ||
{ | ||
writer = new LeanDataWriter(consolidator.Resolution, entryData.Symbol, _destinationFolder.FullName, entryData.TickType); | ||
writer.Write(consolidator.Flush()); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.