Lean  $LEAN_TAG$
MarketHoursDatabase.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 System.Collections.Generic;
18 using System.IO;
19 using System.Linq;
20 using Newtonsoft.Json;
21 using NodaTime;
22 using QuantConnect.Data;
23 using QuantConnect.Logging;
25 using QuantConnect.Util;
26 
28 {
29  /// <summary>
30  /// Provides access to exchange hours and raw data times zones in various markets
31  /// </summary>
32  [JsonConverter(typeof(MarketHoursDatabaseJsonConverter))]
33  public class MarketHoursDatabase
34  {
35  private static MarketHoursDatabase _dataFolderMarketHoursDatabase;
36  private static MarketHoursDatabase _alwaysOpenMarketHoursDatabase;
37  private static readonly object DataFolderMarketHoursDatabaseLock = new object();
38 
39  private readonly Dictionary<SecurityDatabaseKey, Entry> _entries;
40 
41  /// <summary>
42  /// Gets all the exchange hours held by this provider
43  /// </summary>
44  public List<KeyValuePair<SecurityDatabaseKey,Entry>> ExchangeHoursListing => _entries.ToList();
45 
46  /// <summary>
47  /// Gets a <see cref="MarketHoursDatabase"/> that always returns <see cref="SecurityExchangeHours.AlwaysOpen"/>
48  /// </summary>
49  public static MarketHoursDatabase AlwaysOpen
50  {
51  get
52  {
53  if (_alwaysOpenMarketHoursDatabase == null)
54  {
55  _alwaysOpenMarketHoursDatabase = new AlwaysOpenMarketHoursDatabaseImpl();
56  }
57 
58  return _alwaysOpenMarketHoursDatabase;
59  }
60  }
61 
62  /// <summary>
63  /// Initializes a new instance of the <see cref="MarketHoursDatabase"/> class
64  /// </summary>
65  /// <param name="exchangeHours">The full listing of exchange hours by key</param>
66  public MarketHoursDatabase(Dictionary<SecurityDatabaseKey, Entry> exchangeHours)
67  {
68  _entries = exchangeHours;
69  }
70 
71  /// <summary>
72  /// Convenience method for retrieving exchange hours from market hours database using a subscription config
73  /// </summary>
74  /// <param name="configuration">The subscription data config to get exchange hours for</param>
75  /// <returns>The configure exchange hours for the specified configuration</returns>
77  {
78  return GetExchangeHours(configuration.Market, configuration.Symbol, configuration.SecurityType);
79  }
80 
81  /// <summary>
82  /// Convenience method for retrieving exchange hours from market hours database using a subscription config
83  /// </summary>
84  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
85  /// <param name="symbol">The particular symbol being traded</param>
86  /// <param name="securityType">The security type of the symbol</param>
87  /// <returns>The exchange hours for the specified security</returns>
88  public SecurityExchangeHours GetExchangeHours(string market, Symbol symbol, SecurityType securityType)
89  {
90  return GetEntry(market, symbol, securityType).ExchangeHours;
91  }
92 
93  /// <summary>
94  /// Performs a lookup using the specified information and returns the data's time zone if found,
95  /// if an entry is not found, an exception is thrown
96  /// </summary>
97  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
98  /// <param name="symbol">The particular symbol being traded</param>
99  /// <param name="securityType">The security type of the symbol</param>
100  /// <returns>The raw data time zone for the specified security</returns>
101  public DateTimeZone GetDataTimeZone(string market, Symbol symbol, SecurityType securityType)
102  {
103  return GetEntry(market, GetDatabaseSymbolKey(symbol), securityType).DataTimeZone;
104  }
105 
106  /// <summary>
107  /// Resets the market hours database, forcing a reload when reused.
108  /// Called in tests where multiple algorithms are run sequentially,
109  /// and we need to guarantee that every test starts with the same environment.
110  /// </summary>
111  public static void Reset()
112  {
113  lock (DataFolderMarketHoursDatabaseLock)
114  {
115  _dataFolderMarketHoursDatabase = null;
116  }
117  }
118 
119  /// <summary>
120  /// Gets the instance of the <see cref="MarketHoursDatabase"/> class produced by reading in the market hours
121  /// data found in /Data/market-hours/
122  /// </summary>
123  /// <returns>A <see cref="MarketHoursDatabase"/> class that represents the data in the market-hours folder</returns>
125  {
126  var result = _dataFolderMarketHoursDatabase;
127  if (result == null)
128  {
129  lock (DataFolderMarketHoursDatabaseLock)
130  {
131  if (_dataFolderMarketHoursDatabase == null)
132  {
133  var path = Path.Combine(Globals.GetDataFolderPath("market-hours"), "market-hours-database.json");
134  _dataFolderMarketHoursDatabase = FromFile(path);
135  }
136  result = _dataFolderMarketHoursDatabase;
137  }
138  }
139  return result;
140  }
141 
142  /// <summary>
143  /// Reads the specified file as a market hours database instance
144  /// </summary>
145  /// <param name="path">The market hours database file path</param>
146  /// <returns>A new instance of the <see cref="MarketHoursDatabase"/> class</returns>
147  public static MarketHoursDatabase FromFile(string path)
148  {
149  return JsonConvert.DeserializeObject<MarketHoursDatabase>(File.ReadAllText(path));
150  }
151 
152  /// <summary>
153  /// Sets the entry for the specified market/symbol/security-type.
154  /// This is intended to be used by custom data and other data sources that don't have explicit
155  /// entries in market-hours-database.csv. At run time, the algorithm can update the market hours
156  /// database via calls to AddData.
157  /// </summary>
158  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
159  /// <param name="symbol">The particular symbol being traded</param>
160  /// <param name="securityType">The security type of the symbol</param>
161  /// <param name="exchangeHours">The exchange hours for the specified symbol</param>
162  /// <param name="dataTimeZone">The time zone of the symbol's raw data. Optional, defaults to the exchange time zone</param>
163  /// <returns>The entry matching the specified market/symbol/security-type</returns>
164  public virtual Entry SetEntry(string market, string symbol, SecurityType securityType, SecurityExchangeHours exchangeHours, DateTimeZone dataTimeZone = null)
165  {
166  dataTimeZone = dataTimeZone ?? exchangeHours.TimeZone;
167  var key = new SecurityDatabaseKey(market, symbol, securityType);
168  var entry = new Entry(dataTimeZone, exchangeHours);
169  _entries[key] = entry;
170  return entry;
171  }
172 
173  /// <summary>
174  /// Convenience method for the common custom data case.
175  /// Sets the entry for the specified symbol using SecurityExchangeHours.AlwaysOpen(timeZone)
176  /// This sets the data time zone equal to the exchange time zone as well.
177  /// </summary>
178  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
179  /// <param name="symbol">The particular symbol being traded</param>
180  /// <param name="securityType">The security type of the symbol</param>
181  /// <param name="timeZone">The time zone of the symbol's exchange and raw data</param>
182  /// <returns>The entry matching the specified market/symbol/security-type</returns>
183  public virtual Entry SetEntryAlwaysOpen(string market, string symbol, SecurityType securityType, DateTimeZone timeZone)
184  {
185  return SetEntry(market, symbol, securityType, SecurityExchangeHours.AlwaysOpen(timeZone));
186  }
187 
188  /// <summary>
189  /// Gets the entry for the specified market/symbol/security-type
190  /// </summary>
191  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
192  /// <param name="symbol">The particular symbol being traded</param>
193  /// <param name="securityType">The security type of the symbol</param>
194  /// <returns>The entry matching the specified market/symbol/security-type</returns>
195  public virtual Entry GetEntry(string market, string symbol, SecurityType securityType)
196  {
197  Entry entry;
198  // Fall back on the Futures MHDB entry if the FOP lookup failed.
199  // Some FOPs have the same symbol properties as their futures counterparts.
200  // So, to save ourselves some space, we can fall back on the existing entries
201  // so that we don't duplicate the information.
202  if (!TryGetEntry(market, symbol, securityType, out entry))
203  {
204  var key = new SecurityDatabaseKey(market, symbol, securityType);
205  Log.Error($"MarketHoursDatabase.GetExchangeHours(): {Messages.MarketHoursDatabase.ExchangeHoursNotFound(key, _entries.Keys)}");
206 
207  if (securityType == SecurityType.Future && market == Market.USA)
208  {
209  var exception = Messages.MarketHoursDatabase.FutureUsaMarketTypeNoLongerSupported;
210  if (SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(symbol, SecurityType.Future, out market))
211  {
212  // let's suggest a market
213  exception += " " + Messages.MarketHoursDatabase.SuggestedMarketBasedOnTicker(market);
214  }
215 
216  throw new ArgumentException(exception);
217  }
218  // there was nothing that really matched exactly
219  throw new ArgumentException(Messages.MarketHoursDatabase.ExchangeHoursNotFound(key));
220  }
221 
222  return entry;
223  }
224 
225  /// <summary>
226  /// Tries to get the entry for the specified market/symbol/security-type
227  /// </summary>
228  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
229  /// <param name="symbol">The particular symbol being traded</param>
230  /// <param name="securityType">The security type of the symbol</param>
231  /// <param name="entry">The entry found if any</param>
232  /// <returns>True if the entry was present, else false</returns>
233  public bool TryGetEntry(string market, Symbol symbol, SecurityType securityType, out Entry entry)
234  {
235  return TryGetEntry(market, GetDatabaseSymbolKey(symbol), securityType, out entry);
236  }
237 
238  /// <summary>
239  /// Tries to get the entry for the specified market/symbol/security-type
240  /// </summary>
241  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
242  /// <param name="symbol">The particular symbol being traded</param>
243  /// <param name="securityType">The security type of the symbol</param>
244  /// <param name="entry">The entry found if any</param>
245  /// <returns>True if the entry was present, else false</returns>
246  public bool TryGetEntry(string market, string symbol, SecurityType securityType, out Entry entry)
247  {
248  var symbolKey = new SecurityDatabaseKey(market, symbol, securityType);
249  return _entries.TryGetValue(symbolKey, out entry)
250  // now check with null symbol key
251  || _entries.TryGetValue(symbolKey.CreateCommonKey(), out entry)
252  // if FOP check for future
253  || securityType == SecurityType.FutureOption && TryGetEntry(market,
254  FuturesOptionsSymbolMappings.MapFromOption(symbol), SecurityType.Future, out entry)
255  // if custom data type check for type specific entry
256  || (securityType == SecurityType.Base && SecurityIdentifier.TryGetCustomDataType(symbol, out var customType)
257  && _entries.TryGetValue(new SecurityDatabaseKey(market, $"TYPE.{customType}", securityType), out entry));
258  }
259 
260  /// <summary>
261  /// Gets the entry for the specified market/symbol/security-type
262  /// </summary>
263  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
264  /// <param name="symbol">The particular symbol being traded (Symbol class)</param>
265  /// <param name="securityType">The security type of the symbol</param>
266  /// <returns>The entry matching the specified market/symbol/security-type</returns>
267  public virtual Entry GetEntry(string market, Symbol symbol, SecurityType securityType)
268  {
269  return GetEntry(market, GetDatabaseSymbolKey(symbol), securityType);
270  }
271 
272  /// <summary>
273  /// Gets the correct string symbol to use as a database key
274  /// </summary>
275  /// <param name="symbol">The symbol</param>
276  /// <returns>The symbol string used in the database ke</returns>
277  public static string GetDatabaseSymbolKey(Symbol symbol)
278  {
279  string stringSymbol;
280  if (symbol == null)
281  {
282  stringSymbol = string.Empty;
283  }
284  else
285  {
286  switch (symbol.ID.SecurityType)
287  {
288  case SecurityType.Option:
289  stringSymbol = symbol.HasUnderlying ? symbol.Underlying.Value : string.Empty;
290  break;
291  case SecurityType.IndexOption:
292  case SecurityType.FutureOption:
293  stringSymbol = symbol.HasUnderlying ? symbol.ID.Symbol : string.Empty;
294  break;
295  case SecurityType.Base:
296  case SecurityType.Future:
297  stringSymbol = symbol.ID.Symbol;
298  break;
299  default:
300  stringSymbol = symbol.Value;
301  break;
302  }
303  }
304 
305  return stringSymbol;
306  }
307 
308  /// <summary>
309  /// Determines if the database contains the specified key
310  /// </summary>
311  /// <param name="key">The key to search for</param>
312  /// <returns>True if an entry is found, otherwise false</returns>
313  protected bool ContainsKey(SecurityDatabaseKey key)
314  {
315  return _entries.ContainsKey(key);
316  }
317 
318  /// <summary>
319  /// Represents a single entry in the <see cref="MarketHoursDatabase"/>
320  /// </summary>
321  public class Entry
322  {
323  /// <summary>
324  /// Gets the raw data time zone for this entry
325  /// </summary>
326  public readonly DateTimeZone DataTimeZone;
327  /// <summary>
328  /// Gets the exchange hours for this entry
329  /// </summary>
331  /// <summary>
332  /// Initializes a new instance of the <see cref="Entry"/> class
333  /// </summary>
334  /// <param name="dataTimeZone">The raw data time zone</param>
335  /// <param name="exchangeHours">The security exchange hours for this entry</param>
336  public Entry(DateTimeZone dataTimeZone, SecurityExchangeHours exchangeHours)
337  {
338  DataTimeZone = dataTimeZone;
339  ExchangeHours = exchangeHours;
340  }
341  }
342 
343  class AlwaysOpenMarketHoursDatabaseImpl : MarketHoursDatabase
344  {
345  public override Entry GetEntry(string market, string symbol, SecurityType securityType)
346  {
347  var key = new SecurityDatabaseKey(market, symbol, securityType);
348  var tz = ContainsKey(key)
349  ? base.GetEntry(market, symbol, securityType).ExchangeHours.TimeZone
350  : DateTimeZone.Utc;
351 
352  return new Entry(tz, SecurityExchangeHours.AlwaysOpen(tz));
353  }
354 
355  public AlwaysOpenMarketHoursDatabaseImpl()
356  : base(FromDataFolder().ExchangeHoursListing.ToDictionary())
357  {
358  }
359  }
360  }
361 }