Lean  $LEAN_TAG$
PendingRemovalsManager.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 
17 using System.Collections.Generic;
18 using System.Linq;
21 
23 {
24  /// <summary>
25  /// Helper class used to managed pending security removals <see cref="UniverseSelection"/>
26  /// </summary>
28  {
29  private readonly Dictionary<Universe, List<Security>> _pendingRemovals;
30  private readonly IOrderProvider _orderProvider;
31 
32  /// <summary>
33  /// Current pending removals
34  /// </summary>
35  public IReadOnlyDictionary<Universe, List<Security>> PendingRemovals => _pendingRemovals;
36 
37  /// <summary>
38  /// Create a new instance
39  /// </summary>
40  /// <param name="orderProvider">The order provider used to determine if it is safe to remove a security</param>
41  public PendingRemovalsManager(IOrderProvider orderProvider)
42  {
43  _orderProvider = orderProvider;
44  _pendingRemovals = new Dictionary<Universe, List<Security>>();
45  }
46 
47  /// <summary>
48  /// Determines if we can safely remove the security member from a universe.
49  /// We must ensure that we have zero holdings, no open orders, and no existing portfolio targets
50  /// </summary>
51  private bool IsSafeToRemove(Security member, Universe universe)
52  {
53  // but don't physically remove it from the algorithm if we hold stock or have open orders against it or an open target
54  var openOrders = _orderProvider.GetOpenOrders(x => x.Symbol == member.Symbol);
55  if (!member.HoldStock && !openOrders.Any() && (member.Holdings.Target == null || member.Holdings.Target.Quantity == 0))
56  {
57  if (universe.Securities.Any(pair =>
58  pair.Key.Underlying == member.Symbol && !IsSafeToRemove(pair.Value.Security, universe)))
59  {
60  // don't remove if any member in the universe which uses this 'member' as underlying can't be removed
61  // covers the options use case
62  return false;
63  }
64 
65  // don't remove if there are unsettled positions
66  var unsettledCash = member.SettlementModel.GetUnsettledCash();
67  if (unsettledCash != default && unsettledCash.Amount > 0)
68  {
69  return false;
70  }
71 
72  return true;
73  }
74 
75  return false;
76  }
77 
78  /// <summary>
79  /// Will determine if the <see cref="Security"/> can be removed.
80  /// If it can be removed will add it to <see cref="PendingRemovals"/>
81  /// </summary>
82  /// <param name="member">The security to remove</param>
83  /// <param name="universe">The universe which the security is a member of</param>
84  /// <returns>The member to remove</returns>
85  public List<RemovedMember> TryRemoveMember(Security member, Universe universe)
86  {
87  if (IsSafeToRemove(member, universe))
88  {
89  return new List<RemovedMember> {new RemovedMember(universe, member)};
90  }
91 
92  if (_pendingRemovals.ContainsKey(universe))
93  {
94  if (!_pendingRemovals[universe].Contains(member))
95  {
96  _pendingRemovals[universe].Add(member);
97  }
98  }
99  else
100  {
101  _pendingRemovals.Add(universe, new List<Security> { member });
102  }
103 
104  return null;
105  }
106 
107  /// <summary>
108  /// Will check pending security removals
109  /// </summary>
110  /// <param name="selectedSymbols">Currently selected symbols</param>
111  /// <param name="currentUniverse">Current universe</param>
112  /// <returns>The members to be removed</returns>
113  public List<RemovedMember> CheckPendingRemovals(
114  HashSet<Symbol> selectedSymbols,
115  Universe currentUniverse)
116  {
117  var result = new List<RemovedMember>();
118  // remove previously deselected members which were kept in the universe because of holdings or open orders
119  foreach (var kvp in _pendingRemovals.ToList())
120  {
121  var universeRemoving = kvp.Key;
122  foreach (var security in kvp.Value.ToList())
123  {
124  var isSafeToRemove = IsSafeToRemove(security, universeRemoving);
125  if (isSafeToRemove
126  ||
127  // if we are re selecting it we remove it as a pending removal
128  // else we might remove it when we do not want to do so
129  universeRemoving == currentUniverse
130  && selectedSymbols.Contains(security.Symbol))
131  {
132  if (isSafeToRemove)
133  {
134  result.Add(new RemovedMember(universeRemoving, security));
135  }
136 
137  _pendingRemovals[universeRemoving].Remove(security);
138 
139  // if there are no more pending removals for this universe lets remove it
140  if (!_pendingRemovals[universeRemoving].Any())
141  {
142  _pendingRemovals.Remove(universeRemoving);
143  }
144  }
145  }
146  }
147 
148  return result;
149  }
150 
151  /// <summary>
152  /// Helper class used to report removed universe members
153  /// </summary>
154  public class RemovedMember
155  {
156  /// <summary>
157  /// Universe the security was removed from
158  /// </summary>
159  public Universe Universe { get; }
160 
161  /// <summary>
162  /// Security that is removed
163  /// </summary>
164  public Security Security { get; }
165 
166  /// <summary>
167  /// Initialize a new instance of <see cref="RemovedMember"/>
168  /// </summary>
169  /// <param name="universe"><see cref="Universe"/> the security was removed from</param>
170  /// <param name="security"><see cref="Security"/> that is removed</param>
171  public RemovedMember(Universe universe, Security security)
172  {
173  Universe = universe;
174  Security = security;
175  }
176  }
177  }
178 }