Requesting Data
Individual Contracts
Introduction
The AddOptionContract
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 OptionChainProvider.GetOptionContractList
option_chain_provider.get_option_contract_list
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 Option contract, you must configure the underlying Equity and get the contract Symbol
.
public class BasicOptionAlgorithm : QCAlgorithm { private Symbol _underlying; private Option _contract = null; public override void Initialize() { SetStartDate(2020, 1, 1); _underlying = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol; } public override void OnData(Slice data) { if (_contract == null) { var contractSymbols = OptionChainProvider.GetOptionContractList(_underlying, Time); var expiry = contractSymbols.Min(symbol => symbol.ID.Date); var filteredSymbols = contractSymbols .Where(symbol => symbol.ID.Date == expiry && symbol.ID.OptionRight == OptionRight.Call) .ToList(); var symbol = filteredSymbols.OrderBy(symbol => symbol.ID.StrikePrice).First(); _contract = AddOptionContract(symbol); } } }
class BasicOptionAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2020, 1, 1) self._underlying = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol self._contract = None def on_data(self, data): if not self._contract: contract_symbols = self.option_chain_provider.get_option_contract_list(self._underlying, self.time) expiry = min([symbol.id.date for symbol in contract_symbols]) filtered_symbols = [symbol for symbol in contract_symbols if symbol.id.date == expiry and symbol.id.option_right == OptionRight.CALL] symbol = sorted(filtered_symbols, key=lambda symbol: symbol.id.strike_price)[0] self._contract = self.add_option_contract(symbol)
Configure the Underlying Equity
If you want to subscribe to the underlying Equity in the Initialize
initialize
method, set the Equity data normalization to DataNormalizationMode.Raw
DataNormalizationMode.RAW
.
_symbol = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol;
self._symbol = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol
If your algorithm has a dynamic universe of Equities, before you add the Equity universe in the Initialize
initialize
method, set the universe data normalization mode to DataNormalizationMode.Raw
DataNormalizationMode.RAW
.
UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
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 OptionChainProvider
option_chain_provider
.
The GetOptionContractList
get_option_contract_list
method of OptionChainProvider
option_chain_provider
returns a list of Symbol
objects for a given date and underlying Equity, which you can then sort and filter to find the specific contract(s) you want to trade.
var contractSymbols = OptionChainProvider.GetOptionContractList(_symbol, Time); var expiry = contractSymbols.Select(symbol => symbol.ID.Date).Min(); var filteredSymbols = contractSymbols.Where(symbol => symbol.ID.Date == expiry && symbol.ID.OptionRight == OptionRight.Call); _contractSymbol = filteredSymbols.OrderByDescending(symbol => symbol.ID.StrikePrice).Last();
contract_symbols = self.option_chain_provider.get_option_contract_list(self._symbol, self.time) expiry = min([symbol.id.date for symbol in contract_symbols]) filtered_symbols = [symbol for symbol in contract_symbols if symbol.id.date == expiry and symbol.id.option_right == OptionRight.CALL] self._contract_symbol = sorted(filtered_symbols, key=lambda symbol: symbol.id.strike_price)[0]
To filter and select contracts, you can use the following properties of each Symbol
object:
Property | Description |
---|---|
ID.Date id.date | The expiration date of the contract. |
ID.StrikePrice id.strike_price | The strike price of the contract. |
ID.OptionRight id.option_right |
The contract type, OptionRight.Put OptionRight.PUT or OptionRight.Call OptionRight.CALL .
|
ID.OptionStyle id.option_style |
The contract style, OptionStyle.American OptionStyle.AMERICAN or OptionStyle.European OptionStyle.EUROPEAN .
We currently only support American-style Options for US Equity Options.
|
Subscribe to Contracts
To create an Equity Option contract subscription, pass the contract Symbol
to the AddOptionContract
add_option_contract
method. Save a reference to the contract Symbol
symbol
so you can easily access the Option contract in the OptionChain that LEAN passes to the OnData
on_data
method. This method returns an Option
object. To override the default pricing model of the Option, set a pricing model.
var option = AddOptionContract(_contractSymbol); option.PriceModel = OptionPriceModels.BinomialCoxRossRubinstein();
option = self.add_option_contract(self._contract_symbol) option.price_model = OptionPriceModels.binomial_cox_ross_rubinstein()
The AddOptionContract
add_option_contract
method creates a subscription for a single Option contract and adds it to your user-defined universe. To create a dynamic universe of Option contracts, add an Equity Options universe or an Options Universe Selection model.
Warm Up Contract Prices
If you subscribe to an Option contract with AddOptionContract
add_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.
var seeder = new FuncSecuritySeeder(GetLastKnownPrices); SetSecurityInitializer(new BrokerageModelSecurityInitializer(BrokerageModel, seeder));
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 Equities dataset, see the Data Explorer.
Resolutions
The following table shows the available resolutions and data formats for Equity Option contract subscriptions:
Resolution | TradeBar | QuoteBar | Trade Tick | Quote Tick |
---|---|---|---|---|
Tick TICK | ||||
Second SECOND | ||||
Minute MINUTE | ||||
Hour HOUR | ||||
Daily DAILY |
The default resolution for Option contract subscriptions is Resolution.Minute
Resolution.MINUTE
. To change the resolution, pass a resolution
argument to the AddOptionContract
add_option_contract
method.
AddOptionContract(_contractSymbol, Resolution.Minute);
self.add_option_contract(self._contract_symbol, Resolution.MINUTE)
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 fillForward
fill_forward
argument to false when you create the security subscription.
AddOptionContract(_contractSymbol, fillForward: false);
self.add_option_contract(self._contract_symbol, fill_forward=False)
Extended Market Hours
By default, your security subscriptions only cover regular trading hours. To subscribe to pre and post-market trading hours for a specific asset, enable the extendedMarketHours
extended_market_hours
argument when you create the security subscription.
AddOptionContract(_contractSymbol, extendedMarketHours: true);
self.add_option_contract(self._contract_symbol, extended_market_hours=True)
You only receive extended market hours data if you create the subscription with minute, second, or tick resolution. If you create the subscription with daily or hourly resolution, the bars only reflect the regular trading hours.
To view the schedule of regular and extended market hours, see Market Hours.
Data Normalization
The data normalization mode doesn't affect the data that LEAN passes to OnData
on_data
or the data from history request. By default, LEAN doesn't adjust Equity Options data for splits and dividends of their underlying. If you change the data normalization mode, it won't change the outcome.
If you hold an Option contract when a corporate action occurs for the underlying Equity, LEAN automatically closes your position.
Remove Subscriptions
To remove a contract subscription that you created with AddOptionContract
add_option_contract
, call the RemoveOptionContract
remove_option_contract
method. This method is an alias for RemoveSecurity
remove_security
.
RemoveOptionContract(_contractSymbol);
self.remove_option_contract(self._contract_symbol)
The RemoveOptionContract
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
symbol
to access the value in the Securities
securities
object.
var option = Securities[_contractSymbol]; var underlying = Securities[_contractSymbol.Underlying]; var underlyingPrice = underlying.Price;
option = self.securities[self._contract_symbol] underlying = self.securities[self._contract_symbol.underlying] underlying_price = underlying.price
To get the Option payoff, call the GetPayOff
get_pay_off
method.
var payOff = option.GetPayOff(underlyingPrice);
pay_off = option.get_pay_off(underlying_price)
To get the Option intrinsic value, call the GetIntrinsicValue
get_intrinsic_value
method.
var intrinsicValue = option.GetIntrinsicValue(underlyingPrice);
intrinsic_value = option.get_intrinsic_value(underlying_price)
To get the Option out-of-the-money amount, call the OutOfTheMoneyAmount
out_of_the_money_amount
method.
var otmAmount = option.OutOfTheMoneyAmount(underlyingPrice);
otm_amount = option.out_of_the_money_amount(underlying_price)
To check whether the Option can be automatic exercised, call the IsAutoExercised
is_auto_exercised
method.
var isAutoExercised = option.IsAutoExercised(underlyingPrice);
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.
Default Underlying Subscription Settings
If you subscribe to an Equity Option contract but don't have a subscription to the underlying Equity, LEAN automatically subscribes to the underlying Equity with the following settings:
Setting | Value |
---|---|
Fill forward | Same as the Option contract |
Leverage | 0 |
Extended Market Hours | Same as the Option contract |
Data Normalization | DataNormalizationMode.Raw DataNormalizationMode.RAW |
In this case, you still need the Equity Symbol
to subscribe to Equity Option contracts. If you don't have access to it, create it.
_symbol = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
self._symbol = Symbol.create("SPY", SecurityType.EQUITY, Market.USA)
Manually Creating Option Symbol Objects
To subscribe to an Option contract, you need the contract Symbol
.
You can get the contract Symbol
from the CreateOption
create_option
method or from the OptionChainProvider
option_chain_provider
.
If you use the CreateOption
create_option
method, you need to provide the details of an existing contract.
_contractSymbol = QuantConnect.Symbol.CreateOption(_symbol, Market.USA, OptionStyle.American, OptionRight.Call, 365, new DateTime(2022, 6, 17));
self._contract_symbol = Symbol.create_option(self._symbol, Market.USA, OptionStyle.AMERICAN, OptionRight.CALL, 365, datetime(2022, 6, 17))
Overriding the Initial Implied Volatility Guess
To override the initial guess of implied volatility, set and warm up the underlying volatility model.
Examples
Example 4: Scan and Update Option Chain Every 5 Minutes
The following example shows how to update the Option chain every five minutes. The OptionChainManager
class implements the selection logic and manages the contract subscriptions.
namespace QuantConnect.Algorithm.CSharp { public class OptionChainProviderFullExample : QCAlgorithm { private Dictionary<Symbol, OptionChainManager> _chainManager = new(); public override void Initialize() { SetStartDate(2023, 1, 2); SetEndDate(2023, 1, 30); SetCash(100000); UniverseSettings.Asynchronous = true; UniverseSettings.MinimumTimeInUniverse = TimeSpan.Zero; SetSecurityInitializer(new BrokerageModelSecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices))); var spy = AddEquity("SPY", dataNormalizationMode: DataNormalizationMode.Raw).Symbol; _chainManager[QuantConnect.Symbol.CreateCanonicalOption(spy)] = new(-10, 10, 0, 7); PopulateOptionChain(); Schedule.On(DateRules.EveryDay(spy), TimeRules.AfterMarketOpen(spy, 1), PopulateOptionChain); Schedule.On(DateRules.EveryDay(spy), TimeRules.Every(TimeSpan.FromMinutes(5)), Filter); } private void PopulateOptionChain() { // The contract list is updated daily, so we can get it and apply // the expiration filter as soon as the market open foreach (var (symbol, manager) in _chainManager) { manager.SetChain(OptionChainProvider.GetOptionContractList(symbol, Time), Time); } Filter(); } private void Filter() { foreach (var (symbol, manager) in _chainManager) { manager.Select(this, symbol); } } public override void OnData(Slice slice) { foreach (var (symbol, manager) in _chainManager) { if (!slice.OptionChains.TryGetValue(symbol, out var chain)) continue; var expiry = chain.Min(x => x.Expiry); var atmCall = chain .Where(x => x.Expiry == expiry && x.Right == OptionRight.Call && Securities[x.Symbol].IsTradable) .OrderBy(x => Math.Abs(chain.Underlying.Price - x.Strike)) .FirstOrDefault(); if (atmCall != null && !Portfolio[atmCall.Symbol].Invested) MarketOrder(atmCall.Symbol, 1); } } } internal class OptionChainManager { private readonly int _minStrike; private readonly int _maxStrike; private readonly int _minExpiry; private readonly int _maxExpiry; private List<Symbol> _chain = new(); private readonly List<Symbol> _symbols = new(); public OptionChainManager(int minStrike, int maxStrike, int minExpiry, int maxExpiry) { _minStrike = minStrike; _maxStrike = maxStrike; _minExpiry = minExpiry; _maxExpiry = maxExpiry; } public void SetChain(IEnumerable<Symbol> symbols, DateTime time) { _chain = symbols.Where(x => { var totalDays = (x.ID.Date - time).TotalDays; return _minExpiry <= totalDays && totalDays <= _maxExpiry; }).ToList(); } public void Select(QCAlgorithm algorithm, Symbol underlyingSymbol) { if (_chain.IsNullOrEmpty()) return; if (underlyingSymbol.IsCanonical()) underlyingSymbol = underlyingSymbol.Underlying; var strikes = _chain.Select(x => x.ID.StrikePrice).OrderBy(x => x).Distinct().ToList(); var spot = algorithm.Securities[underlyingSymbol].Price; var atm = strikes.OrderBy(x => Math.Abs(spot - x)).FirstOrDefault(); var index = strikes.IndexOf(atm); var minStrike = strikes[Math.Max(0, index + _minStrike)]; var maxStrike = strikes[Math.Min(strikes.Count - 1, index + _maxStrike)]; var symbols = _chain.Where(x => minStrike <= x.ID.StrikePrice && x.ID.StrikePrice <= maxStrike).ToList(); var toRemove = _symbols.Except(symbols).ToList(); foreach (var symbol in toRemove) { if (algorithm.RemoveOptionContract(symbol)) _symbols.Remove(symbol); } var toAdd = symbols.Except(_symbols).ToList(); foreach (var symbol in toAdd) { _symbols.Add(symbol); algorithm.AddOptionContract(symbol); } } } }
class OptionChainProviderFullExample(QCAlgorithm): def initialize(self): 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) self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW).symbol self._chain_manager = { Symbol.create_canonical_option(spy): OptionChainManager(-10, 10, 0, 7) } self._populate_option_chain() self.schedule.on(self.date_rules.every_day(spy), self.time_rules.after_market_open(spy, 1), self._populate_option_chain) self.schedule.on(self.date_rules.every_day(spy), self.time_rules.every(timedelta(minutes=5)), self._filter) def _populate_option_chain(self): # The contract list is updated daily, so we can get it and apply # the expiration filter as soon as the market open for symbol, manager in self._chain_manager.items(): manager.set_chain(self.option_chain_provider.get_option_contract_list(symbol, self.time), self.time) self._filter() def _filter(self): 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(): chain = slice.option_chains.get(symbol) if not chain: continue if self.portfolio[symbol.underlying].invested: self.liquidate(symbol.underlying) 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 = [] def __init__(self, min_strike, max_strike, min_expiry, max_expiry): self._min_strike = min_strike self._max_strike = max_strike self._min_expiry = min_expiry self._max_expiry = max_expiry def set_chain(self, symbols: List[Symbol], time: datetime) -> None: self._chain = [x for x in symbols if self._min_expiry <= (x.id.date - 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 strikes = sorted(set(x.id.strike_price 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 for x in self._chain if min_strike <= x.id.strike_price <= max_strike) to_remove = set(self._symbols).difference(symbols) for symbol in to_remove: if algorithm.remove_option_contract(symbol): self._symbols.remove(symbol) to_add = symbols.difference(self._symbols) for symbol in to_add: self._symbols.append(symbol) algorithm.add_option_contract(symbol)