Lean  $LEAN_TAG$
BacktestingChainProvider.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;
21 using System.Collections.Generic;
22 
24 {
25  /// <summary>
26  /// Base backtesting cache provider which will source symbols from local zip files
27  /// </summary>
28  public abstract class BacktestingChainProvider
29  {
30  // see https://github.com/QuantConnect/Lean/issues/6384
31  private static readonly TickType[] DataTypes = new[] { TickType.Quote, TickType.OpenInterest, TickType.Trade };
32  private static readonly Resolution[] Resolutions = new[] { Resolution.Minute, Resolution.Hour, Resolution.Daily };
33  private bool _loggedPreviousTradableDate;
34 
35  /// <summary>
36  /// The data cache instance to use
37  /// </summary>
39 
40  /// <summary>
41  /// Creates a new instance
42  /// </summary>
43  protected BacktestingChainProvider(IDataCacheProvider dataCacheProvider)
44  {
45  DataCacheProvider = dataCacheProvider;
46  }
47 
48  /// <summary>
49  /// Get the contract symbols associated with the given canonical symbol and date
50  /// </summary>
51  /// <param name="canonicalSymbol">The canonical symbol</param>
52  /// <param name="date">The date to search for</param>
53  protected IEnumerable<Symbol> GetSymbols(Symbol canonicalSymbol, DateTime date)
54  {
55  IEnumerable<string> entries = null;
56  var usedResolution = Resolution.Minute;
57  foreach (var resolution in Resolutions)
58  {
59  usedResolution = resolution;
60  entries = GetZipEntries(canonicalSymbol, date, usedResolution);
61  if (entries != null)
62  {
63  break;
64  }
65  }
66 
67  if (entries == null)
68  {
70  if (mhdb.TryGetEntry(canonicalSymbol.ID.Market, canonicalSymbol, canonicalSymbol.SecurityType, out var entry) && !entry.ExchangeHours.IsDateOpen(date))
71  {
72  if (!_loggedPreviousTradableDate)
73  {
74  _loggedPreviousTradableDate = true;
75  Log.Trace($"BacktestingCacheProvider.GetSymbols(): {date} is not a tradable date for {canonicalSymbol}. When requesting contracts" +
76  $" for non tradable dates, will return contracts of previous tradable date.");
77  }
78 
79  // be user friendly, will return contracts from the previous tradable date
80  foreach (var symbol in GetSymbols(canonicalSymbol, Time.GetStartTimeForTradeBars(entry.ExchangeHours, date, Time.OneDay, 1, false, entry.DataTimeZone)))
81  {
82  yield return symbol;
83  }
84  yield break;
85  }
86 
88  {
89  Log.Debug($"BacktestingCacheProvider.GetSymbols(): found no source of contracts for {canonicalSymbol} for date {date.ToString(DateFormat.EightCharacter)} for any tick type");
90  }
91  yield break;
92  }
93 
94  // generate and return the contract symbol for each zip entry
95  foreach (var zipEntryName in entries)
96  {
97  var symbol = LeanData.ReadSymbolFromZipEntry(canonicalSymbol, usedResolution, zipEntryName);
98  // do not return expired contracts, because we are potentially sourcing this information from daily/hour files we could pick up already expired contracts
99  if (!IsContractExpired(symbol, date))
100  {
101  yield return symbol;
102  }
103  }
104  }
105 
106  /// <summary>
107  /// Helper method to determine if a contract is expired for the requested date
108  /// </summary>
109  protected static bool IsContractExpired(Symbol symbol, DateTime date)
110  {
111  return symbol.ID.Date.Date < date.Date;
112  }
113 
114  private IEnumerable<string> GetZipEntries(Symbol canonicalSymbol, DateTime date, Resolution resolution)
115  {
116  foreach (var tickType in DataTypes)
117  {
118  // build the zip file name and fetch it with our provider
119  var zipFileName = LeanData.GenerateZipFilePath(Globals.DataFolder, canonicalSymbol, date, resolution, tickType);
120  try
121  {
122  return DataCacheProvider.GetZipEntries(zipFileName);
123  }
124  catch
125  {
126  // the cache provider will throw if the file isn't available TODO: it's api should be more like TryGetZipEntries
127  }
128  }
129 
130  return null;
131  }
132  }
133 }