/* * 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.ComponentModel; using System.Runtime.CompilerServices; using NodaTime; using QuantConnect.Data.Consolidators; using QuantConnect.Securities; using QuantConnect.Util; using static QuantConnect.StringExtensions; namespace QuantConnect.Data { /// /// Subscription data required including the type of data. /// public class SubscriptionDataConfig : IEquatable { private readonly SecurityIdentifier _sid; /// /// Type of data /// public readonly Type Type; /// /// Security type of this data subscription /// public readonly SecurityType SecurityType; /// /// Symbol of the asset we're requesting: this is really a perm tick!! /// public Symbol Symbol { get; private set; } /// /// Trade, quote or open interest data /// public readonly TickType TickType; /// /// Resolution of the asset we're requesting, second minute or tick /// public readonly Resolution Resolution; /// /// Timespan increment between triggers of this data: /// public readonly TimeSpan Increment; /// /// True if wish to send old data when time gaps in data feed. /// public readonly bool FillDataForward; /// /// Boolean Send Data from between 4am - 8am (Equities Setting Only) /// public readonly bool ExtendedMarketHours; /// /// True if this subscription was added for the sole purpose of providing currency conversion rates via /// public readonly bool IsInternalFeed; /// /// True if this subscription is for custom user data, false for QC data /// public readonly bool IsCustomData; /// /// The sum of dividends accrued in this subscription, used for scaling total return prices /// public decimal SumOfDividends; /// /// Gets the normalization mode used for this subscription /// public DataNormalizationMode DataNormalizationMode = DataNormalizationMode.Adjusted; /// /// Price Scaling Factor: /// public decimal PriceScaleFactor; /// /// Symbol Mapping: When symbols change over time (e.g. CHASE-> JPM) need to update the symbol requested. /// public string MappedSymbol { get { return (Symbol.ID.SecurityType == SecurityType.Option || Symbol.ID.SecurityType == SecurityType.FutureOption) ? (Symbol.HasUnderlying ? Symbol.Underlying.Value : Symbol.Value) : Symbol.Value; } set { Symbol = Symbol.UpdateMappedSymbol(value); } } /// /// Gets the market / scope of the symbol /// public readonly string Market; /// /// Gets the data time zone for this subscription /// public readonly DateTimeZone DataTimeZone; /// /// Gets the exchange time zone for this subscription /// public readonly DateTimeZone ExchangeTimeZone; /// /// Consolidators that are registred with this subscription /// public readonly ISet Consolidators; /// /// Gets whether or not this subscription should have filters applied to it (market hours/user filters from security) /// public readonly bool IsFilteredSubscription; /// /// Constructor for Data Subscriptions /// /// Type of the data objects. /// Symbol of the asset we're requesting /// Resolution of the asset we're requesting /// The time zone the raw data is time stamped in /// Specifies the time zone of the exchange for the security this subscription is for. This /// is this output time zone, that is, the time zone that will be used on BaseData instances /// Fill in gaps with historical data /// Equities only - send in data from 4am - 8pm /// Set to true if this subscription is added for the sole purpose of providing currency conversion rates, /// setting this flag to true will prevent the data from being sent into the algorithm's OnData methods /// True if this is user supplied custom data, false for normal QC data /// Specifies if trade or quote data is subscribed /// True if this subscription should have filters applied to it (market hours/user filters from security), false otherwise /// Specifies normalization mode used for this subscription public SubscriptionDataConfig(Type objectType, Symbol symbol, Resolution resolution, DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone, bool fillForward, bool extendedHours, bool isInternalFeed, bool isCustom = false, TickType? tickType = null, bool isFilteredSubscription = true, DataNormalizationMode dataNormalizationMode = DataNormalizationMode.Adjusted) { if (objectType == null) throw new ArgumentNullException(nameof(objectType)); if (symbol == null) throw new ArgumentNullException(nameof(symbol)); if (dataTimeZone == null) throw new ArgumentNullException(nameof(dataTimeZone)); if (exchangeTimeZone == null) throw new ArgumentNullException(nameof(exchangeTimeZone)); Type = objectType; SecurityType = symbol.ID.SecurityType; Resolution = resolution; _sid = symbol.ID; Symbol = symbol; FillDataForward = fillForward; ExtendedMarketHours = extendedHours; PriceScaleFactor = 1; IsInternalFeed = isInternalFeed; IsCustomData = isCustom; Market = symbol.ID.Market; DataTimeZone = dataTimeZone; ExchangeTimeZone = exchangeTimeZone; IsFilteredSubscription = isFilteredSubscription; Consolidators = new ConcurrentSet(); DataNormalizationMode = dataNormalizationMode; TickType = tickType ?? LeanData.GetCommonTickTypeForCommonDataTypes(objectType, SecurityType); switch (resolution) { case Resolution.Tick: //Ticks are individual sales and fillforward doesn't apply. Increment = TimeSpan.FromSeconds(0); FillDataForward = false; break; case Resolution.Second: Increment = TimeSpan.FromSeconds(1); break; case Resolution.Minute: Increment = TimeSpan.FromMinutes(1); break; case Resolution.Hour: Increment = TimeSpan.FromHours(1); break; case Resolution.Daily: Increment = TimeSpan.FromDays(1); break; default: throw new InvalidEnumArgumentException(Invariant($"Unexpected Resolution: {resolution}")); } } /// /// Copy constructor with overrides /// /// The config to copy, then overrides are applied and all option /// Type of the data objects. /// Symbol of the asset we're requesting /// Resolution of the asset we're requesting /// The time zone the raw data is time stamped in /// Specifies the time zone of the exchange for the security this subscription is for. This /// is this output time zone, that is, the time zone that will be used on BaseData instances /// Fill in gaps with historical data /// Equities only - send in data from 4am - 8pm /// Set to true if this subscription is added for the sole purpose of providing currency conversion rates, /// setting this flag to true will prevent the data from being sent into the algorithm's OnData methods /// True if this is user supplied custom data, false for normal QC data /// Specifies if trade or quote data is subscribed /// True if this subscription should have filters applied to it (market hours/user filters from security), false otherwise /// Specifies normalization mode used for this subscription public SubscriptionDataConfig(SubscriptionDataConfig config, Type objectType = null, Symbol symbol = null, Resolution? resolution = null, DateTimeZone dataTimeZone = null, DateTimeZone exchangeTimeZone = null, bool? fillForward = null, bool? extendedHours = null, bool? isInternalFeed = null, bool? isCustom = null, TickType? tickType = null, bool? isFilteredSubscription = null, DataNormalizationMode? dataNormalizationMode = null) : this( objectType ?? config.Type, symbol ?? config.Symbol, resolution ?? config.Resolution, dataTimeZone ?? config.DataTimeZone, exchangeTimeZone ?? config.ExchangeTimeZone, fillForward ?? config.FillDataForward, extendedHours ?? config.ExtendedMarketHours, isInternalFeed ?? config.IsInternalFeed, isCustom ?? config.IsCustomData, tickType ?? config.TickType, isFilteredSubscription ?? config.IsFilteredSubscription, dataNormalizationMode ?? config.DataNormalizationMode ) { PriceScaleFactor = config.PriceScaleFactor; SumOfDividends = config.SumOfDividends; Consolidators = config.Consolidators; } /// /// Indicates whether the current object is equal to another object of the same type. /// /// /// true if the current object is equal to the parameter; otherwise, false. /// /// An object to compare with this object. public bool Equals(SubscriptionDataConfig other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return _sid.Equals(other._sid) && Type == other.Type && TickType == other.TickType && Resolution == other.Resolution && FillDataForward == other.FillDataForward && ExtendedMarketHours == other.ExtendedMarketHours && IsInternalFeed == other.IsInternalFeed && IsCustomData == other.IsCustomData && DataTimeZone.Equals(other.DataTimeZone) && ExchangeTimeZone.Equals(other.ExchangeTimeZone) && IsFilteredSubscription == other.IsFilteredSubscription; } /// /// Determines whether the specified object is equal to the current object. /// /// /// true if the specified object is equal to the current object; otherwise, false. /// /// The object to compare with the current object. public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((SubscriptionDataConfig) obj); } /// /// Serves as the default hash function. /// /// /// A hash code for the current object. /// public override int GetHashCode() { unchecked { var hashCode = _sid.GetHashCode(); hashCode = (hashCode*397) ^ Type.GetHashCode(); hashCode = (hashCode*397) ^ (int) TickType; hashCode = (hashCode*397) ^ (int) Resolution; hashCode = (hashCode*397) ^ FillDataForward.GetHashCode(); hashCode = (hashCode*397) ^ ExtendedMarketHours.GetHashCode(); hashCode = (hashCode*397) ^ IsInternalFeed.GetHashCode(); hashCode = (hashCode*397) ^ IsCustomData.GetHashCode(); hashCode = (hashCode*397) ^ DataTimeZone.Id.GetHashCode();// timezone hash is expensive, use id instead hashCode = (hashCode*397) ^ ExchangeTimeZone.Id.GetHashCode();// timezone hash is expensive, use id instead hashCode = (hashCode*397) ^ IsFilteredSubscription.GetHashCode(); return hashCode; } } /// /// Override equals operator /// public static bool operator ==(SubscriptionDataConfig left, SubscriptionDataConfig right) { return Equals(left, right); } /// /// Override not equals operator /// public static bool operator !=(SubscriptionDataConfig left, SubscriptionDataConfig right) { return !Equals(left, right); } /// /// Returns a string that represents the current object. /// /// /// A string that represents the current object. /// /// 2 public override string ToString() { return Invariant($"{Symbol.Value},{MappedSymbol},{Resolution},{Type.Name},{TickType},{DataNormalizationMode}{(IsInternalFeed ? ",Internal" : string.Empty)}"); } } }