/* * 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; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using QuantConnect.Interfaces; using QuantConnect.Securities; using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { /// /// Provides a base class for all universes to derive from. /// public abstract class Universe : IDisposable { /// /// Used to round the members time in universe , this is /// done because we can not guarantee exact selection time in live mode, see GH issue 3287 /// private TimeSpan? _minimumTimeInUniverseRoundingInterval; /// /// Gets a value indicating that no change to the universe should be made /// public static readonly UnchangedUniverse Unchanged = UnchangedUniverse.Instance; private HashSet _previousSelections; /// /// Gets the internal security collection used to define membership in this universe /// public virtual ConcurrentDictionary Securities { get; private set; } /// /// The currently selected symbol set /// /// This set might be different than which might hold members that are no longer selected /// but have not been removed yet, this can be because they have some open position, orders, haven't completed the minimum time in universe public HashSet Selected { get; set; } /// /// True if this universe filter can run async in the data stack /// public virtual bool Asynchronous { get { if (UniverseSettings.Asynchronous.HasValue) { return UniverseSettings.Asynchronous.Value; } return false; } set { UniverseSettings.Asynchronous = value; } } /// /// Event fired when the universe selection has changed /// public event EventHandler SelectionChanged; /// /// Gets the security type of this universe /// public SecurityType SecurityType => Configuration.SecurityType; /// /// Gets the market of this universe /// public string Market => Configuration.Market; /// /// Gets the symbol of this universe /// public Symbol Symbol => Configuration.Symbol; /// /// Gets the data type of this universe /// public Type DataType => Configuration.Type; /// /// Flag indicating if disposal of this universe has been requested /// public virtual bool DisposeRequested { get; protected set; } /// /// Gets the settings used for subscriptions added for this universe /// public virtual UniverseSettings UniverseSettings { get; set; } /// /// Gets the configuration used to get universe data /// public virtual SubscriptionDataConfig Configuration { get; private set; } /// /// Gets the current listing of members in this universe. Modifications /// to this dictionary do not change universe membership. /// public Dictionary Members { get { return Securities.Select(x => x.Value.Security).ToDictionary(x => x.Symbol); } } /// /// Initializes a new instance of the class /// /// The configuration used to source data for this universe protected Universe(SubscriptionDataConfig config) { _previousSelections = new HashSet(); Securities = new ConcurrentDictionary(); Configuration = config; } /// /// Determines whether or not the specified security can be removed from /// this universe. This is useful to prevent securities from being taken /// out of a universe before the algorithm has had enough time to make /// decisions on the security /// /// The current utc time /// The security to check if its ok to remove /// True if we can remove the security, false otherwise public virtual bool CanRemoveMember(DateTime utcTime, Security security) { // can always remove securities after dispose requested if (DisposeRequested) { return true; } // can always remove delisted securities from the universe if (security.IsDelisted) { return true; } Member member; if (Securities.TryGetValue(security.Symbol, out member)) { if (_minimumTimeInUniverseRoundingInterval == null) { // lets set _minimumTimeInUniverseRoundingInterval once _minimumTimeInUniverseRoundingInterval = UniverseSettings.MinimumTimeInUniverse; AdjustMinimumTimeInUniverseRoundingInterval(); } var timeInUniverse = utcTime - member.Added; if (timeInUniverse.Round(_minimumTimeInUniverseRoundingInterval.Value) >= UniverseSettings.MinimumTimeInUniverse) { return true; } } return false; } /// /// Performs universe selection using the data specified /// /// The current utc time /// The symbols to remain in the universe /// The data that passes the filter public IEnumerable PerformSelection(DateTime utcTime, BaseDataCollection data) { // select empty set of symbols after dispose requested if (DisposeRequested) { OnSelectionChanged(); return Enumerable.Empty(); } var selections = data.FilteredContracts; if (selections == null) { // only trigger selection if it hasn't already been run var result = SelectSymbols(utcTime, data); if (ReferenceEquals(result, Unchanged)) { data.FilteredContracts = _previousSelections; return Unchanged; } selections = result.ToHashSet(); data.FilteredContracts = selections; } var hasDiffs = _previousSelections.AreDifferent(selections); _previousSelections = selections; if (!hasDiffs) { return Unchanged; } OnSelectionChanged(selections); return selections; } /// /// Performs universe selection using the data specified /// /// The current utc time /// The symbols to remain in the universe /// The data that passes the filter public abstract IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data); /// /// Creates and configures a security for the specified symbol /// /// The symbol of the security to be created /// The algorithm instance /// The market hours database /// The symbol properties database /// The newly initialized security object /// The CreateSecurity won't be called [Obsolete("CreateSecurity is obsolete and will not be called. The system will create the required Securities based on selected symbols")] public virtual Security CreateSecurity(Symbol symbol, IAlgorithm algorithm, MarketHoursDatabase marketHoursDatabase, SymbolPropertiesDatabase symbolPropertiesDatabase) { throw new InvalidOperationException("CreateSecurity is obsolete and should not be called." + "The system will create the required Securities based on selected symbols"); } /// /// Gets the subscription requests to be added for the specified security /// /// The security to get subscriptions for /// The current time in utc. This is the frontier time of the algorithm /// The max end time /// All subscriptions required by this security [Obsolete("This overload is obsolete and will not be called. It was not capable of creating new SubscriptionDataConfig due to lack of information")] public virtual IEnumerable GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc) { throw new InvalidOperationException("This overload is obsolete and should not be called." + "It was not capable of creating new SubscriptionDataConfig due to lack of information"); } /// /// Gets the subscription requests to be added for the specified security /// /// The security to get subscriptions for /// The current time in utc. This is the frontier time of the algorithm /// The max end time /// Instance which implements interface /// All subscriptions required by this security public virtual IEnumerable GetSubscriptionRequests(Security security, DateTime currentTimeUtc, DateTime maximumEndTimeUtc, ISubscriptionDataConfigService subscriptionService) { var result = subscriptionService.Add(security.Symbol, UniverseSettings.Resolution, UniverseSettings.FillForward, UniverseSettings.ExtendedMarketHours, dataNormalizationMode: UniverseSettings.DataNormalizationMode, subscriptionDataTypes: UniverseSettings.SubscriptionDataTypes, dataMappingMode: UniverseSettings.DataMappingMode, contractDepthOffset: (uint)Math.Abs(UniverseSettings.ContractDepthOffset)); return result.Select(config => new SubscriptionRequest(isUniverseSubscription: false, universe: this, security: security, configuration: config, startTimeUtc: currentTimeUtc, endTimeUtc: maximumEndTimeUtc)); } /// /// Determines whether or not the specified symbol is currently a member of this universe /// /// The symbol whose membership is to be checked /// True if the specified symbol is part of this universe, false otherwise public bool ContainsMember(Symbol symbol) { return Securities.ContainsKey(symbol); } /// /// Adds the specified security to this universe /// /// The current utc date time /// The security to be added /// True if internal member /// True if the security was successfully added, /// false if the security was already in the universe internal virtual bool AddMember(DateTime utcTime, Security security, bool isInternal) { // never add members to disposed universes if (DisposeRequested) { return false; } if (security.IsDelisted) { return false; } return Securities.TryAdd(security.Symbol, new Member(utcTime, security, isInternal)); } /// /// Tries to remove the specified security from the universe. This /// will first check to verify that we can remove the security by /// calling the function. /// /// The current utc time /// The security to be removed /// True if the security was successfully removed, false if /// we're not allowed to remove or if the security didn't exist internal virtual bool RemoveMember(DateTime utcTime, Security security) { if (CanRemoveMember(utcTime, security)) { Member member; return Securities.TryRemove(security.Symbol, out member); } return false; } /// /// Marks this universe as disposed and ready to remove all child subscriptions /// public virtual void Dispose() { DisposeRequested = true; } /// /// Event invocator for the event /// /// The current universe selection protected void OnSelectionChanged(HashSet selection = null) { SelectionChanged?.Invoke(this, new SelectionEventArgs(selection ?? new HashSet())); } /// /// Provides a value to indicate that no changes should be made to the universe. /// This value is intended to be returned by reference via /// public sealed class UnchangedUniverse : IEnumerable, IEnumerable { /// /// Read-only instance of the value /// public static readonly UnchangedUniverse Instance = new UnchangedUniverse(); private UnchangedUniverse() { } IEnumerator IEnumerable.GetEnumerator() { yield break; } IEnumerator IEnumerable.GetEnumerator() { yield break; } IEnumerator IEnumerable.GetEnumerator() { yield break; } } /// /// Will adjust the /// so rounding is performed as expected /// private void AdjustMinimumTimeInUniverseRoundingInterval() { if (_minimumTimeInUniverseRoundingInterval >= Time.OneDay) { _minimumTimeInUniverseRoundingInterval = Time.OneDay; } else if (_minimumTimeInUniverseRoundingInterval >= Time.OneHour) { _minimumTimeInUniverseRoundingInterval = Time.OneHour; } else if (_minimumTimeInUniverseRoundingInterval >= Time.OneMinute) { _minimumTimeInUniverseRoundingInterval = Time.OneMinute; } else if (_minimumTimeInUniverseRoundingInterval >= Time.OneSecond) { _minimumTimeInUniverseRoundingInterval = Time.OneSecond; } } /// /// Member of the Universe /// public sealed class Member { /// /// DateTime when added /// public DateTime Added { get; init; } /// /// The security that was added /// public Security Security { get; init; } /// /// True if the security was added as internal by this universe /// public bool IsInternal { get; init; } /// /// Initialize a new member for the universe /// /// DateTime added /// Security to add /// True if internal member public Member(DateTime added, Security security, bool isInternal) { Added = added; Security = security; IsInternal = isInternal; } } /// /// Event fired when the universe selection changes /// public class SelectionEventArgs : EventArgs { /// /// The current universe selection /// public HashSet CurrentSelection { get; } /// /// Creates a new instance /// public SelectionEventArgs(HashSet currentSelection) { CurrentSelection = currentSelection; } } } }