/*
* 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;
}
}
}
}