Files
quantconnect--lean/Common/Securities/Option/StrategyMatcher/OptionPosition.cs
T
2026-07-13 13:02:50 +08:00

282 lines
10 KiB
C#

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