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