Lean  $LEAN_TAG$
PositionGroupExtensions.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.Collections.Generic;
18 using System.Linq;
19 
20 using QuantConnect.Util;
21 
23 {
24  /// <summary>
25  /// Provides extension methods for <see cref="IPositionGroup"/>
26  /// </summary>
27  public static class PositionGroupExtensions
28  {
29  /// <summary>
30  /// Gets the position in the <paramref name="group"/> matching the provided <param name="symbol"></param>
31  /// </summary>
32  public static IPosition GetPosition(this IPositionGroup group, Symbol symbol)
33  {
34  IPosition position;
35  if (!group.TryGetPosition(symbol, out position))
36  {
37  throw new KeyNotFoundException($"No position with symbol '{symbol}' exists in the group: {group}");
38  }
39 
40  return position;
41  }
42 
43  /// <summary>
44  /// Creates a new <see cref="IPositionGroup"/> with the specified <paramref name="groupQuantity"/>.
45  /// If the quantity provided equals the template's quantity then the template is returned.
46  /// </summary>
47  /// <param name="template">The group template</param>
48  /// <param name="groupQuantity">The quantity of the new group</param>
49  /// <param name="positionMananger">The position manager to use to resolve positions</param>
50  /// <returns>A position group with the same position ratios as the template but with the specified group quantity</returns>
51  public static IPositionGroup WithQuantity(this IPositionGroup template, decimal groupQuantity, SecurityPositionGroupModel positionMananger)
52  {
53  var positions = template.ToArray(p => p.WithLots(groupQuantity));
54 
55  // Could result in an inverse strategy that would not get resolved by using the same key
56  if (groupQuantity < 0)
57  {
58  return positionMananger.ResolvePositionGroups(new PositionCollection(positions)).Single();
59  }
60 
61  return new PositionGroup(template.Key, groupQuantity, positions);
62  }
63 
64  /// <summary>
65  /// Creates a new <see cref="IPositionGroup"/> with each position's quantity equaling it's unit quantity
66  /// </summary>
67  /// <param name="template">The group template</param>
68  /// <returns>A position group with the same position ratios as the template but with the specified group quantity</returns>
69  public static IPositionGroup CreateUnitGroup(this IPositionGroup template, SecurityPositionGroupModel positionMananger)
70  {
71  return template.WithQuantity(1, positionMananger);
72  }
73 
74  /// <summary>
75  /// Determines whether the position group is empty
76  /// </summary>
77  /// <param name="positionGroup">The position group</param>
78  /// <returns>True if the position group is empty, that is, it has no positions, false otherwise</returns>
79  public static bool IsEmpty(this IPositionGroup positionGroup)
80  {
81  return positionGroup.Count == 0;
82  }
83 
84  /// <summary>
85  /// Checks whether the provided groups are in opposite sides, that is, each of their positions are in opposite sides.
86  /// </summary>
87  /// <param name="group">The group to check</param>
88  /// <param name="other">The group to check against</param>
89  /// <returns>
90  /// Whether the position groups are the inverted version of each other, that is, contain the same positions each on the opposite side
91  /// </returns>
92  public static bool IsInvertedOf(this IPositionGroup group, IPositionGroup other)
93  {
94  return group.Count == other.Count
95  && group.All(position => Math.Sign(position.Quantity) == -Math.Sign(other.GetPosition(position.Symbol).Quantity));
96  }
97 
98  /// <summary>
99  /// Checks whether the provided groups are closing/reducing each other, that is, each of their positions are in opposite sides.
100  /// </summary>
101  /// <param name="finalGroup">The final position group that would result from a trade</param>
102  /// <param name="initialGroup">The initial position group before a trade</param>
103  /// <returns>Whether final resulting position group is a reduction of the initial one</returns>
104  public static bool Closes(this IPositionGroup finalGroup, IPositionGroup initialGroup)
105  {
106  // Liquidating
107  if (finalGroup.IsEmpty())
108  {
109  return true;
110  }
111 
112  if (finalGroup.Count != initialGroup.Count)
113  {
114  return false;
115  }
116 
117  // Liquidating
118  if (finalGroup.Quantity == 0 &&
119  // The initial group includes all positions being liquidated
120  finalGroup.All(position => initialGroup.TryGetPosition(position.Symbol, out _)))
121  {
122  return true;
123  }
124 
125  // Each of the positions have opposite quantity signs
126  if (finalGroup.IsInvertedOf(initialGroup))
127  {
128  return true;
129  }
130 
131  // The final group has a smaller quantity than the initial group
132  return Math.Abs(finalGroup.Quantity) < Math.Abs(initialGroup.Quantity) &&
133  finalGroup.All(position => Math.Sign(position.Quantity) == Math.Sign(initialGroup.GetPosition(position.Symbol).Quantity));
134  }
135 
136  /// <summary>
137  /// Gets a user friendly name for the provided <paramref name="group"/>
138  /// </summary>
139  public static string GetUserFriendlyName(this IPositionGroup group)
140  {
141  if (group.Count == 1)
142  {
143  return group.Single().Symbol.ToString();
144  }
145 
146  return string.Join("|", group.Select(p => p.Symbol.ToString()));
147  }
148  }
149 }