Lean  $LEAN_TAG$
CompositePositionGroupResolver.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 System.Collections.Generic;
19 
21 {
22  /// <summary>
23  /// Provides an implementation of <see cref="IPositionGroupResolver"/> that invokes multiple wrapped implementations
24  /// in succession. Each successive call to <see cref="IPositionGroupResolver.Resolve"/> will receive
25  /// the remaining positions that have yet to be grouped. Any non-grouped positions are placed into identity groups.
26  /// </summary>
28  {
29  /// <summary>
30  /// Gets the count of registered resolvers
31  /// </summary>
32  public int Count => _resolvers.Count;
33 
34  private readonly List<IPositionGroupResolver> _resolvers;
35 
36  /// <summary>
37  /// Initializes a new instance of the <see cref="CompositePositionGroupResolver"/> class
38  /// </summary>
39  /// <param name="resolvers">The position group resolvers to be invoked in order</param>
41  : this((IEnumerable<IPositionGroupResolver>)resolvers)
42  {
43  }
44 
45  /// <summary>
46  /// Initializes a new instance of the <see cref="CompositePositionGroupResolver"/> class
47  /// </summary>
48  /// <param name="resolvers">The position group resolvers to be invoked in order</param>
49  public CompositePositionGroupResolver(IEnumerable<IPositionGroupResolver> resolvers)
50  {
51  _resolvers = resolvers.ToList();
52  }
53 
54  /// <summary>
55  /// Adds the specified <paramref name="resolver"/> to the end of the list of resolvers. This resolver will run last.
56  /// </summary>
57  /// <param name="resolver">The resolver to be added</param>
58  public void Add(IPositionGroupResolver resolver)
59  {
60  _resolvers.Add(resolver);
61  }
62 
63  /// <summary>
64  /// Inserts the specified <paramref name="resolver"/> into the list of resolvers at the specified index.
65  /// </summary>
66  /// <param name="resolver">The resolver to be inserted</param>
67  /// <param name="index">The zero based index indicating where to insert the resolver, zero inserts to the beginning
68  /// of the list making this resolver un first and <see cref="Count"/> inserts the resolver to the end of the list
69  /// making this resolver run last</param>
70  public void Add(IPositionGroupResolver resolver, int index)
71  {
72  // insert handles bounds checking
73  _resolvers.Insert(index, resolver);
74  }
75 
76  /// <summary>
77  /// Removes the specified <paramref name="resolver"/> from the list of resolvers
78  /// </summary>
79  /// <param name="resolver">The resolver to be removed</param>
80  /// <returns>True if the resolver was removed, false if it wasn't found in the list</returns>
81  public bool Remove(IPositionGroupResolver resolver)
82  {
83  return _resolvers.Remove(resolver);
84  }
85 
86  /// <summary>
87  /// Resolves the optimal set of <see cref="IPositionGroup"/> from the provided <paramref name="positions"/>.
88  /// Implementations are required to deduct grouped positions from the <paramref name="positions"/> collection.
89  /// </summary>
91  {
92  // we start with no groups, each resolver's result will get merged in
93  var groups = PositionGroupCollection.Empty;
94 
95  // each call to ResolvePositionGroups is expected to deduct grouped positions from the PositionCollection
96  foreach (var resolver in _resolvers)
97  {
98  var resolved = resolver.Resolve(positions);
99  groups = groups.CombineWith(resolved);
100  }
101 
102  if (positions.Count > 0)
103  {
104  throw new InvalidOperationException("All positions must be resolved into groups.");
105  }
106 
107  return groups;
108  }
109 
110  /// <summary>
111  /// Attempts to group the specified positions into a new <see cref="IPositionGroup"/> using an
112  /// appropriate <see cref="IPositionGroupBuyingPowerModel"/> for position groups created via this
113  /// resolver.
114  /// </summary>
115  /// <param name="newPositions">The positions to be grouped</param>
116  /// <param name="currentPositions">The currently grouped positions</param>
117  /// <param name="group">The grouped positions when this resolver is able to, otherwise null</param>
118  /// <returns>True if this resolver can group the specified positions, otherwise false</returns>
119  public bool TryGroup(IReadOnlyCollection<IPosition> newPositions, PositionGroupCollection currentPositions, out IPositionGroup group)
120  {
121  foreach (var resolver in _resolvers)
122  {
123  if (resolver.TryGroup(newPositions, currentPositions, out group))
124  {
125  return true;
126  }
127  }
128 
129  group = null;
130  return false;
131  }
132 
133  /// <summary>
134  /// Determines the position groups that would be evaluated for grouping of the specified
135  /// positions were passed into the <see cref="Resolve"/> method.
136  /// </summary>
137  /// <remarks>
138  /// This function allows us to determine a set of impacted groups and run the resolver on just
139  /// those groups in order to support what-if analysis
140  /// </remarks>
141  /// <param name="groups">The existing position groups</param>
142  /// <param name="positions">The positions being changed</param>
143  /// <returns>An enumerable containing the position groups that could be impacted by the specified position changes</returns>
144  public IEnumerable<IPositionGroup> GetImpactedGroups(PositionGroupCollection groups, IReadOnlyCollection<IPosition> positions)
145  {
146  // we keep track of yielded groups for all resolvers
147  var seen = new HashSet<PositionGroupKey>();
148  foreach (var resolver in _resolvers)
149  {
150  foreach (var group in resolver.GetImpactedGroups(groups, positions))
151  {
152  if (seen.Add(group.Key))
153  {
154  yield return group;
155  }
156  }
157  }
158  }
159  }
160 }