Lean  $LEAN_TAG$
LocalZipFactorFileProvider.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using QuantConnect.Util;
18 using QuantConnect.Logging;
19 using System.Threading.Tasks;
21 using System.Collections.Generic;
22 
24 {
25  /// <summary>
26  /// Provides an implementation of <see cref="IFactorFileProvider"/> that searches the local disk for a zip file containing all factor files
27  /// </summary>
29  {
30  private readonly object _lock;
31  private IDataProvider _dataProvider;
32  private IMapFileProvider _mapFileProvider;
33  private Dictionary<AuxiliaryDataKey, bool> _seededMarket;
34  private readonly Dictionary<Symbol, IFactorProvider> _factorFiles;
35 
36  /// <summary>
37  /// The cached refresh period for the factor files
38  /// </summary>
39  /// <remarks>Exposed for testing</remarks>
40  protected virtual TimeSpan CacheRefreshPeriod
41  {
42  get
43  {
44  var dueTime = Time.GetNextLiveAuxiliaryDataDueTime();
45  if (dueTime > TimeSpan.FromMinutes(10))
46  {
47  // Clear the cache before the auxiliary due time to avoid race conditions with consumers
48  return dueTime - TimeSpan.FromMinutes(10);
49  }
50  return dueTime;
51  }
52  }
53 
54  /// <summary>
55  /// Creates a new instance of the <see cref="LocalZipFactorFileProvider"/> class.
56  /// </summary>
58  {
59  _factorFiles = new Dictionary<Symbol, IFactorProvider>();
60  _seededMarket = new Dictionary<AuxiliaryDataKey, bool>();
61  _lock = new object();
62  }
63 
64  /// <summary>
65  /// Initializes our FactorFileProvider by supplying our mapFileProvider
66  /// and dataProvider
67  /// </summary>
68  /// <param name="mapFileProvider">MapFileProvider to use</param>
69  /// <param name="dataProvider">DataProvider to use</param>
70  public void Initialize(IMapFileProvider mapFileProvider, IDataProvider dataProvider)
71  {
72  if (_mapFileProvider != null || _dataProvider != null)
73  {
74  return;
75  }
76 
77  _mapFileProvider = mapFileProvider;
78  _dataProvider = dataProvider;
80  }
81 
82  /// <summary>
83  /// Gets a <see cref="FactorFile{T}"/> instance for the specified symbol, or null if not found
84  /// </summary>
85  /// <param name="symbol">The security's symbol whose factor file we seek</param>
86  /// <returns>The resolved factor file, or null if not found</returns>
87  public IFactorProvider Get(Symbol symbol)
88  {
89  symbol = symbol.GetFactorFileSymbol();
90  var key = AuxiliaryDataKey.Create(symbol);
91  lock (_lock)
92  {
93  if (!_seededMarket.ContainsKey(key))
94  {
95  HydrateFactorFileFromLatestZip(key);
96  _seededMarket[key] = true;
97  }
98 
99  IFactorProvider factorFile;
100  if (!_factorFiles.TryGetValue(symbol, out factorFile))
101  {
102  // Could not find factor file for symbol
103  Log.Error($"LocalZipFactorFileProvider.Get({symbol}): No factor file found.");
104  _factorFiles[symbol] = factorFile = symbol.GetEmptyFactorFile();
105  }
106  return factorFile;
107  }
108  }
109 
110  /// <summary>
111  /// Helper method that will clear any cached factor files in a daily basis, this is useful for live trading
112  /// </summary>
113  protected virtual void StartExpirationTask()
114  {
115  lock (_lock)
116  {
117  // we clear the seeded markets so they are reloaded
118  _seededMarket = new Dictionary<AuxiliaryDataKey, bool>();
119  }
120  _ = Task.Delay(CacheRefreshPeriod).ContinueWith(_ => StartExpirationTask());
121  }
122 
123  /// Hydrate the <see cref="_factorFiles"/> from the latest zipped factor file on disk
124  private void HydrateFactorFileFromLatestZip(AuxiliaryDataKey key)
125  {
126  var market = key.Market;
127  // start the search with yesterday, today's file will be available tomorrow
128  var todayNewYork = DateTime.UtcNow.ConvertFromUtc(TimeZones.NewYork).Date;
129  var date = todayNewYork.AddDays(-1);
130 
131  var count = 0;
132 
133  do
134  {
135  var factorFilePath = FactorFileZipHelper.GetFactorFileZipFileName(market, date, key.SecurityType);
136 
137  // Fetch a stream for our zip from our data provider
138  var stream = _dataProvider.Fetch(factorFilePath);
139 
140  // If the file was found we can read the file
141  if (stream != null)
142  {
143  var mapFileResolver = _mapFileProvider.Get(key);
144  foreach (var keyValuePair in FactorFileZipHelper.ReadFactorFileZip(stream, mapFileResolver, market, key.SecurityType))
145  {
146  // we merge with existing, this will allow to hold multiple markets
147  _factorFiles[keyValuePair.Key] = keyValuePair.Value;
148  }
149  stream.DisposeSafely();
150  Log.Trace($"LocalZipFactorFileProvider.Get({market}): Fetched factor files for: {date.ToShortDateString()} NY");
151 
152  return;
153  }
154 
155  // Otherwise we will search back another day
156  Log.Debug($"LocalZipFactorFileProvider.Get({market}): No factor file found for date {date.ToShortDateString()}");
157 
158  // prevent infinite recursion if something is wrong
159  if (count++ > 7)
160  {
161  throw new InvalidOperationException($"LocalZipFactorFileProvider.Get(): Could not find any factor files going all the way back to {date} for {market}");
162  }
163 
164  date = date.AddDays(-1);
165  }
166  while (true);
167  }
168  }
169 }