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 ExerciseOption
exercise_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 ExerciseOption
exercise_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.MarketOrderFillTimeout
transactions.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 True
true
.
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.
Examples
The following backtest verifies the ExerciseOption
exercise_option
behavior. The following table shows the first three trades in the backtest:
Time | Symbol | Price | Quantity | Type | Status | Value | Tag |
---|---|---|---|---|---|---|---|
2021-07-01T09:31:00Z | SPY 221216C00085000 | 345.30 | 1 | Buy Market | Filled | 34530.00 | |
2021-07-01T09:31:00Z | SPY 221216C00085000 | 0.00 | -1 | Sell Option Exercise | Filled | 0.00 | Automatic Exercise |
2021-07-01T09:31:00Z | SPY | 85.00 | 100 | Sell Option Exercise | Filled | 8500.00 | Option 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()