Problem Description
Solution
To handle option exercise and assignment in QuantConnect, you primarily utilize the on_assignment_order_event and on_order_event event handlers.
When an option is exercised (either automatically at expiration or manually) or assigned (when you are short an option), the LEAN engine generates an order with the type OrderType.OPTION_EXERCISE.
Key Concepts
- Assignment: Occurs when you are Short an option and the counterparty exercises. You are forced to buy (Short Put) or sell (Short Call) the underlying asset.
- Exercise: Occurs when you are Long an option and choose to exercise it (or it expires ITM). You buy (Long Call) or sell (Long Put) the underlying asset.
Implementation Strategy
The following algorithm demonstrates how to:
- Sell a Put option (Short position).
- Detect when that option is assigned using
on_assignment_order_event. - Manage the resulting underlying stock position (e.g., liquidate it immediately).
# region imports
from AlgorithmImports import *
# endregion
class OptionExerciseAssignmentAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2023, 1, 1)
self.set_end_date(2023, 6, 1)
self.set_cash(100000)
# Add the underlying equity
self.equity = self.add_equity("SPY", Resolution.MINUTE)
self.equity.set_data_normalization_mode(DataNormalizationMode.RAW)
self.symbol = self.equity.symbol
# Add Options
option = self.add_option("SPY", Resolution.MINUTE)
option.set_filter(-5, 0, 0, 30) # Look for OTM/ATM puts expiring within 30 days
self.option_symbol = option.symbol
def on_data(self, slice: Slice):
# Simple logic to open a Short Put position if we aren't invested
if not self.portfolio.invested and self.is_market_open(self.symbol):
chain = slice.option_chains.get(self.option_symbol)
if chain:
# Select an ATM Put
puts = [x for x in chain if x.right == OptionRight.PUT]
if not puts: return
# Sort by expiry and strike to find a near-the-money put
contract = sorted(puts, key=lambda x: abs(x.strike - x.underlying_last_price))[0]
# Sell 1 contract (Short Put)
self.market_order(contract.symbol, -1)
self.log(f"Sold Put: {contract.symbol} Strike: {contract.strike}")
def on_assignment_order_event(self, assignment_event: OrderEvent):
"""
This event handler is triggered specifically for Option Assignment events.
It allows you to handle the resulting equity position immediately.
"""
self.log(f"Assignment Detected: {assignment_event}")
# assignment_event.symbol is the Option Symbol.
# To get the underlying (Stock) symbol, we access the .underlying property.
option_symbol = assignment_event.symbol
underlying_symbol = option_symbol.underlying
# Check the resulting holdings of the Underlying Asset
underlying_holdings = self.portfolio[underlying_symbol]
if underlying_holdings.invested:
self.log(f"Assigned Stock Position: {underlying_holdings.quantity} shares of {underlying_symbol}")
# Example Logic: Liquidate the assigned stock immediately
# If we were Short Put, we are now Long Stock. We sell to close.
self.liquidate(underlying_symbol)
self.log(f"Liquidated assigned position for {underlying_symbol}")
def on_order_event(self, order_event: OrderEvent):
"""
Standard order event handler.
Useful for detecting automatic exercises of Long positions at expiration.
"""
if order_event.status == OrderStatus.FILLED:
# Check if this was an Option Exercise (applies to both Long Exercise and Short Assignment)
order = self.transactions.get_order_by_id(order_event.order_id)
if order.type == OrderType.OPTION_EXERCISE:
direction = "Assigned" if order_event.is_assignment else "Exercised"
self.log(f"{direction} on {order_event.symbol}. Fill Price: {order_event.fill_price}")
Detailed Breakdown
1. on_assignment_order_event(self, assignment_event)
This is the preferred method for handling assignments. It isolates assignment logic from standard trading logic.
- Trigger: Fires only when a short option position is assigned.
- Data: The
assignment_eventcontains details about the option contract. - Action: In the example above, we identify the underlying symbol (
assignment_event.symbol.underlying) and liquidate the resulting stock position. This is common in strategies that want to capture the premium but do not wish to hold the stock.
2. on_order_event(self, order_event)
This handler catches all order updates. You can use it to detect exercises of Long positions (which on_assignment_order_event does not catch).
- Detection: You check
order.type == OrderType.OPTION_EXERCISE. - Differentiation: You can check
order_event.is_assignment(boolean) to see if it was an assignment (Short) or an exercise (Long).
3. Automatic vs. Manual
- Expiration: LEAN automatically exercises ITM options at expiration. This triggers the events above.
- Manual Exercise: You can manually exercise a long option in your code using
self.exercise_option(option_symbol, quantity). This will also triggeron_order_event.
Common Pitfall: Buying Power
When an option is assigned, you are purchasing (or selling) 100 shares of the underlying.
- Risk: If you do not have enough cash/margin to hold the 100 shares, the brokerage model may issue a Margin Call immediately after assignment.
- Solution: If your strategy cannot support the underlying position, ensure you liquidate inside
on_assignment_order_eventor ensure your position sizing leaves enough margin room for potential assignment.
Q&A: Options Assignment in QuantConnect
Q: Does on_assignment_order_event trigger for Long options that expire ITM?
A: No. on_assignment_order_event is specifically for when you are Short and get assigned. For Long options that are exercised (automatically or manually), use on_order_event and check for OrderType.OPTION_EXERCISE.
Q: How do I stop LEAN from automatically exercising my Long options at expiration?
A: By default, LEAN exercises ITM options at expiration. To disable this, you can set the option assignment model in initialize:
option.set_option_exercise_model(DefaultExerciseModel()) is the default. You would need to implement a custom model or simply liquidate your options before market close on the expiration date to avoid exercise.
Q: What happens to my cash when I am assigned a Put?
A: When assigned a Short Put, you are forced to buy shares. Your cash balance will decrease by Strike Price * 100 * Contracts, and your portfolio will show a Long position in the underlying stock.
Q: Can I partially exercise an option?
A: Yes, if you hold multiple contracts. The self.exercise_option(symbol, quantity) method allows you to specify the quantity. However, assignment (when you are short) is random and determined by the brokerage/exchange simulation; you cannot control "partial" assignment, though you may be assigned on only a portion of your short contracts.