/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using QuantConnect.Util; namespace QuantConnect.Securities.Option.StrategyMatcher { /// /// Defines a condition under which a particular can be combined with /// a preceding list of leg (also of type ) to achieve a particular /// option strategy. /// public class OptionStrategyLegPredicate { private readonly BinaryComparison _comparison; private readonly IOptionStrategyLegPredicateReferenceValue _reference; private readonly Func, OptionPosition, bool> _predicate; private readonly Expression, OptionPosition, bool>> _expression; /// /// Determines whether or not this predicate is able to utilize indexes. /// public bool IsIndexed => _comparison != null && _reference != null; /// /// Initializes a new instance of the class /// /// The invoked /// The reference value, such as a strike price, encapsulated within the /// to enable resolving the value from different potential sets. /// The compiled predicate expression /// The predicate expression, from which, all other values were derived. public OptionStrategyLegPredicate( BinaryComparison comparison, IOptionStrategyLegPredicateReferenceValue reference, Func, OptionPosition, bool> predicate, Expression, OptionPosition, bool>> expression ) { _reference = reference; _predicate = predicate; _comparison = comparison; _expression = expression; } /// /// Determines whether or not the provided combination of preceding /// and current adhere to this predicate's requirements. /// public bool Matches(IReadOnlyList legs, OptionPosition position) { try { return _predicate(legs, position); } catch (InvalidOperationException) { // attempt to access option SecurityIdentifier values, such as strike, on the underlying // this simply means we don't match and can safely ignore this exception. now, this does // somewhat indicate a potential design flaw, but I content that this is better than having // to manage the underlying position separately throughout the entire matching process. return false; } } /// /// Filters the specified by applying this predicate based on the referenced legs. /// public OptionPositionCollection Filter(IReadOnlyList legs, OptionPositionCollection positions, bool includeUnderlying) { if (!IsIndexed) { // if the predicate references non-indexed properties or contains complex/multiple conditions then // we'll need to do a full table scan. this is not always avoidable, but we should try to avoid it return OptionPositionCollection.Empty.AddRange( positions.Where(position => _predicate(legs, position)) ); } var referenceValue = _reference.Resolve(legs); switch (_reference.Target) { case PredicateTargetValue.Right: return positions.Slice((OptionRight) referenceValue, includeUnderlying); case PredicateTargetValue.Strike: return positions.Slice(_comparison, (decimal) referenceValue, includeUnderlying); case PredicateTargetValue.Expiration: return positions.Slice(_comparison, (DateTime) referenceValue, includeUnderlying); default: throw new ArgumentOutOfRangeException(); } } /// /// Gets the underlying value used by this predicate. /// public IOptionStrategyLegPredicateReferenceValue GetReferenceValue() { return _reference; } /// /// Creates a new from the specified predicate /// public static OptionStrategyLegPredicate Create( Expression, OptionPosition, bool>> expression ) { // expr must NOT include compound comparisons // expr is a lambda of one of the following forms: // (legs, position) => position.{target} {comparison} legs[i].{reference-target} // (legs, position) => legs[i].{reference-target} {comparison} position.{target} // (legs, position) => position.{target} {comparison} {literal-reference-target} // (legs, position) => {literal-reference-target} {comparison} position.{target} // we want to make the comparison of a common form, specifically: // position.{target} {comparison} {reference-target} // this is so when we invoke OptionPositionCollection we have the correct comparison type // for example, legs[0].Strike > position.Strike // needs to be inverted into position.Strike < legs[0].Strike // so we can call OptionPositionCollection.Slice(BinaryComparison.LessThan, legs[0].Strike); try { var legsParameter = expression.Parameters[0]; var positionParameter = expression.Parameters[1]; var binary = expression.OfType().Single(e => e.NodeType.IsBinaryComparison()); var comparison = BinaryComparison.FromExpressionType(binary.NodeType); var leftReference = CreateReferenceValue(legsParameter, positionParameter, binary.Left); var rightReference = CreateReferenceValue(legsParameter, positionParameter, binary.Right); if (leftReference != null && rightReference != null) { throw new ArgumentException($"The provided expression is not of the required form: {expression}"); } // we want the left side to be null, indicating position.{target} // if not, then we need to flip the comparison operand var reference = rightReference; if (rightReference == null) { reference = leftReference; comparison = comparison.FlipOperands(); } return new OptionStrategyLegPredicate(comparison, reference, expression.Compile(), expression); } catch { // we can still handle arbitrary predicates, they just require a full search of the positions // as we're unable to leverage any of the pre-build indexes via Slice methods. return new OptionStrategyLegPredicate(null, null, expression.Compile(), expression); } } /// /// Creates a new from the specified lambda parameters /// and expression to be evaluated. /// private static IOptionStrategyLegPredicateReferenceValue CreateReferenceValue( Expression legsParameter, Expression positionParameter, Expression expression ) { // if we're referencing the position parameter then this isn't a reference value // this 'value' is the positions being matched in OptionPositionCollection // verify the legs parameter doesn't appear in here either var expressions = expression.AsEnumerable().ToList(); var containsLegParameter = expressions.Any(e => ReferenceEquals(e, legsParameter)); var containsPositionParameter = expressions.Any(e => ReferenceEquals(e, positionParameter)); if (containsPositionParameter) { if (containsLegParameter) { throw new NotSupportedException("Expressions containing references to both parameters " + "(legs and positions) on the same side of an equality operator are not supported." ); } // this expression is of the form position.Strike/position.Expiration/position.Right // and as such, is not a reference value, simply return null return null; } if (!containsLegParameter) { // this is a literal and we'll attempt to evaluate it. var value = Expression.Lambda(expression).Compile().DynamicInvoke(); if (value == null) { throw new ArgumentNullException($"Failed to evaluate expression literal: {expressions}"); } return ConstantOptionStrategyLegReferenceValue.Create(value); } // we're looking for an array indexer into the legs list var methodCall = expressions.Single(); Debug.Assert(methodCall.Method.Name == "get_Item"); // compile and dynamically invoke the argument to get_Item(x) {legs[x]} var arrayIndex = (int) Expression.Lambda(methodCall.Arguments[0]).Compile().DynamicInvoke(); // and then a member expression denoting the property (target) var member = expressions.Single().Member; var target = GetPredicateTargetValue(member.Name); return new OptionStrategyLegPredicateReferenceValue(arrayIndex, target); } private static PredicateTargetValue GetPredicateTargetValue(string memberName) { switch (memberName) { case nameof(OptionPosition.Right): return PredicateTargetValue.Right; case nameof(OptionPosition.Strike): return PredicateTargetValue.Strike; case nameof(OptionPosition.Expiration): return PredicateTargetValue.Expiration; default: throw new NotImplementedException( $"Failed to resolve member name '{memberName}' to {nameof(PredicateTargetValue)}" ); } } /// Returns a string that represents the current object. /// A string that represents the current object. public override string ToString() { return _expression.ToString(); } } }