Options Models
Exercise
Introduction
If you exercise a long Option position or are assigned on your short Option position, LEAN processes an Option exercise order. The Option exercise model converts the Option exercise order into an OrderEvent.
Set Models
To set the exercise model of an Option, call the SetOptionExerciseModelset_option_exercise_model method of the Option object inside a security initializer.
public class AddSecurityInitializerExampleAlgorithm : QCAlgorithm
{
public override void Initialize()
{
// In the Initialize method, set the security initializer to set models of assets.
AddSecurityInitializer(CustomSecurityInitializer);
}
private void CustomSecurityInitializer(Security security)
{
// Overwrite the Option exercise model
if (security.Type == SecurityType.Option) // Option type
{
(security as Option).SetOptionExerciseModel(new DefaultExerciseModel());
}
}
} class AddSecurityInitializerExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
# In the Initialize method, set the security initializer to set models of assets.
self.add_security_initializer(self._custom_security_initializer)
def _custom_security_initializer(self, security: Security) -> None:
# Overwrite the Option exercise model
if security.Type == SecurityType.OPTION: # Option type
security.set_option_exercise_model(DefaultExerciseModel())
Default Behavior
The default Option exercise model is the DefaultExerciseModel. The DefaultExerciseModel fills exercise orders to the full quantity with zero fees and applies an order tag to represent if the order is an exercise or assignment.
For more information about this model, see the class reference and implementation.
For more information about this model, see the class reference and implementation.
Model Structure
Option exercise models should implement the IOptionExerciseModel interface. The IOptionExerciseModel interface must implement the OptionExerciseoption_exercise method, which receives Option and OptionExerciseOrder objects and then returns a list of OrderEvent objects that contain the order fill information.
Option exercise models should extend the DefaultExerciseModel class. Extensions of the DefaultExerciseModel must implement the OptionExerciseoption_exercise method, which receives Option and OptionExerciseOrder objects and then returns a list of OrderEvent objects that contain the order fill information.
public class CustomOptionExerciseModelExampleAlgorithm : QCAlgorithm
{
public override void Initialize()
{
var security = AddOption("SPY");
// Set custom option exercise model for mimicking specific Brokerage most realistic actions
(security as Option).SetOptionExerciseModel(new MyOptionExerciseModel());
}
}
// Define the custom Option exercise model outside of the algorithm
public class MyOptionExerciseModel : IOptionExerciseModel
{
public IEnumerable<OrderEvent> OptionExercise(Option option, OptionExerciseOrder order)
{
var inTheMoney = option.IsAutoExercised(option.Underlying.Close);
var isAssignment = inTheMoney && option.Holdings.IsShort;
yield return new OrderEvent(
order.Id,
option.Symbol,
option.LocalTime.ConvertToUtc(option.Exchange.TimeZone),
OrderStatus.Filled,
Extensions.GetOrderDirection(order.Quantity),
0.0m,
order.Quantity,
OrderFee.Zero,
"Tag"
) { IsAssignment = isAssignment };
}
} class CustomOptionExerciseModelExampleAlgorithm(QCAlgorithm):
def initialize(self) -> None:
security = self.add_option("SPY")
# Set custom option exercise model for mimicking specific Brokerage most realistic actions
security.set_option_exercise_model(MyOptionExerciseModel())
# Define the custom Option exercise model outside of the algorithm
class MyOptionExerciseModel(DefaultExerciseModel):
def option_exercise(self, option: Option, order: OptionExerciseOrder) -> List[OrderEvent]:
in_the_money = option.is_auto_exercised(option.underlying.close)
is_assignment = in_the_money and option.holdings.is_short
order_event = OrderEvent(
order.id,
option.symbol,
Extensions.convert_to_utc(option.local_time, option.exchange.time_zone),
OrderStatus.FILLED,
Extensions.get_order_direction(order.quantity),
0.0,
order.quantity,
OrderFee.ZERO,
"Tag"
)
order_event.is_assignment = is_assignment
return [ order_event ]
OptionExerciseOrder objects have the following properties:
The following table describes the arguments of the OrderEvent constructor:
| Argument Details |
|---|
Argument: |
Argument: |
Argument: |
Argument: |
Argument: |
Argument: |
Argument: |
Argument: |
OrderEvent objects have the following attributes:
Examples
The following examples demonstrate some common practices for implementing a custom Option exercise model.
Example 1: Cash Settlement
The following algorithm trades GOOG 30-day expiring straddle. Yet, instead of settling with the underlying stock, some brokerages will settle with cash for ITM options. To simulate this behavior, we can create a custom option exercise model.
using static QuantConnect.Extensions;
public class OptionExerciseModelAlgorithm : QCAlgorithm
{
private Symbol _goog;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
// Request GOOG option data for trading.
var security = AddOption("GOOG");
_goog = security.Symbol;
// Filter for the 2 ATM contracts expiring in 30 days to form a straddle strategy.
security.SetFilter((universe) => universe.IncludeWeeklys().Straddle(30));
// Set custom option exercise model for disabling exercise through security initializer.
SetSecurityInitializer(new MySecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices)));
}
public override void OnData(Slice slice)
{
// Open position on updated option chain data.
if (!Portfolio.Invested && slice.OptionChains.TryGetValue(_goog, out var chain))
{
// Only one strike and expiry for the straddle universe.
var strike = chain.Min(x => x.Strike);
var expiry = chain.Min(x => x.Expiry);
// Open the straddle position.
var optionStrategy = OptionStrategies.Straddle(_goog, strike, expiry);
Buy(optionStrategy, 5);
}
}
private class MySecurityInitializer : BrokerageModelSecurityInitializer
{
public MySecurityInitializer(IBrokerageModel brokerageModel, ISecuritySeeder securitySeeder)
: base(brokerageModel, securitySeeder) {}
public override void Initialize(Security security)
{
base.Initialize(security);
// Set the custom Option exercise model for Option securities
if (security.Type == SecurityType.Option)
{
(security as Option).SetOptionExerciseModel(new MyOptionExerciseModel());
}
}
}
// Define the custom Option exercise model outside of the algorithm
private class MyOptionExerciseModel : IOptionExerciseModel
{
public IEnumerable<OrderEvent> OptionExercise(Option option, OptionExerciseOrder order)
{
var underlying = option.Underlying;
var utcTime = option.LocalTime.ConvertToUtc(option.Exchange.TimeZone);
var inTheMoney = option.IsAutoExercised(underlying.Close);
// Cash settle: using payoff.
var payoff = option.GetIntrinsicValue(underlying.Close);
// Only liquidate option positions, but do not add equity positions.
yield return new OrderEvent(
order.Id,
option.Symbol,
utcTime,
OrderStatus.Filled,
GetOrderDirection(order.Quantity),
payoff,
order.Quantity,
OrderFee.Zero,
"Option Settlement"
)
{
IsInTheMoney = inTheMoney
};
}
}
} class OptionExerciseModelAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
# Request GOOG option data for trading.
security = self.add_option("GOOG")
self.goog = security.symbol
# Filter for the 2 ATM contracts expiring in 30 days to form a straddle strategy.
security.set_filter(lambda universe: universe.include_weeklys().straddle(30))
# Set custom option exercise model for disabling exercise through security initializer.
self.set_security_initializer(MySecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
def on_data(self, slice: Slice) -> None:
# Open position on updated option chain data.
chain = slice.option_chains.get(self.goog)
if chain and not self.portfolio.invested:
# Only one strike and expiry for the straddle universe.
strike = min(x.strike for x in chain)
expiry = min(x.expiry for x in chain)
# Open the straddle position.
option_straddle = OptionStrategies.straddle(self.goog, strike, expiry)
self.buy(option_straddle, 5)
class MySecurityInitializer(BrokerageModelSecurityInitializer):
def __init__(self, brokerageModel, securitySeeder):
super().__init__(brokerageModel, securitySeeder)
def initialize(self, security: Security) -> None:
super().initialize(security)
# Set the custom Option exercise model for Option securities
if security.type == SecurityType.OPTION:
security.set_option_exercise_model(MyOptionExerciseModel())
# Define the custom Option exercise model outside of the algorithm
class MyOptionExerciseModel(DefaultExerciseModel):
def option_exercise(self, option: Option, order: OptionExerciseOrder) -> List[OrderEvent]:
underlying = option.underlying
utc_time = Extensions.convert_to_utc(option.local_time, option.exchange.time_zone)
in_the_money = option.is_auto_exercised(underlying.close)
# Cash settle: using payoff.
payoff = option.get_intrinsic_value(underlying.close)
# Only liquidate option positions, but do not add equity positions.
order_event = OrderEvent(
order.id,
option.symbol,
utc_time,
OrderStatus.FILLED,
Extensions.get_order_direction(order.quantity),
payoff,
order.quantity,
OrderFee.ZERO,
"Option Settlement"
)
order_event.is_in_the_money = in_the_money
return [ order_event ]
Example 2: Default Model Implementation
The following algorithm implements the DefaultExerciseModel so you can easily customize the parts you need.
public class CustomExerciseModelAlgorithm : QCAlgorithm
{
private Symbol _optionSymbol;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
SetCash(100000);
SetSecurityInitializer(new MySecurityInitializer(this));
var option = AddOption("SPY");
_optionSymbol = option.Symbol;
}
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// Slice object keyed by symbol containing the stock data
public override void OnData(Slice data)
{
if (Portfolio.Invested)
{
return;
}
if (data.OptionChains.TryGetValue(_optionSymbol, out var chain))
{
var contract = chain
.Where(x => x.Right == OptionRight.Call)
.OrderBy(x => x.Strike)
.FirstOrDefault();
MarketOrder(contract.Symbol, 1);
ExerciseOption(contract.Symbol, 1);
Quit();
}
}
}
public class MySecurityInitializer : BrokerageModelSecurityInitializer
{
private readonly QCAlgorithm _algorithm;
public MySecurityInitializer(QCAlgorithm algorithm)
: base(algorithm.BrokerageModel, new FuncSecuritySeeder(algorithm.GetLastKnownPrices))
{
_algorithm = algorithm;
}
public override void Initialize(Security security)
{
base.Initialize(security);
if (security.Type == SecurityType.Option)
{
(security as Option).SetOptionExerciseModel(new MyOptionExerciseModel(_algorithm));
}
}
}
public class MyOptionExerciseModel : IOptionExerciseModel
{
private readonly QCAlgorithm _algorithm;
public MyOptionExerciseModel(QCAlgorithm algorithm) => _algorithm = algorithm;
public IEnumerable<OrderEvent> OptionExercise(Option option, OptionExerciseOrder order)
{
var underlying = option.Underlying;
var utcTime = option.LocalTime.ConvertToUtc(option.Exchange.TimeZone);
var inTheMoney = option.IsAutoExercised(underlying.Close);
var isAssignment = inTheMoney && option.Holdings.IsShort;
yield return new OrderEvent(
order.Id,
option.Symbol,
utcTime,
OrderStatus.Filled,
GetOrderDirection(order.Quantity),
0.0m,
order.Quantity,
OrderFee.Zero,
Messages.DefaultExerciseModel.ContractHoldingsAdjustmentFillTag(inTheMoney, isAssignment, option)
)
{
IsAssignment = isAssignment,
IsInTheMoney = inTheMoney
};
if (inTheMoney && option.ExerciseSettlement == SettlementType.PhysicalDelivery)
{
var exerciseQuantity = option.GetExerciseQuantity(order.Quantity);
yield return new OrderEvent(
order.Id,
underlying.Symbol,
utcTime,
OrderStatus.Filled,
GetOrderDirection(exerciseQuantity),
option.StrikePrice,
exerciseQuantity,
OrderFee.Zero,
isAssignment ? Messages.DefaultExerciseModel.OptionAssignment : Messages.DefaultExerciseModel.OptionExercise
) { IsInTheMoney = true };
}
}
}
class CustomExerciseModelAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
self.set_cash(100000)
self.set_security_initializer(MySecurityInitializer(self))
option = self.add_option("SPY")
self.option_symbol = option.symbol
def on_data(self, data: Slice):
if self.portfolio.invested:
return
chain = data.option_chains.get(self.option_symbol)
if chain:
contract = sorted([x for x in chain if x.right == OptionRight.CALL],
key=lambda x: x.strike)[0]
self.market_order(contract.symbol, 1)
self.exercise_option(contract.symbol, 1)
self.quit()
class MySecurityInitializer(BrokerageModelSecurityInitializer):
def __init__(self, algorithm) -> None:
super().__init__(algorithm.brokerage_model, FuncSecuritySeeder(algorithm.get_last_known_prices))
self.algorithm = algorithm
def initialize(self, security: Security) -> None:
super().initialize(security)
if security.type == SecurityType.OPTION:
security.set_option_exercise_model(MyOptionExerciseModel(self.algorithm))
class MyOptionExerciseModel(DefaultExerciseModel):
def __init__(self, algorithm):
self.algorithm = algorithm
def option_exercise(self, option: Option, order: OptionExerciseOrder) -> List[OrderEvent]:
order_events = []
underlying = option.underlying
utc_time = Extensions.convert_to_utc(option.local_time, option.exchange.time_zone)
in_the_money = option.is_auto_exercised(underlying.close)
is_assignment = in_the_money and option.holdings.is_short
messages = Messages.DefaultExerciseModel
order_event = OrderEvent(
order.id,
option.symbol,
utc_time,
OrderStatus.FILLED,
Extensions.get_order_direction(order.quantity),
0,
order.quantity,
OrderFee.ZERO,
messages.contract_holdings_adjustment_fill_tag(in_the_money, is_assignment, option)
)
order_event.is_assignment = is_assignment
order_event.is_in_the_money = in_the_money
order_events.append(order_event)
if in_the_money and option.exercise_settlement == SettlementType.PHYSICAL_DELIVERY:
exercise_quantity = option.get_exercise_quantity(order.quantity);
order_event = OrderEvent(
order.id,
underlying.symbol,
utc_time,
OrderStatus.FILLED,
Extensions.get_order_direction(exercise_quantity),
option.strike_price,
exercise_quantity,
OrderFee.ZERO,
messages.option_assignment if is_assignment else messages.option_exercise
)
order_event.is_in_the_money = True
order_events.append(order_event)
return order_events
Other Examples
For more examples, see the following algorithms: