/* * 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.IO; using System.Linq; using Newtonsoft.Json; using NodaTime; using QuantConnect.Configuration; using QuantConnect.Data; using QuantConnect.Logging; using QuantConnect.Securities.Future; using QuantConnect.Util; namespace QuantConnect.Securities { /// /// Provides access to exchange hours and raw data times zones in various markets /// [JsonConverter(typeof(MarketHoursDatabaseJsonConverter))] public class MarketHoursDatabase : BaseSecurityDatabase { private readonly bool _forceExchangeAlwaysOpen = Config.GetBool("force-exchange-always-open"); private static MarketHoursDatabase _alwaysOpenMarketHoursDatabase; /// /// Gets all the exchange hours held by this provider /// public List> ExchangeHoursListing => Entries.ToList(); /// /// Gets a that always returns /// public static MarketHoursDatabase AlwaysOpen { get { if (_alwaysOpenMarketHoursDatabase == null) { _alwaysOpenMarketHoursDatabase = new AlwaysOpenMarketHoursDatabaseImpl(); } return _alwaysOpenMarketHoursDatabase; } } /// /// Initializes a new instance of the class /// private MarketHoursDatabase() : this(new()) { } /// /// Initializes a new instance of the class /// /// The full listing of exchange hours by key public MarketHoursDatabase(Dictionary exchangeHours) : base(exchangeHours, FromDataFolder, (entry, other) => entry.Update(other)) { } /// /// Convenience method for retrieving exchange hours from market hours database using a subscription config /// /// The subscription data config to get exchange hours for /// The configure exchange hours for the specified configuration public SecurityExchangeHours GetExchangeHours(SubscriptionDataConfig configuration) { return GetExchangeHours(configuration.Market, configuration.Symbol, configuration.SecurityType); } /// /// Convenience method for retrieving exchange hours from market hours database using a subscription config /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded /// The security type of the symbol /// The exchange hours for the specified security public SecurityExchangeHours GetExchangeHours(string market, Symbol symbol, SecurityType securityType) { return GetEntry(market, symbol, securityType).ExchangeHours; } /// /// Performs a lookup using the specified information and returns the data's time zone if found, /// if an entry is not found, an exception is thrown /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded /// The security type of the symbol /// The raw data time zone for the specified security public DateTimeZone GetDataTimeZone(string market, Symbol symbol, SecurityType securityType) { return GetEntry(market, GetDatabaseSymbolKey(symbol), securityType).DataTimeZone; } /// /// Gets the instance of the class produced by reading in the market hours /// data found in /Data/market-hours/ /// /// A class that represents the data in the market-hours folder public static MarketHoursDatabase FromDataFolder() { if (DataFolderDatabase == null) { lock (DataFolderDatabaseLock) { if (DataFolderDatabase == null) { var path = Path.Combine(Globals.GetDataFolderPath("market-hours"), "market-hours-database.json"); DataFolderDatabase = FromFile(path); } } } return DataFolderDatabase; } /// /// Reads the specified file as a market hours database instance /// /// The market hours database file path /// A new instance of the class public static MarketHoursDatabase FromFile(string path) { return JsonConvert.DeserializeObject(File.ReadAllText(path)); } /// /// Sets the entry for the specified market/symbol/security-type. /// This is intended to be used by custom data and other data sources that don't have explicit /// entries in market-hours-database.csv. At run time, the algorithm can update the market hours /// database via calls to AddData. /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded /// The security type of the symbol /// The exchange hours for the specified symbol /// The time zone of the symbol's raw data. Optional, defaults to the exchange time zone /// The entry matching the specified market/symbol/security-type public virtual Entry SetEntry(string market, string symbol, SecurityType securityType, SecurityExchangeHours exchangeHours, DateTimeZone dataTimeZone = null) { dataTimeZone = dataTimeZone ?? exchangeHours.TimeZone; var key = new SecurityDatabaseKey(market, symbol, securityType); var entry = new Entry(dataTimeZone, exchangeHours); lock (DataFolderDatabaseLock) { Entries[key] = entry; CustomEntries.Add(key); } return entry; } /// /// Convenience method for the common custom data case. /// Sets the entry for the specified symbol using SecurityExchangeHours.AlwaysOpen(timeZone) /// This sets the data time zone equal to the exchange time zone as well. /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded /// The security type of the symbol /// The time zone of the symbol's exchange and raw data /// The entry matching the specified market/symbol/security-type public virtual Entry SetEntryAlwaysOpen(string market, string symbol, SecurityType securityType, DateTimeZone timeZone) { return SetEntry(market, symbol, securityType, SecurityExchangeHours.AlwaysOpen(timeZone)); } /// /// Gets the entry for the specified market/symbol/security-type /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded /// The security type of the symbol /// The entry matching the specified market/symbol/security-type public virtual Entry GetEntry(string market, string symbol, SecurityType securityType) { Entry entry; // Fall back on the Futures MHDB entry if the FOP lookup failed. // Some FOPs have the same symbol properties as their futures counterparts. // So, to save ourselves some space, we can fall back on the existing entries // so that we don't duplicate the information. if (!TryGetEntry(market, symbol, securityType, out entry)) { var key = new SecurityDatabaseKey(market, symbol, securityType); Log.Error($"MarketHoursDatabase.GetExchangeHours(): {Messages.MarketHoursDatabase.ExchangeHoursNotFound(key, Entries.Keys)}"); if (securityType == SecurityType.Future && market == Market.USA) { var exception = Messages.MarketHoursDatabase.FutureUsaMarketTypeNoLongerSupported; if (SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(symbol, SecurityType.Future, out market)) { // let's suggest a market exception += " " + Messages.MarketHoursDatabase.SuggestedMarketBasedOnTicker(market); } throw new ArgumentException(exception); } // there was nothing that really matched exactly throw new ArgumentException(Messages.MarketHoursDatabase.ExchangeHoursNotFound(key)); } return entry; } /// /// Tries to get the entry for the specified market/symbol/security-type /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded /// The security type of the symbol /// The entry found if any /// True if the entry was present, else false public bool TryGetEntry(string market, Symbol symbol, SecurityType securityType, out Entry entry) { return TryGetEntry(market, GetDatabaseSymbolKey(symbol), securityType, out entry); } /// /// Tries to get the entry for the specified market/symbol/security-type /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded /// The security type of the symbol /// The entry found if any /// True if the entry was present, else false public virtual bool TryGetEntry(string market, string symbol, SecurityType securityType, out Entry entry) { if (_forceExchangeAlwaysOpen) { return AlwaysOpen.TryGetEntry(market, symbol, securityType, out entry); } return TryGetEntryImpl(market, symbol, securityType, out entry); } private bool TryGetEntryImpl(string market, string symbol, SecurityType securityType, out Entry entry) { var symbolKey = new SecurityDatabaseKey(market, symbol, securityType); return Entries.TryGetValue(symbolKey, out entry) // now check with null symbol key || Entries.TryGetValue(symbolKey.CreateCommonKey(), out entry) // if FOP check for future || securityType == SecurityType.FutureOption && TryGetEntry(market, FuturesOptionsSymbolMappings.MapFromOption(symbol), SecurityType.Future, out entry) // if custom data type check for type specific entry || (securityType == SecurityType.Base && SecurityIdentifier.TryGetCustomDataType(symbol, out var customType) && Entries.TryGetValue(new SecurityDatabaseKey(market, $"TYPE.{customType}", securityType), out entry)); } /// /// Gets the entry for the specified market/symbol/security-type /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded (Symbol class) /// The security type of the symbol /// The entry matching the specified market/symbol/security-type public virtual Entry GetEntry(string market, Symbol symbol, SecurityType securityType) { return GetEntry(market, GetDatabaseSymbolKey(symbol), securityType); } /// /// Represents a single entry in the /// public class Entry { /// /// Gets the raw data time zone for this entry /// public DateTimeZone DataTimeZone { get; private set; } /// /// Gets the exchange hours for this entry /// public SecurityExchangeHours ExchangeHours { get; init; } /// /// Initializes a new instance of the class /// /// The raw data time zone /// The security exchange hours for this entry public Entry(DateTimeZone dataTimeZone, SecurityExchangeHours exchangeHours) { DataTimeZone = dataTimeZone; ExchangeHours = exchangeHours; } internal void Update(Entry other) { DataTimeZone = other.DataTimeZone; ExchangeHours.Update(other.ExchangeHours); } } class AlwaysOpenMarketHoursDatabaseImpl : MarketHoursDatabase { public override bool TryGetEntry(string market, string symbol, SecurityType securityType, out Entry entry) { DateTimeZone dataTimeZone; DateTimeZone exchangeTimeZone; if (TryGetEntryImpl(market, symbol, securityType, out entry)) { dataTimeZone = entry.DataTimeZone; exchangeTimeZone = entry.ExchangeHours.TimeZone; } else { dataTimeZone = exchangeTimeZone = TimeZones.Utc; } entry = new Entry(dataTimeZone, SecurityExchangeHours.AlwaysOpen(exchangeTimeZone)); return true; } public AlwaysOpenMarketHoursDatabaseImpl() : base(FromDataFolder().ExchangeHoursListing.ToDictionary()) { } } } }