/* * 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 NodaTime; using ProtoBuf; using System.IO; using System.Linq; using Newtonsoft.Json; using QuantConnect.Util; using QuantConnect.Data.Market; using System.Collections.Generic; using QuantConnect.Python; namespace QuantConnect.Data { /// /// Abstract base data class of QuantConnect. It is intended to be extended to define /// generic user customizable data types while at the same time implementing the basics of data where possible /// [ProtoContract(SkipConstructor = true)] [ProtoInclude(8, typeof(Tick))] [ProtoInclude(100, typeof(TradeBar))] [ProtoInclude(200, typeof(QuoteBar))] [ProtoInclude(300, typeof(Dividend))] [ProtoInclude(400, typeof(Split))] [PandasIgnoreMembers] public abstract class BaseData : IBaseData { private decimal _value; /// /// A list of all /// protected static readonly List AllResolutions = Enum.GetValues(typeof(Resolution)).Cast().ToList(); /// /// A list of /// protected static readonly List DailyResolution = new List { Resolution.Daily }; /// /// A list of /// protected static readonly List MinuteResolution = new List { Resolution.Minute }; /// /// A list of high , including minute, second, and tick. /// protected static readonly List HighResolution = new List { Resolution.Minute, Resolution.Second, Resolution.Tick }; /// /// A list of resolutions support by Options /// protected static readonly List OptionResolutions = new List { Resolution.Daily, Resolution.Hour, Resolution.Minute }; /// /// Market Data Type of this data - does it come in individual price packets or is it grouped into OHLC. /// /// Data is classed into two categories - streams of instantaneous prices and groups of OHLC data. [ProtoMember(1)] public MarketDataType DataType { get; set; } = MarketDataType.Base; /// /// True if this is a fill forward piece of data /// public bool IsFillForward { get; private set; } /// /// Current time marker of this data packet. /// /// All data is timeseries based. [ProtoMember(2)] public DateTime Time { get; set; } /// /// The end time of this data. Some data covers spans (trade bars) and as such we want /// to know the entire time span covered /// // NOTE: This is needed event though the class is marked with [PandasIgnoreMembers] because the property is virtual. // If a derived class overrides it, without [PandasIgnore], the property will not be ignored. [PandasIgnore] public virtual DateTime EndTime { get { return Time; } set { Time = value; } } /// /// Symbol representation for underlying Security /// public Symbol Symbol { get; set; } = Symbol.Empty; /// /// Value representation of this data packet. All data requires a representative value for this moment in time. /// For streams of data this is the price now, for OHLC packets this is the closing price. /// [ProtoMember(4)] [PandasIgnore] public virtual decimal Value { get { return _value; } set { _value = value; } } /// /// As this is a backtesting platform we'll provide an alias of value as price. /// [PandasIgnore] public virtual decimal Price => Value; /// /// Constructor for initialising the dase data class /// public BaseData() { //Empty constructor required for fast-reflection initialization } /// /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object /// each time it is called. The returned object is assumed to be time stamped in the config.ExchangeTimeZone. /// /// Subscription data config setup object /// Line of the source document /// Date of the requested data /// true if we're in live mode, false for backtesting mode /// Instance of the T:BaseData object generated by this line of the CSV public virtual BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) { // stub implementation to prevent compile errors in user algorithms var dataFeed = isLiveMode ? DataFeedEndpoint.LiveTrading : DataFeedEndpoint.Backtesting; #pragma warning disable 618 // This implementation is left here for backwards compatibility of the BaseData API return Reader(config, line, date, dataFeed); #pragma warning restore 618 } /// /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object /// each time it is called. The returned object is assumed to be time stamped in the config.ExchangeTimeZone. /// /// Subscription data config setup object /// The data stream /// Date of the requested data /// true if we're in live mode, false for backtesting mode /// Instance of the T:BaseData object generated by this line of the CSV [StubsIgnore] public virtual BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode) { throw new NotImplementedException("Each data types has to implement is own Stream reader"); } /// /// Return the URL string source of the file. This will be converted to a stream /// /// Configuration object /// Date of this source file /// true if we're in live mode, false for backtesting mode /// String URL of source file. public virtual SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) { // stub implementation to prevent compile errors in user algorithms var dataFeed = isLiveMode ? DataFeedEndpoint.LiveTrading : DataFeedEndpoint.Backtesting; #pragma warning disable 618 // This implementation is left here for backwards compatibility of the BaseData API var source = GetSource(config, date, dataFeed); #pragma warning restore 618 if (isLiveMode) { // live trading by default always gets a rest endpoint return new SubscriptionDataSource(source, SubscriptionTransportMedium.Rest); } // construct a uri to determine if we have a local or remote file var uri = new Uri(source, UriKind.RelativeOrAbsolute); if (uri.IsAbsoluteUri && !uri.IsLoopback) { return new SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile); } return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile); } /// /// Indicates if there is support for mapping /// /// Relies on the property value /// True indicates mapping should be used public virtual bool RequiresMapping() { return Symbol.RequiresMapping(); } /// /// Indicates that the data set is expected to be sparse /// /// Relies on the property value /// This is a method and not a property so that python /// custom data types can override it /// True if the data set represented by this type is expected to be sparse public virtual bool IsSparseData() { // by default, we'll assume all custom data is sparse data return Symbol.SecurityType == SecurityType.Base; } /// /// Indicates whether this contains data that should be stored in the security cache /// /// Whether this contains data that should be stored in the security cache public virtual bool ShouldCacheToSecurity() { return true; } /// /// Gets the default resolution for this data and security type /// /// This is a method and not a property so that python /// custom data types can override it public virtual Resolution DefaultResolution() { return Resolution.Minute; } /// /// Gets the supported resolution for this data and security type /// /// Relies on the property value /// This is a method and not a property so that python /// custom data types can override it public virtual List SupportedResolutions() { if (Symbol.SecurityType.IsOption()) { return OptionResolutions; } return AllResolutions; } /// /// Specifies the data time zone for this data type. This is useful for custom data types /// /// Will throw for security types /// other than /// The of this data type public virtual DateTimeZone DataTimeZone() { if (Symbol.SecurityType != SecurityType.Base) { throw new InvalidOperationException("BaseData.DataTimeZone(): is only valid for base data types"); } return TimeZones.NewYork; } /// /// Updates this base data with a new trade /// /// The price of the last trade /// The quantity traded public void UpdateTrade(decimal lastTrade, decimal tradeSize) { Update(lastTrade, 0, 0, tradeSize, 0, 0); } /// /// Updates this base data with new quote information /// /// The current bid price /// The current bid size /// The current ask price /// The current ask size public void UpdateQuote(decimal bidPrice, decimal bidSize, decimal askPrice, decimal askSize) { Update(0, bidPrice, askPrice, 0, bidSize, askSize); } /// /// Updates this base data with the new quote bid information /// /// The current bid price /// The current bid size public void UpdateBid(decimal bidPrice, decimal bidSize) { Update(0, bidPrice, 0, 0, bidSize, 0); } /// /// Updates this base data with the new quote ask information /// /// The current ask price /// The current ask size public void UpdateAsk(decimal askPrice, decimal askSize) { Update(0, 0, askPrice, 0, 0, askSize); } /// /// Update routine to build a bar/tick from a data update. /// /// The last trade price /// Current bid price /// Current asking price /// Volume of this trade /// The size of the current bid, if available /// The size of the current ask, if available public virtual void Update(decimal lastTrade, decimal bidPrice, decimal askPrice, decimal volume, decimal bidSize, decimal askSize) { Value = lastTrade; } /// /// Return a new instance clone of this object, used in fill forward /// /// /// This base implementation uses reflection to copy all public fields and properties /// /// True if this is a fill forward clone /// A clone of the current object public virtual BaseData Clone(bool fillForward) { var clone = Clone(); clone.IsFillForward = fillForward; return clone; } /// /// Return a new instance clone of this object, used in fill forward /// /// /// This base implementation uses reflection to copy all public fields and properties /// /// A clone of the current object public virtual BaseData Clone() { return (BaseData) ObjectActivator.Clone((object)this); } /// /// Formats a string with the symbol and value. /// /// string - a string formatted as SPY: 167.753 public override string ToString() { return $"{Symbol}: {Value.ToStringInvariant("C")}"; } /// /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object /// each time it is called. /// /// OBSOLETE:: This implementation is added for backward/forward compatibility purposes. This function is no longer called by the LEAN engine. /// Subscription data config setup object /// Line of the source document /// Date of the requested data /// Type of datafeed we're requesting - a live or backtest feed. /// Instance of the T:BaseData object generated by this line of the CSV [Obsolete("Reader(SubscriptionDataConfig, string, DateTime, DataFeedEndpoint) method has been made obsolete, use Reader(SubscriptionDataConfig, string, DateTime, bool) instead.")] [StubsIgnore] public virtual BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, DataFeedEndpoint dataFeed) { throw new InvalidOperationException( $"Please implement Reader(SubscriptionDataConfig, string, DateTime, bool) on your custom data type: {GetType().Name}" ); } /// /// Return the URL string source of the file. This will be converted to a stream /// /// OBSOLETE:: This implementation is added for backward/forward compatibility purposes. This function is no longer called by the LEAN engine. /// Configuration object /// Date of this source file /// Type of datafeed we're reqesting - backtest or live /// String URL of source file. [Obsolete("GetSource(SubscriptionDataConfig, DateTime, DataFeedEndpoint) method has been made obsolete, use GetSource(SubscriptionDataConfig, DateTime, bool) instead.")] [StubsIgnore] public virtual string GetSource(SubscriptionDataConfig config, DateTime date, DataFeedEndpoint datafeed) { throw new InvalidOperationException( $"Please implement GetSource(SubscriptionDataConfig, DateTime, bool) on your custom data type: {GetType().Name}" ); } /// /// Deserialize the message from the data server /// /// The data server's message /// An enumerable of base data, if unsuccessful, returns an empty enumerable public static IEnumerable DeserializeMessage(string serialized) { var deserialized = JsonConvert.DeserializeObject(serialized, JsonSerializerSettings); var enumerable = deserialized as IEnumerable; if (enumerable != null) { return enumerable; } var data = deserialized as BaseData; if (data != null) { return new[] { data }; } return Enumerable.Empty(); } private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; } }