/* * 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.Linq; using QuantConnect.Securities; using System.Collections.Generic; namespace QuantConnect.Data.UniverseSelection { /// /// Defines the additions and subtractions to the algorithm's security subscriptions /// public class SecurityChanges { /// /// Gets an instance that represents no changes have been made /// public static readonly SecurityChanges None = new (Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()); private readonly IReadOnlySet _addedSecurities; private readonly IReadOnlySet _removedSecurities; private readonly IReadOnlySet _internalAddedSecurities; private readonly IReadOnlySet _internalRemovedSecurities; /// /// Gets the total count of added and removed securities /// public int Count => _addedSecurities.Count + _removedSecurities.Count + _internalAddedSecurities.Count + _internalRemovedSecurities.Count; /// /// True will filter out custom securities from the /// and properties /// /// This allows us to filter but also to disable /// the filtering if desired public bool FilterCustomSecurities { get; set; } /// /// True will filter out internal securities from the /// and properties /// /// This allows us to filter but also to disable /// the filtering if desired public bool FilterInternalSecurities { get; set; } /// /// Gets the symbols that were added by universe selection /// /// Will use value /// to determine if custom securities should be filtered /// Will use value /// to determine if internal securities should be filtered public IReadOnlyList AddedSecurities => GetFilteredList(_addedSecurities, !FilterInternalSecurities ? _internalAddedSecurities : null); /// /// Gets the symbols that were removed by universe selection. This list may /// include symbols that were removed, but are still receiving data due to /// existing holdings or open orders /// /// Will use value /// to determine if custom securities should be filtered /// Will use value /// to determine if internal securities should be filtered public IReadOnlyList RemovedSecurities => GetFilteredList(_removedSecurities, !FilterInternalSecurities ? _internalRemovedSecurities : null); /// /// Initializes a new instance of the class /// /// Added symbols list /// Removed symbols list /// Internal added symbols list /// Internal removed symbols list private SecurityChanges(IEnumerable additions, IEnumerable removals, IEnumerable internalAdditions, IEnumerable internalRemovals) { _addedSecurities = additions.ToHashSet(); _removedSecurities = removals.ToHashSet(); _internalAddedSecurities = internalAdditions.ToHashSet(); _internalRemovedSecurities = internalRemovals.ToHashSet(); } /// /// Initializes a new instance of the class /// as a shallow clone of a given instance, sharing the same collections /// /// The instance to clone public SecurityChanges(SecurityChanges changes) { _addedSecurities = changes._addedSecurities; _removedSecurities = changes._removedSecurities; _internalAddedSecurities = changes._internalAddedSecurities; _internalRemovedSecurities = changes._internalRemovedSecurities; } /// /// Combines the results of two /// /// The left side of the operand /// The right side of the operand /// Adds the additions together and removes any removals found in the additions, that is, additions take precedence public static SecurityChanges operator +(SecurityChanges left, SecurityChanges right) { // common case is adding something to nothing, shortcut these to prevent linqness if (left == None || left.Count == 0) return right; if (right == None || right.Count == 0) return left; var additions = Merge(left._addedSecurities, right._addedSecurities); var internalAdditions = Merge(left._internalAddedSecurities, right._internalAddedSecurities); var removals = Merge(left._removedSecurities, right._removedSecurities, security => !additions.Contains(security) && !internalAdditions.Contains(security)); var internalRemovals = Merge(left._internalRemovedSecurities, right._internalRemovedSecurities, security => !additions.Contains(security) && !internalAdditions.Contains(security)); return new SecurityChanges(additions, removals, internalAdditions, internalRemovals); } /// /// Initializes a new instance of the class all none internal /// /// Added symbols list /// Removed symbols list /// Internal added symbols list /// Internal removed symbols list /// Useful for testing public static SecurityChanges Create(IReadOnlyCollection additions, IReadOnlyCollection removals, IReadOnlyCollection internalAdditions, IReadOnlyCollection internalRemovals) { // return None if there's no changes, otherwise return what we've modified return additions?.Count + removals?.Count + internalAdditions?.Count + internalRemovals?.Count > 0 ? new SecurityChanges(additions, removals, internalAdditions, internalRemovals) : None; } #region Overrides of Object /// /// Returns a string that represents the current object. /// /// /// A string that represents the current object. /// /// 2 public override string ToString() { if (Count == 0) { return "SecurityChanges: None"; } var added = string.Empty; if (AddedSecurities.Count != 0) { added = $" Added: {string.Join(",", AddedSecurities.Select(x => x.Symbol.ID))}"; } var removed = string.Empty; if (RemovedSecurities.Count != 0) { removed = $" Removed: {string.Join(",", RemovedSecurities.Select(x => x.Symbol.ID))}"; } return $"SecurityChanges: {added}{removed}"; } #endregion /// /// Helper method to filter added and removed securities based on current settings /// private IReadOnlyList GetFilteredList(IReadOnlySet source, IReadOnlySet secondSource = null) { IEnumerable enumerable = source; if (secondSource != null && secondSource.Count > 0) { enumerable = enumerable.Union(secondSource); } return enumerable.Where(kvp => !FilterCustomSecurities || kvp.Type != SecurityType.Base) .Select(kvp => kvp) .OrderBy(security => security.Symbol.Value) .ToList(); } /// /// Helper method that will merge two security sets, taken into account an optional filter /// /// Will return merged set private static HashSet Merge(IReadOnlyCollection left, IReadOnlyCollection right, Func filter = null) { // if right is emtpy we just use left IEnumerable result = left; if (right.Count != 0) { if (left.Count == 0) { // left is emtpy so let's just use right result = right; } else { // merge, both are not empty result = result.Concat(right); } } if (filter != null) { result = result.Where(filter.Invoke); } return new HashSet(result); } } /// /// Helper method to create security changes /// public class SecurityChangesConstructor { private readonly List _internalAdditions = new(); private readonly List _internalRemovals = new(); private readonly List _additions = new(); private readonly List _removals = new(); /// /// Inserts a security addition change /// public void Add(Security security, bool isInternal) { if (isInternal) { _internalAdditions.Add(security); } else { _additions.Add(security); } } /// /// Inserts a security removal change /// public void Remove(Security security, bool isInternal) { if (isInternal) { _internalRemovals.Add(security); } else { _removals.Add(security); } } /// /// Get the current security changes clearing state /// public SecurityChanges Flush() { var result = SecurityChanges.Create(_additions, _removals, _internalAdditions, _internalRemovals); _internalAdditions.Clear(); _removals.Clear(); _internalRemovals.Clear(); _additions.Clear(); return result; } } }