/* * 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 QuantConnect.Util; using QuantConnect.Securities; using System.Collections.Generic; using QuantConnect.Data.Consolidators; using static QuantConnect.StringExtensions; namespace QuantConnect.Data { /// /// Subscription data required including the type of data. /// public class SubscriptionDataConfig : IEquatable { private readonly bool _mappedConfig; private readonly SecurityIdentifier _sid; /// /// Event fired when there is a new symbol due to mapping /// public event EventHandler NewSymbol; /// /// Type of data /// public Type Type { get; } /// /// Security type of this data subscription /// public SecurityType SecurityType => Symbol.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 TickType TickType { get; } /// /// Resolution of the asset we're requesting, second minute or tick /// public Resolution Resolution { get; } /// /// Timespan increment between triggers of this data: /// public TimeSpan Increment { get; } /// /// True if wish to send old data when time gaps in data feed. /// public bool FillDataForward { get; } /// /// Boolean Send Data from between 4am - 8am (Equities Setting Only) /// public bool ExtendedMarketHours { get; } /// /// True if this subscription was added for the sole purpose of providing currency conversion rates via /// public bool IsInternalFeed { get; } /// /// True if this subscription is for custom user data, false for QC data /// public bool IsCustomData { get; } /// /// The sum of dividends accrued in this subscription, used for scaling total return prices /// public decimal SumOfDividends{ get; set; } /// /// Gets the normalization mode used for this subscription /// public DataNormalizationMode DataNormalizationMode { get; set; } /// /// Gets the securities mapping mode used for this subscription /// /// This is particular useful when generating continuous futures public DataMappingMode DataMappingMode { get; } /// /// The continuous contract desired offset from the current front month. /// For example, 0 (default) will use the front month, 1 will use the back month contract /// public uint ContractDepthOffset { get; } /// /// Price Scaling Factor: /// public decimal PriceScaleFactor { get; set; } /// /// Symbol Mapping: When symbols change over time (e.g. CHASE-> JPM) need to update the symbol requested. /// public string MappedSymbol { get { if (Symbol.HasUnderlying) { if (SecurityType == SecurityType.Future) { return Symbol.Underlying.ID.ToString(); } if (SecurityType.IsOption()) { return Symbol.Underlying.Value; } } return Symbol.Value; } set { var oldMappedValue = MappedSymbol; if(ContractDepthOffset == 0 && oldMappedValue == value) { // Do less if we can. // We can only do this for sure if 'ContractDepthOffset' is 0 else the value we got might be outdated and will change bellow return; } var oldSymbol = Symbol; Symbol = Symbol.UpdateMappedSymbol(value, ContractDepthOffset); if (MappedSymbol != oldMappedValue) { NewSymbol?.Invoke(this, new NewSymbolEventArgs(Symbol, oldSymbol)); } } } /// /// Gets the market / scope of the symbol /// public string Market => Symbol.ID.Market; /// /// Gets the data time zone for this subscription /// public DateTimeZone DataTimeZone { get; } /// /// Gets the exchange time zone for this subscription /// public DateTimeZone ExchangeTimeZone { get; } /// /// Consolidators that are registred with this subscription /// public ISet Consolidators { get; } /// /// Gets whether or not this subscription should have filters applied to it (market hours/user filters from security) /// public bool IsFilteredSubscription { get; } /// /// 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 /// The contract mapping mode to use for the security /// The continuous contract desired offset from the current front month. /// For example, 0 (default) will use the front month, 1 will use the back month contract 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, DataMappingMode dataMappingMode = DataMappingMode.OpenInterest, uint contractDepthOffset = 0, bool mappedConfig = false) { 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; Resolution = resolution; _sid = symbol.ID; Symbol = symbol; ExtendedMarketHours = extendedHours && LeanData.SupportsExtendedMarketHours(Type); PriceScaleFactor = 1; IsInternalFeed = isInternalFeed; IsCustomData = isCustom; DataTimeZone = dataTimeZone; _mappedConfig = mappedConfig; DataMappingMode = dataMappingMode; ExchangeTimeZone = exchangeTimeZone; ContractDepthOffset = contractDepthOffset; IsFilteredSubscription = isFilteredSubscription; Consolidators = new ConcurrentSet(); DataNormalizationMode = dataNormalizationMode; TickType = tickType ?? LeanData.GetCommonTickTypeForCommonDataTypes(objectType, SecurityType); Increment = resolution.ToTimeSpan(); //Ticks are individual sales and fillforward doesn't apply. FillDataForward = resolution == Resolution.Tick ? false : fillForward; } /// /// 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 /// The contract mapping mode to use for the security /// The continuous contract desired offset from the current front month. /// For example, 0 (default) will use the front month, 1 will use the back month contract /// True if this is created as a mapped config. This is useful for continuous contract at live trading /// where we subscribe to the mapped symbol but want to preserve uniqueness 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, DataMappingMode? dataMappingMode = null, uint? contractDepthOffset = null, bool? mappedConfig = 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, dataMappingMode ?? config.DataMappingMode, contractDepthOffset ?? config.ContractDepthOffset, mappedConfig ?? false ) { 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) && DataMappingMode == other.DataMappingMode && ExchangeTimeZone.Equals(other.ExchangeTimeZone) && ContractDepthOffset == other.ContractDepthOffset && IsFilteredSubscription == other.IsFilteredSubscription && _mappedConfig == other._mappedConfig; } /// /// 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) ^ DataMappingMode.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) ^ ContractDepthOffset.GetHashCode(); hashCode = (hashCode*397) ^ IsFilteredSubscription.GetHashCode(); hashCode = (hashCode*397) ^ _mappedConfig.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 ToString(Symbol.Value); } /// /// Returns a string that represents the current object. /// /// Symbol to use in the string representation of the object /// /// A string that represents the current object. public string ToString(string symbol) { return Invariant($"{symbol},#{ContractDepthOffset},{MappedSymbol},{Resolution},{Type.Name},{TickType},{DataNormalizationMode},{DataMappingMode}{(IsInternalFeed ? ",Internal" : string.Empty)}"); } /// /// New base class for all event classes. /// public class NewSymbolEventArgs : EventArgs { /// /// The old symbol instance /// public Symbol Old { get; } /// /// The new symbol instance /// public Symbol New { get; } /// /// Create an instance of NewSymbolEventArgs /// /// /// public NewSymbolEventArgs(Symbol @new, Symbol old) { New = @new; Old = old; } } } }