Lean  $LEAN_TAG$
SecurityPositionGroupModel.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;
18 using QuantConnect.Orders;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
21 
23 {
24  /// <summary>
25  /// Responsible for managing the resolution of position groups for an algorithm
26  /// </summary>
28  {
29  /// <summary>
30  /// Gets an implementation of <see cref="SecurityPositionGroupModel"/> that will not group multiple securities
31  /// </summary>
33 
34  private bool _requiresGroupResolution;
35 
36  private SecurityManager _securities;
37  private PositionGroupCollection _groups;
38  private IPositionGroupResolver _resolver;
39 
40  /// <summary>
41  /// Get's the single security position group buying power model to use
42  /// </summary>
44 
45 
46  /// <summary>
47  /// Gets the set of currently resolved position groups
48  /// </summary>
50  {
51  get
52  {
54  return _groups;
55  }
56  private set
57  {
58  _groups = value;
59  }
60  }
61 
62  /// <summary>
63  /// Gets whether or not the algorithm is using only default position groups
64  /// </summary>
66 
67  /// <summary>
68  /// Initializes a new instance of the <see cref="SecurityPositionGroupModel"/> class
69  /// </summary>
70  /// <param name="securities">The algorithm's security manager</param>
71  public virtual void Initialize(SecurityManager securities)
72  {
73  _securities = securities;
75  _resolver = GetPositionGroupResolver();
76 
77  foreach (var security in _securities.Values)
78  {
79  // if any security already present let's wire the holdings change event
80  security.Holdings.QuantityChanged += HoldingsOnQuantityChanged;
81  }
82 
83  // we must be notified each time our holdings change, so each time a security is added, we
84  // want to bind to its SecurityHolding.QuantityChanged event so we can trigger the resolver
85 
86  securities.CollectionChanged += (sender, args) =>
87  {
88  var items = args.NewItems ?? new List<object>();
89  if (args.OldItems != null)
90  {
91  foreach (var item in args.OldItems)
92  {
93  items.Add(item);
94  }
95  }
96 
97  foreach (Security security in items)
98  {
99  if (args.Action == NotifyCollectionChangedAction.Add)
100  {
101  security.Holdings.QuantityChanged += HoldingsOnQuantityChanged;
102  if (security.Invested)
103  {
104  // if this security has holdings then we'll need to resolve position groups
105  _requiresGroupResolution = true;
106  }
107  }
108  else if (args.Action == NotifyCollectionChangedAction.Remove)
109  {
110  security.Holdings.QuantityChanged -= HoldingsOnQuantityChanged;
111  if (security.Invested)
112  {
113  // only trigger group resolution if we had holdings in the removed security
114  _requiresGroupResolution = true;
115  }
116  }
117  }
118  };
119  }
120 
121  /// <summary>
122  /// Gets the <see cref="IPositionGroup"/> matching the specified <paramref name="key"/>. If one is not found,
123  /// then a new empty position group is returned.
124  /// </summary>
125  public IPositionGroup this[PositionGroupKey key] => Groups[key];
126 
127  /// <summary>
128  /// Creates a position group for the specified order, pulling
129  /// </summary>
130  /// <param name="orders">The order</param>
131  /// <param name="group">The resulting position group</param>
132  /// <returns>A new position group matching the provided order</returns>
133  public bool TryCreatePositionGroup(List<Order> orders, out IPositionGroup group)
134  {
135  var newPositions = orders.Select(order => order.CreatePositions(_securities)).SelectMany(x => x).ToList();
136 
137  // We send new and current positions to try resolve any strategy being executed by multiple orders
138  // else the PositionGroup we will get out here will just be the default in those cases
139  if (!_resolver.TryGroup(newPositions, Groups, out group))
140  {
141  return false;
142  }
143 
144  return true;
145  }
146 
147  /// <summary>
148  /// Resolves position groups using the specified collection of positions
149  /// </summary>
150  /// <param name="positions">The positions to be grouped</param>
151  /// <returns>A collection of position groups containing all of the provided positions</returns>
153  {
154  return _resolver.Resolve(positions);
155  }
156 
157  /// <summary>
158  /// Determines which position groups could be impacted by changes in the specified positions
159  /// </summary>
160  /// <param name="positions">The positions to be changed</param>
161  /// <returns>All position groups that need to be re-evaluated due to changes in the positions</returns>
162  public IEnumerable<IPositionGroup> GetImpactedGroups(IReadOnlyCollection<IPosition> positions)
163  {
164  return _resolver.GetImpactedGroups(Groups, positions);
165  }
166 
167  /// <summary>
168  /// Creates a <see cref="PositionGroupKey"/> for the security's default position group
169  /// </summary>
171  {
172  return new PositionGroupKey(PositionGroupBuyingPowerModel, security);
173  }
174 
175  /// <summary>
176  /// Gets or creates the default position group for the specified <paramref name="security"/>
177  /// </summary>
178  /// <remarks>
179  /// TODO: position group used here is the default, is this what callers want?
180  /// </remarks>
182  {
183  var key = CreateDefaultKey(security);
184  return Groups[key];
185  }
186 
187  /// <summary>
188  /// Get the position group resolver instance to use
189  /// </summary>
190  /// <returns>The position group resolver instance</returns>
192  {
194  }
195 
196  private void HoldingsOnQuantityChanged(object sender, SecurityHoldingQuantityChangedEventArgs e)
197  {
198  _requiresGroupResolution = true;
199  }
200 
201  /// <summary>
202  /// Resolves the algorithm's position groups from all of its holdings
203  /// </summary>
204  private void ResolvePositionGroups()
205  {
206  if (_requiresGroupResolution)
207  {
208  _requiresGroupResolution = false;
209  // TODO : Replace w/ special IPosition impl to always equal security.Quantity and we'll
210  // use them explicitly for resolution collection so we don't do this each time
211  var investedPositions = _securities.Where(kvp => kvp.Value.Invested).Select(kvp => (IPosition)new Position(kvp.Value));
212  var positionsCollection = new PositionCollection(investedPositions);
213  Groups = ResolvePositionGroups(positionsCollection);
214  }
215  }
216  }
217 }