Lean  $LEAN_TAG$
OptionStrategyLegPredicate.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.Diagnostics;
19 using System.Linq;
20 using System.Linq.Expressions;
21 using QuantConnect.Util;
22 
24 {
25  /// <summary>
26  /// Defines a condition under which a particular <see cref="OptionPosition"/> can be combined with
27  /// a preceding list of leg (also of type <see cref="OptionPosition"/>) to achieve a particular
28  /// option strategy.
29  /// </summary>
31  {
32  private readonly BinaryComparison _comparison;
33  private readonly IOptionStrategyLegPredicateReferenceValue _reference;
34  private readonly Func<IReadOnlyList<OptionPosition>, OptionPosition, bool> _predicate;
35  private readonly Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> _expression;
36 
37  /// <summary>
38  /// Determines whether or not this predicate is able to utilize <see cref="OptionPositionCollection"/> indexes.
39  /// </summary>
40  public bool IsIndexed => _comparison != null && _reference != null;
41 
42  /// <summary>
43  /// Initializes a new instance of the <see cref="OptionStrategyLegPredicate"/> class
44  /// </summary>
45  /// <param name="comparison">The <see cref="BinaryComparison"/> invoked</param>
46  /// <param name="reference">The reference value, such as a strike price, encapsulated within the
47  /// <see cref="IOptionStrategyLegPredicateReferenceValue"/> to enable resolving the value from different potential sets.</param>
48  /// <param name="predicate">The compiled predicate expression</param>
49  /// <param name="expression">The predicate expression, from which, all other values were derived.</param>
51  BinaryComparison comparison,
53  Func<IReadOnlyList<OptionPosition>, OptionPosition, bool> predicate,
54  Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> expression
55  )
56  {
57  _reference = reference;
58  _predicate = predicate;
59  _comparison = comparison;
60  _expression = expression;
61  }
62 
63  /// <summary>
64  /// Determines whether or not the provided combination of preceding <paramref name="legs"/>
65  /// and current <paramref name="position"/> adhere to this predicate's requirements.
66  /// </summary>
67  public bool Matches(IReadOnlyList<OptionPosition> legs, OptionPosition position)
68  {
69  try
70  {
71  return _predicate(legs, position);
72  }
73  catch (InvalidOperationException)
74  {
75  // attempt to access option SecurityIdentifier values, such as strike, on the underlying
76  // this simply means we don't match and can safely ignore this exception. now, this does
77  // somewhat indicate a potential design flaw, but I content that this is better than having
78  // to manage the underlying position separately throughout the entire matching process.
79  return false;
80  }
81  }
82 
83  /// <summary>
84  /// Filters the specified <paramref name="positions"/> by applying this predicate based on the referenced legs.
85  /// </summary>
86  public OptionPositionCollection Filter(IReadOnlyList<OptionPosition> legs, OptionPositionCollection positions, bool includeUnderlying)
87  {
88  if (!IsIndexed)
89  {
90  // if the predicate references non-indexed properties or contains complex/multiple conditions then
91  // we'll need to do a full table scan. this is not always avoidable, but we should try to avoid it
93  positions.Where(position => _predicate(legs, position))
94  );
95  }
96 
97  var referenceValue = _reference.Resolve(legs);
98  switch (_reference.Target)
99  {
100  case PredicateTargetValue.Right: return positions.Slice((OptionRight) referenceValue, includeUnderlying);
101  case PredicateTargetValue.Strike: return positions.Slice(_comparison, (decimal) referenceValue, includeUnderlying);
102  case PredicateTargetValue.Expiration: return positions.Slice(_comparison, (DateTime) referenceValue, includeUnderlying);
103  default:
104  throw new ArgumentOutOfRangeException();
105  }
106  }
107 
108  /// <summary>
109  /// Gets the underlying <see cref="IOptionStrategyLegPredicateReferenceValue"/> value used by this predicate.
110  /// </summary>
112  {
113  return _reference;
114  }
115 
116  /// <summary>
117  /// Creates a new <see cref="OptionStrategyLegPredicate"/> from the specified predicate <paramref name="expression"/>
118  /// </summary>
120  Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> expression
121  )
122  {
123  // expr must NOT include compound comparisons
124  // expr is a lambda of one of the following forms:
125  // (legs, position) => position.{target} {comparison} legs[i].{reference-target}
126  // (legs, position) => legs[i].{reference-target} {comparison} position.{target}
127  // (legs, position) => position.{target} {comparison} {literal-reference-target}
128  // (legs, position) => {literal-reference-target} {comparison} position.{target}
129 
130  // we want to make the comparison of a common form, specifically:
131  // position.{target} {comparison} {reference-target}
132  // this is so when we invoke OptionPositionCollection we have the correct comparison type
133  // for example, legs[0].Strike > position.Strike
134  // needs to be inverted into position.Strike < legs[0].Strike
135  // so we can call OptionPositionCollection.Slice(BinaryComparison.LessThan, legs[0].Strike);
136 
137  try
138  {
139  var legsParameter = expression.Parameters[0];
140  var positionParameter = expression.Parameters[1];
141  var binary = expression.OfType<BinaryExpression>().Single(e => e.NodeType.IsBinaryComparison());
142  var comparison = BinaryComparison.FromExpressionType(binary.NodeType);
143  var leftReference = CreateReferenceValue(legsParameter, positionParameter, binary.Left);
144  var rightReference = CreateReferenceValue(legsParameter, positionParameter, binary.Right);
145  if (leftReference != null && rightReference != null)
146  {
147  throw new ArgumentException($"The provided expression is not of the required form: {expression}");
148  }
149 
150  // we want the left side to be null, indicating position.{target}
151  // if not, then we need to flip the comparison operand
152  var reference = rightReference;
153  if (rightReference == null)
154  {
155  reference = leftReference;
156  comparison = comparison.FlipOperands();
157  }
158 
159  return new OptionStrategyLegPredicate(comparison, reference, expression.Compile(), expression);
160  }
161  catch
162  {
163  // we can still handle arbitrary predicates, they just require a full search of the positions
164  // as we're unable to leverage any of the pre-build indexes via Slice methods.
165  return new OptionStrategyLegPredicate(null, null, expression.Compile(), expression);
166  }
167  }
168 
169  /// <summary>
170  /// Creates a new <see cref="IOptionStrategyLegPredicateReferenceValue"/> from the specified lambda parameters
171  /// and expression to be evaluated.
172  /// </summary>
173  private static IOptionStrategyLegPredicateReferenceValue CreateReferenceValue(
174  Expression legsParameter,
175  Expression positionParameter,
176  Expression expression
177  )
178  {
179  // if we're referencing the position parameter then this isn't a reference value
180  // this 'value' is the positions being matched in OptionPositionCollection
181  // verify the legs parameter doesn't appear in here either
182  var expressions = expression.AsEnumerable().ToList();
183  var containsLegParameter = expressions.Any(e => ReferenceEquals(e, legsParameter));
184  var containsPositionParameter = expressions.Any(e => ReferenceEquals(e, positionParameter));
185  if (containsPositionParameter)
186  {
187  if (containsLegParameter)
188  {
189  throw new NotSupportedException("Expressions containing references to both parameters " +
190  "(legs and positions) on the same side of an equality operator are not supported."
191  );
192  }
193 
194  // this expression is of the form position.Strike/position.Expiration/position.Right
195  // and as such, is not a reference value, simply return null
196  return null;
197  }
198 
199  if (!containsLegParameter)
200  {
201  // this is a literal and we'll attempt to evaluate it.
202  var value = Expression.Lambda(expression).Compile().DynamicInvoke();
203  if (value == null)
204  {
205  throw new ArgumentNullException($"Failed to evaluate expression literal: {expressions}");
206  }
207 
208  return ConstantOptionStrategyLegReferenceValue.Create(value);
209  }
210 
211  // we're looking for an array indexer into the legs list
212  var methodCall = expressions.Single<MethodCallExpression>();
213  Debug.Assert(methodCall.Method.Name == "get_Item");
214  // compile and dynamically invoke the argument to get_Item(x) {legs[x]}
215  var arrayIndex = (int) Expression.Lambda(methodCall.Arguments[0]).Compile().DynamicInvoke();
216 
217  // and then a member expression denoting the property (target)
218  var member = expressions.Single<MemberExpression>().Member;
219  var target = GetPredicateTargetValue(member.Name);
220 
221  return new OptionStrategyLegPredicateReferenceValue(arrayIndex, target);
222  }
223 
224  private static PredicateTargetValue GetPredicateTargetValue(string memberName)
225  {
226  switch (memberName)
227  {
228  case nameof(OptionPosition.Right): return PredicateTargetValue.Right;
229  case nameof(OptionPosition.Strike): return PredicateTargetValue.Strike;
230  case nameof(OptionPosition.Expiration): return PredicateTargetValue.Expiration;
231  default:
232  throw new NotImplementedException(
233  $"Failed to resolve member name '{memberName}' to {nameof(PredicateTargetValue)}"
234  );
235  }
236  }
237 
238  /// <summary>Returns a string that represents the current object.</summary>
239  /// <returns>A string that represents the current object.</returns>
240  public override string ToString()
241  {
242  return _expression.ToString();
243  }
244  }
245 }