Requesting Data
Individual Contracts
Introduction
The add_option_contract
method enables you to add an individual Option contract to your algorithm.
To check which contracts are currently available to add to your algorithm, use the option_chain
method.
If you want to subscribe to a set of contracts instead of individual contracts one-by-one, see Universes.
Create Subscriptions
Before you can subscribe to an Index Option contract, you must configure the underlying Index and get the contract symbol
.
class BasicIndexOptionAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2024, 1, 1) self._underlying = self.add_index("SPX").symbol self._contract_symbol = None def on_data(self, data): if self._contract_symbol: return chain = self.option_chain(self._underlying, flatten=True).data_frame expiry = chain.expiry.min() self._contract_symbol = chain[ (chain.expiry == expiry) & (chain.right == OptionRight.CALL) & (chain.delta < 0.7) & (chain.delta > 0.3) ].sort_values('openinterest').index[-1] self.add_option_contract(self._contract_symbol)
Configure the Underlying Index
In most cases, you should subscribe to the underlying Index before you subscribe to an Index Option contract.
self._underlying = self.add_index("SPX").symbol
Get Contract Symbols
To subscribe to an Option contract, you need the contract Symbol
.
The preferred method to getting Option contract Symbol
objects is to use the option_chain
method.
This method returns an OptionChain
object, which represent an entire chain of Option contracts for a single underlying security.
You can even format the chain data into a DataFrame where each row in the DataFrame represents a single contract.
With the chain, sort and filter the data to find the specific contract(s) you want to trade.
# Get the contracts available to trade (in DataFrame format). # Option A: Standard contracts. chain = self.option_chain( Symbol.create_canonical_option(self._underlying, Market.USA, "?SPX"), flatten=True ).data_frame # Option B: Weekly contracts. #chain = self.option_chain( # Symbol.create_canonical_option(self._underlying, "SPXW", Market.USA, "?SPXW"), flatten=True #).data_frame # Select a contract. expiry = chain.expiry.min() self._contract_symbol = chain[ # Select call contracts with the closest expiry. (chain.expiry == expiry) & (chain.right == OptionRight.CALL) & # Select contracts with a 0.3-0.7 delta. (chain.delta < 0.7) & (chain.delta > 0.3) # Select the contract with the largest open interest. ].sort_values('openinterest').index[-1]
Subscribe to Contracts
To create an Index Option contract subscription, pass the contract Symbol
to the add_index_option_contract
method. Save a reference to the contract Symbol
so you can easily access the contract in the OptionChain that LEAN passes to the on_data
method. To override the default pricing model of the Option, set a pricing model.
option = self.add_index_option_contract(self._contract_symbol) option.PriceModel = OptionPriceModels.black_scholes()
The add_index_option_contract
method creates a subscription for a single Index Option contract and adds it to your user-defined universe. To create a dynamic universe of Index Option contracts, add an Index Option universe.
Warm Up Contract Prices
If you subscribe to an Index Option contract with add_index_option_contract
, you'll need to wait until the next Slice
to receive data and trade the contract. To trade the contract in the same time step you subscribe to the contract, set the current price of the contract in a security initializer.
seeder = FuncSecuritySeeder(self.get_last_known_prices) self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, seeder))
Supported Assets
To view the supported assets in the US Index Options dataset, see Supported Assets.
Resolutions
The following table shows the available resolutions and data formats for Index Option contract subscriptions:
Resolution | TradeBar | QuoteBar | Trade Tick | Quote Tick |
---|---|---|---|---|
TICK | ||||
SECOND | ||||
MINUTE | ![]() | ![]() | ||
HOUR | ![]() | ![]() | ||
DAILY | ![]() | ![]() |
The default resolution for Index Option subscriptions is Resolution.MINUTE
. To change the resolution, pass a resolution
argument to the add_index_option_contract
method.
self.add_index_option_contract(self._contract_symbol, Resolution.HOUR)
To create custom resolution periods, see Consolidating Data.
Fill Forward
Fill forward means if there is no data point for the current slice, LEAN uses the previous data point. Fill forward is the default data setting. If you disable fill forward, you may get stale fills or you may see trade volume as zero.
To disable fill forward for a security, set the fill_forward
argument to false when you create the security subscription.
self.add_index_option_contract(self._contract_symbol, fill_forward=False)
Data Normalization
The data normalization mode doesn't affect the data that LEAN passes to on_data
or the data from history request. If you change the data normalization mode, it won't change the outcome.
Remove Subscriptions
To remove a contract subscription that you created with add_index_option_contract
, call the remove_option_contract
method. This method is an alias for remove_security
.
self.remove_option_contract(self._contract_symbol)
The remove_option_contract
method cancels your open orders for the contract and liquidates your holdings.
Helper Methods
The Option
object provides methods you can use for basic calculations. These methods require the underlying price. To get the Option
object and the Security
object for its underlying in any function, use the Option symbol
to access the value in the securities
object.
option = self.securities[self._contract_symbol] underlying = self.securities[self._contract_symbol.underlying] underlying_price = underlying.price
To get the Option payoff, call the get_pay_off
method.
pay_off = option.get_pay_off(underlying_price)
To get the Option intrinsic value, call the get_intrinsic_value
method.
intrinsic_value = option.get_intrinsic_value(underlying_price)
To get the Option out-of-the-money amount, call the out_of_the_money_amount
method.
otm_amount = option.out_of_the_money_amount(underlying_price)
To check whether the Option can be automatic exercised, call the is_auto_exercised
method.
is_auto_exercised = option.is_auto_exercised(underlying_price)
Exceptions and Edge Cases
The following sections explain exceptions and edge cases with subscribing to individual Option contracts.
Manually Creating Option Symbol Objects
To subscribe to an Option contract, you need the contract Symbol
.
To get Index Option contract Symbol
objects, call the create_option
or option_chain
methods.
If you use the create_option
method, you need to know the specific contract details.
# Standard contracts self._contract_symbol = Symbol.create_option(self._underlying, Market.USA, OptionStyle.EUROPEAN, OptionRight.CALL, 3650, datetime(2022, 6, 17)) # Weekly contracts self._weekly_contract_symbol = Symbol.create_option(self._underlying, "SPXW", Market.USA, OptionStyle.EUROPEAN, OptionRight.CALL, 3650, datetime(2022, 6, 17))
Default Underlying Subscription Settings
If you subscribe to an Index Option contract but don't have a subscription to the underlying Index, LEAN automatically subscribes to the underlying Index and sets its fill forward property to match that of the Index Option contract. In this case, you still need the Index symbol
to subscribe to Index Option contracts. If you don't have access to it, create it.
self._underlying = Symbol.create("SPX", SecurityType.INDEX, Market.USA)
Overriding the Initial Implied Volatility Guess
To override the initial guess of implied volatility, set and warm up the underlying volatility model.
Examples
The following examples demonstrate common practices for requesting individual index option contract data.
Example 1: 5-Minute Option Chain
The following example shows how to update the Option chain every five minutes. The custom
OptionChainManager
class implements the selection logic and manages the contract subscriptions.
class OptionChainFullExample(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2023, 1, 2) self.set_end_date(2023, 1, 30) self.set_cash(100000) self.universe_settings.asynchronous = True self.universe_settings.minimum_time_in_universe = timedelta(minutes=0) # Seed the last price to ensure filtering using the underlying price is available immediately. self.set_security_initializer( BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)) ) # Request the index underlying data for filtering. spx = self.add_index("SPX").symbol # Create the 5-minute option chain manager on SPXW weekly index option. self._chain_manager = { Symbol.create_canonical_option(spx, "SPXW", Market.USA, "?SPXW"): OptionChainManager(-10, 10, 0, 1) } # Populate the updated option chain immediately to trade with. self._populate_option_chain() # Set schedule event to populate the option chain at market open, since the option contracts are updating daily. self.schedule.on( self.date_rules.every_day(spx), self.time_rules.after_market_open(spx, 1), self._populate_option_chain ) # Set a scheduled event to filter the closed ATM calls every 5 minutes. self.schedule.on(self.date_rules.every_day(spx), self.time_rules.every(timedelta(minutes=5)), self._filter) def _populate_option_chain(self) -> None: # The contract list is updated daily, so we can get it and apply # the expiration filter as soon as the market opens. for symbol, manager in self._chain_manager.items(): manager.set_chain(self.option_chain(symbol), self.time) self._filter() def _filter(self) -> None: for symbol, manager in self._chain_manager.items(): manager.select(self, symbol) def on_data(self, slice: Slice) -> None: for symbol, _ in self._chain_manager.items(): # Only trade on updated data. chain = slice.option_chains.get(symbol) if not chain: continue # Filter the closest ATM call contract and trade. expiry = min([x.expiry for x in chain]) contracts = [ x for x in chain if x.expiry == expiry and x.right == OptionRight.CALL and self.securities[x.symbol].is_tradable ] if not contracts: continue atm_call = sorted(contracts, key=lambda x: abs(chain.underlying.price-x.strike))[0] if not self.portfolio[atm_call.symbol].invested: self.market_order(atm_call.symbol, 1) class OptionChainManager: _chain = [] _symbols = set([]) def __init__(self, min_strike: int, max_strike: int, min_expiry: int, max_expiry: int) -> None: self._min_strike = min_strike self._max_strike = max_strike self._min_expiry = min_expiry self._max_expiry = max_expiry def set_chain(self, chain:OptionChain, time: datetime) -> None: # Filter the expiry daily only since contract list is updated daily. self._chain = [x for x in chain if self._min_expiry <= (x.expiry - time).days <= self._max_expiry] def select(self, algorithm: QCAlgorithm, symbol: Symbol) -> None: if not self._chain: return if symbol.is_canonical(): symbol = symbol.underlying # Filter the contracts with strike range spread between the preset level. strikes = sorted(set(x.strike for x in self._chain)) spot = algorithm.securities[symbol].price atm = sorted(strikes, key=lambda x: abs(spot-x))[0] index = strikes.index(atm) min_strike = strikes[max(0, index + self._min_strike)] max_strike = strikes[min(len(strikes) - 1, index + self._max_strike)] symbols = set(x.symbol for x in self._chain if min_strike <= x.strike <= max_strike) # Remove subscriptions if the contracts are not being ATM anymore. for symbol in self._symbols - symbols: if algorithm.remove_option_contract(symbol): self._symbols.remove(symbol) # Request data of the newly identified ATM contracts. for symbol in symbols - self._symbols: self._symbols.add(symbol) algorithm.add_option_contract(symbol)