🚀 AI One-Click Generation of quantconnect Strategy Code

QuantConnect Strategy: Automatically Liquidating Options Contracts Before a Stock Split

quantconnect | Published on: 2025/12/8
The following content is generated by EasyQuant .

Problem Description

How can I automatically sell my options before the underlying stock splits?

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

  1. Monitor Data: In on_data, check slice.splits for any new split events.
  2. Filter for Warnings: Check if the split event type is SplitType.WARNING. This indicates the split will happen soon (usually the next trading day).
  3. Identify Options: Iterate through your portfolio to find option contracts where the option.symbol.underlying matches the symbol issuing the split warning.
  4. Liquidate: Execute a liquidate order 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

  1. slice.splits: This dictionary contains all split data for the current time step.
  2. 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 for SplitType.SPLIT_OCCURRED, the price and contract quantities will likely already be adjusted.
  3. symbol.underlying: When iterating through self.portfolio, we check symbol.underlying to 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.