/*
* 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();
}
}
}