Problem Description
Solution
To automatically sell your options before the underlying stock splits, you need to monitor the Slice object in your on_data method for Split Events.
QuantConnect's LEAN engine generates a Split object with a SplitType.WARNING usually one day prior to the split taking effect (the ex-date). You can intercept this warning to identify the underlying asset and liquidate any associated option positions immediately.
Strategy Logic
- Monitor Data: In
on_data, checkslice.splitsfor any new split events. - Filter for Warnings: Check if the split event type is
SplitType.WARNING. This indicates the split will happen soon (usually the next trading day). - Identify Options: Iterate through your portfolio to find option contracts where the
option.symbol.underlyingmatches the symbol issuing the split warning. - Liquidate: Execute a
liquidateorder for those specific option contracts.
Python Implementation
Here is the complete algorithm demonstrating this logic. This example uses TSLA during its 2020 stock split to demonstrate the liquidation trigger.
# region imports
from AlgorithmImports import *
# endregion
class OptionSplitLiquidationAlgorithm(QCAlgorithm):
def initialize(self):
# Set start/end date to cover a known split (TSLA split on 2020-08-31)
self.set_start_date(2020, 8, 1)
self.set_end_date(2020, 9, 5)
self.set_cash(100000)
# Add the underlying equity
self.equity_symbol = self.add_equity("TSLA", Resolution.MINUTE).symbol
# Add options for the underlying
option = self.add_option("TSLA", Resolution.MINUTE)
self.option_symbol = option.symbol
# Set a filter to select contracts (ATM, expiring within 60 days)
option.set_filter(-2, 2, timedelta(0), timedelta(60))
def on_data(self, slice: Slice):
# 1. Check for Split Events in the current slice
if slice.splits:
for symbol, split in slice.splits.items():
# 2. Check if the event is a Split Warning (occurs before the actual split)
if split.type == SplitType.WARNING:
self.log(f"Split Warning detected for {symbol} at {self.time}. Factor: {split.split_factor}")
self.liquidate_options_for_underlying(symbol)
# Logic to open a position for demonstration purposes
# We only buy if we don't have options and no split warning has just triggered
if not self.portfolio.invested and self.time.hour == 10:
self.buy_call_option(slice)
def liquidate_options_for_underlying(self, underlying_symbol: Symbol):
"""
Finds all option positions derived from the specific underlying symbol
and liquidates them.
"""
options_liquidated = False
for kvp in self.portfolio:
security_holding = kvp.value
symbol = security_holding.symbol
# Check if the security is an Option and if its underlying matches the split symbol
if security_holding.invested and \
symbol.security_type == SecurityType.OPTION and \
symbol.underlying == underlying_symbol:
self.log(f"Liquidating option {symbol} due to underlying split warning.")
self.liquidate(symbol)
options_liquidated = True
if options_liquidated:
self.debug(f"All options for {underlying_symbol} have been liquidated to avoid split adjustments.")
def buy_call_option(self, slice: Slice):
"""
Helper to enter a position so we have something to liquidate.
"""
# Get the OptionChain for the symbol
chain = slice.option_chains.get(self.option_symbol)
if not chain:
return
# Select an ATM call expiring soon
calls = [x for x in chain if x.right == OptionRight.CALL]
if not calls:
return
# Sort by expiry and closeness to current price
contracts = sorted(calls, key=lambda x: (x.expiry, abs(chain.underlying.price - x.strike)))
if contracts:
target_contract = contracts[0]
self.market_order(target_contract.symbol, 1)
self.log(f"Bought 1 contract of {target_contract.symbol} at {self.time}")
def on_order_event(self, order_event: OrderEvent):
# Log order fills for debugging
if order_event.status == OrderStatus.FILLED:
self.log(f"Order Filled: {order_event}")
Key Implementation Details
slice.splits: This dictionary contains all split data for the current time step.SplitType.WARNING: This is the critical check. LEAN emits a "Warning" event typically the day before the split is applied to the price. If you wait forSplitType.SPLIT_OCCURRED, the price and contract quantities will likely already be adjusted.symbol.underlying: When iterating throughself.portfolio, we checksymbol.underlyingto ensure we only sell options related to the specific stock splitting, leaving other positions in your portfolio untouched.
Common Questions & Answers
Q: Does this work for reverse splits as well?
A: Yes. The Split object and SplitType.WARNING are generated for both forward splits (e.g., 2-for-1) and reverse splits (e.g., 1-for-10). The logic remains the same.
Q: What happens if I don't sell?
A: If you hold the option through the split, the Option Clearing Corporation (OCC) adjusts the contract. In LEAN, the engine will attempt to modify your holdings (quantity and strike price) to reflect the split. However, this can sometimes result in "non-standard" options which may have lower liquidity or different deliverables.
Q: Can I filter by split factor?
A: Yes. The split object has a split_factor property. For example, a 5-for-1 split has a factor of 0.2 (price becomes 1/5th). You could add logic to only liquidate if split.split_factor < 1.0 (forward split) if you wanted to keep positions during reverse splits.