chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,88 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Stub class providing an idea towards an optimal <see cref="IOptionPositionCollectionEnumerator"/> implementation
/// that still needs to be implemented.
/// </summary>
public class AbsoluteRiskOptionPositionCollectionEnumerator : IOptionPositionCollectionEnumerator
{
private readonly Func<Symbol, decimal> _marketPriceProvider;
/// <summary>
/// Intializes a new instance of the <see cref="AbsoluteRiskOptionPositionCollectionEnumerator"/> class
/// </summary>
/// <param name="marketPriceProvider">Function providing the current market price for a provided symbol</param>
public AbsoluteRiskOptionPositionCollectionEnumerator(Func<Symbol, decimal> marketPriceProvider)
{
_marketPriceProvider = marketPriceProvider;
}
/// <summary>
/// Enumerates the provided <paramref name="positions"/>. Positions enumerated first are more
/// likely to be matched than those appearing later in the enumeration.
/// </summary>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
if (positions.IsEmpty)
{
yield break;
}
var marketPrice = _marketPriceProvider(positions.Underlying);
var longPositions = new List<OptionPosition>();
var shortPuts = new SortedDictionary<decimal, OptionPosition>();
var shortCalls = new SortedDictionary<decimal, OptionPosition>();
foreach (var position in positions)
{
if (!position.Symbol.HasUnderlying)
{
yield return position;
}
if (position.Quantity > 0)
{
longPositions.Add(position);
}
else
{
switch (position.Right)
{
case OptionRight.Put:
shortPuts.Add(position.Strike, position);
break;
case OptionRight.Call:
shortCalls.Add(position.Strike, position);
break;
default:
throw new ApplicationException(
"The skies are falling, the oceans rising - you're having a bad time"
);
}
}
}
throw new NotImplementedException("This implementation needs to be completed.");
}
}
}
@@ -0,0 +1,82 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyLegPredicateReferenceValue"/> that represents a constant value.
/// </summary>
public class ConstantOptionStrategyLegPredicateReferenceValue<T> : IOptionStrategyLegPredicateReferenceValue
{
private readonly T _value;
/// <summary>
/// Gets the target of this value
/// </summary>
public PredicateTargetValue Target { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ConstantOptionStrategyLegPredicateReferenceValue{T}"/> class
/// </summary>
/// <param name="value">The constant reference value</param>
/// <param name="target">The value target in relation to the <see cref="OptionPosition"/></param>
public ConstantOptionStrategyLegPredicateReferenceValue(T value, PredicateTargetValue target)
{
_value = value;
Target = target;
}
/// <summary>
/// Returns the constant value provided at initialization
/// </summary>
public object Resolve(IReadOnlyList<OptionPosition> legs)
{
return _value;
}
}
/// <summary>
/// Provides methods for easily creating instances of <see cref="ConstantOptionStrategyLegPredicateReferenceValue{T}"/>
/// </summary>
public static class ConstantOptionStrategyLegReferenceValue
{
/// <summary>
/// Creates a new instance of the <see cref="ConstantOptionStrategyLegPredicateReferenceValue{T}"/> class for
/// the specified <paramref name="value"/>
/// </summary>
public static IOptionStrategyLegPredicateReferenceValue Create(object value)
{
if (value is DateTime)
{
return new ConstantOptionStrategyLegPredicateReferenceValue<DateTime>((DateTime) value, PredicateTargetValue.Expiration);
}
if (value is decimal)
{
return new ConstantOptionStrategyLegPredicateReferenceValue<decimal>((decimal) value, PredicateTargetValue.Strike);
}
if (value is OptionRight)
{
return new ConstantOptionStrategyLegPredicateReferenceValue<OptionRight>((OptionRight) value, PredicateTargetValue.Right);
}
throw new NotSupportedException($"{value?.GetType().GetBetterTypeName()} is not supported.");
}
}
}
@@ -0,0 +1,33 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a default implementation of the <see cref="IOptionPositionCollectionEnumerator"/> abstraction.
/// </summary>
public class DefaultOptionPositionCollectionEnumerator : IOptionPositionCollectionEnumerator
{
/// <summary>
/// Enumerates <paramref name="positions"/> according to its default enumerator implementation.
/// </summary>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
return positions;
}
}
}
@@ -0,0 +1,35 @@
/*
* 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.Collections.Generic;
using System.Linq;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyDefinitionEnumerator"/> that enumerates definitions
/// requiring more leg matches first. This ensures more complex definitions are evaluated before simpler definitions.
/// </summary>
public class DescendingByLegCountOptionStrategyDefinitionEnumerator : IOptionStrategyDefinitionEnumerator
{
/// <summary>
/// Enumerates definitions in descending order of <see cref="OptionStrategyDefinition.LegCount"/>
/// </summary>
public IEnumerable<OptionStrategyDefinition> Enumerate(IReadOnlyList<OptionStrategyDefinition> definitions)
{
return definitions.OrderByDescending(d => d.LegCount);
}
}
}
@@ -0,0 +1,49 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a functional implementation of <see cref="IOptionPositionCollectionEnumerator"/>
/// </summary>
public class FunctionalOptionPositionCollectionEnumerator : IOptionPositionCollectionEnumerator
{
private readonly Func<OptionPositionCollection, IEnumerable<OptionPosition>> _enumerate;
/// <summary>
/// Initializes a new instance of the <see cref="FunctionalOptionPositionCollectionEnumerator"/> class
/// </summary>
/// <param name="enumerate"></param>
public FunctionalOptionPositionCollectionEnumerator(
Func<OptionPositionCollection, IEnumerable<OptionPosition>> enumerate
)
{
_enumerate = enumerate;
}
/// <summary>
/// Enumerate the Option Positions Collection
/// </summary>
/// <param name="positions">The positions to enumerate on</param>
/// <returns>Enumerable of Option Positions</returns>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
return _enumerate(positions);
}
}
}
@@ -0,0 +1,36 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Enumerates an <see cref="OptionPositionCollection"/>. The intent is to evaluate positions that
/// may be more important sooner. Positions appearing earlier in the enumeration are evaluated before
/// positions showing later. This effectively prioritizes individual positions. This should not be
/// used filter filtering, but it could also be used to split a position, for example a position with
/// 10 could be changed to two 5s and they don't need to be enumerated back to-back either. In this
/// way you could prioritize the first 5 and then delay matching of the final 5.
/// </summary>
public interface IOptionPositionCollectionEnumerator
{
/// <summary>
/// Enumerates the provided <paramref name="positions"/>. Positions enumerated first are more
/// likely to be matched than those appearing later in the enumeration.
/// </summary>
IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions);
}
}
@@ -0,0 +1,31 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Enumerates <see cref="OptionStrategyDefinition"/> for the purposes of providing a bias towards definitions
/// that are more favorable to be matched before matching less favorable definitions.
/// </summary>
public interface IOptionStrategyDefinitionEnumerator
{
/// <summary>
/// Enumerates the <paramref name="definitions"/> according to the implementation's own concept of favorability.
/// </summary>
IEnumerable<OptionStrategyDefinition> Enumerate(IReadOnlyList<OptionStrategyDefinition> definitions);
}
}
@@ -0,0 +1,43 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// When decoding leg predicates, we extract the value we're comparing against
/// If we're comparing against another leg's value (such as legs[0].Strike), then
/// we'll create a OptionStrategyLegPredicateReferenceValue. If we're comparing against a literal/constant value,
/// then we'll create a ConstantOptionStrategyLegPredicateReferenceValue. These reference values are used to slice
/// the <see cref="OptionPositionCollection"/> to only include positions matching the
/// predicate.
/// </summary>
public interface IOptionStrategyLegPredicateReferenceValue
{
/// <summary>
/// Gets the target of this value
/// </summary>
PredicateTargetValue Target { get; }
/// <summary>
/// Resolves the value of the comparand specified in an <see cref="OptionStrategyLegPredicate"/>.
/// For example, the predicate may include ... > legs[0].Strike, and upon evaluation, we need to
/// be able to extract leg[0].Strike for the currently contemplated set of legs adhering to a
/// strategy's definition.
/// </summary>
object Resolve(IReadOnlyList<OptionPosition> legs);
}
}
@@ -0,0 +1,30 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Evaluates the provided match to assign an objective score. Higher scores are better.
/// </summary>
public interface IOptionStrategyMatchObjectiveFunction
{
/// <summary>
/// Evaluates the objective function for the provided match solution. Solution with the highest score will be selected
/// as the solution. NOTE: This part of the match has not been implemented as of 2020-11-06 as it's only evaluating the
/// first solution match (MatchOnce).
/// </summary>
decimal ComputeScore(OptionPositionCollection input, OptionStrategyMatch match, OptionPositionCollection unmatched);
}
}
@@ -0,0 +1,34 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a default implementation of <see cref="IOptionStrategyDefinitionEnumerator"/> that enumerates
/// definitions according to the order that they were provided to <see cref="OptionStrategyMatcherOptions"/>
/// </summary>
public class IdentityOptionStrategyDefinitionEnumerator : IOptionStrategyDefinitionEnumerator
{
/// <summary>
/// Enumerates the <paramref name="definitions"/> in the same order as provided.
/// </summary>
public IEnumerable<OptionStrategyDefinition> Enumerate(IReadOnlyList<OptionStrategyDefinition> definitions)
{
return definitions;
}
}
}
@@ -0,0 +1,281 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines a lightweight structure representing a position in an option contract or underlying.
/// This type is heavily utilized by the options strategy matcher and is the parameter type of
/// option strategy definition predicates. Underlying quantities should be represented in lot sizes,
/// which is equal to the quantity of shares divided by the contract's multiplier and then rounded
/// down towards zero (truncate)
/// </summary>
public struct OptionPosition : IEquatable<OptionPosition>
{
/// <summary>
/// Gets a new <see cref="OptionPosition"/> with zero <see cref="Quantity"/>
/// </summary>
public static OptionPosition Empty(Symbol symbol)
=> new OptionPosition(symbol, 0);
/// <summary>
/// Determines whether or not this position has any quantity
/// </summary>
public bool HasQuantity => Quantity != 0;
/// <summary>
/// Determines whether or not this position is for the underlying symbol
/// </summary>
public bool IsUnderlying => !Symbol.HasUnderlying;
/// <summary>
/// Number of contracts held, can be positive or negative
/// </summary>
public int Quantity { get; }
/// <summary>
/// Option contract symbol
/// </summary>
public Symbol Symbol { get; }
/// <summary>
/// Gets the underlying symbol. If this position represents the underlying,
/// then this property is the same as the <see cref="Symbol"/> property
/// </summary>
public Symbol Underlying => IsUnderlying ? Symbol : Symbol.Underlying;
/// <summary>
/// Option contract expiration date
/// </summary>
public DateTime Expiration
{
get
{
if (Symbol.HasUnderlying)
{
return Symbol.ID.Date;
}
throw new InvalidOperationException($"{nameof(Expiration)} is not valid for underlying symbols: {Symbol}");
}
}
/// <summary>
/// Option contract strike price
/// </summary>
public decimal Strike
{
get
{
if (Symbol.HasUnderlying)
{
return Symbol.ID.StrikePrice;
}
throw new InvalidOperationException($"{nameof(Strike)} is not valid for underlying symbols: {Symbol}");
}
}
/// <summary>
/// Option contract right (put/call)
/// </summary>
public OptionRight Right
{
get
{
if (Symbol.HasUnderlying)
{
return Symbol.ID.OptionRight;
}
throw new InvalidOperationException($"{nameof(Right)} is not valid for underlying symbols: {Symbol}");
}
}
/// <summary>
/// Gets whether this position is short/long/none
/// </summary>
public PositionSide Side => (PositionSide) Math.Sign(Quantity);
/// <summary>
/// Initializes a new instance of the <see cref="OptionPosition"/> structure
/// </summary>
/// <param name="symbol">The option contract symbol</param>
/// <param name="quantity">The number of contracts held</param>
public OptionPosition(Symbol symbol, int quantity)
{
Symbol = symbol;
Quantity = quantity;
}
/// <summary>
/// Creates a new <see cref="OptionPosition"/> instance with negative <see cref="Quantity"/>
/// </summary>
public OptionPosition Negate()
{
return new OptionPosition(Symbol, -Quantity);
}
/// <summary>
/// Creates a new <see cref="OptionPosition"/> with this position's <see cref="Symbol"/>
/// and the provided <paramref name="quantity"/>
/// </summary>
public OptionPosition WithQuantity(int quantity)
{
return new OptionPosition(Symbol, quantity);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
public bool Equals(OptionPosition other)
{
return Equals(Symbol, other.Symbol) && Quantity == other.Quantity;
}
/// <summary>Indicates whether this instance and a specified object are equal.</summary>
/// <param name="obj">The object to compare with the current instance. </param>
/// <returns>true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((OptionPosition) obj);
}
/// <summary>Returns the hash code for this instance.</summary>
/// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
public override int GetHashCode()
{
unchecked
{
return ((Symbol != null ? Symbol.GetHashCode() : 0) * 397) ^ Quantity;
}
}
/// <summary>Returns the fully qualified type name of this instance.</summary>
/// <returns>The fully qualified type name.</returns>
public override string ToString()
{
var s = Quantity == 1 ? "" : "s";
if (Symbol.HasUnderlying)
{
return $"{Quantity} {Right.ToLower()}{s} on {Symbol.Underlying.Value} at ${Strike} expiring on {Expiration:yyyy-MM-dd}";
}
return $"{Quantity} share{s} of {Symbol.Value}";
}
/// <summary>
/// OptionPosition * Operator, will multiple quantity by given factor
/// </summary>
/// <param name="left">OptionPosition to operate on</param>
/// <param name="factor">Factor to multiply by</param>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator *(OptionPosition left, int factor)
{
return new OptionPosition(left.Symbol, factor * left.Quantity);
}
/// <summary>
/// OptionPosition * Operator, will multiple quantity by given factor
/// </summary>
/// <param name="right">OptionPosition to operate on</param>
/// <param name="factor">Factor to multiply by</param>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator *(int factor, OptionPosition right)
{
return new OptionPosition(right.Symbol, factor * right.Quantity);
}
/// <summary>
/// OptionPosition + Operator, will add quantities together if they are for the same symbol.
/// </summary>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator +(OptionPosition left, OptionPosition right)
{
if (!Equals(left.Symbol, right.Symbol))
{
if (left == default(OptionPosition))
{
return right;
}
if (right == default(OptionPosition))
{
return left;
}
throw new InvalidOperationException("Unable to add OptionPosition instances with different symbols");
}
return new OptionPosition(left.Symbol, left.Quantity + right.Quantity);
}
/// <summary>
/// OptionPosition - Operator, will subtract left - right quantities if they are for the same symbol.
/// </summary>
/// <returns>Resulting OptionPosition</returns>
public static OptionPosition operator -(OptionPosition left, OptionPosition right)
{
if (!Equals(left.Symbol, right.Symbol))
{
if (left == default(OptionPosition))
{
// 0 - right
return right.Negate();
}
if (right == default(OptionPosition))
{
// left - 0
return left;
}
throw new InvalidOperationException("Unable to subtract OptionPosition instances with different symbols");
}
return new OptionPosition(left.Symbol, left.Quantity - right.Quantity);
}
/// <summary>
/// Option Position == Operator
/// </summary>
/// <returns>True if they are the same</returns>
public static bool operator ==(OptionPosition left, OptionPosition right)
{
return Equals(left, right);
}
/// <summary>
/// Option Position != Operator
/// </summary>
/// <returns>True if they are not the same</returns>
public static bool operator !=(OptionPosition left, OptionPosition right)
{
return !Equals(left, right);
}
}
}
@@ -0,0 +1,652 @@
/*
* 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.Linq;
using QuantConnect.Util;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using QuantConnect.Securities.Positions;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides indexing of option contracts
/// </summary>
public class OptionPositionCollection : IEnumerable<OptionPosition>
{
/// <summary>
/// Gets an empty instance of <see cref="OptionPositionCollection"/>
/// </summary>
public static OptionPositionCollection Empty { get; } = new OptionPositionCollection(
ImmutableDictionary<Symbol, OptionPosition>.Empty,
ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty,
ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty,
ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty,
ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty
);
private readonly ImmutableDictionary<Symbol, OptionPosition> _positions;
private readonly ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>> _rights;
private readonly ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>> _sides;
private readonly ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>> _strikes;
private readonly ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>> _expirations;
/// <summary>
/// Gets the underlying security's symbol
/// </summary>
public Symbol Underlying => UnderlyingPosition.Symbol ?? Symbol.Empty;
/// <summary>
/// Gets the total count of unique positions, including the underlying
/// </summary>
public int Count => _positions.Count;
/// <summary>
/// Gets whether or not there's any positions in this collection.
/// </summary>
public bool IsEmpty => _positions.IsEmpty;
/// <summary>
/// Gets the quantity of underlying shares held
/// TODO : Change to UnderlyingLots
/// </summary>
public int UnderlyingQuantity => UnderlyingPosition.Quantity;
/// <summary>
/// Gets the number of unique put contracts held (long or short)
/// </summary>
public int UniquePuts => _rights[OptionRight.Put].Count;
/// <summary>
/// Gets the unique number of expirations
/// </summary>
public int UniqueExpirations => _expirations.Count;
/// <summary>
/// Gets the number of unique call contracts held (long or short)
/// </summary>
public int UniqueCalls => _rights[OptionRight.Call].Count;
/// <summary>
/// Determines if this collection contains a position in the underlying
/// </summary>
public bool HasUnderlying => UnderlyingQuantity != 0;
/// <summary>
/// Gets the <see cref="Underlying"/> position
/// </summary>
public OptionPosition UnderlyingPosition { get; }
/// <summary>
/// Gets all unique strike prices in the collection, in ascending order.
/// </summary>
public IEnumerable<decimal> Strikes => _strikes.Keys;
/// <summary>
/// Gets all unique expiration dates in the collection, in chronological order.
/// </summary>
public IEnumerable<DateTime> Expirations => _expirations.Keys;
/// <summary>
/// Initializes a new instance of the <see cref="OptionPositionCollection"/> class
/// </summary>
/// <param name="positions">All positions</param>
/// <param name="rights">Index of position symbols by option right</param>
/// <param name="sides">Index of position symbols by position side (short/long/none)</param>
/// <param name="strikes">Index of position symbols by strike price</param>
/// <param name="expirations">Index of position symbols by expiration</param>
public OptionPositionCollection(
ImmutableDictionary<Symbol, OptionPosition> positions,
ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>> rights,
ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>> sides,
ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>> strikes,
ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>> expirations
)
{
_sides = sides;
_rights = rights;
_strikes = strikes;
_positions = positions;
_expirations = expirations;
if (_rights.Count != 2)
{
// ensure we always have both rights indexed, even if empty
ImmutableHashSet<Symbol> value;
if (!_rights.TryGetValue(OptionRight.Call, out value))
{
_rights = _rights.SetItem(OptionRight.Call, ImmutableHashSet<Symbol>.Empty);
}
if (!_rights.TryGetValue(OptionRight.Put, out value))
{
_rights = _rights.SetItem(OptionRight.Put, ImmutableHashSet<Symbol>.Empty);
}
}
if (_sides.Count != 3)
{
// ensure we always have all three sides indexed, even if empty
ImmutableHashSet<Symbol> value;
if (!_sides.TryGetValue(PositionSide.None, out value))
{
_sides = _sides.SetItem(PositionSide.None, ImmutableHashSet<Symbol>.Empty);
}
if (!_sides.TryGetValue(PositionSide.Short, out value))
{
_sides = _sides.SetItem(PositionSide.Short, ImmutableHashSet<Symbol>.Empty);
}
if (!_sides.TryGetValue(PositionSide.Long, out value))
{
_sides = _sides.SetItem(PositionSide.Long, ImmutableHashSet<Symbol>.Empty);
}
}
if (!positions.IsEmpty)
{
// assumption here is that 'positions' includes the underlying equity position and
// ONLY option contracts, so all symbols have the underlying equity symbol embedded
// via the Underlying property, except of course, for the underlying itself.
var underlying = positions.First().Key;
if (underlying.HasUnderlying)
{
underlying = underlying.Underlying;
}
// OptionPosition is struct, so no worry about null ref via .Quantity
var underlyingQuantity = positions.GetValueOrDefault(underlying).Quantity;
UnderlyingPosition = new OptionPosition(underlying, underlyingQuantity);
}
#if DEBUG
var errors = Validate().ToList();
if (errors.Count > 0)
{
throw new ArgumentException("OptionPositionCollection validation failed: "
+ Environment.NewLine + string.Join(Environment.NewLine, errors)
);
}
#endif
}
/// <summary>
/// Determines if a position is held in the specified <paramref name="symbol"/>
/// </summary>
public bool HasPosition(Symbol symbol)
{
OptionPosition position;
return TryGetPosition(symbol, out position) && position.Quantity != 0;
}
/// <summary>
/// Retrieves the <see cref="OptionPosition"/> for the specified <paramref name="symbol"/>
/// if one exists in this collection.
/// </summary>
public bool TryGetPosition(Symbol symbol, out OptionPosition position)
{
return _positions.TryGetValue(symbol, out position);
}
/// <summary>
/// Creates a new <see cref="OptionPositionCollection"/> from the specified enumerable of <paramref name="positions"/>
/// </summary>
public static OptionPositionCollection FromPositions(IEnumerable<OptionPosition> positions)
{
return Empty.AddRange(positions);
}
/// <summary>
/// Creates a new <see cref="OptionPositionCollection"/> from the specified enumerable of <paramref name="positions"/>
/// </summary>
public static OptionPositionCollection FromPositions(IEnumerable<IPosition> positions, decimal contractMultiplier)
{
return Empty.AddRange(positions.Select(position =>
{
var quantity = (int)position.Quantity;
if (position.Symbol.SecurityType.HasOptions())
{
quantity = (int) (quantity / contractMultiplier);
}
return new OptionPosition(position.Symbol, quantity);
}));
}
/// <summary>
/// Creates a new <see cref="OptionPositionCollection"/> from the specified <paramref name="holdings"/>,
/// filtering based on the <paramref name="underlying"/>
/// </summary>
public static OptionPositionCollection Create(Symbol underlying, decimal contractMultiplier, IEnumerable<SecurityHolding> holdings)
{
var positions = Empty;
foreach (var holding in holdings)
{
var symbol = holding.Symbol;
if (!symbol.HasUnderlying)
{
if (symbol == underlying)
{
var underlyingLots = (int) (holding.Quantity / contractMultiplier);
positions = positions.Add(new OptionPosition(symbol, underlyingLots));
}
continue;
}
if (symbol.Underlying != underlying)
{
continue;
}
var position = new OptionPosition(symbol, (int) holding.Quantity);
positions = positions.Add(position);
}
return positions;
}
/// <summary>
/// Creates a new collection that is the result of adding the specified <paramref name="position"/> to this collection.
/// </summary>
public OptionPositionCollection Add(OptionPosition position)
{
if (!position.HasQuantity)
{
// adding nothing doesn't change the collection
return this;
}
var sides = _sides;
var rights = _rights;
var strikes = _strikes;
var positions = _positions;
var expirations = _expirations;
var exists = false;
OptionPosition existing;
var symbol = position.Symbol;
if (positions.TryGetValue(symbol, out existing))
{
exists = true;
position += existing;
}
if (position.HasQuantity)
{
positions = positions.SetItem(symbol, position);
if (!exists && symbol.HasUnderlying)
{
// update indexes when adding a new option contract
sides = sides.Add(position.Side, symbol);
rights = rights.Add(position.Right, symbol);
strikes = strikes.Add(position.Strike, symbol);
positions = positions.SetItem(symbol, position);
expirations = expirations.Add(position.Expiration, symbol);
}
}
else
{
// if the position's quantity went to zero, remove it entirely from the collection when
// removing, be sure to remove strike/expiration indexes. we purposefully keep the rights
// index populated, even with a zero count entry because it's bounded to 2 items (put/call)
positions = positions.Remove(symbol);
if (symbol.HasUnderlying)
{
// keep call/put entries even if goes to zero
var rightsValue = rights[position.Right].Remove(symbol);
rights = rights.SetItem(position.Right, rightsValue);
// keep short/none/long entries even if goes to zero
var sidesValue = sides[position.Side].Remove(symbol);
sides = sides.SetItem(position.Side, sidesValue);
var strikesValue = strikes[position.Strike].Remove(symbol);
strikes = strikesValue.Count > 0
? strikes.SetItem(position.Strike, strikesValue)
: strikes.Remove(position.Strike);
var expirationsValue = expirations[position.Expiration].Remove(symbol);
expirations = expirationsValue.Count > 0
? expirations.SetItem(position.Expiration, expirationsValue)
: expirations.Remove(position.Expiration);
}
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Creates a new collection that is the result of removing the specified <paramref name="position"/>
/// </summary>
public OptionPositionCollection Remove(OptionPosition position)
{
return Add(position.Negate());
}
/// <summary>
/// Creates a new collection that is the result of adding the specified <paramref name="positions"/> to this collection.
/// </summary>
public OptionPositionCollection AddRange(params OptionPosition[] positions)
{
return AddRange((IEnumerable<OptionPosition>) positions);
}
/// <summary>
/// Creates a new collection that is the result of adding the specified <paramref name="positions"/> to this collection.
/// </summary>
public OptionPositionCollection AddRange(IEnumerable<OptionPosition> positions)
{
return positions.Aggregate(this, (current, position) => current + position);
}
/// <summary>
/// Creates a new collection that is the result of removing the specified <paramref name="positions"/>
/// </summary>
public OptionPositionCollection RemoveRange(IEnumerable<OptionPosition> positions)
{
return AddRange(positions.Select(position => position.Negate()));
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions with the specified <paramref name="right"/>
/// </summary>
public OptionPositionCollection Slice(OptionRight right, bool includeUnderlying = true)
{
var rights = _rights.Remove(right.Invert());
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying && HasUnderlying)
{
positions = positions.Add(Underlying, UnderlyingPosition);
}
var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in rights.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
sides = sides.Add(position.Side, symbol);
positions = positions.Add(symbol, position);
strikes = strikes.Add(position.Strike, symbol);
expirations = expirations.Add(position.Expiration, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions with the specified <paramref name="side"/>
/// </summary>
public OptionPositionCollection Slice(PositionSide side, bool includeUnderlying = true)
{
var otherSides = GetOtherSides(side);
var sides = _sides.Remove(otherSides[0]).Remove(otherSides[1]);
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying && HasUnderlying)
{
positions = positions.Add(Underlying, UnderlyingPosition);
}
var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in sides.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
rights = rights.Add(position.Right, symbol);
positions = positions.Add(symbol, position);
strikes = strikes.Add(position.Strike, symbol);
expirations = expirations.Add(position.Expiration, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions matching the specified <paramref name="comparison"/> and <paramref name="strike"/>
/// </summary>
public OptionPositionCollection Slice(BinaryComparison comparison, decimal strike, bool includeUnderlying = true)
{
var strikes = comparison.Filter(_strikes, strike);
if (strikes.IsEmpty)
{
return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
}
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying)
{
OptionPosition underlyingPosition;
if (_positions.TryGetValue(Underlying, out underlyingPosition))
{
positions = positions.Add(Underlying, underlyingPosition);
}
}
var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in strikes.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
sides = sides.Add(position.Side, symbol);
positions = positions.Add(symbol, position);
rights = rights.Add(symbol.ID.OptionRight, symbol);
expirations = expirations.Add(symbol.ID.Date, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Slices this collection, returning a new collection containing only
/// positions matching the specified <paramref name="comparison"/> and <paramref name="expiration"/>
/// </summary>
public OptionPositionCollection Slice(BinaryComparison comparison, DateTime expiration, bool includeUnderlying = true)
{
var expirations = comparison.Filter(_expirations, expiration);
if (expirations.IsEmpty)
{
return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
}
var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
if (includeUnderlying)
{
OptionPosition underlyingPosition;
if (_positions.TryGetValue(Underlying, out underlyingPosition))
{
positions = positions.Add(Underlying, underlyingPosition);
}
}
var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
foreach (var symbol in expirations.SelectMany(kvp => kvp.Value))
{
var position = _positions[symbol];
sides = sides.Add(position.Side, symbol);
positions = positions.Add(symbol, position);
rights = rights.Add(symbol.ID.OptionRight, symbol);
strikes = strikes.Add(symbol.ID.StrikePrice, symbol);
}
return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="symbols"/>
/// </summary>
public IEnumerable<OptionPosition> ForSymbols(IEnumerable<Symbol> symbols)
{
foreach (var symbol in symbols)
{
OptionPosition position;
if (_positions.TryGetValue(symbol, out position))
{
yield return position;
}
}
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="right"/>
/// </summary>
public IEnumerable<OptionPosition> ForRight(OptionRight right)
{
ImmutableHashSet<Symbol> symbols;
return _rights.TryGetValue(right, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="side"/>
/// </summary>
public IEnumerable<OptionPosition> ForSide(PositionSide side)
{
ImmutableHashSet<Symbol> symbols;
return _sides.TryGetValue(side, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="strike"/>
/// </summary>
public IEnumerable<OptionPosition> ForStrike(decimal strike)
{
ImmutableHashSet<Symbol> symbols;
return _strikes.TryGetValue(strike, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>
/// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="expiration"/>
/// </summary>
public IEnumerable<OptionPosition> ForExpiration(DateTime expiration)
{
ImmutableHashSet<Symbol> symbols;
return _expirations.TryGetValue(expiration, out symbols)
? ForSymbols(symbols)
: Enumerable.Empty<OptionPosition>();
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
if (Count == 0)
{
return "Empty";
}
return HasUnderlying
? $"{UnderlyingQuantity} {Underlying.Value}: {_positions.Count - 1} contract positions"
: $"{Underlying.Value}: {_positions.Count} contract positions";
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<OptionPosition> GetEnumerator()
{
return _positions.Select(kvp => kvp.Value).GetEnumerator();
}
/// <summary>
/// Validates this collection returning an enumerable of validation errors.
/// This should only be invoked via tests and is automatically invoked via
/// the constructor in DEBUG builds.
/// </summary>
internal IEnumerable<string> Validate()
{
foreach (var kvp in _positions)
{
var position = kvp.Value;
var symbol = position.Symbol;
if (position.Quantity == 0)
{
yield return $"{position}: Quantity == 0";
}
if (!symbol.HasUnderlying)
{
continue;
}
ImmutableHashSet<Symbol> strikes;
if (!_strikes.TryGetValue(position.Strike, out strikes) || !strikes.Contains(symbol))
{
yield return $"{position}: Not indexed by strike price";
}
ImmutableHashSet<Symbol> expirations;
if (!_expirations.TryGetValue(position.Expiration, out expirations) || !expirations.Contains(symbol))
{
yield return $"{position}: Not indexed by expiration date";
}
}
}
private static readonly PositionSide[] OtherSidesForNone = {PositionSide.Short, PositionSide.Long};
private static readonly PositionSide[] OtherSidesForShort = {PositionSide.None, PositionSide.Long};
private static readonly PositionSide[] OtherSidesForLong = {PositionSide.Short, PositionSide.None};
private static PositionSide[] GetOtherSides(PositionSide side)
{
switch (side)
{
case PositionSide.Short: return OtherSidesForShort;
case PositionSide.None: return OtherSidesForNone;
case PositionSide.Long: return OtherSidesForLong;
default:
throw new ArgumentOutOfRangeException(nameof(side), side, null);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// OptionPositionCollection + Operator
/// </summary>
/// <param name="positions">Collection to add to</param>
/// <param name="position">OptionPosition to add</param>
/// <returns>OptionPositionCollection with the new position added</returns>
public static OptionPositionCollection operator+(OptionPositionCollection positions, OptionPosition position)
{
return positions.Add(position);
}
/// <summary>
/// OptionPositionCollection - Operator
/// </summary>
/// <param name="positions">Collection to remove from</param>
/// <param name="position">OptionPosition to remove</param>
/// <returns>OptionPositionCollection with the position removed</returns>
public static OptionPositionCollection operator-(OptionPositionCollection positions, OptionPosition position)
{
return positions.Remove(position);
}
}
}
@@ -0,0 +1,320 @@
/*
* 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Expressions;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a definitional object for an <see cref="OptionStrategy"/>. This definition is used to 'match' option
/// positions via <see cref="OptionPositionCollection"/>. The <see cref="OptionStrategyMatcher"/> utilizes a full
/// collection of these definitional objects in order to match an algorithm's option position holdings to the
/// set of strategies in an effort to reduce the total margin required for holding the positions.
/// </summary>
public class OptionStrategyDefinition : IEnumerable<OptionStrategyLegDefinition>
{
/// <summary>
/// Gets the definition's name
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the number of underlying lots required to match this definition. A lot size
/// is equal to the contract's multiplier and is usually equal to 100.
/// </summary>
public int UnderlyingLots { get; }
/// <summary>
/// Gets the option leg definitions. This list does NOT contain a definition for the
/// required underlying lots, due to its simplicity. Instead the required underlying
/// lots are defined via the <see cref="UnderlyingLots"/> property of the definition.
/// </summary>
public IReadOnlyList<OptionStrategyLegDefinition> Legs { get; }
/// <summary>
/// Gets the total number of legs, INCLUDING the underlying leg if applicable. This
/// is used to perform a coarse filter as the minimum number of unique positions in
/// the positions collection.
/// </summary>
public int LegCount => Legs.Count + (UnderlyingLots == 0 ? 0 : 1);
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyDefinition"/> class
/// </summary>
/// <param name="name">The definition's name</param>
/// <param name="underlyingLots">The required number of underlying lots</param>
/// <param name="legs">Definitions for each option leg</param>
public OptionStrategyDefinition(string name, int underlyingLots, IEnumerable<OptionStrategyLegDefinition> legs)
{
Name = name;
Legs = legs.ToList();
UnderlyingLots = underlyingLots;
}
/// <summary>
/// Creates the <see cref="OptionStrategy"/> instance using this definition and the provided leg matches
/// </summary>
public OptionStrategy CreateStrategy(IReadOnlyList<OptionStrategyLegDefinitionMatch> legs)
{
return OptionStrategy.Create(Name, Enumerable.Range(0, Math.Min(Legs.Count, legs.Count)).Select(i => Legs[i].CreateLegData(legs[i])));
}
/// <summary>
/// Attempts to match the positions to this definition exactly once, by evaluating the enumerable and
/// taking the first entry matched. If not match is found, then false is returned and <paramref name="match"/>
/// will be null.
/// </summary>
public bool TryMatchOnce(OptionStrategyMatcherOptions options, OptionPositionCollection positions, out OptionStrategyDefinitionMatch match)
{
match = Match(options, positions).FirstOrDefault();
return match != null;
}
/// <summary>
/// Determines all possible matches for this definition using the provided <paramref name="positions"/>.
/// This includes OVERLAPPING matches. It's up to the actual matcher to make decisions based on which
/// matches to accept. This allows the matcher to prioritize matching certain positions over others.
/// </summary>
public IEnumerable<OptionStrategyDefinitionMatch> Match(OptionPositionCollection positions)
{
return Match(OptionStrategyMatcherOptions.ForDefinitions(this), positions);
}
/// <summary>
/// Determines all possible matches for this definition using the provided <paramref name="positions"/>.
/// This includes OVERLAPPING matches. It's up to the actual matcher to make decisions based on which
/// matches to accept. This allows the matcher to prioritize matching certain positions over others.
/// </summary>
public IEnumerable<OptionStrategyDefinitionMatch> Match(
OptionStrategyMatcherOptions options,
OptionPositionCollection positions
)
{
// TODO : Pass OptionStrategyMatcherOptions in and respect applicable options
if (positions.Count < LegCount)
{
return Enumerable.Empty<OptionStrategyDefinitionMatch>();
}
var multiplier = int.MaxValue;
// first check underlying lots has correct sign and sufficient magnitude
var underlyingLotsSign = Math.Sign(UnderlyingLots);
if (underlyingLotsSign != 0)
{
var underlyingPositionSign = Math.Sign(positions.UnderlyingQuantity);
if (underlyingLotsSign != underlyingPositionSign ||
Math.Abs(positions.UnderlyingQuantity) < Math.Abs(UnderlyingLots))
{
return Enumerable.Empty<OptionStrategyDefinitionMatch>();
}
// set multiplier for underlying
multiplier = positions.UnderlyingQuantity / UnderlyingLots;
}
// TODO : Consider add OptionStrategyLegDefinition for underlying for consistency purposes.
// Might want to enforce that it's always the first leg definition as well for easier slicing.
return Match(options,
ImmutableList<OptionStrategyLegDefinitionMatch>.Empty,
ImmutableList<OptionPosition>.Empty,
positions,
multiplier
).Distinct();
}
private IEnumerable<OptionStrategyDefinitionMatch> Match(
OptionStrategyMatcherOptions options,
ImmutableList<OptionStrategyLegDefinitionMatch> legMatches,
ImmutableList<OptionPosition> legPositions,
OptionPositionCollection positions,
int multiplier
)
{
var nextLegIndex = legPositions.Count;
if (nextLegIndex == Legs.Count)
{
if (nextLegIndex > 0)
{
yield return new OptionStrategyDefinitionMatch(this, legMatches, multiplier);
}
}
else if (positions.Count >= LegCount - nextLegIndex)
{
// grab the next leg definition and perform the match, restricting total to configured maximum per leg
var nextLeg = Legs[nextLegIndex];
var maxLegMatch = options.GetMaximumLegMatches(nextLegIndex);
foreach (var legMatch in nextLeg.Match(options, legPositions, positions).Take(maxLegMatch))
{
// add match to the match we're constructing and deduct matched position from positions collection
// we track the min multiplier in line so when we're done, we have the total number of matches for
// the matched set of positions in this 'thread' (OptionStrategy.Quantity)
foreach (var definitionMatch in Match(options,
legMatches.Add(legMatch),
legPositions.Add(legMatch.Position),
positions - legMatch.Position,
Math.Min(multiplier, legMatch.Multiplier)
))
{
yield return definitionMatch;
}
}
}
else
{
// positions.Count < LegsCount indicates a failed match
// could include partial matches, would allow an algorithm to determine if adding a
// new position could help reduce overall margin exposure by completing a strategy
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return Name;
}
/// <summary>
/// Factory function for creating definitions
/// </summary>
public static OptionStrategyDefinition Create(string name, int underlyingLots, params OptionStrategyLegDefinition[] legs)
{
return new OptionStrategyDefinition(name, underlyingLots, legs);
}
/// <summary>
/// Factory function for creating definitions
/// </summary>
public static OptionStrategyDefinition Create(string name, params OptionStrategyLegDefinition[] legs)
{
return new OptionStrategyDefinition(name, 0, legs);
}
/// <summary>
/// Factory function for creating definitions
/// </summary>
public static OptionStrategyDefinition Create(string name, params Func<Builder, Builder>[] predicates)
{
return predicates.Aggregate(new Builder(name),
(builder, predicate) => predicate(builder)
).Build();
}
/// <summary>
/// Factory function for creating a call leg definition
/// </summary>
public static OptionStrategyLegDefinition CallLeg(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
return OptionStrategyLegDefinition.Create(OptionRight.Call, quantity, predicates);
}
/// <summary>
/// Factory function for creating a put leg definition
/// </summary>
public static OptionStrategyLegDefinition PutLeg(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
return OptionStrategyLegDefinition.Create(OptionRight.Put, quantity, predicates);
}
/// <summary>
/// Builder class supporting fluent syntax in constructing <see cref="OptionStrategyDefinition"/>.
/// </summary>
public class Builder
{
private readonly string _name;
private int _underlyingLots;
private List<OptionStrategyLegDefinition> _legs;
/// <summary>
/// Initializes a new instance of the <see cref="Builder"/> class
/// </summary>
public Builder(string name)
{
_name = name;
_legs = new List<OptionStrategyLegDefinition>();
}
/// <summary>
/// Sets the required number of underlying lots
/// </summary>
public Builder WithUnderlyingLots(int lots)
{
if (_underlyingLots != 0)
{
throw new InvalidOperationException("Underlying lots has already been set.");
}
_underlyingLots = lots;
return this;
}
/// <summary>
/// Adds a call leg
/// </summary>
public Builder WithCall(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
_legs.Add(OptionStrategyLegDefinition.Create(OptionRight.Call, quantity, predicates));
return this;
}
/// <summary>
/// Adds a put leg
/// </summary>
public Builder WithPut(int quantity,
params Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>[] predicates
)
{
_legs.Add(OptionStrategyLegDefinition.Create(OptionRight.Put, quantity, predicates));
return this;
}
/// <summary>
/// Builds the <see cref="OptionStrategyDefinition"/>
/// </summary>
public OptionStrategyDefinition Build()
{
return new OptionStrategyDefinition(_name, _underlyingLots, _legs);
}
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<OptionStrategyLegDefinition> GetEnumerator()
{
return Legs.GetEnumerator();
}
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,209 @@
/*
* 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.Linq;
using System.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines a match of <see cref="OptionPosition"/> to a <see cref="OptionStrategyDefinition"/>
/// </summary>
public class OptionStrategyDefinitionMatch : IEquatable<OptionStrategyDefinitionMatch>
{
/// <summary>
/// The <see cref="OptionStrategyDefinition"/> matched
/// </summary>
public OptionStrategyDefinition Definition { get; }
/// <summary>
/// The number of times the definition is able to match the available positions.
/// Since definitions are formed at the 'unit' level, such as having 1 contract,
/// the multiplier defines how many times the definition matched. This multiplier
/// is used to scale the quantity defined in each leg definition when creating the
/// <see cref="OptionStrategy"/> objects.
/// </summary>
public int Multiplier { get; }
/// <summary>
/// The <see cref="OptionStrategyLegDefinitionMatch"/> instances matched to the definition.
/// </summary>
public IReadOnlyList<OptionStrategyLegDefinitionMatch> Legs { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyDefinitionMatch"/> class
/// </summary>
public OptionStrategyDefinitionMatch(
OptionStrategyDefinition definition,
IReadOnlyList<OptionStrategyLegDefinitionMatch> legs,
int multiplier
)
{
Legs = legs;
Multiplier = multiplier;
Definition = definition;
}
/// <summary>
/// Deducts the matched positions from the specified <paramref name="positions"/> taking into account the multiplier
/// </summary>
public OptionPositionCollection RemoveFrom(OptionPositionCollection positions)
{
var optionPositions = Legs.Select(leg => leg.CreateOptionPosition(Multiplier));
if (Definition.UnderlyingLots != 0)
{
optionPositions = optionPositions.Concat(new[]
{
new OptionPosition(Legs[0].Position.Symbol.Underlying, Definition.UnderlyingLots * Multiplier)
});
}
return positions.RemoveRange(optionPositions);
}
/// <summary>
/// Creates the <see cref="OptionStrategy"/> instance this match represents
/// </summary>
public OptionStrategy CreateStrategy()
{
var legs = Legs
// if Definition.UnderlyingLots is not 0, we will create the underlying leg separately
.Where(leg => leg.Position.Symbol.HasUnderlying || Definition.UnderlyingLots == 0)
.Select(leg => leg.CreateOptionStrategyLeg(Multiplier));
if (Definition.UnderlyingLots != 0)
{
legs = legs.Concat([OptionStrategy.UnderlyingLegData.Create(Definition.UnderlyingLots * Multiplier, Legs[0].Position.Underlying)]);
}
return OptionStrategy.Create(Definition.Name, legs);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
public bool Equals(OptionStrategyDefinitionMatch other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!Equals(Definition, other.Definition))
{
return false;
}
// index legs by OptionPosition so we can do the equality while ignoring ordering
var positions = other.Legs.ToDictionary(leg => leg.Position, leg => leg.Multiplier);
foreach (var leg in other.Legs)
{
int multiplier;
if (!positions.TryGetValue(leg.Position, out multiplier))
{
return false;
}
if (leg.Multiplier != multiplier)
{
return false;
}
}
return true;
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object. </param>
/// <returns>true if the specified object is equal to the current object; otherwise, false.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((OptionStrategyDefinitionMatch) obj);
}
/// <summary>Serves as the default hash function. </summary>
/// <returns>A hash code for the current object.</returns>
public override int GetHashCode()
{
unchecked
{
// we want to ensure that the ordering of legs does not impact equality operators in
// pursuit of this, we compute the hash codes of each leg, placing them into an array
// and then sort the array. using the sorted array, aggregates the hash codes
var hashCode = Definition.GetHashCode();
var arr = new int[Legs.Count];
for (int i = 0; i < Legs.Count; i++)
{
arr[i] = Legs[i].GetHashCode();
}
Array.Sort(arr);
for (int i = 0; i < arr.Length; i++)
{
hashCode = (hashCode * 397) ^ arr[i];
}
return hashCode;
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return $"{Definition.Name}: {string.Join("|", Legs.Select(leg => leg.Position))}";
}
/// <summary>
/// OptionStrategyDefinitionMatch == Operator
/// </summary>
/// <returns>True if they are the same</returns>
public static bool operator ==(OptionStrategyDefinitionMatch left, OptionStrategyDefinitionMatch right)
{
return Equals(left, right);
}
/// <summary>
/// OptionStrategyDefinitionMatch != Operator
/// </summary>
/// <returns>True if they are not the same</returns>
public static bool operator !=(OptionStrategyDefinitionMatch left, OptionStrategyDefinitionMatch right)
{
return !Equals(left, right);
}
}
}
@@ -0,0 +1,571 @@
/*
* 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.Immutable;
using System.Linq;
using System.Reflection;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides a listing of pre-defined <see cref="OptionStrategyDefinition"/>
/// These definitions are blueprints for <see cref="OptionStrategy"/> instances.
/// Factory functions for those can be found at <see cref="OptionStrategies"/>
/// </summary>
public static class OptionStrategyDefinitions
{
// lazy since 'AllDefinitions' is at top of file and static members are evaluated in order
private static readonly Lazy<ImmutableList<OptionStrategyDefinition>> All
= new Lazy<ImmutableList<OptionStrategyDefinition>>(() =>
typeof(OptionStrategyDefinitions)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(property => property.PropertyType == typeof(OptionStrategyDefinition))
.Select(property => (OptionStrategyDefinition)property.GetValue(null))
.ToImmutableList()
);
/// <summary>
/// Collection of all OptionStrategyDefinitions
/// </summary>
public static ImmutableList<OptionStrategyDefinition> AllDefinitions
{
get
{
var strategies = All.Value;
return strategies
.SelectMany(optionStrategy => {
// when selling the strategy can get reverted and it's still valid, we need the definition to match against
var inverted = new OptionStrategyDefinition(optionStrategy.Name, optionStrategy.UnderlyingLots * -1,
optionStrategy.Legs.Select(leg => new OptionStrategyLegDefinition(leg.Right, leg.Quantity * -1, leg)));
if (strategies.Any(strategy => strategy.UnderlyingLots == inverted.UnderlyingLots
&& strategy.Legs.Count == inverted.Legs.Count
&& strategy.Legs.All(leg => inverted.Legs.
Any(invertedLeg => invertedLeg.Right == leg.Right
&& leg.Quantity == invertedLeg.Quantity
&& leg.All(predicate => invertedLeg.Any(invertedPredicate => invertedPredicate.ToString() == predicate.ToString()))))))
{
// some strategies inverted have a different name we already know, let's skip those
return new[] { optionStrategy };
}
return new[] { optionStrategy, inverted };
})
.ToImmutableList();
}
}
/// <summary>
/// Hold 1 lot of the underlying and sell 1 call contract
/// </summary>
/// <remarks>Inverse of the <see cref="ProtectiveCall"/></remarks>
public static OptionStrategyDefinition CoveredCall { get; }
= OptionStrategyDefinition.Create("Covered Call", 1,
OptionStrategyDefinition.CallLeg(-1)
);
/// <summary>
/// Hold -1 lot of the underlying and buy 1 call contract
/// </summary>
/// <remarks>Inverse of the <see cref="CoveredCall"/></remarks>
public static OptionStrategyDefinition ProtectiveCall { get; }
= OptionStrategyDefinition.Create("Protective Call", -1,
OptionStrategyDefinition.CallLeg(1)
);
/// <summary>
/// Hold -1 lot of the underlying and sell 1 put contract
/// </summary>
/// <remarks>Inverse of the <see cref="ProtectivePut"/></remarks>
public static OptionStrategyDefinition CoveredPut { get; }
= OptionStrategyDefinition.Create("Covered Put", -1,
OptionStrategyDefinition.PutLeg(-1)
);
/// <summary>
/// Hold 1 lot of the underlying and buy 1 put contract
/// </summary>
/// <remarks>Inverse of the <see cref="CoveredPut"/></remarks>
public static OptionStrategyDefinition ProtectivePut { get; }
= OptionStrategyDefinition.Create("Protective Put", 1,
OptionStrategyDefinition.PutLeg(1)
);
/// <summary>
/// Hold 1 lot of the underlying, sell 1 call contract and buy 1 put contract.
/// The strike price of the short call is below the strike of the long put with the same expiration.
/// </summary>
/// <remarks>Combination of <see cref="CoveredCall"/> and <see cref="ProtectivePut"/></remarks>
public static OptionStrategyDefinition ProtectiveCollar { get; }
= OptionStrategyDefinition.Create("Protective Collar", 1,
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Hold 1 lot of the underlying, sell 1 call contract and buy 1 put contract.
/// The strike price of the call and put are the same, with the same expiration.
/// </summary>
/// <remarks>A special case of <see cref="ProtectiveCollar"/></remarks>
public static OptionStrategyDefinition Conversion { get; }
= OptionStrategyDefinition.Create("Conversion", 1,
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Hold 1 lot of the underlying, sell 1 call contract and buy 1 put contract.
/// The strike price of the call and put are the same, with the same expiration.
/// </summary>
/// <remarks>Inverse of <see cref="Conversion"/></remarks>
public static OptionStrategyDefinition ReverseConversion { get; }
= OptionStrategyDefinition.Create("Reverse Conversion", -1,
OptionStrategyDefinition.CallLeg(1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Sell 1 call contract without holding the underlying
/// </summary>
public static OptionStrategyDefinition NakedCall { get; }
= OptionStrategyDefinition.Create("Naked Call",
OptionStrategyDefinition.CallLeg(-1)
);
/// <summary>
/// Sell 1 put contract without holding the underlying
/// </summary>
public static OptionStrategyDefinition NakedPut { get; }
= OptionStrategyDefinition.Create("Naked Put",
OptionStrategyDefinition.PutLeg(-1)
);
/// <summary>
/// Bear Call Spread strategy consists of two calls with the same expiration but different strikes.
/// The strike price of the short call is below the strike of the long call. This is a credit spread.
/// </summary>
public static OptionStrategyDefinition BearCallSpread { get; }
= OptionStrategyDefinition.Create("Bear Call Spread",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bear Put Spread strategy consists of two puts with the same expiration but different strikes.
/// The strike price of the short put is below the strike of the long put. This is a debit spread.
/// </summary>
public static OptionStrategyDefinition BearPutSpread { get; }
= OptionStrategyDefinition.Create("Bear Put Spread",
OptionStrategyDefinition.PutLeg(1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bull Call Spread strategy consists of two calls with the same expiration but different strikes.
/// The strike price of the short call is higher than the strike of the long call. This is a debit spread.
/// </summary>
public static OptionStrategyDefinition BullCallSpread { get; }
= OptionStrategyDefinition.Create("Bull Call Spread",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Method creates new Bull Put Spread strategy, that consists of two puts with the same expiration but
/// different strikes. The strike price of the short put is above the strike of the long put. This is a
/// credit spread.
/// </summary>
public static OptionStrategyDefinition BullPutSpread { get; }
= OptionStrategyDefinition.Create("Bull Put Spread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Straddle strategy is a combination of buying a call and buying a put, both with the same strike price
/// and expiration.
/// </summary>
public static OptionStrategyDefinition Straddle { get; }
= OptionStrategyDefinition.Create("Straddle",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Straddle strategy is a combination of selling a call and selling a put, both with the same strike price
/// and expiration.
/// </summary>
/// <remarks>Inverse of the <see cref="Straddle"/></remarks>
public static OptionStrategyDefinition ShortStraddle { get; }
= OptionStrategyDefinition.Create("Short Straddle",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Strangle strategy consists of buying a call option and a put option with the same expiration date.
/// The strike price of the call is above the strike of the put.
/// </summary>
public static OptionStrategyDefinition Strangle { get; }
= OptionStrategyDefinition.Create("Strangle",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Strangle strategy consists of selling a call option and a put option with the same expiration date.
/// The strike price of the call is above the strike of the put.
/// </summary>
/// <remarks>Inverse of the <see cref="Strangle"/></remarks>
public static OptionStrategyDefinition ShortStrangle { get; }
= OptionStrategyDefinition.Create("Short Strangle",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Butterfly Call strategy consists of two short calls at a middle strike, and one long call each at a lower
/// and upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ButterflyCall { get; }
= OptionStrategyDefinition.Create("Butterfly Call",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Butterfly Call strategy consists of two long calls at a middle strike, and one short call each at a lower
/// and upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ShortButterflyCall { get; }
= OptionStrategyDefinition.Create("Short Butterfly Call",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Butterfly Put strategy consists of two short puts at a middle strike, and one long put each at a lower and
/// upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ButterflyPut { get; }
= OptionStrategyDefinition.Create("Butterfly Put",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Short Butterfly Put strategy consists of two long puts at a middle strike, and one short put each at a lower and
/// upper strike. The upper and lower strikes must both be equidistant from the middle strike.
/// </summary>
public static OptionStrategyDefinition ShortButterflyPut { get; }
= OptionStrategyDefinition.Create("Short Butterfly Put",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+2, (legs, p) => p.Strike >= legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike >= legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration,
(legs, p) => p.Strike - legs[1].Strike == legs[1].Strike - legs[0].Strike)
);
/// <summary>
/// Call Calendar Spread strategy is a short one call option and long a second call option with a more distant
/// expiration.
/// </summary>
public static OptionStrategyDefinition CallCalendarSpread { get; }
= OptionStrategyDefinition.Create("Call Calendar Spread",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Short Call Calendar Spread strategy is long one call option and short a second call option with a more distant
/// expiration.
/// </summary>
/// <remarks>Inverse of the <see cref="CallCalendarSpread"/></remarks>
public static OptionStrategyDefinition ShortCallCalendarSpread { get; }
= OptionStrategyDefinition.Create("Short Call Calendar Spread",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Put Calendar Spread strategy is a short one put option and long a second put option with a more distant
/// expiration.
/// </summary>
public static OptionStrategyDefinition PutCalendarSpread { get; }
= OptionStrategyDefinition.Create("Put Calendar Spread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Short Put Calendar Spread strategy is long one put option and short a second put option with a more distant
/// expiration.
/// </summary>
/// <remarks>Inverse of the <see cref="PutCalendarSpread"/></remarks>
public static OptionStrategyDefinition ShortPutCalendarSpread { get; }
= OptionStrategyDefinition.Create("Short Put Calendar Spread",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration > legs[0].Expiration)
);
/// <summary>
/// Iron Butterfly strategy consists of a short ATM call, a short ATM put, a long OTM call, and a long OTM put.
/// The strike spread between ATM and OTM call and put are the same. All at the same expiration date.
/// </summary>
public static OptionStrategyDefinition IronButterfly { get; }
= OptionStrategyDefinition.Create("Iron Butterfly",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike * 2 - legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Iron Butterfly strategy consists of a long ATM call, a long ATM put, a short OTM call, and a short OTM put.
/// The strike spread between ATM and OTM call and put are the same. All at the same expiration date.
/// </summary>
public static OptionStrategyDefinition ShortIronButterfly { get; }
= OptionStrategyDefinition.Create("Short Iron Butterfly",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike * 2 - legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Iron Condor strategy is buying a put, selling a put with a higher strike price, selling a call and buying a call with a higher strike price.
/// All at the same expiration date
/// </summary>
public static OptionStrategyDefinition IronCondor { get; }
= OptionStrategyDefinition.Create("Iron Condor",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike > legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(1, (legs, p) => p.Strike > legs[2].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Iron Condor strategy is selling a put, buying a put with a higher strike price, buying a call and selling a call with a higher strike price.
/// All at the same expiration date
/// </summary>
public static OptionStrategyDefinition ShortIronCondor { get; }
= OptionStrategyDefinition.Create("Short Iron Condor",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike > legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, p) => p.Strike > legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, p) => p.Strike > legs[2].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Long Box Spread strategy is long 1 call and short 1 put with the same strike,
/// while short 1 call and long 1 put with a higher, same strike. All options have the same expiry.
/// expiration.
/// </summary>
public static OptionStrategyDefinition BoxSpread { get; }
= OptionStrategyDefinition.Create("Box Spread",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Box Spread strategy is short 1 call and long 1 put with the same strike,
/// while long 1 call and short 1 put with a higher, same strike. All options have the same expiry.
/// expiration.
/// </summary>
public static OptionStrategyDefinition ShortBoxSpread { get; }
= OptionStrategyDefinition.Create("Short Box Spread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Jelly Roll is short 1 call and long 1 call with the same strike but further expiry, together with
/// long 1 put and short 1 put with the same strike and expiries as calls.
/// </summary>
public static OptionStrategyDefinition JellyRoll { get; }
= OptionStrategyDefinition.Create("Jelly Roll",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration > legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[1].Expiration)
);
/// <summary>
/// Short Jelly Roll is long 1 call and short 1 call with the same strike but further expiry, together with
/// short 1 put and long 1 put with the same strike and expiries as calls.
/// </summary>
public static OptionStrategyDefinition ShortJellyRoll { get; }
= OptionStrategyDefinition.Create("Short Jelly Roll",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike == legs[0].Strike,
(legs, c) => c.Expiration > legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike == legs[0].Strike,
(legs, p) => p.Expiration == legs[1].Expiration)
);
/// <summary>
/// Bear Call Ladder strategy is short 1 call and long 2 calls, with ascending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BearCallLadder { get; }
= OptionStrategyDefinition.Create("Bear Call Ladder",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(+1, (legs, c) => c.Strike > legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bear Put Ladder strategy is long 1 put and short 2 puts, with descending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BearPutLadder { get; }
= OptionStrategyDefinition.Create("Bear Put Ladder",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(-1, (legs, p) => p.Strike < legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bull Call Ladder strategy is long 1 call and short 2 calls, with ascending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BullCallLadder { get; }
= OptionStrategyDefinition.Create("Bull Call Ladder",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration),
OptionStrategyDefinition.CallLeg(-1, (legs, c) => c.Strike > legs[1].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Bull Put Ladder strategy is short 1 put and long 2 puts, with descending strike prices in order,
/// All options have the same expiry.
/// </summary>
public static OptionStrategyDefinition BullPutLadder { get; }
= OptionStrategyDefinition.Create("Bull Put Ladder",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration),
OptionStrategyDefinition.PutLeg(+1, (legs, p) => p.Strike < legs[1].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Call Backspread strategy is short 1 call and long 2 calls, with ascending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition CallBackspread { get; }
= OptionStrategyDefinition.Create("Call Backspread",
OptionStrategyDefinition.CallLeg(-1),
OptionStrategyDefinition.CallLeg(+2, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Put Backspread strategy is short 1 put and long 2 puts, with descending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition PutBackspread { get; }
= OptionStrategyDefinition.Create("Put Backspread",
OptionStrategyDefinition.PutLeg(-1),
OptionStrategyDefinition.PutLeg(+2, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Call Backspread strategy is long 1 call and short 2 calls, with ascending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition ShortCallBackspread { get; }
= OptionStrategyDefinition.Create("Short Call Backspread",
OptionStrategyDefinition.CallLeg(+1),
OptionStrategyDefinition.CallLeg(-2, (legs, c) => c.Strike > legs[0].Strike,
(legs, c) => c.Expiration == legs[0].Expiration)
);
/// <summary>
/// Short Put Backspread strategy is long 1 put and short 2 puts, with descending strike prices in order,
/// both options have the same expiry.
/// </summary>
public static OptionStrategyDefinition ShortPutBackspread { get; }
= OptionStrategyDefinition.Create("Short Put Backspread",
OptionStrategyDefinition.PutLeg(+1),
OptionStrategyDefinition.PutLeg(-2, (legs, p) => p.Strike < legs[0].Strike,
(legs, p) => p.Expiration == legs[0].Expiration)
);
}
}
@@ -0,0 +1,199 @@
/*
* 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 QuantConnect.Orders;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines a single option leg in an option strategy. This definition supports direct
/// match (does position X match the definition) and position collection filtering (filter
/// collection to include matches)
/// </summary>
public class OptionStrategyLegDefinition : IEnumerable<OptionStrategyLegPredicate>
{
private readonly OptionStrategyLegPredicate[] _predicates;
/// <summary>
/// Gets the unit quantity
/// </summary>
public int Quantity { get; }
/// <summary>
/// Gets the contract right
/// </summary>
public OptionRight Right { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyLegDefinition"/> class
/// </summary>
/// <param name="right">The leg's contract right</param>
/// <param name="quantity">The leg's unit quantity</param>
/// <param name="predicates">The conditions a position must meet in order to match this definition</param>
public OptionStrategyLegDefinition(OptionRight right, int quantity, IEnumerable<OptionStrategyLegPredicate> predicates)
{
Right = right;
Quantity = quantity;
_predicates = predicates.ToArray();
}
/// <summary>
/// Yields all possible matches for this leg definition held within the collection of <paramref name="positions"/>
/// </summary>
/// <param name="options">Strategy matcher options guiding matching behaviors</param>
/// <param name="legs">The preceding legs already matched for the parent strategy definition</param>
/// <param name="positions">The remaining, unmatched positions available to be matched against</param>
/// <returns>An enumerable of potential matches</returns>
public IEnumerable<OptionStrategyLegDefinitionMatch> Match(
OptionStrategyMatcherOptions options,
IReadOnlyList<OptionPosition> legs,
OptionPositionCollection positions
)
{
foreach (var position in options.Enumerate(Filter(legs, positions, false)))
{
var multiplier = position.Quantity / Quantity;
if (multiplier != 0)
{
yield return new OptionStrategyLegDefinitionMatch(multiplier,
position.WithQuantity(multiplier * Quantity)
);
}
}
}
/// <summary>
/// Filters the provided <paramref name="positions"/> collection such that any remaining positions are all
/// valid options that match this leg definition instance.
/// </summary>
public OptionPositionCollection Filter(IReadOnlyList<OptionPosition> legs, OptionPositionCollection positions, bool includeUnderlying = true)
{
// first filter down to applicable right
positions = positions.Slice(Right, includeUnderlying);
if (positions.IsEmpty)
{
return positions;
}
// second filter according to the required side
var side = (PositionSide) Math.Sign(Quantity);
positions = positions.Slice(side, includeUnderlying);
if (positions.IsEmpty)
{
return positions;
}
// these are ordered such that indexed filters are performed force and
// opaque/complex predicates follow since they require full table scans
foreach (var predicate in _predicates)
{
positions = predicate.Filter(legs, positions, includeUnderlying);
if (positions.IsEmpty)
{
break;
}
}
// at this point, every position in the positions
// collection is a valid match for this definition
return positions;
}
/// <summary>
/// Creates the appropriate <see cref="Leg"/> for the specified <paramref name="match"/>
/// </summary>
public Leg CreateLegData(OptionStrategyLegDefinitionMatch match)
{
return CreateLegData(
match.Position.Symbol,
match.Position.Quantity / Quantity
);
}
/// <summary>
/// Creates the appropriate <see cref="OptionStrategy.LegData"/> with the specified <paramref name="quantity"/>
/// </summary>
public static Leg CreateLegData(Symbol symbol, int quantity)
{
if (symbol.SecurityType == SecurityType.Option)
{
return OptionStrategy.OptionLegData.Create(quantity, symbol);
}
return OptionStrategy.UnderlyingLegData.Create(quantity);
}
/// <summary>
/// Determines whether or not this leg definition matches the specified <paramref name="position"/>,
/// and if so, what the resulting quantity of the <see cref="OptionStrategy.OptionLegData"/> should be.
/// </summary>
public bool TryMatch(OptionPosition position, out Leg leg)
{
if (Right != position.Right ||
Math.Sign(Quantity) != Math.Sign(position.Quantity))
{
leg = null;
return false;
}
var quantity = position.Quantity / Quantity;
if (quantity == 0)
{
leg = null;
return false;
}
leg = position.Symbol.SecurityType == SecurityType.Option
? (Leg) OptionStrategy.OptionLegData.Create(quantity, position.Symbol)
: OptionStrategy.UnderlyingLegData.Create(quantity);
return true;
}
/// <summary>
/// Creates a new <see cref="OptionStrategyLegDefinition"/> matching the specified parameters
/// </summary>
public static OptionStrategyLegDefinition Create(OptionRight right, int quantity,
IEnumerable<Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>>> predicates
)
{
return new OptionStrategyLegDefinition(right, quantity,
// sort predicates such that indexed predicates are evaluated first
// this leaves fewer positions to be evaluated by the full table scan
predicates.Select(OptionStrategyLegPredicate.Create).OrderBy(p => p.IsIndexed ? 0 : 1)
);
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<OptionStrategyLegPredicate> GetEnumerator()
{
foreach (var predicate in _predicates)
{
yield return predicate;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,161 @@
/*
* 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 QuantConnect.Orders;
using System;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines the item result type of <see cref="OptionStrategyLegDefinition.Match"/>, containing the number of
/// times the leg definition matched the position (<see cref="Multiplier"/>) and applicable portion of the position.
/// </summary>
public struct OptionStrategyLegDefinitionMatch : IEquatable<OptionStrategyLegDefinitionMatch>
{
/// <summary>
/// The number of times the definition is able to match the position. For example,
/// if the definition requires +2 contracts and the algorithm's position has +5
/// contracts, then this multiplier would equal 2.
/// </summary>
public int Multiplier { get; }
/// <summary>
/// The position that was successfully matched with the total quantity matched. For example,
/// if the definition requires +2 contracts and this multiplier equals 2, then this position
/// would have a quantity of 4. This may be different than the remaining/total quantity
/// available in the positions collection.
/// </summary>
public OptionPosition Position { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyLegDefinitionMatch"/> struct
/// </summary>
/// <param name="multiplier">The number of times the positions matched the leg definition</param>
/// <param name="position">The position that matched the leg definition</param>
public OptionStrategyLegDefinitionMatch(int multiplier, OptionPosition position)
{
Position = position;
Multiplier = multiplier;
}
/// <summary>
/// Creates the appropriate type of <see cref="Leg"/> for this matched position
/// </summary>
/// <param name="multiplier">The multiplier to use for creating the leg data. This multiplier will be
/// the minimum multiplier of all legs within a strategy definition match. Each leg defines its own
/// multiplier which is the max matches for that leg and the strategy definition's multiplier is the
/// min of the individual legs.</param>
public Leg CreateOptionStrategyLeg(int multiplier)
{
var quantity = Position.Quantity;
if (Multiplier != multiplier)
{
if (multiplier > Multiplier)
{
throw new ArgumentOutOfRangeException(nameof(multiplier), "Unable to create strategy leg with a larger multiplier than matched.");
}
// back out the unit quantity and scale it up to the requested multiplier
var unit = Position.Quantity / Multiplier;
quantity = unit * multiplier;
}
return Position.IsUnderlying
? (Leg) OptionStrategy.UnderlyingLegData.Create(quantity, Position.Symbol)
: OptionStrategy.OptionLegData.Create(quantity, Position.Symbol);
}
/// <summary>
/// Creates the appropriate <see cref="OptionPosition"/> for this matched position
/// </summary>
/// <param name="multiplier">The multiplier to use for creating the OptionPosition. This multiplier will be
/// the minimum multiplier of all legs within a strategy definition match. Each leg defines its own
/// multiplier which is the max matches for that leg and the strategy definition's multiplier is the
/// min of the individual legs.</param>
public OptionPosition CreateOptionPosition(int multiplier)
{
var quantity = Position.Quantity;
if (Multiplier != multiplier)
{
if (multiplier > Multiplier)
{
throw new ArgumentOutOfRangeException(nameof(multiplier), "Unable to create strategy leg with a larger multiplier than matched.");
}
// back out the unit quantity and scale it up to the requested multiplier
var unit = Position.Quantity / Multiplier;
quantity = unit * multiplier;
}
return new OptionPosition(Position.Symbol, quantity);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
public bool Equals(OptionStrategyLegDefinitionMatch other)
{
return Multiplier == other.Multiplier && Position.Equals(other.Position);
}
/// <summary>Indicates whether this instance and a specified object are equal.</summary>
/// <param name="obj">The object to compare with the current instance. </param>
/// <returns>true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is OptionStrategyLegDefinitionMatch && Equals((OptionStrategyLegDefinitionMatch) obj);
}
/// <summary>Returns the hash code for this instance.</summary>
/// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
public override int GetHashCode()
{
unchecked
{
return (Multiplier * 397) ^ Position.GetHashCode();
}
}
/// <summary>Returns the fully qualified type name of this instance.</summary>
/// <returns>The fully qualified type name.</returns>
public override string ToString()
{
return $"{Multiplier} Matches|{Position}";
}
/// <summary>
/// OptionStrategyLegDefinitionMatch == Operator
/// </summary>
/// <returns>True if they are equal</returns>
public static bool operator ==(OptionStrategyLegDefinitionMatch left, OptionStrategyLegDefinitionMatch right)
{
return left.Equals(right);
}
/// <summary>
/// OptionStrategyLegDefinitionMatch != Operator
/// </summary>
/// <returns>True if they are not equal</returns>
public static bool operator !=(OptionStrategyLegDefinitionMatch left, OptionStrategyLegDefinitionMatch right)
{
return !left.Equals(right);
}
}
}
@@ -0,0 +1,245 @@
/*
* 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
{
/// <summary>
/// Defines a condition under which a particular <see cref="OptionPosition"/> can be combined with
/// a preceding list of leg (also of type <see cref="OptionPosition"/>) to achieve a particular
/// option strategy.
/// </summary>
public class OptionStrategyLegPredicate
{
private readonly BinaryComparison _comparison;
private readonly IOptionStrategyLegPredicateReferenceValue _reference;
private readonly Func<IReadOnlyList<OptionPosition>, OptionPosition, bool> _predicate;
private readonly Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> _expression;
/// <summary>
/// Determines whether or not this predicate is able to utilize <see cref="OptionPositionCollection"/> indexes.
/// </summary>
public bool IsIndexed => _comparison != null && _reference != null;
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyLegPredicate"/> class
/// </summary>
/// <param name="comparison">The <see cref="BinaryComparison"/> invoked</param>
/// <param name="reference">The reference value, such as a strike price, encapsulated within the
/// <see cref="IOptionStrategyLegPredicateReferenceValue"/> to enable resolving the value from different potential sets.</param>
/// <param name="predicate">The compiled predicate expression</param>
/// <param name="expression">The predicate expression, from which, all other values were derived.</param>
public OptionStrategyLegPredicate(
BinaryComparison comparison,
IOptionStrategyLegPredicateReferenceValue reference,
Func<IReadOnlyList<OptionPosition>, OptionPosition, bool> predicate,
Expression<Func<IReadOnlyList<OptionPosition>, OptionPosition, bool>> expression
)
{
_reference = reference;
_predicate = predicate;
_comparison = comparison;
_expression = expression;
}
/// <summary>
/// Determines whether or not the provided combination of preceding <paramref name="legs"/>
/// and current <paramref name="position"/> adhere to this predicate's requirements.
/// </summary>
public bool Matches(IReadOnlyList<OptionPosition> 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;
}
}
/// <summary>
/// Filters the specified <paramref name="positions"/> by applying this predicate based on the referenced legs.
/// </summary>
public OptionPositionCollection Filter(IReadOnlyList<OptionPosition> 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();
}
}
/// <summary>
/// Gets the underlying <see cref="IOptionStrategyLegPredicateReferenceValue"/> value used by this predicate.
/// </summary>
public IOptionStrategyLegPredicateReferenceValue GetReferenceValue()
{
return _reference;
}
/// <summary>
/// Creates a new <see cref="OptionStrategyLegPredicate"/> from the specified predicate <paramref name="expression"/>
/// </summary>
public static OptionStrategyLegPredicate Create(
Expression<Func<IReadOnlyList<OptionPosition>, 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<BinaryExpression>().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);
}
}
/// <summary>
/// Creates a new <see cref="IOptionStrategyLegPredicateReferenceValue"/> from the specified lambda parameters
/// and expression to be evaluated.
/// </summary>
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<MethodCallExpression>();
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<MemberExpression>().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)}"
);
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return _expression.ToString();
}
}
}
@@ -0,0 +1,71 @@
/*
* 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;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyLegPredicateReferenceValue"/> that references an option
/// leg from the list of already matched legs by index. The property referenced is defined by <see cref="PredicateTargetValue"/>
/// </summary>
public class OptionStrategyLegPredicateReferenceValue : IOptionStrategyLegPredicateReferenceValue
{
private readonly int _index;
/// <summary>
/// Gets the target of this value
/// </summary>
public PredicateTargetValue Target { get; }
/// <summary>
/// Initializes a new instance of the <see cref="IOptionStrategyLegPredicateReferenceValue"/> class
/// </summary>
/// <param name="index">The legs list index</param>
/// <param name="target">The property value being referenced</param>
public OptionStrategyLegPredicateReferenceValue(int index, PredicateTargetValue target)
{
_index = index;
Target = target;
}
/// <summary>
/// Resolves the value of the comparand specified in an <see cref="OptionStrategyLegPredicate"/>.
/// For example, the predicate may include ... > legs[0].Strike, and upon evaluation, we need to
/// be able to extract leg[0].Strike for the currently contemplated set of legs adhering to a
/// strategy's definition.
/// </summary>
public object Resolve(IReadOnlyList<OptionPosition> legs)
{
if (_index >= legs.Count)
{
throw new InvalidOperationException(
$"OptionStrategyLegPredicateReferenceValue[{_index}] is unable to be resolved. Only {legs.Count} legs were provided."
);
}
var leg = legs[_index];
switch (Target)
{
case PredicateTargetValue.Right: return leg.Right;
case PredicateTargetValue.Strike: return leg.Strike;
case PredicateTargetValue.Expiration: return leg.Expiration;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines a complete result from running the matcher on a collection of positions.
/// The matching process will return one these matches for every potential combination
/// of strategies conforming to the search settings and the positions provided.
/// </summary>
public class OptionStrategyMatch
{
/// <summary>
/// The strategies that were matched
/// </summary>
public List<OptionStrategy> Strategies { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyMatch"/> class
/// </summary>
public OptionStrategyMatch(List<OptionStrategy> strategies)
{
Strategies = strategies;
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.Collections.Generic;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Matches <see cref="OptionPositionCollection"/> against a collection of <see cref="OptionStrategyDefinition"/>
/// according to the <see cref="OptionStrategyMatcherOptions"/> provided.
/// </summary>
public class OptionStrategyMatcher
{
/// <summary>
/// Specifies options controlling how the matcher operates
/// </summary>
public OptionStrategyMatcherOptions Options { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyMatcher"/> class
/// </summary>
/// <param name="options">Specifies definitions and other options controlling the matcher</param>
public OptionStrategyMatcher(OptionStrategyMatcherOptions options)
{
Options = options;
}
// TODO : Implement matching multiple permutations and using the objective function to select the best solution
/// <summary>
/// Using the definitions provided in <see cref="Options"/>, attempts to match all <paramref name="positions"/>.
/// The resulting <see cref="OptionStrategyMatch"/> presents a single, valid solution for matching as many positions
/// as possible.
/// </summary>
public OptionStrategyMatch MatchOnce(OptionPositionCollection positions)
{
// these definitions are enumerated according to the configured IOptionStrategyDefinitionEnumerator
var strategies = new List<OptionStrategy>();
foreach (var definition in Options.Definitions)
{
// simplest implementation here is to match one at a time, updating positions in between
// a better implementation would be to evaluate all possible matches and make decisions
// prioritizing positions that would require more margin if not matched
OptionStrategyDefinitionMatch match;
while (definition.TryMatchOnce(Options, positions, out match))
{
positions = match.RemoveFrom(positions);
strategies.Add(match.CreateStrategy());
}
if (positions.IsEmpty)
{
break;
}
}
return new OptionStrategyMatch(strategies);
}
}
}
@@ -0,0 +1,258 @@
/*
* 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.Linq;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Defines options that influence how the matcher operates.
/// </summary>
/// <remarks>
/// Many properties in this type are not implemented in the matcher but are provided to document
/// the types of things that can be added to the matcher in the future as necessary. Some of the
/// features contemplated in this class would require updating the various matching/filtering/slicing
/// functions to accept these options, or a particular property. This is the case for the enumerators
/// which would be used to prioritize which positions to try and match first. A great implementation
/// of the <see cref="IOptionPositionCollectionEnumerator"/> would be to yield positions with the
/// highest margin requirements first. At time of writing, the goal is to achieve a workable rev0,
/// and we can later improve the efficiency/optimization of the matching process.
/// </remarks>
public class OptionStrategyMatcherOptions
{
/// <summary>
/// The maximum amount of time spent trying to find an optimal solution.
/// </summary>
public TimeSpan MaximumDuration { get; }
/// <summary>
/// The maximum number of matches to evaluate for the entire portfolio.
/// </summary>
public int MaximumSolutionCount { get; }
/// <summary>
/// Indexed by leg index, defines the max matches to evaluate per leg.
/// For example, MaximumCountPerLeg[1] is the max matches to evaluate
/// for the second leg (index=1).
/// </summary>
public IReadOnlyList<int> MaximumCountPerLeg { get; }
/// <summary>
/// The definitions to be used for matching.
/// </summary>
public IEnumerable<OptionStrategyDefinition> Definitions
=> _definitionEnumerator.Enumerate(_definitions);
/// <summary>
/// Objective function used to compare different match solutions for a given set of positions/definitions
/// </summary>
public IOptionStrategyMatchObjectiveFunction ObjectiveFunction { get; }
private readonly IReadOnlyList<OptionStrategyDefinition> _definitions;
private readonly IOptionPositionCollectionEnumerator _positionEnumerator;
private readonly IOptionStrategyDefinitionEnumerator _definitionEnumerator;
/// <summary>
/// Initializes a new instance of the <see cref="OptionStrategyMatcherOptions"/> class, providing
/// options that control the behavior of the <see cref="OptionStrategyMatcher"/>
/// </summary>
public OptionStrategyMatcherOptions(
IReadOnlyList<OptionStrategyDefinition> definitions,
IReadOnlyList<int> maximumCountPerLeg,
TimeSpan maximumDuration = default(TimeSpan),
int maximumSolutionCount = 100,
IOptionStrategyDefinitionEnumerator definitionEnumerator = null,
IOptionStrategyMatchObjectiveFunction objectiveFunction = null,
IOptionPositionCollectionEnumerator positionEnumerator = null
)
{
if (maximumDuration == default(TimeSpan))
{
maximumDuration = Time.OneMinute;
}
if (definitionEnumerator == null)
{
// by default we want more complex option strategies to have matching priority
definitionEnumerator = new DescendingByLegCountOptionStrategyDefinitionEnumerator();
}
if (objectiveFunction == null)
{
objectiveFunction = new UnmatchedPositionCountOptionStrategyMatchObjectiveFunction();
}
if (positionEnumerator == null)
{
positionEnumerator = new DefaultOptionPositionCollectionEnumerator();
}
_definitions = definitions;
MaximumDuration = maximumDuration;
ObjectiveFunction = objectiveFunction;
MaximumCountPerLeg = maximumCountPerLeg;
_positionEnumerator = positionEnumerator;
_definitionEnumerator = definitionEnumerator;
MaximumSolutionCount = maximumSolutionCount;
}
/// <summary>
/// Gets the maximum number of leg matches to be evaluated. This is to limit evaluating exponential
/// numbers of potential matches as a result of large numbers of unique option positions for the same
/// underlying security.
/// </summary>
public int GetMaximumLegMatches(int legIndex)
{
return MaximumCountPerLeg[legIndex];
}
/// <summary>
/// Enumerates the specified <paramref name="positions"/> according to the configured
/// <see cref="IOptionPositionCollectionEnumerator"/>
/// </summary>
public IEnumerable<OptionPosition> Enumerate(OptionPositionCollection positions)
{
return _positionEnumerator.Enumerate(positions);
}
/// <summary>
/// Creates a new <see cref="OptionStrategyMatcherOptions"/> with the specified <paramref name="definitions"/>,
/// with no limits of maximum matches per leg and default values for the remaining options
/// </summary>
public static OptionStrategyMatcherOptions ForDefinitions(params OptionStrategyDefinition[] definitions)
{
return ForDefinitions(definitions.AsEnumerable());
}
/// <summary>
/// Creates a new <see cref="OptionStrategyMatcherOptions"/> with the specified <paramref name="definitions"/>,
/// with no limits of maximum matches per leg and default values for the remaining options
/// </summary>
public static OptionStrategyMatcherOptions ForDefinitions(IEnumerable<OptionStrategyDefinition> definitions)
{
var maximumCountPerLeg = new[] {int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue};
return new OptionStrategyMatcherOptions(definitions.ToList(), maximumCountPerLeg);
}
/// <summary>
/// Specifies the maximum time provided for obtaining an optimal solution.
/// </summary>
public OptionStrategyMatcherOptions WithMaximumDuration(TimeSpan duration)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
duration,
MaximumSolutionCount,
_definitionEnumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies the maximum number of solutions to evaluate via the objective function.
/// </summary>
public OptionStrategyMatcherOptions WithMaximumSolutionCount(int count)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
count,
_definitionEnumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies the maximum number of solutions per leg index in a solution. Matching is a recursive
/// process, for example, we'll find a very large number of positions to match the first leg. Matching
/// the second leg we'll see less, and third still even less. This is because each subsequent leg must
/// abide by all the previous legs. This parameter defines how many potential matches to evaluate at
/// each leg. For the first leg, we'll evaluate counts[0] matches. For the second leg we'll evaluate
/// counts[1] matches and so on. By decreasing this parameter we can evaluate more total, complete
/// solutions for the entire portfolio rather than evaluation every single permutation of matches for
/// a particular strategy definition, which grows in absurd exponential fashion as the portfolio grows.
/// </summary>
public OptionStrategyMatcherOptions WithMaximumCountPerLeg(IReadOnlyList<int> counts)
{
return new OptionStrategyMatcherOptions(
_definitions,
counts,
MaximumDuration,
MaximumSolutionCount,
_definitionEnumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies a function used to evaluate how desirable a particular solution is. A good implementation for
/// this would be to minimize the total margin required to hold all of the positions.
/// </summary>
public OptionStrategyMatcherOptions WithObjectiveFunction(IOptionStrategyMatchObjectiveFunction function)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
MaximumSolutionCount,
_definitionEnumerator,
function,
_positionEnumerator
);
}
/// <summary>
/// Specifies the order in which definitions are evaluated. Definitions evaluated sooner are more likely to
/// find matches than ones evaluated later.
/// </summary>
public OptionStrategyMatcherOptions WithDefinitionEnumerator(IOptionStrategyDefinitionEnumerator enumerator)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
MaximumSolutionCount,
enumerator,
ObjectiveFunction,
_positionEnumerator
);
}
/// <summary>
/// Specifies the order in which positions are evaluated. Positions evaluated sooner are more likely to
/// find matches than ones evaluated later. A good implementation for this is its stand-alone margin required,
/// which would encourage the algorithm to match higher margin positions before matching lower margin positiosn.
/// </summary>
public OptionStrategyMatcherOptions WithPositionEnumerator(IOptionPositionCollectionEnumerator enumerator)
{
return new OptionStrategyMatcherOptions(
_definitions,
MaximumCountPerLeg,
MaximumDuration,
MaximumSolutionCount,
_definitionEnumerator,
ObjectiveFunction,
enumerator
);
}
}
}
@@ -0,0 +1,45 @@
/*
* 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.
*/
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Specifies the type of value being compared against in a <see cref="OptionStrategyLegPredicate"/>.
/// These values define the limits of what can be filtered and must match available slice methods in
/// <see cref="OptionPositionCollection"/>
/// </summary>
public enum PredicateTargetValue
{
/// <summary>
/// Predicate matches on <see cref="OptionPosition.Right"/> (0)
/// </summary>
Right,
/// <summary>
/// Predicate match on <see cref="OptionPosition.Quantity"/> (1)
/// </summary>
Quantity,
/// <summary>
/// Predicate matches on <see cref="OptionPosition.Strike"/> (2)
/// </summary>
Strike,
/// <summary>
/// Predicate matches on <see cref="OptionPosition.Expiration"/> (3)
/// </summary>
Expiration
}
}
@@ -0,0 +1,44 @@
/*
* 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 QuantConnect.Orders;
using System.Linq;
namespace QuantConnect.Securities.Option.StrategyMatcher
{
/// <summary>
/// Provides an implementation of <see cref="IOptionStrategyMatchObjectiveFunction"/> that evaluates the number of unmatched
/// positions, in number of contracts, giving precedence to solutions that have fewer unmatched contracts.
/// </summary>
public class UnmatchedPositionCountOptionStrategyMatchObjectiveFunction : IOptionStrategyMatchObjectiveFunction
{
/// <summary>
/// Computes the delta in matched vs unmatched positions, which gives precedence to solutions that match more contracts.
/// </summary>
public decimal ComputeScore(OptionPositionCollection input, OptionStrategyMatch match, OptionPositionCollection unmatched)
{
var value = 0m;
foreach (var strategy in match.Strategies)
{
foreach (var leg in strategy.OptionLegs.Concat<Leg>(strategy.UnderlyingLegs))
{
value += leg.Quantity;
}
}
return value - unmatched.Count;
}
}
}