/* * 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 NodaTime; using QuantConnect.Data; using QuantConnect.Data.Auxiliary; using QuantConnect.Data.Market; using QuantConnect.Logging; using QuantConnect.Securities; using QuantConnect.Securities.Future; using static QuantConnect.StringExtensions; namespace QuantConnect.Util { /// /// Provides methods for generating lean data file content /// public static class LeanData { /// /// The different used for data paths /// /// This includes 'alternative' public static IReadOnlyList SecurityTypeAsDataPath => Enum.GetNames(typeof(SecurityType)) .Select(x => x.ToLowerInvariant()).Union(new[] { "alternative" }).ToList(); /// /// 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); } /// /// 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.Exchange, 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.Exchange, 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: switch (resolution) { case Resolution.Tick: var tick = data as Tick; if (tick == null) { throw new ArgumentException("Cryto tick could not be created", nameof(data)); } if (tick.TickType == TickType.Trade) { return ToCsv(milliseconds, tick.LastPrice, tick.Quantity); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize); } throw new ArgumentException("Cryto 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("Cryto 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("Cryto 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.Option: switch (resolution) { case Resolution.Tick: var tick = (Tick)data; if (tick.TickType == TickType.Trade) { return ToCsv(milliseconds, Scale(tick.LastPrice), tick.Quantity, tick.Exchange, 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.Exchange, 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(milliseconds, 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.Exchange, tick.SaleCondition, tick.Suspicious ? "1" : "0"); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.Exchange, 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(milliseconds, 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.Exchange, tick.SaleCondition, tick.Suspicious ? "1": "0"); } if (tick.TickType == TickType.Quote) { return ToCsv(milliseconds, tick.BidPrice, tick.BidSize, tick.AskPrice, tick.AskSize, tick.Exchange, 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(TradeBar) || baseDataType == typeof(QuoteBar) || baseDataType == typeof(OpenInterest)) { return true; } return false; } /// /// Generates the full zip file path rooted in the /// public static string GenerateZipFilePath(string dataDirectory, Symbol symbol, DateTime date, Resolution resolution, TickType tickType) { return Path.Combine(dataDirectory, GenerateRelativeZipFilePath(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.Forex: case SecurityType.Cfd: case SecurityType.Crypto: return !isHourOrDaily ? Path.Combine(directory, symbol.Value.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 !isHourOrDaily ? Path.Combine(directory, futureOptionPath) : directory; case SecurityType.Future: 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)); } /// /// 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.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: // We want the future option ticker as the lookup name inside the ZIP file var optionPath = symbol.Underlying.Value.ToLowerInvariant(); if (isHourOrDaily) { return string.Join("_", optionPath, tickType.TickTypeToLower(), symbol.ID.OptionStyle.ToLower(), symbol.ID.OptionRight.ToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; } return string.Join("_", formattedDate, optionPath, resolution.ResolutionToLower(), tickType.TickTypeToLower(), symbol.ID.OptionStyle.ToLower(), symbol.ID.OptionRight.ToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; case SecurityType.FutureOption: // We want the future option ticker as the lookup name inside the ZIP file var futureOptionPath = symbol.ID.Symbol.ToLowerInvariant(); if (isHourOrDaily) { return string.Join("_", futureOptionPath, tickType.TickTypeToLower(), symbol.ID.OptionStyle.ToLower(), symbol.ID.OptionRight.ToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; } return string.Join("_", formattedDate, futureOptionPath, resolution.ResolutionToLower(), tickType.TickTypeToLower(), symbol.ID.OptionStyle.ToLower(), symbol.ID.OptionRight.ToLower(), Scale(symbol.ID.StrikePrice), symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; case SecurityType.Future: var expiryDate = symbol.ID.Date; var monthsToAdd = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(symbol.ID.Symbol, expiryDate.Date); var contractYearMonth = expiryDate.AddMonths(monthsToAdd).ToStringInvariant(DateFormat.YearMonth); if (isHourOrDaily) { return string.Join("_", symbol.ID.Symbol.ToLowerInvariant(), tickType.TickTypeToLower(), contractYearMonth, expiryDate.ToStringInvariant(DateFormat.EightCharacter) ) + ".csv"; } return string.Join("_", formattedDate, symbol.ID.Symbol.ToLowerInvariant(), resolution.ResolutionToLower(), tickType.TickTypeToLower(), contractYearMonth, expiryDate.ToStringInvariant(DateFormat.EightCharacter) ) + ".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.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) { var optionPath = symbol.Underlying.Value.ToLowerInvariant(); return $"{optionPath}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip"; } return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip"; case SecurityType.FutureOption: if (isHourOrDaily) { var futureOptionPath = symbol.ID.Symbol.ToLowerInvariant(); return $"{futureOptionPath}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip"; } return $"{formattedDate}_{tickTypeString}_{symbol.ID.OptionStyle.ToLower()}.zip"; case SecurityType.Future: 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); tickType = tickType ?? (securityType == SecurityType.Forex || securityType == SecurityType.Cfd ? TickType.Quote : 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: if (isHourlyOrDaily) { var style = (OptionStyle)Enum.Parse(typeof(OptionStyle), parts[2], true); var right = (OptionRight)Enum.Parse(typeof(OptionRight), parts[3], true); var strike = Parse.Decimal(parts[4]) / 10000m; var expiry = Parse.DateTimeExact(parts[5], DateFormat.EightCharacter); return Symbol.CreateOption(symbol.Underlying, symbol.ID.Market, style, right, strike, expiry); } else { var style = (OptionStyle)Enum.Parse(typeof(OptionStyle), parts[4], true); var right = (OptionRight)Enum.Parse(typeof(OptionRight), parts[5], true); var strike = Parse.Decimal(parts[6]) / 10000m; var expiry = DateTime.ParseExact(parts[7], DateFormat.EightCharacter, CultureInfo.InvariantCulture); return Symbol.CreateOption(symbol.Underlying, 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(); } } return string.Join(",", args); } /// /// 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 == typeof(ZipEntryName)) { return TickType.Quote; } if (type == typeof(Tick)) { if (securityType == SecurityType.Forex || securityType == SecurityType.Cfd || securityType == SecurityType.Crypto) { return TickType.Quote; } } 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 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) { symbol = null; resolution = Resolution.Daily; date = default(DateTime); var pathSeparators = new[] { '/', '\\' }; try { // Removes file extension fileName = fileName.Replace(fileName.GetExtension(), ""); // remove any relative file path while (fileName.First() == '.' || pathSeparators.Any(x => x == fileName.First())) { fileName = fileName.Remove(0, 1); } // split path into components var info = fileName.Split(pathSeparators, StringSplitOptions.RemoveEmptyEntries).ToList(); // find where the useful part of the path starts - i.e. the securityType var startIndex = info.FindIndex(x => SecurityTypeAsDataPath.Contains(x.ToLowerInvariant())); var securityType = ParseDataSecurityType(info[startIndex]); var market = Market.USA; string ticker; if (securityType == SecurityType.Base) { if (!Enum.TryParse(info[startIndex + 2], true, out resolution)) { resolution = Resolution.Daily; } // 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 { resolution = (Resolution)Enum.Parse(typeof(Resolution), info[startIndex + 2], true); // Gather components used to create the security market = info[startIndex + 1]; ticker = info[startIndex + 3]; // If resolution is Daily or Hour, we do not need to set the date and tick type if (resolution < Resolution.Hour) { date = Parse.DateTimeExact(info[startIndex + 4].Substring(0, 8), DateFormat.EightCharacter); } if (securityType == SecurityType.Crypto) { ticker = ticker.Split('_').First(); } } symbol = Symbol.Create(ticker, securityType, market); } catch (Exception ex) { Log.Error($"LeanData.TryParsePath(): Error encountered while parsing the path {fileName}. Error: {ex.GetBaseException()}"); return false; } return true; } } }