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