/*
* 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();
}
}
}