/* * 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.Collections; using System.Collections.Generic; using NodaTime; using QuantConnect.Data; using QuantConnect.Data.Auxiliary; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Interfaces; using QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.Lean.Engine.DataFeeds.Enumerators; using QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories; using QuantConnect.Securities; using QuantConnect.Util; using HistoryRequest = QuantConnect.Data.HistoryRequest; namespace QuantConnect.Lean.Engine.HistoricalData { /// /// Provides an implementation of that uses /// instances to retrieve historical data /// public class SubscriptionDataReaderHistoryProvider : SynchronizingHistoryProvider { private IMapFileProvider _mapFileProvider; private IFactorFileProvider _factorFileProvider; private IDataCacheProvider _dataCacheProvider; private IDataPermissionManager _dataPermissionManager; private bool _parallelHistoryRequestsEnabled; private bool _initialized; /// /// Initializes this history provider to work for the specified job /// /// The initialization parameters public override void Initialize(HistoryProviderInitializeParameters parameters) { if (_initialized) { // let's make sure no one tries to change our parameters values throw new InvalidOperationException("SubscriptionDataReaderHistoryProvider can only be initialized once"); } _initialized = true; _mapFileProvider = parameters.MapFileProvider; _dataCacheProvider = parameters.DataCacheProvider; _factorFileProvider = parameters.FactorFileProvider; _dataPermissionManager = parameters.DataPermissionManager; _parallelHistoryRequestsEnabled = parameters.ParallelHistoryRequestsEnabled; } /// /// Gets the history for the requested securities /// /// The historical data requests /// The time zone used when time stamping the slice instances /// An enumerable of the slices of data covering the span specified in each request public override IEnumerable GetHistory(IEnumerable requests, DateTimeZone sliceTimeZone) { // create subscription objects from the configs var subscriptions = new List(); foreach (var request in requests) { var subscription = CreateSubscription(request, request.StartTimeUtc, request.EndTimeUtc); subscriptions.Add(subscription); } return CreateSliceEnumerableFromSubscriptions(subscriptions, sliceTimeZone); } /// /// Creates a subscription to process the request /// private Subscription CreateSubscription(HistoryRequest request, DateTime startUtc, DateTime endUtc) { // data reader expects these values in local times var startTimeLocal = startUtc.ConvertFromUtc(request.ExchangeHours.TimeZone); var endTimeLocal = endUtc.ConvertFromUtc(request.ExchangeHours.TimeZone); var config = new SubscriptionDataConfig(request.DataType, request.Symbol, request.Resolution, request.DataTimeZone, request.ExchangeHours.TimeZone, request.FillForwardResolution.HasValue, request.IncludeExtendedMarketHours, false, request.IsCustomData, request.TickType, true, request.DataNormalizationMode ); _dataPermissionManager.AssertConfiguration(config); var security = new Security( request.ExchangeHours, config, new Cash(Currencies.NullCurrency, 0, 1m), SymbolProperties.GetDefault(Currencies.NullCurrency), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache() ); var mapFileResolver = MapFileResolver.Empty; if (config.TickerShouldBeMapped()) { mapFileResolver = _mapFileProvider.Get(config.Market); var mapFile = mapFileResolver.ResolveMapFile(config.Symbol.ID.Symbol, config.Symbol.ID.Date); config.MappedSymbol = mapFile.GetMappedSymbol(startTimeLocal, config.MappedSymbol); } // Tradable dates are defined with the data time zone to access the right source var tradableDates = Time.EachTradeableDayInTimeZone(request.ExchangeHours, startTimeLocal, endTimeLocal, request.DataTimeZone, request.IncludeExtendedMarketHours); var dataReader = new SubscriptionDataReader(config, startTimeLocal, endTimeLocal, mapFileResolver, _factorFileProvider, tradableDates, false, _dataCacheProvider ); dataReader.InvalidConfigurationDetected += (sender, args) => { OnInvalidConfigurationDetected(args); }; dataReader.NumericalPrecisionLimited += (sender, args) => { OnNumericalPrecisionLimited(args); }; dataReader.StartDateLimited += (sender, args) => { OnStartDateLimited(args); }; dataReader.DownloadFailed += (sender, args) => { OnDownloadFailed(args); }; dataReader.ReaderErrorDetected += (sender, args) => { OnReaderErrorDetected(args); }; IEnumerator reader = dataReader; var intraday = GetIntradayDataEnumerator(dataReader, request); if (intraday != null) { // we optionally concatenate the intraday data enumerator reader = new ConcatEnumerator(true, reader, intraday); } reader = CorporateEventEnumeratorFactory.CreateEnumerators( reader, config, _factorFileProvider, dataReader, mapFileResolver, false, startTimeLocal); // optionally apply fill forward behavior if (request.FillForwardResolution.HasValue) { // copy forward Bid/Ask bars for QuoteBars if (request.DataType == typeof(QuoteBar)) { reader = new QuoteBarFillForwardEnumerator(reader); } var readOnlyRef = Ref.CreateReadOnly(() => request.FillForwardResolution.Value.ToTimeSpan()); reader = new FillForwardEnumerator(reader, security.Exchange, readOnlyRef, request.IncludeExtendedMarketHours, endTimeLocal, config.Increment, config.DataTimeZone, startTimeLocal); } // since the SubscriptionDataReader performs an any overlap condition on the trade bar's entire // range (time->end time) we can end up passing the incorrect data (too far past, possibly future), // so to combat this we deliberately filter the results from the data reader to fix these cases // which only apply to non-tick data reader = new SubscriptionFilterEnumerator(reader, security, endTimeLocal, config.ExtendedMarketHours, false); reader = new FilterEnumerator(reader, data => { // allow all ticks if (config.Resolution == Resolution.Tick) return true; // filter out future data if (data.EndTime > endTimeLocal) return false; // filter out data before the start return data.EndTime > startTimeLocal; }); var subscriptionRequest = new SubscriptionRequest(false, null, security, config, request.StartTimeUtc, request.EndTimeUtc); if (_parallelHistoryRequestsEnabled) { return SubscriptionUtils.CreateAndScheduleWorker(subscriptionRequest, reader, _factorFileProvider, false); } return SubscriptionUtils.Create(subscriptionRequest, reader); } /// /// Gets the intraday data enumerator if any /// protected virtual IEnumerator GetIntradayDataEnumerator(IEnumerator rawData, HistoryRequest request) { return null; } private class FilterEnumerator : IEnumerator { private readonly IEnumerator _enumerator; private readonly Func _filter; public FilterEnumerator(IEnumerator enumerator, Func filter) { _enumerator = enumerator; _filter = filter; } #region Implementation of IDisposable public void Dispose() { _enumerator.Dispose(); } #endregion #region Implementation of IEnumerator public bool MoveNext() { // run the enumerator until it passes the specified filter while (_enumerator.MoveNext()) { if (_filter(_enumerator.Current)) { return true; } } return false; } public void Reset() { _enumerator.Reset(); } public T Current { get { return _enumerator.Current; } } object IEnumerator.Current { get { return _enumerator.Current; } } #endregion } } }