Order Types

Option Exercise Orders

Introduction

If you buy an Option contract, you can exercise your right to buy or sell the underlying asset at the strike price. However, you don't need to exercise it. You can sell the Option or let it expire worthless if it's out of the money. If you hold a long position in an Option that expires in the money, LEAN automatically exercises it at the expiration date. If you sell an Option contract and the buyer exercises their right to buy or sell the underlying asset, you are assigned the Option and must trade with the buyer at the strike price.

Place Orders

You can exercise American-style Option contracts anytime before they expire. Depending on your brokerage and the delivery method, you may be able to exercise European-style Option contracts on their expiration date. To exercise an Option, call the ExerciseOptionexercise_option method with the Option contract Symbol and a quantity. If you do not have sufficient capital for the order, it's rejected. By default, Option exercise orders are synchronous and fill immediately.

var ticket = ExerciseOption(contractSymbol, quantity);
ticket = self.exercise_option(contract_symbol, quantity)

You can provide a tag and order properties to the ExerciseOptionexercise_option method.

ExerciseOption(symbol, quantity, tag: tag, orderProperties: orderProperties);
self.exercise_option(symbol, quantity, tag=tag, order_properties=order_properties)

Monitor Order Fills

To monitor the fills of your order, save a reference to the order ticket.

var ticket = ExerciseOption(contractSymbol, quantity);
Debug($"Quantity filled: {ticket.QuantityFilled}; Fill price: {ticket.AverageFillPrice}");
ticket = self.exercise_option(contract_symbol, quantity)
self.debug(f"Quantity filled: {ticket.quantity_filled}; Fill price: {ticket.average_fill_price}")

For more information about how LEAN models Option exercise orders in backtests, see the Exercise Option model.

Synchronous Timeouts

Option exercise orders are synchronous by default, so your algorithm waits for the order to fill before moving to the next line of code. If your order takes longer than five seconds to fill, your algorithm continues executing even if the trade isn't filled. To adjust the timeout period, set the Transactions.MarketOrderFillTimeouttransactions.market_order_fill_timeout property.

// Adjust the market fill-timeout to 30 seconds.
Transactions.MarketOrderFillTimeout = TimeSpan.FromSeconds(30);
 # Adjust the market fill-timeout to 30 seconds.
self.transactions.market_order_fill_timeout = timedelta(seconds=30)

Place Asynchronous Orders

When you trade a large portfolio of assets, you may want to send orders in batches and not wait for the response of each one. To send asynchronous orders, set the asynchronous argument to Truetrue.

var ticket = ExerciseOption(contractSymbol, quantity, true);
ticket = self.exercise_option(contract_symbol, quantity, True)

Option Assignments

If you sell an Option in a backtest, LEAN can simulate an Option exercise order on behalf of the buyer. By default, LEAN scans your portfolio every hour. It considers exercising American-style Options if they are within 4 days of their expiration and it considers exercising European-style Options on their day of expiration. If you have sold an Option that's 5% in-the-money and the Option exercise order is profitable after the cost of fees, LEAN exercises the Option. For more information about how we simulate Option assignments, see the Assignment reality model.

Brokerage Support

Each brokerage has a set of assets and order types they support. To avoid issues with Option exercise orders, set the brokerage model to a brokerage that supports them.

SetBrokerageModel(BrokerageName.QuantConnectBrokerage);
self.set_brokerage_model(BrokerageName.QuantConnectBrokerage)

To check if your brokerage has any special requirements for Option exercise orders, see the Orders section of the brokerage model documentation.

Requirements

Option exercise orders can only be submitted for option contracts with a long position. European-style options cannot be exercised before their expiration date.

Examples

The following backtest verifies the ExerciseOptionexercise_option behavior. The following table shows the first three trades in the backtest:

TimeSymbolPriceQuantityTypeStatusValueTag
2021-07-01T09:31:00ZSPY 221216C00085000345.301Buy MarketFilled34530.00
2021-07-01T09:31:00ZSPY 221216C000850000.00-1Sell Option ExerciseFilled0.00Automatic Exercise
2021-07-01T09:31:00ZSPY85.00100Sell Option ExerciseFilled8500.00Option Exercise

The algorithm first brought a deep ITM option based on the set option selection conditions by $345.30, then exercise it actively, so 100 shares of SPY (in which 1 contract represents) was brought at its strike price at $85.00, with the option discarded from the portfolio.

To reproduce these results, backtest the following algorithm:

public class OptionExerciseOrderAlgorithm : QCAlgorithm
{
    private Symbol _spy;
    private decimal _strike;
    private Symbol _contract;

    public override void Initialize()
    {
        SetStartDate(2021, 7, 1);
        SetEndDate(2021, 7, 4);
        SetCash(100000);
    
        SetSecurityInitializer(CustomSecurityInitializer);
    
        var spy = AddEquity("SPY", Resolution.Minute);
        _spy = spy.Symbol;
    }

    private void CustomSecurityInitializer(Security security)
    {
        security.SetDataNormalizationMode(DataNormalizationMode.Raw);
        foreach (var bar in GetLastKnownPrices(security.Symbol))
        {
            security.SetMarketPrice(bar);
        }
    }

    public override void OnData(Slice data)
    {
        if (!Portfolio.Invested)
        {
            var contracts = OptionChainProvider.GetOptionContractList(_spy, Time);
            contracts = from contract in contracts
                        where contract.ID.OptionRight == OptionRight.Call
                        orderby contract.ID.StrikePrice ascending
                        select contract;
            if (contracts.Count() == 0) return;
            
            _strike = contracts.First().ID.StrikePrice;

            contracts = from contract in contracts
                        where contract.ID.StrikePrice == _strike
                        orderby contract.ID.Date ascending
                        select contract;
            
            _contract = contracts.First();
            AddOptionContract(_contract, Resolution.Minute);

            Buy(_contract, 1);
        }

        if (Portfolio[_contract].Invested && _strike < Securities[_spy].Price)
        {
            ExerciseOption(_contract, 1);
            Quit();
        }
    }
}
class OptionExerciseOrderAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2021, 7, 1)
        self.set_end_date(2021, 7, 31)
        self.set_cash(100000)

        self.set_security_initializer(self.custom_security_initializer)
        
        spy = self.add_equity("SPY", Resolution.MINUTE)
        self.spy = spy.symbol
    
    def custom_security_initializer(self, security):
        security.set_data_normalization_mode(DataNormalizationMode.RAW)
        for bar in self.get_last_known_prices(security.symbol):
            security.set_market_price(bar)

    def on_data(self, data):
        if not self.portfolio.invested:
            contracts = self.option_chain_provider.get_option_contract_list(self.spy, self.time)
            contracts = sorted([x for x in contracts if x.id.option_right == OptionRight.CALL], key=lambda x: x.id.strike_price)
            if not contracts: return
            
            self.strike = contracts[0].id.strike_price

            contracts = sorted([x for x in contracts if x.id.strike_price == self.strike], key=lambda x: x.id.date)
            
            self.contract = contracts[0]
            self.add_option_contract(self.contract, Resolution.MINUTE)

            self.buy(self.contract, 1)

        if self.portfolio[self.contract].invested and self.strike < self.securities[self.spy].price:
            self.exercise_option(self.contract, 1)
            self.quit()

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: