/* * 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 ProtoBuf; using System.IO; using System.Threading; using QuantConnect.Util; using System.Globalization; using QuantConnect.Logging; using static QuantConnect.StringExtensions; using QuantConnect.Python; namespace QuantConnect.Data.Market { /// /// TradeBar class for second and minute resolution data: /// An OHLC implementation of the QuantConnect BaseData class with parameters for candles. /// [ProtoContract(SkipConstructor = true)] public class TradeBar : BaseData, IBaseDataBar { // scale factor used in QC equity/forex data files private const decimal _scaleFactor = 1 / 10000m; private int _initialized; private decimal _open; private decimal _high; private decimal _low; /// /// Volume: /// [ProtoMember(101)] public virtual decimal Volume { get; set; } /// /// Opening price of the bar: Defined as the price at the start of the time period. /// [ProtoMember(102)] public virtual decimal Open { get { return _open; } set { Initialize(value); _open = value; } } /// /// High price of the TradeBar during the time period. /// [ProtoMember(103)] public virtual decimal High { get { return _high; } set { Initialize(value); _high = value; } } /// /// Low price of the TradeBar during the time period. /// [ProtoMember(104)] public virtual decimal Low { get { return _low; } set { Initialize(value); _low = value; } } /// /// Closing price of the TradeBar. Defined as the price at Start Time + TimeSpan. /// [ProtoMember(105)] public virtual decimal Close { get { return Value; } set { Initialize(value); Value = value; } } /// /// The closing time of this bar, computed via the Time and Period /// [PandasIgnore] public override DateTime EndTime { get { return Time + Period; } set { Period = value - Time; } } /// /// The period of this trade bar, (second, minute, daily, ect...) /// [ProtoMember(106)] [PandasIgnore] public virtual TimeSpan Period { get; set; } //In Base Class: Alias of Closing: //public decimal Price; //Symbol of Asset. //In Base Class: public Symbol Symbol; //In Base Class: DateTime Of this TradeBar //public DateTime Time; /// /// Default initializer to setup an empty tradebar. /// public TradeBar() { Symbol = Symbol.Empty; DataType = MarketDataType.TradeBar; Period = QuantConnect.Time.OneMinute; } /// /// Cloner constructor for implementing fill forward. /// Return a new instance with the same values as this original. /// /// Original tradebar object we seek to clone public TradeBar(TradeBar original) { DataType = MarketDataType.TradeBar; Time = new DateTime(original.Time.Ticks); Symbol = original.Symbol; Value = original.Close; Open = original.Open; High = original.High; Low = original.Low; Close = original.Close; Volume = original.Volume; Period = original.Period; _initialized = 1; } /// /// Initialize Trade Bar with OHLC Values: /// /// DateTime Timestamp of the bar /// Market MarketType Symbol /// Decimal Opening Price /// Decimal High Price of this bar /// Decimal Low Price of this bar /// Decimal Close price of this bar /// Volume sum over day /// The period of this bar, specify null for default of 1 minute public TradeBar(DateTime time, Symbol symbol, decimal open, decimal high, decimal low, decimal close, decimal volume, TimeSpan? period = null) { Time = time; Symbol = symbol; Value = close; Open = open; High = high; Low = low; Close = close; Volume = volume; Period = period ?? QuantConnect.Time.OneMinute; DataType = MarketDataType.TradeBar; _initialized = 1; } /// /// TradeBar Reader: Fetch the data from the QC storage and feed it line by line into the engine. /// /// Symbols, Resolution, DataType, /// Line from the data file requested /// Date of this reader request /// true if we're in live mode, false for backtesting mode /// Enumerable iterator for returning each line of the required data. public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) { //Handle end of file: if (line == null) { return null; } if (isLiveMode) { return new TradeBar(); } try { switch (config.SecurityType) { //Equity File Data Format: case SecurityType.Equity: return ParseEquity(config, line, date); //FOREX has a different data file format: case SecurityType.Forex: return ParseForex(config, line, date); case SecurityType.Crypto: case SecurityType.CryptoFuture: return ParseCrypto(config, line, date); case SecurityType.Cfd: return ParseCfd(config, line, date); case SecurityType.Index: return ParseIndex(config, line, date); case SecurityType.Option: case SecurityType.FutureOption: case SecurityType.IndexOption: return ParseOption(config, line, date); case SecurityType.Future: return ParseFuture(config, line, date); } } catch (Exception err) { Log.Error(Invariant($"TradeBar.Reader(): Error parsing line: '{line}', Symbol: {config.Symbol.Value}, SecurityType: ") + Invariant($"{config.SecurityType}, Resolution: {config.Resolution}, Date: {date:yyyy-MM-dd}, Message: {err}") ); } // if we couldn't parse it above return a default instance return new TradeBar { Symbol = config.Symbol, Period = config.Increment }; } /// /// TradeBar Reader: Fetch the data from the QC storage and feed it directly from the stream into the engine. /// /// Symbols, Resolution, DataType, /// The file data stream /// Date of this reader request /// true if we're in live mode, false for backtesting mode /// Enumerable iterator for returning each line of the required data. [StubsIgnore] public override BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode) { //Handle end of file: if (stream == null || stream.EndOfStream) { return null; } if (isLiveMode) { return new TradeBar(); } try { switch (config.SecurityType) { //Equity File Data Format: case SecurityType.Equity: return ParseEquity(config, stream, date); //FOREX has a different data file format: case SecurityType.Forex: return ParseForex(config, stream, date); case SecurityType.Crypto: case SecurityType.CryptoFuture: return ParseCrypto(config, stream, date); case SecurityType.Index: return ParseIndex(config, stream, date); case SecurityType.Cfd: return ParseCfd(config, stream, date); case SecurityType.Option: case SecurityType.FutureOption: case SecurityType.IndexOption: return ParseOption(config, stream, date); case SecurityType.Future: return ParseFuture(config, stream, date); } } catch (Exception err) { Log.Error(Invariant($"TradeBar.Reader(): Error parsing stream, Symbol: {config.Symbol.Value}, SecurityType: ") + Invariant($"{config.SecurityType}, Resolution: {config.Resolution}, Date: {date:yyyy-MM-dd}, Message: {err}") ); } // we need to consume a line anyway, to advance the stream stream.ReadLine(); // if we couldn't parse it above return a default instance return new TradeBar { Symbol = config.Symbol, Period = config.Increment }; } /// /// Parses the trade bar data line assuming QC data formats /// public static TradeBar Parse(SubscriptionDataConfig config, string line, DateTime baseDate) { switch (config.SecurityType) { case SecurityType.Equity: return ParseEquity(config, line, baseDate); case SecurityType.Forex: case SecurityType.Crypto: case SecurityType.CryptoFuture: return ParseForex(config, line, baseDate); case SecurityType.Cfd: return ParseCfd(config, line, baseDate); } return null; } /// /// Parses equity trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// Line from the data file requested /// Date of this reader request /// public static T ParseEquity(SubscriptionDataConfig config, string line, DateTime date) where T : TradeBar, new() { var tradeBar = new T { Symbol = config.Symbol, Period = config.Increment }; ParseEquity(tradeBar, config, line, date); return tradeBar; } /// /// Parses equity trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// The data stream of the requested file /// Date of this reader request /// public static TradeBar ParseEquity(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) { var tradeBar = new TradeBar { Symbol = config.Symbol, Period = config.Increment }; StreamParseScale(config, streamReader, date, useScaleFactor: true, tradeBar, true); return tradeBar; } private static void ParseEquity(TradeBar tradeBar, SubscriptionDataConfig config, string line, DateTime date) { LineParseScale(config, line, date, useScaleFactor: true, tradeBar, hasVolume: true); } /// /// Parses equity trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// Line from the data file requested /// Date of this reader request /// public static TradeBar ParseEquity(SubscriptionDataConfig config, string line, DateTime date) { var tradeBar = new TradeBar { Symbol = config.Symbol, Period = config.Increment }; ParseEquity(tradeBar, config, line, date); return tradeBar; } /// /// Parses forex trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static T ParseForex(SubscriptionDataConfig config, string line, DateTime date) where T : TradeBar, new() { var tradeBar = new T { Symbol = config.Symbol, Period = config.Increment }; LineParseNoScale(config, line, date, tradeBar, hasVolume: false); return tradeBar; } /// /// Parses crypto trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight public static T ParseCrypto(SubscriptionDataConfig config, string line, DateTime date) where T : TradeBar, new() { var tradeBar = new T { Symbol = config.Symbol, Period = config.Increment }; LineParseNoScale(config, line, date, tradeBar); return tradeBar; } /// /// Parses crypto trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight public static TradeBar ParseCrypto(SubscriptionDataConfig config, string line, DateTime date) { return LineParseNoScale(config, line, date); } /// /// Parses crypto trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// The data stream of the requested file /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight public static TradeBar ParseCrypto(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) { return StreamParseNoScale(config, streamReader, date); } /// /// Parses forex trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseForex(SubscriptionDataConfig config, string line, DateTime date) { return LineParseNoScale(config, line, date, hasVolume: false); } /// /// Parses forex trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// The data stream of the requested file /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseForex(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) { return StreamParseNoScale(config, streamReader, date, hasVolume: false); } /// /// Parses CFD trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static T ParseCfd(SubscriptionDataConfig config, string line, DateTime date) where T : TradeBar, new() { // CFD has the same data format as Forex return ParseForex(config, line, date); } /// /// Parses CFD trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseCfd(SubscriptionDataConfig config, string line, DateTime date) { // CFD has the same data format as Forex return ParseForex(config, line, date); } /// /// Parses CFD trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// The data stream of the requested file /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseCfd(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) { // CFD has the same data format as Forex return ParseForex(config, streamReader, date); } /// /// Parses Option trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static T ParseOption(SubscriptionDataConfig config, string line, DateTime date) where T : TradeBar, new() { var tradeBar = new T { Period = config.Increment, Symbol = config.Symbol }; LineParseScale(config, line, date, useScaleFactor: LeanData.OptionUseScaleFactor(config.Symbol), tradeBar, hasVolume: true); return tradeBar; } /// /// Parses Option trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// The data stream of the requested file /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static T ParseOption(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) where T : TradeBar, new() { var tradeBar = new T { Period = config.Increment, Symbol = config.Symbol }; StreamParseScale(config, streamReader, date, useScaleFactor: LeanData.OptionUseScaleFactor(config.Symbol), tradeBar, true); return tradeBar; } /// /// Parses Future trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// The data stream of the requested file /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static T ParseFuture(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) where T : TradeBar, new() { var tradeBar = new T { Period = config.Increment, Symbol = config.Symbol }; StreamParseNoScale(config, streamReader, date, tradeBar); return tradeBar; } /// /// Parses Future trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// The requested output type, must derive from TradeBar /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static T ParseFuture(SubscriptionDataConfig config, string line, DateTime date) where T : TradeBar, new() { var tradeBar = new T { Period = config.Increment, Symbol = config.Symbol }; LineParseNoScale(config, line, date, tradeBar); return tradeBar; } /// /// Parse an index bar from the LEAN disk format /// public static TradeBar ParseIndex(SubscriptionDataConfig config, string line, DateTime date) { return LineParseNoScale(config, line, date); } /// /// Parse an index bar from the LEAN disk format /// private static TradeBar LineParseNoScale(SubscriptionDataConfig config, string line, DateTime date, TradeBar bar = null, bool hasVolume = true) { var tradeBar = bar ?? new TradeBar { Period = config.Increment, Symbol = config.Symbol }; var csv = line.ToCsv(hasVolume ? 6 : 5); if (config.Resolution == Resolution.Daily || config.Resolution == Resolution.Hour) { // hourly and daily have different time format, and can use slow, robust c# parser. tradeBar.Time = DateTime.ParseExact(csv[0], DateFormat.TwelveCharacter, CultureInfo.InvariantCulture).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone); } else { // Using custom "ToDecimal" conversion for speed on high resolution data. tradeBar.Time = date.Date.AddMilliseconds(csv[0].ToInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone); } tradeBar.Open = csv[1].ToDecimal(); tradeBar.High = csv[2].ToDecimal(); tradeBar.Low = csv[3].ToDecimal(); tradeBar.Close = csv[4].ToDecimal(); if (hasVolume) { tradeBar.Volume = csv[5].ToDecimal(); } return tradeBar; } /// /// Parse an index bar from the LEAN disk format /// private static TradeBar StreamParseNoScale(SubscriptionDataConfig config, StreamReader streamReader, DateTime date, TradeBar bar = null, bool hasVolume = true) { var tradeBar = bar ?? new TradeBar { Period = config.Increment, Symbol = config.Symbol }; if (config.Resolution == Resolution.Daily || config.Resolution == Resolution.Hour) { // hourly and daily have different time format, and can use slow, robust c# parser. tradeBar.Time = streamReader.GetDateTime().ConvertTo(config.DataTimeZone, config.ExchangeTimeZone); } else { // Using custom "ToDecimal" conversion for speed on high resolution data. tradeBar.Time = date.Date.AddMilliseconds(streamReader.GetInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone); } tradeBar.Open = streamReader.GetDecimal(); tradeBar.High = streamReader.GetDecimal(); tradeBar.Low = streamReader.GetDecimal(); tradeBar.Close = streamReader.GetDecimal(); if (hasVolume) { tradeBar.Volume = streamReader.GetDecimal(); } return tradeBar; } private static TradeBar LineParseScale(SubscriptionDataConfig config, string line, DateTime date, bool useScaleFactor, TradeBar bar = null, bool hasVolume = true) { var tradeBar = bar ?? new TradeBar { Period = config.Increment, Symbol = config.Symbol }; LineParseNoScale(config, line, date, tradeBar, hasVolume); if (useScaleFactor) { tradeBar.Open *= _scaleFactor; tradeBar.High *= _scaleFactor; tradeBar.Low *= _scaleFactor; tradeBar.Close *= _scaleFactor; } return tradeBar; } private static TradeBar StreamParseScale(SubscriptionDataConfig config, StreamReader streamReader, DateTime date, bool useScaleFactor, TradeBar bar = null, bool hasVolume = true) { var tradeBar = bar ?? new TradeBar { Period = config.Increment, Symbol = config.Symbol }; StreamParseNoScale(config, streamReader, date, tradeBar, hasVolume); if (useScaleFactor) { tradeBar.Open *= _scaleFactor; tradeBar.High *= _scaleFactor; tradeBar.Low *= _scaleFactor; tradeBar.Close *= _scaleFactor; } return tradeBar; } /// /// Parse an index bar from the LEAN disk format /// public static TradeBar ParseIndex(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) { return StreamParseNoScale(config, streamReader, date); } /// /// Parses Option trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseOption(SubscriptionDataConfig config, string line, DateTime date) { return ParseOption(config, line, date); } /// /// Parses Option trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// The data stream of the requested file /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseOption(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) { return ParseOption(config, streamReader, date); } /// /// Parses Future trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// Line from the data file requested /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseFuture(SubscriptionDataConfig config, string line, DateTime date) { return ParseFuture(config, line, date); } /// /// Parses Future trade bar data into the specified tradebar type, useful for custom types with OHLCV data deriving from TradeBar /// /// Symbols, Resolution, DataType, /// The data stream of the requested file /// The base data used to compute the time of the bar since the line specifies a milliseconds since midnight /// public static TradeBar ParseFuture(SubscriptionDataConfig config, StreamReader streamReader, DateTime date) { return ParseFuture(config, streamReader, date); } /// /// Update the tradebar - build the bar from this pricing information: /// /// This trade price /// Current bid price (not used) /// Current asking price (not used) /// Volume of this trade /// The size of the current bid, if available /// The size of the current ask, if available public override void Update(decimal lastTrade, decimal bidPrice, decimal askPrice, decimal volume, decimal bidSize, decimal askSize) { Initialize(lastTrade); if (lastTrade > High) High = lastTrade; if (lastTrade < Low) Low = lastTrade; //Volume is the total summed volume of trades in this bar: Volume += volume; //Always set the closing price; Close = lastTrade; } /// /// Get Source for Custom Data File /// >> What source file location would you prefer for each type of usage: /// /// Configuration object /// Date of this source request if source spread across multiple files /// true if we're in live mode, false for backtesting mode /// String source location of the file public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) { if (isLiveMode) { // this data type is streamed in live mode return new SubscriptionDataSource(string.Empty, SubscriptionTransportMedium.Streaming); } var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType); if (config.SecurityType == SecurityType.Future || config.SecurityType.IsOption()) { source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType); } return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile, FileFormat.Csv); } /// /// Return a new instance clone of this object, used in fill forward /// /// True if this is a fill forward clone /// A clone of the current object public override BaseData Clone(bool fillForward) { var clone = base.Clone(fillForward); if (fillForward) { // zero volume out, since it would skew calculations in volume-based indicators ((TradeBar)clone).Volume = 0; } return clone; } /// /// Return a new instance clone of this object /// public override BaseData Clone() { return (BaseData)MemberwiseClone(); } /// /// Formats a string with the symbol and value. /// /// string - a string formatted as SPY: 167.753 public override string ToString() { return $"{Symbol}: " + $"O: {Open.SmartRounding()} " + $"H: {High.SmartRounding()} " + $"L: {Low.SmartRounding()} " + $"C: {Close.SmartRounding()} " + $"V: {Volume.SmartRounding()}"; } /// /// Initializes this bar with a first data point /// /// The seed value for this bar private void Initialize(decimal value) { if (Interlocked.CompareExchange(ref _initialized, 1, 0) == 0) { _open = value; _low = value; _high = value; } } } }