/* * 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.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using QuantConnect.Data; using QuantConnect.Interfaces; using QuantConnect.Lean.Engine.DataFeeds.Transport; using QuantConnect.Util; using System.Runtime.Caching; using QuantConnect.Data.Fundamental; using QuantConnect.Data.UniverseSelection; namespace QuantConnect.Lean.Engine.DataFeeds { /// /// Provides an implementations of that uses the /// /// method to read lines of text from a /// public class TextSubscriptionDataSourceReader : ISubscriptionDataSourceReader { private readonly bool _isLiveMode; private readonly BaseData _factory; private readonly DateTime _date; private readonly SubscriptionDataConfig _config; private bool _shouldCacheDataPoints; private readonly IDataCacheProvider _dataCacheProvider; private static readonly MemoryCache BaseDataSourceCache = new MemoryCache("BaseDataSourceCache", // Cache can use up to 70% of the installed physical memory new NameValueCollection{ { "physicalMemoryLimitPercentage", "70"} }); private static readonly CacheItemPolicy CachePolicy = new CacheItemPolicy { // Cache entry should be evicted if it has not been accessed in given span of time: SlidingExpiration = TimeSpan.FromMinutes(5) }; /// /// Event fired when the specified source is considered invalid, this may /// be from a missing file or failure to download a remote source /// public event EventHandler InvalidSource; /// /// Event fired when an exception is thrown during a call to /// /// public event EventHandler ReaderError; /// /// Event fired when there's an error creating an or the /// instantiated has no data. /// public event EventHandler CreateStreamReaderError; /// /// Initializes a new instance of the class /// /// This provider caches files if needed /// The subscription's configuration /// The date this factory was produced to read data for /// True if we're in live mode, false for backtesting public TextSubscriptionDataSourceReader(IDataCacheProvider dataCacheProvider, SubscriptionDataConfig config, DateTime date, bool isLiveMode) { _dataCacheProvider = dataCacheProvider; _date = date; _config = config; _isLiveMode = isLiveMode; _factory = (BaseData) ObjectActivator.GetActivator(config.Type).Invoke(new object[] { config.Type }); _shouldCacheDataPoints = !_config.IsCustomData && _config.Resolution >= Resolution.Hour && _config.Type != typeof(FineFundamental) && _config.Type != typeof(CoarseFundamental) && !_dataCacheProvider.IsDataEphemeral; } /// /// Reads the specified /// /// The source to be read /// An that contains the data in the source public IEnumerable Read(SubscriptionDataSource source) { List cache; _shouldCacheDataPoints = _shouldCacheDataPoints && // only cache local files source.TransportMedium == SubscriptionTransportMedium.LocalFile; var cacheItem = _shouldCacheDataPoints ? BaseDataSourceCache.GetCacheItem(source.Source + _config.Type) : null; if (cacheItem == null) { cache = new List(); using (var reader = CreateStreamReader(source)) { // if the reader doesn't have data then we're done with this subscription if (reader == null || reader.EndOfStream) { OnCreateStreamReaderError(_date, source); yield break; } // while the reader has data while (!reader.EndOfStream) { // read a line and pass it to the base data factory var line = reader.ReadLine(); BaseData instance = null; try { instance = _factory.Reader(_config, line, _date, _isLiveMode); } catch (Exception err) { OnReaderError(line, err); } if (instance != null && instance.EndTime != default(DateTime)) { if (_shouldCacheDataPoints) { cache.Add(instance); } else { yield return instance; } } else if (reader.ShouldBeRateLimited) { yield return instance; } } } if (!_shouldCacheDataPoints) { yield break; } cacheItem = new CacheItem(source.Source + _config.Type, cache); BaseDataSourceCache.Add(cacheItem, CachePolicy); } cache = cacheItem.Value as List; if (cache == null) { throw new InvalidOperationException("CacheItem can not be cast into expected type. " + $"Type is: {cacheItem.Value.GetType()}"); } // Find the first data point 10 days (just in case) before the desired date // and subtract one item (just in case there was a time gap and data.Time is after _date) var frontier = _date.AddDays(-10); var index = cache.FindIndex(data => data.Time > frontier); index = index > 0 ? (index - 1) : 0; foreach (var data in cache.Skip(index)) { var clone = data.Clone(); clone.Symbol = _config.Symbol; yield return clone; } } /// /// Creates a new for the specified /// /// The source to produce an for /// A new instance of to read the source, or null if there was an error private IStreamReader CreateStreamReader(SubscriptionDataSource subscriptionDataSource) { IStreamReader reader; switch (subscriptionDataSource.TransportMedium) { case SubscriptionTransportMedium.LocalFile: reader = HandleLocalFileSource(subscriptionDataSource); break; case SubscriptionTransportMedium.RemoteFile: reader = HandleRemoteSourceFile(subscriptionDataSource); break; case SubscriptionTransportMedium.Rest: reader = new RestSubscriptionStreamReader(subscriptionDataSource.Source, subscriptionDataSource.Headers, _isLiveMode); break; default: throw new InvalidEnumArgumentException("Unexpected SubscriptionTransportMedium specified: " + subscriptionDataSource.TransportMedium); } return reader; } /// /// Event invocator for the event /// /// The that was invalid /// The exception if one was raised, otherwise null private void OnInvalidSource(SubscriptionDataSource source, Exception exception) { var handler = InvalidSource; if (handler != null) handler(this, new InvalidSourceEventArgs(source, exception)); } /// /// Event invocator for the event /// /// The line that caused the exception /// The exception that was caught private void OnReaderError(string line, Exception exception) { var handler = ReaderError; if (handler != null) handler(this, new ReaderErrorEventArgs(line, exception)); } /// /// Event invocator for the event /// /// The date of the source /// The source that caused the error private void OnCreateStreamReaderError(DateTime date, SubscriptionDataSource source) { var handler = CreateStreamReaderError; if (handler != null) handler(this, new CreateStreamReaderErrorEventArgs(date, source)); } /// /// Opens up an IStreamReader for a local file source /// private IStreamReader HandleLocalFileSource(SubscriptionDataSource source) { // handles zip or text files return new LocalFileSubscriptionStreamReader(_dataCacheProvider, source.Source); } /// /// Opens up an IStreamReader for a remote file source /// private IStreamReader HandleRemoteSourceFile(SubscriptionDataSource source) { SubscriptionDataSourceReader.CheckRemoteFileCache(); try { // this will fire up a web client in order to download the 'source' file to the cache return new RemoteFileSubscriptionStreamReader(_dataCacheProvider, source.Source, Globals.Cache, source.Headers); } catch (Exception err) { OnInvalidSource(source, err); return null; } } } }