/* * 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.Globalization; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using NodaTime; using QuantConnect.Data; using QuantConnect.Data.Auxiliary; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Interfaces; using QuantConnect.Logging; using QuantConnect.Securities; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using static QuantConnect.StringExtensions; namespace QuantConnect.Util { /// /// Provides methods for generating lean data file content /// public static class LeanData { private static readonly HashSet _strictDailyEndTimesDataTypes = new() { // the underlying could yield auxiliary data which we don't want to change typeof(TradeBar), typeof(QuoteBar), typeof(BaseDataCollection), typeof(OpenInterest) }; /// /// The different used for data paths /// /// This includes 'alternative' public static HashSet SecurityTypeAsDataPath => Enum.GetNames(typeof(SecurityType)) .Select(x => x.ToLowerInvariant()).Union(new[] { "alternative" }).ToHashSet(); /// /// Converts the specified base data instance into a lean data file csv line. /// This method takes into account the fake that base data instances typically /// are time stamped in the exchange time zone, but need to be written to disk /// in the data time zone. /// public static string GenerateLine(IBaseData data, Resolution resolution, DateTimeZone exchangeTimeZone, DateTimeZone dataTimeZone) { var clone = data.Clone(); clone.Time = data.Time.ConvertTo(exchangeTimeZone, dataTimeZone); return GenerateLine(clone, clone.Symbol.ID.SecurityType, resolution); } /// /// Helper method that will parse a given data line in search of an associated date time /// public static DateTime ParseTime(string line, DateTime date, Resolution resolution) { switch (resolution) { case Resolution.Tick: case Resolution.Second: case Resolution.Minute: var index = line.IndexOf(',', StringComparison.InvariantCulture); return date.AddTicks(Convert.ToInt64(10000 * decimal.Parse(line.AsSpan(0, index)))); case Resolution.Hour: case Resolution.Daily: return DateTime.ParseExact(line.AsSpan(0, DateFormat.TwelveCharacter.Length), DateFormat.TwelveCharacter, CultureInfo.InvariantCulture); default: throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null); } } /// /// Converts the specified base data instance into a lean data file csv line /// public static string GenerateLine(IBaseData data, SecurityType securityType, Resolution resolution) { var milliseconds = data.Time.TimeOfDay.TotalMilliseconds.ToString(CultureInfo.InvariantCulture); var longTime = data.Time.ToStringInvariant(DateFormat.TwelveCharacter); switch (securityType) { case SecurityType.Equity: switch (resolution) { case Resolution.Tick: var tick = (Tick)data; if (tick.TickType == TickType.Trade) { return ToCsv(milliseconds, Scale(tick.LastPrice), tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, Scale(tick.BidPrice), tick.BidSize, Scale(tick.AskPrice), tick.AskSize, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0"); } break; case Resolution.Minute: case Resolution.Second: var tradeBar = data as TradeBar; if (tradeBar != null) { return ToCsv(milliseconds, Scale(tradeBar.Open), Scale(tradeBar.High), Scale(tradeBar.Low), Scale(tradeBar.Close), tradeBar.Volume); } var quoteBar = data as QuoteBar; if (quoteBar != null) { return ToCsv(milliseconds, ToScaledCsv(quoteBar.Bid), quoteBar.LastBidSize, ToScaledCsv(quoteBar.Ask), quoteBar.LastAskSize); } break; case Resolution.Hour: case Resolution.Daily: var bigTradeBar = data as TradeBar; if (bigTradeBar != null) { return ToCsv(longTime, Scale(bigTradeBar.Open), Scale(bigTradeBar.High), Scale(bigTradeBar.Low), Scale(bigTradeBar.Close), bigTradeBar.Volume); } var bigQuoteBar = data as QuoteBar; if (bigQuoteBar != null) { return ToCsv(longTime, ToScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize, ToScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize); } break; } break; case SecurityType.Crypto: case SecurityType.CryptoFuture: switch (resolution) { case Resolution.Tick: var tick = data as Tick; if (tick == null) { throw new ArgumentException($"{securityType} tick could not be created", nameof(data)); } if (tick.TickType == TickType.Trade) { return ToCsv(milliseconds, tick.LastPrice, tick.Quantity, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.Suspicious ? "1" : "0"); } throw new ArgumentException($"{securityType} tick could not be created"); case Resolution.Second: case Resolution.Minute: var quoteBar = data as QuoteBar; if (quoteBar != null) { return ToCsv(milliseconds, ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize, ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize); } var tradeBar = data as TradeBar; if (tradeBar != null) { return ToCsv(milliseconds, tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume); } throw new ArgumentException($"{securityType} minute/second bar could not be created", nameof(data)); case Resolution.Hour: case Resolution.Daily: var bigQuoteBar = data as QuoteBar; if (bigQuoteBar != null) { return ToCsv(longTime, ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize, ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize); } var bigTradeBar = data as TradeBar; if (bigTradeBar != null) { return ToCsv(longTime, bigTradeBar.Open, bigTradeBar.High, bigTradeBar.Low, bigTradeBar.Close, bigTradeBar.Volume); } throw new ArgumentException($"{securityType} hour/daily bar could not be created", nameof(data)); } break; case SecurityType.Forex: case SecurityType.Cfd: switch (resolution) { case Resolution.Tick: var tick = data as Tick; if (tick == null) { throw new ArgumentException("Expected data of type 'Tick'", nameof(data)); } return ToCsv(milliseconds, tick.BidPrice, tick.AskPrice); case Resolution.Second: case Resolution.Minute: var bar = data as QuoteBar; if (bar == null) { throw new ArgumentException("Expected data of type 'QuoteBar'", nameof(data)); } return ToCsv(milliseconds, ToNonScaledCsv(bar.Bid), bar.LastBidSize, ToNonScaledCsv(bar.Ask), bar.LastAskSize); case Resolution.Hour: case Resolution.Daily: var bigBar = data as QuoteBar; if (bigBar == null) { throw new ArgumentException("Expected data of type 'QuoteBar'", nameof(data)); } return ToCsv(longTime, ToNonScaledCsv(bigBar.Bid), bigBar.LastBidSize, ToNonScaledCsv(bigBar.Ask), bigBar.LastAskSize); } break; case SecurityType.Index: switch (resolution) { case Resolution.Tick: var tick = (Tick)data; return ToCsv(milliseconds, tick.LastPrice, tick.Quantity, string.Empty, string.Empty, "0"); case Resolution.Second: case Resolution.Minute: var bar = data as TradeBar; if (bar == null) { throw new ArgumentException("Expected data of type 'TradeBar'", nameof(data)); } return ToCsv(milliseconds, bar.Open, bar.High, bar.Low, bar.Close, bar.Volume); case Resolution.Hour: case Resolution.Daily: var bigTradeBar = data as TradeBar; return ToCsv(longTime, bigTradeBar.Open, bigTradeBar.High, bigTradeBar.Low, bigTradeBar.Close, bigTradeBar.Volume); } break; case SecurityType.Option: case SecurityType.IndexOption: switch (resolution) { case Resolution.Tick: var tick = (Tick)data; if (tick.TickType == TickType.Trade) { return ToCsv(milliseconds, Scale(tick.LastPrice), tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, Scale(tick.BidPrice), tick.BidSize, Scale(tick.AskPrice), tick.AskSize, tick.ExchangeCode, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.OpenInterest) { return ToCsv(milliseconds, tick.Value); } break; case Resolution.Second: case Resolution.Minute: // option and future data can be quote or trade bars var quoteBar = data as QuoteBar; if (quoteBar != null) { return ToCsv(milliseconds, ToScaledCsv(quoteBar.Bid), quoteBar.LastBidSize, ToScaledCsv(quoteBar.Ask), quoteBar.LastAskSize); } var tradeBar = data as TradeBar; if (tradeBar != null) { return ToCsv(milliseconds, Scale(tradeBar.Open), Scale(tradeBar.High), Scale(tradeBar.Low), Scale(tradeBar.Close), tradeBar.Volume); } var openInterest = data as OpenInterest; if (openInterest != null) { return ToCsv(milliseconds, openInterest.Value); } break; case Resolution.Hour: case Resolution.Daily: // option and future data can be quote or trade bars var bigQuoteBar = data as QuoteBar; if (bigQuoteBar != null) { return ToCsv(longTime, ToScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize, ToScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize); } var bigTradeBar = data as TradeBar; if (bigTradeBar != null) { return ToCsv(longTime, ToScaledCsv(bigTradeBar), bigTradeBar.Volume); } var bigOpenInterest = data as OpenInterest; if (bigOpenInterest != null) { return ToCsv(longTime, bigOpenInterest.Value); } break; default: throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null); } break; case SecurityType.FutureOption: switch (resolution) { case Resolution.Tick: var tick = (Tick)data; if (tick.TickType == TickType.Trade) { return ToCsv(milliseconds, tick.LastPrice, tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.ExchangeCode, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.OpenInterest) { return ToCsv(milliseconds, tick.Value); } break; case Resolution.Second: case Resolution.Minute: // option and future data can be quote or trade bars var quoteBar = data as QuoteBar; if (quoteBar != null) { return ToCsv(milliseconds, ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize, ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize); } var tradeBar = data as TradeBar; if (tradeBar != null) { return ToCsv(milliseconds, tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume); } var openInterest = data as OpenInterest; if (openInterest != null) { return ToCsv(milliseconds, openInterest.Value); } break; case Resolution.Hour: case Resolution.Daily: // option and future data can be quote or trade bars var bigQuoteBar = data as QuoteBar; if (bigQuoteBar != null) { return ToCsv(longTime, ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize, ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize); } var bigTradeBar = data as TradeBar; if (bigTradeBar != null) { return ToCsv(longTime, ToNonScaledCsv(bigTradeBar), bigTradeBar.Volume); } var bigOpenInterest = data as OpenInterest; if (bigOpenInterest != null) { return ToCsv(longTime, bigOpenInterest.Value); } break; default: throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null); } break; case SecurityType.Future: switch (resolution) { case Resolution.Tick: var tick = (Tick)data; if (tick.TickType == TickType.Trade) { return ToCsv(milliseconds, tick.LastPrice, tick.Quantity, tick.ExchangeCode, tick.SaleCondition, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.ExchangeCode, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.OpenInterest) { return ToCsv(milliseconds, tick.Value); } break; case Resolution.Second: case Resolution.Minute: // option and future data can be quote or trade bars var quoteBar = data as QuoteBar; if (quoteBar != null) { return ToCsv(milliseconds, ToNonScaledCsv(quoteBar.Bid), quoteBar.LastBidSize, ToNonScaledCsv(quoteBar.Ask), quoteBar.LastAskSize); } var tradeBar = data as TradeBar; if (tradeBar != null) { return ToCsv(milliseconds, tradeBar.Open, tradeBar.High, tradeBar.Low, tradeBar.Close, tradeBar.Volume); } var openInterest = data as OpenInterest; if (openInterest != null) { return ToCsv(milliseconds, openInterest.Value); } break; case Resolution.Hour: case Resolution.Daily: // option and future data can be quote or trade bars var bigQuoteBar = data as QuoteBar; if (bigQuoteBar != null) { return ToCsv(longTime, ToNonScaledCsv(bigQuoteBar.Bid), bigQuoteBar.LastBidSize, ToNonScaledCsv(bigQuoteBar.Ask), bigQuoteBar.LastAskSize); } var bigTradeBar = data as TradeBar; if (bigTradeBar != null) { return ToCsv(longTime, ToNonScaledCsv(bigTradeBar), bigTradeBar.Volume); } var bigOpenInterest = data as OpenInterest; if (bigOpenInterest != null) { return ToCsv(longTime, bigOpenInterest.Value); } break; default: throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null); } break; default: throw new ArgumentOutOfRangeException(nameof(securityType), securityType, null); } throw new NotImplementedException(Invariant( $"LeanData.GenerateLine has not yet been implemented for security type: {securityType} at resolution: {resolution}" )); } /// /// Gets the data type required for the specified combination of resolution and tick type /// /// The resolution, if Tick, the Type returned is always Tick /// The that primarily dictates the type returned /// The Type used to create a subscription public static Type GetDataType(Resolution resolution, TickType tickType) { if (resolution == Resolution.Tick) return typeof(Tick); if (tickType == TickType.OpenInterest) return typeof(OpenInterest); if (tickType == TickType.Quote) return typeof(QuoteBar); return typeof(TradeBar); } /// /// Determines if the Type is a 'common' type used throughout lean /// This method is helpful in creating /// /// The Type to check /// A bool indicating whether the type is of type /// or public static bool IsCommonLeanDataType(Type baseDataType) { if (baseDataType == typeof(Tick) || baseDataType == typeof(TradeBar) || baseDataType == typeof(QuoteBar) || baseDataType == typeof(OpenInterest)) { return true; } return false; } /// /// Helper method to determine if a configuration set is valid /// public static bool IsValidConfiguration(SecurityType securityType, Resolution resolution, TickType tickType) { if (securityType == SecurityType.Equity && (resolution == Resolution.Daily || resolution == Resolution.Hour)) { return tickType != TickType.Quote; } return true; } /// /// Generates the full zip file path rooted in the /// public static string GenerateZipFilePath(string dataDirectory, Symbol symbol, DateTime date, Resolution resolution, TickType tickType) { // we could call 'GenerateRelativeZipFilePath' but we don't to avoid an extra string & path combine we are doing to drop right away return Path.Combine(dataDirectory, GenerateRelativeZipFileDirectory(symbol, resolution), GenerateZipFileName(symbol, date, resolution, tickType)); } /// /// Generates the full zip file path rooted in the /// public static string GenerateZipFilePath(string dataDirectory, string symbol, SecurityType securityType, string market, DateTime date, Resolution resolution) { return Path.Combine(dataDirectory, GenerateRelativeZipFilePath(symbol, securityType, market, date, resolution)); } /// /// Generates the relative zip directory for the specified symbol/resolution /// public static string GenerateRelativeZipFileDirectory(Symbol symbol, Resolution resolution) { var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily; var securityType = symbol.SecurityType.SecurityTypeToLower(); var market = symbol.ID.Market.ToLowerInvariant(); var res = resolution.ResolutionToLower(); var directory = Path.Combine(securityType, market, res); switch (symbol.ID.SecurityType) { case SecurityType.Base: case SecurityType.Equity: case SecurityType.Index: case SecurityType.Forex: case SecurityType.Cfd: case SecurityType.Crypto: return !isHourOrDaily ? Path.Combine(directory, symbol.Value.ToLowerInvariant()) : directory; case SecurityType.IndexOption: // For index options, we use the canonical option ticker since it can differ from the underlying's ticker. return !isHourOrDaily ? Path.Combine(directory, symbol.ID.Symbol.ToLowerInvariant()) : directory; case SecurityType.Option: // options uses the underlying symbol for pathing. return !isHourOrDaily ? Path.Combine(directory, symbol.Underlying.Value.ToLowerInvariant()) : directory; case SecurityType.FutureOption: // For futures options, we use the canonical option ticker plus the underlying's expiry // since it can differ from the underlying's ticker. We differ from normal futures // because the option chain can be extraordinarily large compared to equity option chains. var futureOptionPath = Path.Combine(symbol.ID.Symbol, symbol.Underlying.ID.Date.ToStringInvariant(DateFormat.EightCharacter)) .ToLowerInvariant(); return Path.Combine(directory, futureOptionPath); case SecurityType.Future: case SecurityType.CryptoFuture: return !isHourOrDaily ? Path.Combine(directory, symbol.ID.Symbol.ToLowerInvariant()) : directory; case SecurityType.Commodity: default: throw new ArgumentOutOfRangeException(); } } /// /// Generates relative factor file paths for equities /// public static string GenerateRelativeFactorFilePath(Symbol symbol) { return Path.Combine(Globals.DataFolder, "equity", symbol.ID.Market, "factor_files", symbol.Value.ToLowerInvariant() + ".csv"); } /// /// Generates the relative zip file path rooted in the /Data directory /// public static string GenerateRelativeZipFilePath(Symbol symbol, DateTime date, Resolution resolution, TickType tickType) { return Path.Combine(GenerateRelativeZipFileDirectory(symbol, resolution), GenerateZipFileName(symbol, date, resolution, tickType)); } /// /// Generates the relative zip file path rooted in the /Data directory /// public static string GenerateRelativeZipFilePath(string symbol, SecurityType securityType, string market, DateTime date, Resolution resolution) { var directory = Path.Combine(securityType.SecurityTypeToLower(), market.ToLowerInvariant(), resolution.ResolutionToLower()); if (resolution != Resolution.Daily && resolution != Resolution.Hour) { directory = Path.Combine(directory, symbol.ToLowerInvariant()); } return Path.Combine(directory, GenerateZipFileName(symbol, securityType, date, resolution)); } /// /// Generates the relative directory to the universe files for the specified symbol /// public static string GenerateRelativeUniversesDirectory(Symbol symbol) { var path = Path.Combine(symbol.SecurityType.SecurityTypeToLower(), symbol.ID.Market, "universes"); switch (symbol.SecurityType) { case SecurityType.Option: path = Path.Combine(path, symbol.Underlying.Value.ToLowerInvariant()); break; case SecurityType.IndexOption: path = Path.Combine(path, symbol.ID.Symbol.ToLowerInvariant()); break; case SecurityType.FutureOption: path = Path.Combine(path, symbol.Underlying.ID.Symbol.ToLowerInvariant(), symbol.Underlying.ID.Date.ToStringInvariant(DateFormat.EightCharacter)); break; case SecurityType.Future: path = Path.Combine(path, symbol.ID.Symbol.ToLowerInvariant()); break; default: throw new ArgumentOutOfRangeException($"Unsupported security type {symbol.SecurityType}"); } return path; } /// /// Generates the directory to the universe files for the specified symbol /// public static string GenerateUniversesDirectory(string dataDirectory, Symbol symbol) { return Path.Combine(dataDirectory, GenerateRelativeUniversesDirectory(symbol)); } /// /// Generate's the zip entry name to hold the specified data. /// public static string GenerateZipEntryName(Symbol symbol, DateTime date, Resolution resolution, TickType tickType) { var formattedDate = date.ToStringInvariant(DateFormat.EightCharacter); var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily; switch (symbol.ID.SecurityType) { case SecurityType.Base: case SecurityType.Equity: case SecurityType.Index: case SecurityType.Forex: case SecurityType.Cfd: case SecurityType.Crypto: if (resolution == Resolution.Tick && symbol.SecurityType == SecurityType.Equity) { return Invariant($"{formattedDate}_{symbol.Value.ToLowerInvariant()}_{tickType}_{resolution}.csv"); } if (isHourOrDaily) { return $"{symbol.Value.ToLowerInvariant()}.csv"; } return Invariant($"{formattedDate}_{symbol.Value.ToLowerInvariant()}_{resolution.ResolutionToLower()}_{tickType.TickTypeToLower()}.csv"); case SecurityType.Option: var optionPath = symbol.Underlying.Value.ToLowerInvariant(); if (isHourOrDaily) { return string.Join("_", optionPath, tickType.TickTypeToLower(), symbol.ID.OptionStyle.OptionStyleToLower(), symbol.ID.OptionRight.OptionRightToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; } return string.Join("_", formattedDate, optionPath, resolution.ResolutionToLower(), tickType.TickTypeToLower(), symbol.ID.OptionStyle.OptionStyleToLower(), symbol.ID.OptionRight.OptionRightToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; case SecurityType.IndexOption: case SecurityType.FutureOption: // We want the future/index option ticker as the lookup name inside the ZIP file var optionTickerBasedPath = symbol.ID.Symbol.ToLowerInvariant(); if (isHourOrDaily) { return string.Join("_", optionTickerBasedPath, tickType.TickTypeToLower(), symbol.ID.OptionStyle.OptionStyleToLower(), symbol.ID.OptionRight.OptionRightToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; } return string.Join("_", formattedDate, optionTickerBasedPath, resolution.ResolutionToLower(), tickType.TickTypeToLower(), symbol.ID.OptionStyle.OptionStyleToLower(), symbol.ID.OptionRight.OptionRightToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; case SecurityType.Future: case SecurityType.CryptoFuture: if (symbol.HasUnderlying) { symbol = symbol.Underlying; } string expirationTag; var expiryDate = symbol.ID.Date; if (expiryDate != SecurityIdentifier.DefaultDate) { var monthsToAdd = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(symbol.ID.Symbol, expiryDate.Date); var contractYearMonth = expiryDate.AddMonths(monthsToAdd).ToStringInvariant(DateFormat.YearMonth); expirationTag = $"{contractYearMonth}_{expiryDate.ToStringInvariant(DateFormat.EightCharacter)}"; } else { expirationTag = "perp"; } if (isHourOrDaily) { return string.Join("_", symbol.ID.Symbol.ToLowerInvariant(), tickType.TickTypeToLower(), expirationTag ) + ".csv"; } return string.Join("_", formattedDate, symbol.ID.Symbol.ToLowerInvariant(), resolution.ResolutionToLower(), tickType.TickTypeToLower(), expirationTag ) + ".csv"; case SecurityType.Commodity: default: throw new ArgumentOutOfRangeException(); } } /// /// Generates the zip file name for the specified date of data. /// public static string GenerateZipFileName(Symbol symbol, DateTime date, Resolution resolution, TickType tickType) { var tickTypeString = tickType.TickTypeToLower(); var formattedDate = date.ToStringInvariant(DateFormat.EightCharacter); var isHourOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily; switch (symbol.ID.SecurityType) { case SecurityType.Base: case SecurityType.Index: case SecurityType.Equity: case SecurityType.Forex: case SecurityType.Cfd: if (isHourOrDaily) { return $"{symbol.Value.ToLowerInvariant()}.zip"; } return $"{formattedDate}_{tickTypeString}.zip"; case SecurityType.Crypto: if (isHourOrDaily) { return $"{symbol.Value.ToLowerInvariant()}_{tickTypeString}.zip"; } return $"{formattedDate}_{tickTypeString}.zip"; case SecurityType.Option: if (isHourOrDaily) { // see TryParsePath: he knows tick type position is 3 var optionPath = symbol.Underlying.Value.ToLowerInvariant(); return $"{optionPath}_{date.Year}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip"; } return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip"; case SecurityType.IndexOption: case SecurityType.FutureOption: if (isHourOrDaily) { // see TryParsePath: he knows tick type position is 3 var optionTickerBasedPath = symbol.ID.Symbol.ToLowerInvariant(); return $"{optionTickerBasedPath}_{date.Year}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip"; } return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.OptionStyleToLower()}.zip"; case SecurityType.Future: case SecurityType.CryptoFuture: if (isHourOrDaily) { return $"{symbol.ID.Symbol.ToLowerInvariant()}_{tickTypeString}.zip"; } return $"{formattedDate}_{tickTypeString}.zip"; case SecurityType.Commodity: default: throw new ArgumentOutOfRangeException(); } } /// /// Creates the zip file name for a QC zip data file /// public static string GenerateZipFileName(string symbol, SecurityType securityType, DateTime date, Resolution resolution, TickType? tickType = null) { if (resolution == Resolution.Hour || resolution == Resolution.Daily) { return $"{symbol.ToLowerInvariant()}.zip"; } var zipFileName = date.ToStringInvariant(DateFormat.EightCharacter); if (tickType == null) { if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd) { tickType = TickType.Quote; } else { tickType = TickType.Trade; } } var suffix = Invariant($"_{tickType.Value.TickTypeToLower()}.zip"); return zipFileName + suffix; } /// /// Gets the tick type most commonly associated with the specified security type /// /// The security type /// The most common tick type for the specified security type public static TickType GetCommonTickType(SecurityType securityType) { if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd || securityType == SecurityType.Crypto) { return TickType.Quote; } return TickType.Trade; } /// /// Creates a symbol from the specified zip entry name /// /// The root symbol of the output symbol /// The resolution of the data source producing the zip entry name /// The zip entry name to be parsed /// A new symbol representing the zip entry name public static Symbol ReadSymbolFromZipEntry(Symbol symbol, Resolution resolution, string zipEntryName) { var isHourlyOrDaily = resolution == Resolution.Hour || resolution == Resolution.Daily; var parts = zipEntryName.Replace(".csv", string.Empty).Split('_'); switch (symbol.ID.SecurityType) { case SecurityType.Option: case SecurityType.FutureOption: case SecurityType.IndexOption: if (isHourlyOrDaily) { var style = parts[2].ParseOptionStyle(); var right = parts[3].ParseOptionRight(); var strike = Parse.Decimal(parts[4]) / 10000m; var expiry = Parse.DateTimeExact(parts[5], DateFormat.EightCharacter); return Symbol.CreateOption(symbol.Underlying, symbol.ID.Symbol, symbol.ID.Market, style, right, strike, expiry); } else { var style = parts[4].ParseOptionStyle(); var right = parts[5].ParseOptionRight(); var strike = Parse.Decimal(parts[6]) / 10000m; var expiry = DateTime.ParseExact(parts[7], DateFormat.EightCharacter, CultureInfo.InvariantCulture); return Symbol.CreateOption(symbol.Underlying, symbol.ID.Symbol, symbol.ID.Market, style, right, strike, expiry); } case SecurityType.Future: if (isHourlyOrDaily) { var expiryYearMonth = Parse.DateTimeExact(parts[2], DateFormat.YearMonth); var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(symbol); var futureExpiry = futureExpiryFunc(expiryYearMonth); return Symbol.CreateFuture(parts[0], symbol.ID.Market, futureExpiry); } else { var expiryYearMonth = Parse.DateTimeExact(parts[4], DateFormat.YearMonth); var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(symbol); var futureExpiry = futureExpiryFunc(expiryYearMonth); return Symbol.CreateFuture(parts[1], symbol.ID.Market, futureExpiry); } default: throw new NotImplementedException(Invariant( $"ReadSymbolFromZipEntry is not implemented for {symbol.ID.SecurityType} {symbol.ID.Market} {resolution}" )); } } /// /// Scale and convert the resulting number to deci-cents int. /// private static long Scale(decimal value) { return (long)(value * 10000); } /// /// Create a csv line from the specified arguments /// private static string ToCsv(params object[] args) { // use culture neutral formatting for decimals for (var i = 0; i < args.Length; i++) { var value = args[i]; if (value is decimal) { args[i] = ((decimal)value).Normalize(); } } var argsFormatted = args.Select(x => Convert.ToString(x, CultureInfo.InvariantCulture)); return string.Join(",", argsFormatted); } /// /// Creates a scaled csv line for the bar, if null fills in empty strings /// private static string ToScaledCsv(IBar bar) { if (bar == null) { return ToCsv(string.Empty, string.Empty, string.Empty, string.Empty); } return ToCsv(Scale(bar.Open), Scale(bar.High), Scale(bar.Low), Scale(bar.Close)); } /// /// Creates a non scaled csv line for the bar, if null fills in empty strings /// private static string ToNonScaledCsv(IBar bar) { if (bar == null) { return ToCsv(string.Empty, string.Empty, string.Empty, string.Empty); } return ToCsv(bar.Open, bar.High, bar.Low, bar.Close); } /// /// Get the for common Lean data types. /// If not a Lean common data type, return a TickType of Trade. /// /// A Type used to determine the TickType /// The SecurityType used to determine the TickType /// A TickType corresponding to the type public static TickType GetCommonTickTypeForCommonDataTypes(Type type, SecurityType securityType) { if (type == typeof(TradeBar)) { return TickType.Trade; } if (type == typeof(QuoteBar)) { return TickType.Quote; } if (type == typeof(OpenInterest)) { return TickType.OpenInterest; } if (type.IsAssignableTo(typeof(BaseChainUniverseData))) { return TickType.Quote; } if (type == typeof(Tick)) { return GetCommonTickType(securityType); } return TickType.Trade; } /// /// Matches a data path security type with the /// /// This includes 'alternative' /// The data path security type /// The matching security type for the given data path public static SecurityType ParseDataSecurityType(string securityType) { if (securityType.Equals("alternative", StringComparison.InvariantCultureIgnoreCase)) { return SecurityType.Base; } return (SecurityType)Enum.Parse(typeof(SecurityType), securityType, true); } /// /// Parses file name into a and DateTime /// /// File name to be parsed /// The securityType as parsed from the fileName /// The market as parsed from the fileName public static bool TryParseSecurityType(string fileName, out SecurityType securityType, out string market) { securityType = SecurityType.Base; market = string.Empty; try { var info = SplitDataPath(fileName); // find the securityType and parse it var typeString = info.Find(x => SecurityTypeAsDataPath.Contains(x.ToLowerInvariant())); securityType = ParseDataSecurityType(typeString); var existingMarkets = Market.SupportedMarkets(); var foundMarket = info.Find(x => existingMarkets.Contains(x.ToLowerInvariant())); if (foundMarket != null) { market = foundMarket; } } catch (Exception e) { Log.Error($"LeanData.TryParsePath(): Error encountered while parsing the path {fileName}. Error: {e.GetBaseException()}"); return false; } return true; } /// /// Parses file name into a and DateTime /// /// File path to be parsed /// The symbol as parsed from the fileName /// Date of data in the file path. Only returned if the resolution is lower than Hourly /// The resolution of the symbol as parsed from the filePath /// The tick type /// The data type public static bool TryParsePath(string filePath, out Symbol symbol, out DateTime date, out Resolution resolution, out TickType tickType, out Type dataType) { symbol = default; tickType = default; dataType = default; date = default; resolution = default; try { if (!TryParsePath(filePath, out symbol, out date, out resolution, out var isUniverses)) { return false; } tickType = GetCommonTickType(symbol.SecurityType); var fileName = Path.GetFileNameWithoutExtension(filePath); if (fileName.Contains('_', StringComparison.InvariantCulture)) { // example: 20140606_openinterest_american.zip var tickTypePosition = 1; if (resolution >= Resolution.Hour && symbol.SecurityType.IsOption()) { // daily and hourly have the year too, example: aapl_2014_openinterest_american.zip // see GenerateZipFileName he's creating these paths tickTypePosition = 2; } tickType = (TickType)Enum.Parse(typeof(TickType), fileName.Split('_')[tickTypePosition], true); } dataType = isUniverses ? typeof(OptionUniverse) : GetDataType(resolution, tickType); return true; } catch (Exception ex) { Log.Debug($"LeanData.TryParsePath(): Error encountered while parsing the path {filePath}. Error: {ex.GetBaseException()}"); } return false; } /// /// Parses file name into a and DateTime /// /// File name to be parsed /// The symbol as parsed from the fileName /// Date of data in the file path. Only returned if the resolution is lower than Hourly /// The resolution of the symbol as parsed from the filePath public static bool TryParsePath(string fileName, out Symbol symbol, out DateTime date, out Resolution resolution) { return TryParsePath(fileName, out symbol, out date, out resolution, out _); } /// /// Parses file name into a and DateTime /// /// File name to be parsed /// The symbol as parsed from the fileName /// Date of data in the file path. Only returned if the resolution is lower than Hourly /// The resolution of the symbol as parsed from the filePath /// Outputs whether the file path represents a universe data file. public static bool TryParsePath(string fileName, out Symbol symbol, out DateTime date, out Resolution resolution, out bool isUniverses) { symbol = null; resolution = Resolution.Daily; date = default(DateTime); isUniverses = default; try { var info = SplitDataPath(fileName); // find where the useful part of the path starts - i.e. the securityType var startIndex = info.FindIndex(x => SecurityTypeAsDataPath.Contains(x.ToLowerInvariant())); if (startIndex == -1) { if (Log.DebuggingEnabled) { Log.Debug($"LeanData.TryParsePath(): Failed to parse '{fileName}' unexpected SecurityType"); } // SPDB & MHDB folders return false; } var securityType = ParseDataSecurityType(info[startIndex]); var market = Market.USA; string ticker; if (!Enum.TryParse(info[startIndex + 2], true, out resolution)) { resolution = Resolution.Daily; isUniverses = info[startIndex + 2].Equals("universes", StringComparison.InvariantCultureIgnoreCase); if (securityType != SecurityType.Base) { if (!isUniverses) { if (Log.DebuggingEnabled) { Log.Debug($"LeanData.TryParsePath(): Failed to parse '{fileName}' unexpected Resolution"); } // only acept a failure to parse resolution if we are facing a universes path return false; } (symbol, date) = ParseUniversePath(info, securityType); return true; } } if (securityType == SecurityType.Base) { // the last part of the path is the file name var fileNameNoPath = info[info.Count - 1].Split('_').First(); if (!DateTime.TryParseExact(fileNameNoPath, DateFormat.EightCharacter, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out date)) { // if parsing the date failed we assume filename is ticker ticker = fileNameNoPath; } else { // ticker must be the previous part of the path ticker = info[info.Count - 2]; } } else { // Gather components used to create the security market = info[startIndex + 1]; var components = info[startIndex + 3].Split('_'); // Remove the ticktype from the ticker (Only exists in Crypto and Future data but causes no issues) ticker = components[0]; if (resolution < Resolution.Hour) { // Future options are special and have the following format Market/Resolution/Ticker/FutureExpiry/Date var dateIndex = securityType == SecurityType.FutureOption ? startIndex + 5 : startIndex + 4; date = Parse.DateTimeExact(info[dateIndex].Substring(0, 8), DateFormat.EightCharacter); } // If resolution is Daily or Hour for options and index options, we can only get the year from the path else if (securityType == SecurityType.Option || securityType == SecurityType.IndexOption) { var year = int.Parse(components[1], CultureInfo.InvariantCulture); date = new DateTime(year, 01, 01); } } if (securityType == SecurityType.FutureOption) { // Future options have underlying FutureExpiry date as the parent dir for the zips, we need this for our underlying symbol = CreateSymbol(ticker, securityType, market, null, Parse.DateTimeExact(info[startIndex + 4].Substring(0, 8), DateFormat.EightCharacter)); } else { symbol = CreateSymbol(ticker, securityType, market, null, date); } } catch (Exception ex) { Log.Debug($"LeanData.TryParsePath(): Error encountered while parsing the path {fileName}. Error: {ex.GetBaseException()}"); return false; } return true; } /// /// Parses the universe file path and extracts the corresponding symbol and file date. /// /// /// A list of strings representing the file path segments. The expected structure is: /// General format: ["data", SecurityType, Market, "universes", ...] /// Examples: /// /// Equity: data/equity/usa/universes/etf/spy/20201130.csv /// Option: data/option/usa/universes/aapl/20241112.csv /// Future: data/future/cme/universes/es/20130710.csv /// Future Option: data/futureoption/cme/universes/20120401/20111230.csv /// /// /// The type of security for which the symbol is being created. /// A tuple containing the parsed and the universe processing file date. /// Thrown if the file path does not contain 'universes'. /// Thrown if the security type is not supported. private static (Symbol symbol, DateTime processingDate) ParseUniversePath(IReadOnlyList filePathParts, SecurityType securityType) { if (!filePathParts.Contains("universes", StringComparer.InvariantCultureIgnoreCase)) { throw new ArgumentException($"LeanData.{nameof(ParseUniversePath)}:The file path must contain a 'universes' part, but it was not found."); } var symbol = default(Symbol); var market = filePathParts[2]; var ticker = filePathParts[^2]; var universeFileDate = DateTime.ParseExact(filePathParts[^1], DateFormat.EightCharacter, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None); switch (securityType) { case SecurityType.Equity: securityType = SecurityType.Base; var dataType = filePathParts.Contains("etf", StringComparer.InvariantCultureIgnoreCase) ? typeof(ETFConstituentUniverse) : default; symbol = CreateSymbol(ticker, securityType, market, dataType, universeFileDate); break; case SecurityType.Option: symbol = CreateSymbol(ticker, securityType, market, null, universeFileDate); break; case SecurityType.IndexOption: symbol = CreateSymbol(ticker, securityType, market, null, default); break; case SecurityType.FutureOption: symbol = CreateSymbol(filePathParts[^3], securityType, market, null, Parse.DateTimeExact(filePathParts[^2], DateFormat.EightCharacter)); break; case SecurityType.Future: var mapUnderlyingTicker = OptionSymbol.MapToUnderlying(ticker, securityType); symbol = Symbol.CreateFuture(mapUnderlyingTicker, market, universeFileDate); break; default: throw new NotSupportedException($"LeanData.{nameof(ParseUniversePath)}:The security type '{securityType}' is not supported for data universe files."); } return (symbol, universeFileDate); } /// /// Creates a new Symbol based on parsed data path information. /// /// The parsed ticker symbol. /// The parsed type of security. /// The parsed market or exchange. /// Optional type used for generating the base data SID (applicable only for SecurityType.Base). /// The date used in path parsing to create the correct symbol. /// A unique security identifier. /// /// /// path: equity/usa/minute/spwr/20071223_trade.zip /// ticker: spwr /// securityType: equity /// market: usa /// mappingResolveDate: 2007/12/23 /// /// private static Symbol CreateSymbol(string ticker, SecurityType securityType, string market, Type dataType, DateTime mappingResolveDate = default) { if (mappingResolveDate != default && (securityType == SecurityType.Equity || securityType == SecurityType.Option)) { var symbol = new Symbol(SecurityIdentifier.GenerateEquity(ticker, market, mappingResolveDate: mappingResolveDate), ticker); return securityType == SecurityType.Option ? Symbol.CreateCanonicalOption(symbol) : symbol; } else if (securityType == SecurityType.FutureOption) { var underlyingTicker = OptionSymbol.MapToUnderlying(ticker, securityType); // Create our underlying future and then the Canonical option for this future var underlyingFuture = Symbol.CreateFuture(underlyingTicker, market, mappingResolveDate); return Symbol.CreateCanonicalOption(underlyingFuture); } else if (securityType == SecurityType.IndexOption) { var underlyingTicker = OptionSymbol.MapToUnderlying(ticker, securityType); // Create our underlying index and then the Canonical option var underlyingIndex = Symbol.Create(underlyingTicker, SecurityType.Index, market); return Symbol.CreateCanonicalOption(underlyingIndex, ticker, market, null); } else { return Symbol.Create(ticker, securityType, market, baseDataType: dataType); } } private static List SplitDataPath(string fileName) { var pathSeparators = new[] { '/', '\\' }; // Removes file extension fileName = fileName.Replace(fileName.GetExtension(), string.Empty); // remove any relative file path while (fileName.First() == '.' || pathSeparators.Any(x => x == fileName.First())) { fileName = fileName.Remove(0, 1); } // split path into components return fileName.Split(pathSeparators, StringSplitOptions.RemoveEmptyEntries).ToList(); } /// /// Aggregates a list of second/minute bars at the requested resolution /// /// List of s /// Symbol of all tradeBars /// Desired resolution for new s /// List of aggregated s public static IEnumerable AggregateTradeBars(IEnumerable bars, Symbol symbol, TimeSpan resolution) { return Aggregate(new TradeBarConsolidator(resolution), bars, symbol); } /// /// Aggregates a list of second/minute bars at the requested resolution /// /// List of s /// Symbol of all QuoteBars /// Desired resolution for new s /// List of aggregated s public static IEnumerable AggregateQuoteBars(IEnumerable bars, Symbol symbol, TimeSpan resolution) { return Aggregate(new QuoteBarConsolidator(resolution), bars, symbol); } /// /// Aggregates a list of ticks at the requested resolution /// /// List of quote ticks /// Symbol of all ticks /// Desired resolution for new s /// List of aggregated s public static IEnumerable AggregateTicks(IEnumerable ticks, Symbol symbol, TimeSpan resolution) { return Aggregate(new TickQuoteBarConsolidator(resolution), ticks, symbol); } /// /// Aggregates a list of ticks at the requested resolution /// /// List of trade ticks /// Symbol of all ticks /// Desired resolution for new s /// List of aggregated s public static IEnumerable AggregateTicksToTradeBars(IEnumerable ticks, Symbol symbol, TimeSpan resolution) { return Aggregate(new TickConsolidator(resolution), ticks, symbol); } /// /// Helper method to return the start time and period of a bar the given point time should be part of /// /// The point in time we want to get the bar information about /// The associated security exchange /// True if extended market hours should be taken into consideration /// The calendar information that holds a start time and a period public static CalendarInfo GetDailyCalendar(DateTime exchangeTimeZoneDate, SecurityExchange exchange, bool extendedMarketHours) { return GetDailyCalendar(exchangeTimeZoneDate, exchange.Hours, extendedMarketHours); } /// /// Helper method to return the start time and period of a bar the given point time should be part of /// /// The point in time we want to get the bar information about /// The associated exchange hours /// True if extended market hours should be taken into consideration /// The calendar information that holds a start time and a period public static CalendarInfo GetDailyCalendar(DateTime exchangeTimeZoneDate, SecurityExchangeHours exchangeHours, bool extendedMarketHours) { var startTime = exchangeHours.GetFirstDailyMarketOpen(exchangeTimeZoneDate, extendedMarketHours); var endTime = exchangeHours.GetLastDailyMarketClose(startTime, extendedMarketHours); var period = endTime - startTime; return new CalendarInfo(startTime, period); } /// /// Helper method to get the next daily end time, taking into account strict end times if appropriate /// public static DateTime GetNextDailyEndTime(Symbol symbol, DateTime exchangeTimeZoneDate, SecurityExchangeHours exchangeHours) { var nextMidnight = exchangeTimeZoneDate.Date.AddDays(1); if (!UseStrictEndTime(true, symbol, Time.OneDay, exchangeHours)) { return nextMidnight; } var nextMarketClose = exchangeHours.GetLastDailyMarketClose(exchangeTimeZoneDate, extendedMarketHours: false); if (nextMarketClose > nextMidnight) { // if exchangeTimeZoneDate is after the previous close, the next close might be tomorrow if (!exchangeHours.IsOpen(exchangeTimeZoneDate, extendedMarketHours: false)) { return nextMarketClose; } return nextMidnight; } return nextMarketClose; } /// /// Helper method that defines the types of options that should use scale factor /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool OptionUseScaleFactor(Symbol symbol) { return symbol.SecurityType == SecurityType.Option || symbol.SecurityType == SecurityType.IndexOption; } /// /// Helper method to determine if we should use strict end time /// /// The associated symbol /// The datas time increment [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool UseStrictEndTime(bool dailyStrictEndTimeEnabled, Symbol symbol, TimeSpan increment, SecurityExchangeHours exchangeHours) { if (exchangeHours.IsMarketAlwaysOpen || increment <= Time.OneHour || symbol.SecurityType == SecurityType.Cfd && symbol.ID.Market == Market.Oanda || symbol.SecurityType == SecurityType.Forex || symbol.SecurityType == SecurityType.Base) { return false; } return dailyStrictEndTimeEnabled; } /// /// Helper method to determine if we should use strict end time /// public static bool UseDailyStrictEndTimes(IAlgorithmSettings settings, BaseDataRequest request, Symbol symbol, TimeSpan increment, SecurityExchangeHours exchangeHours = null) { return UseDailyStrictEndTimes(settings, request.DataType, symbol, increment, exchangeHours ?? request.ExchangeHours); } /// /// Helper method to determine if we should use strict end time /// public static bool UseDailyStrictEndTimes(IAlgorithmSettings settings, Type dataType, Symbol symbol, TimeSpan increment, SecurityExchangeHours exchangeHours) { return UseDailyStrictEndTimes(settings.DailyPreciseEndTime, dataType, symbol, increment, exchangeHours); } /// /// Helper method to determine if we should use strict end time /// public static bool UseDailyStrictEndTimes(bool dailyStrictEndTimeEnabled, Type dataType, Symbol symbol, TimeSpan increment, SecurityExchangeHours exchangeHours) { return UseDailyStrictEndTimes(dataType) && UseStrictEndTime(dailyStrictEndTimeEnabled, symbol, increment, exchangeHours); } /// /// True if this data type should use strict daily end times /// public static bool UseDailyStrictEndTimes(Type dataType) { return dataType != null && _strictDailyEndTimesDataTypes.Contains(dataType); } /// /// Helper method that if appropiate, will set the Time and EndTime of the given data point to it's daily strict times /// /// The target data point /// The associated exchange hours /// This method is used to set daily times on pre existing data, assuming it does not cover extended market hours [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool SetStrictEndTimes(IBaseData baseData, SecurityExchangeHours exchange) { if (baseData == null) { return false; } var dataType = baseData.GetType(); if (!UseDailyStrictEndTimes(dataType)) { return false; } var dailyCalendar = GetDailyCalendar(baseData.EndTime, exchange, extendedMarketHours: false); if (dailyCalendar.End < baseData.Time) { // this data point we were given is probably from extended market hours which we don't support for daily backtesting data return false; } baseData.Time = dailyCalendar.Start; baseData.EndTime = dailyCalendar.End; return true; } /// /// Helper to separate filename and entry from a given key for DataProviders /// /// The key to parse /// File name extracted /// Entry name extracted public static void ParseKey(string key, out string fileName, out string entryName) { // Default scenario, no entryName included in key entryName = null; // default to all entries fileName = key; if (key == null) { return; } // Try extracting an entry name; Anything after a # sign var hashIndex = key.LastIndexOf("#", StringComparison.Ordinal); if (hashIndex != -1) { entryName = key.Substring(hashIndex + 1); fileName = key.Substring(0, hashIndex); } } /// /// Helper method to determine if the specified data type supports extended market hours /// /// The data type /// Whether the specified data type supports extended market hours public static bool SupportsExtendedMarketHours(Type dataType) { return !dataType.IsAssignableTo(typeof(BaseChainUniverseData)); } /// /// Helper method to aggregate ticks or bars into lower frequency resolutions /// /// Output type /// Input type /// The consolidator to use /// The data point source /// The symbol to output private static IEnumerable Aggregate(PeriodCountConsolidatorBase consolidator, IEnumerable dataPoints, Symbol symbol) where T : BaseData where K : BaseData { IBaseData lastAggregated = null; var getConsolidatedBar = () => { if (lastAggregated != consolidator.Consolidated && consolidator.Consolidated != null) { // if there's a new aggregated bar we set the symbol & return it lastAggregated = consolidator.Consolidated; lastAggregated.Symbol = symbol; return lastAggregated; } return null; }; foreach (var dataPoint in dataPoints) { consolidator.Update(dataPoint); var consolidated = getConsolidatedBar(); if (consolidated != null) { yield return (T)consolidated; } } // flush any partial bar consolidator.Scan(Time.EndOfTime); var lastConsolidated = getConsolidatedBar(); if (lastConsolidated != null) { yield return (T)lastConsolidated; } // cleanup consolidator.DisposeSafely(); } } }