Lean  $LEAN_TAG$
SecurityChanges.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Linq;
19 using System.Collections.Generic;
20 
22 {
23  /// <summary>
24  /// Defines the additions and subtractions to the algorithm's security subscriptions
25  /// </summary>
26  public class SecurityChanges
27  {
28  /// <summary>
29  /// Gets an instance that represents no changes have been made
30  /// </summary>
31  public static readonly SecurityChanges None = new (Enumerable.Empty<Security>(), Enumerable.Empty<Security>(),
32  Enumerable.Empty<Security>(), Enumerable.Empty<Security>());
33 
34  private readonly IReadOnlySet<Security> _addedSecurities;
35  private readonly IReadOnlySet<Security> _removedSecurities;
36  private readonly IReadOnlySet<Security> _internalAddedSecurities;
37  private readonly IReadOnlySet<Security> _internalRemovedSecurities;
38 
39  /// <summary>
40  /// Gets the total count of added and removed securities
41  /// </summary>
42  public int Count => _addedSecurities.Count + _removedSecurities.Count
43  + _internalAddedSecurities.Count + _internalRemovedSecurities.Count;
44 
45  /// <summary>
46  /// True will filter out custom securities from the
47  /// <see cref="AddedSecurities"/> and <see cref="RemovedSecurities"/> properties
48  /// </summary>
49  /// <remarks>This allows us to filter but also to disable
50  /// the filtering if desired</remarks>
51  public bool FilterCustomSecurities { get; set; }
52 
53  /// <summary>
54  /// True will filter out internal securities from the
55  /// <see cref="AddedSecurities"/> and <see cref="RemovedSecurities"/> properties
56  /// </summary>
57  /// <remarks>This allows us to filter but also to disable
58  /// the filtering if desired</remarks>
59  public bool FilterInternalSecurities { get; set; }
60 
61  /// <summary>
62  /// Gets the symbols that were added by universe selection
63  /// </summary>
64  /// <remarks>Will use <see cref="FilterCustomSecurities"/> value
65  /// to determine if custom securities should be filtered</remarks>
66  /// <remarks>Will use <see cref="FilterInternalSecurities"/> value
67  /// to determine if internal securities should be filtered</remarks>
68  public IReadOnlyList<Security> AddedSecurities => GetFilteredList(_addedSecurities,
69  !FilterInternalSecurities ? _internalAddedSecurities : null);
70 
71  /// <summary>
72  /// Gets the symbols that were removed by universe selection. This list may
73  /// include symbols that were removed, but are still receiving data due to
74  /// existing holdings or open orders
75  /// </summary>
76  /// <remarks>Will use <see cref="FilterCustomSecurities"/> value
77  /// to determine if custom securities should be filtered</remarks>
78  /// <remarks>Will use <see cref="FilterInternalSecurities"/> value
79  /// to determine if internal securities should be filtered</remarks>
80  public IReadOnlyList<Security> RemovedSecurities => GetFilteredList(_removedSecurities,
81  !FilterInternalSecurities ? _internalRemovedSecurities : null);
82 
83  /// <summary>
84  /// Initializes a new instance of the <see cref="SecurityChanges"/> class
85  /// </summary>
86  /// <param name="additions">Added symbols list</param>
87  /// <param name="removals">Removed symbols list</param>
88  /// <param name="internalAdditions">Internal added symbols list</param>
89  /// <param name="internalRemovals">Internal removed symbols list</param>
90  private SecurityChanges(IEnumerable<Security> additions, IEnumerable<Security> removals,
91  IEnumerable<Security> internalAdditions, IEnumerable<Security> internalRemovals)
92  {
93  _addedSecurities = additions.ToHashSet();
94  _removedSecurities = removals.ToHashSet();
95  _internalAddedSecurities = internalAdditions.ToHashSet();
96  _internalRemovedSecurities = internalRemovals.ToHashSet();
97  }
98 
99  /// <summary>
100  /// Initializes a new instance of the <see cref="SecurityChanges"/> class
101  /// as a shallow clone of a given instance, sharing the same collections
102  /// </summary>
103  /// <param name="changes">The instance to clone</param>
105  {
106  _addedSecurities = changes._addedSecurities;
107  _removedSecurities = changes._removedSecurities;
108  _internalAddedSecurities = changes._internalAddedSecurities;
109  _internalRemovedSecurities = changes._internalRemovedSecurities;
110  }
111 
112  /// <summary>
113  /// Combines the results of two <see cref="SecurityChanges"/>
114  /// </summary>
115  /// <param name="left">The left side of the operand</param>
116  /// <param name="right">The right side of the operand</param>
117  /// <returns>Adds the additions together and removes any removals found in the additions, that is, additions take precedence</returns>
119  {
120  // common case is adding something to nothing, shortcut these to prevent linqness
121  if (left == None || left.Count == 0) return right;
122  if (right == None || right.Count == 0) return left;
123 
124  var additions = Merge(left._addedSecurities, right._addedSecurities);
125  var internalAdditions = Merge(left._internalAddedSecurities, right._internalAddedSecurities);
126 
127  var removals = Merge(left._removedSecurities, right._removedSecurities,
128  security => !additions.Contains(security) && !internalAdditions.Contains(security));
129  var internalRemovals = Merge(left._internalRemovedSecurities, right._internalRemovedSecurities,
130  security => !additions.Contains(security) && !internalAdditions.Contains(security));
131 
132  return new SecurityChanges(additions, removals, internalAdditions, internalRemovals);
133  }
134 
135  /// <summary>
136  /// Initializes a new instance of the <see cref="SecurityChanges"/> class all none internal
137  /// </summary>
138  /// <param name="additions">Added symbols list</param>
139  /// <param name="removals">Removed symbols list</param>
140  /// <param name="internalAdditions">Internal added symbols list</param>
141  /// <param name="internalRemovals">Internal removed symbols list</param>
142  /// <remarks>Useful for testing</remarks>
143  public static SecurityChanges Create(IReadOnlyCollection<Security> additions, IReadOnlyCollection<Security> removals,
144  IReadOnlyCollection<Security> internalAdditions, IReadOnlyCollection<Security> internalRemovals)
145  {
146  // return None if there's no changes, otherwise return what we've modified
147  return additions?.Count + removals?.Count + internalAdditions?.Count + internalRemovals?.Count > 0
148  ? new SecurityChanges(additions, removals, internalAdditions, internalRemovals)
149  : None;
150  }
151 
152  #region Overrides of Object
153 
154  /// <summary>
155  /// Returns a string that represents the current object.
156  /// </summary>
157  /// <returns>
158  /// A string that represents the current object.
159  /// </returns>
160  /// <filterpriority>2</filterpriority>
161  public override string ToString()
162  {
163  if (Count == 0)
164  {
165  return "SecurityChanges: None";
166  }
167 
168  var added = string.Empty;
169  if (AddedSecurities.Count != 0)
170  {
171  added = $" Added: {string.Join(",", AddedSecurities.Select(x => x.Symbol.ID))}";
172  }
173  var removed = string.Empty;
174  if (RemovedSecurities.Count != 0)
175  {
176  removed = $" Removed: {string.Join(",", RemovedSecurities.Select(x => x.Symbol.ID))}";
177  }
178 
179  return $"SecurityChanges: {added}{removed}";
180  }
181 
182  #endregion
183 
184  /// <summary>
185  /// Helper method to filter added and removed securities based on current settings
186  /// </summary>
187  private IReadOnlyList<Security> GetFilteredList(IReadOnlySet<Security> source, IReadOnlySet<Security> secondSource = null)
188  {
189  IEnumerable<Security> enumerable = source;
190  if (secondSource != null && secondSource.Count > 0)
191  {
192  enumerable = enumerable.Union(secondSource);
193  }
194  return enumerable.Where(kvp => !FilterCustomSecurities || kvp.Type != SecurityType.Base)
195  .Select(kvp => kvp)
196  .OrderBy(security => security.Symbol.Value)
197  .ToList();
198  }
199 
200  /// <summary>
201  /// Helper method that will merge two security sets, taken into account an optional filter
202  /// </summary>
203  /// <returns>Will return merged set</returns>
204  private static HashSet<Security> Merge(IReadOnlyCollection<Security> left, IReadOnlyCollection<Security> right, Func<Security, bool> filter = null)
205  {
206  // if right is emtpy we just use left
207  IEnumerable<Security> result = left;
208  if (right.Count != 0)
209  {
210  if (left.Count == 0)
211  {
212  // left is emtpy so let's just use right
213  result = right;
214  }
215  else
216  {
217  // merge, both are not empty
218  result = result.Concat(right);
219  }
220  }
221 
222  if (filter != null)
223  {
224  result = result.Where(filter.Invoke);
225  }
226 
227  return new HashSet<Security>(result);
228  }
229  }
230 
231  /// <summary>
232  /// Helper method to create security changes
233  /// </summary>
235  {
236  private readonly List<Security> _internalAdditions = new();
237  private readonly List<Security> _internalRemovals = new();
238  private readonly List<Security> _additions = new();
239  private readonly List<Security> _removals = new();
240 
241  /// <summary>
242  /// Inserts a security addition change
243  /// </summary>
244  public void Add(Security security, bool isInternal)
245  {
246  if (isInternal)
247  {
248  _internalAdditions.Add(security);
249  }
250  else
251  {
252  _additions.Add(security);
253  }
254  }
255 
256  /// <summary>
257  /// Inserts a security removal change
258  /// </summary>
259  public void Remove(Security security, bool isInternal)
260  {
261  if (isInternal)
262  {
263  _internalRemovals.Add(security);
264  }
265  else
266  {
267  _removals.Add(security);
268  }
269  }
270 
271  /// <summary>
272  /// Get the current security changes clearing state
273  /// </summary>
275  {
276  var result = SecurityChanges.Create(_additions, _removals, _internalAdditions, _internalRemovals);
277 
278  _internalAdditions.Clear();
279  _removals.Clear();
280  _internalRemovals.Clear();
281  _additions.Clear();
282 
283  return result;
284  }
285  }
286 }