QuantConnect
US Index Option Universe
Introduction
The US Index Option Universe dataset by QuantConnect lists the available US Index Options contracts and the current Implied Volatility and Greeks. The data covers European Option contracts for 3 US Indices: SPX, VIX, and NDX. It starts in January 2012 and is delivered on a daily update frequency. To create this dataset, we use our implementation of the forward tree pricing model, which accounts for the interest rate, dividend payments, and daily closing prices. The values in this dataset are the same values you can get from daily indicators with mirror Options.
This dataset does not
contain market data. For market data, see US Index Options by AlgoSeek.
For more information about the US Index Option Universe dataset, including CLI commands and pricing, see the dataset listing.
About the Provider
QuantConnect was founded in 2012 to serve quants everywhere with the best possible algorithmic trading technology. Seeking to disrupt a notoriously closed-source industry, QuantConnect takes a radically open-source approach to algorithmic trading. Through the QuantConnect web platform, more than 50,000 quants are served every month.
Getting Started
The following snippet demonstrates how to request data from the US Index Options Universe dataset:
option = self.add_index_option('VIX') option.set_filter(lambda universe: universe.delta(0.4, 0.6)) self.option_symbol = option.symbol
var option = AddIndexOption("VIX"); option.SetFilter(universe => universe.delta(0.4m, 0.6m)); _optionSymbol = option.Symbol;
Specification Over Time
According to the SPX Options contract specification, some SPX contracts expire every month and SPXW contracts expires every day. Before 2021, you could only trade SPX contracts with the following expiration dates:
- Expires within the next 4 months
- Expires in September within the next 14 months
- Expires in January, March, or June within the next 2 years
- Expires in December within the next 3 years
During this time, SPXW didn't have 0DTE every day.
Sources
:
- Cboe Options Exchange to List Three Long-Dated SPX Options Expirations, Beginning November 1, 2021
- S&P 500 Weekly Options Now Expire Five Days a Week
Requesting Data
To add US Index Options Universe data to your algorithm, call the AddIndexOption
add_index_option
method. Save a reference to the Index Option Symbol
so you can access the data later in your algorithm. To define which contracts should be in your universe, call the SetFilter
set_filter
method of the IndexOption
object.
The AddIndexOption
add_index_option
method provides a daily stream of Option chain data. To get the most recent daily chain, call the OptionChain
option_chain
method with the canonical Index Option Symbol. The OptionChain
option_chain
method returns data on all the tradable contracts, not just the contracts that pass your universe filter.
class IndexOptionsDataAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2021, 1, 1) self.set_end_date(2021, 6, 1) self.set_cash(1_000_000) self.universe_settings.asynchronous = True self.index_symbol = self.add_index('VIX').symbol standard_option = self.add_index_option(self.index_symbol) standard_option.set_filter(self._option_filter) self.standard_option_symbol = standard_option.symbol standard_chain = self.option_chain(standard_option.symbol, flatten=True).data_frame weekly_option = self.add_index_option(self.index_symbol, "VIXW") weekly_option.set_filter(self._option_filter) self.weekly_option_symbol = weekly_option.symbol weekly_chain = self.option_chain(weekly_option.symbol, flatten=True).data_frame weekly_chain = weekly_chain[ weekly_chain.index.map(lambda symbol: not IndexOptionSymbol.is_standard(symbol)) ] def _option_filter(self, universe: OptionFilterUniverse) -> OptionFilterUniverse: # Contracts can be filtered by greeks, implied volatility, open interest: return universe \ .delta(0.5, 1.5) \ .gamma(0.0001, 0.0006) \ .vega(0.01, 1.5) \ .theta(-2.0, -0.5) \ .rho(0.5, 3.0) \ .implied_volatility(1, 3) \ .open_interest(100,500)
public class IndexOptionsDataAlgorithm : QCAlgorithm { private Symbol _indexSymbol, _standardOptionSymbol, _weeklyOptionSymbol; public override void Initialize() { SetStartDate(2021, 1, 1); SetEndDate(2021, 6, 1); SetCash(100000); UniverseSettings.Asynchronous = True; _indexSymbol = AddIndex("VIX").Symbol; var standardOption = AddIndexOption(_indexSymbol); standardOption.SetFilter(OptionFilter); _standardOptionSymbol = standardOption.Symbol; var standardChain = OptionChain(_standardOptionSymbol); var weeklyOption = AddIndexOption(_indexSymbol, "VIXW"); weeklyOption.SetFilter(OptionFilter); _weeklyOptionSymbol = weeklyOption.Symbol; var weeklyChain = OptionChain(_weeklyOptionSymbol) .Where(contract => !IndexOptionSymbol.IsStandard(contract.Symbol)); } private virtual OptionFilterUniverse OptionFilter(OptionFilterUniverse universe) { // Contracts can be filtered by greeks, implied volatility, open interest: return universe .Delta(0.5m, 1.5m) .Gamma(0.0001m, 0.0006m) .Vega(0.01m, 1.5m) .Theta(-2.0m, -0.5m) .Rho(0.5m, 3.0m) .ImpliedVolatility(1.0m, 3.0m) .OpenInterest(100, 500); } }
The Index resolution must be less than or equal to the Index Option resolution. For example, if you set the Index resolution to minute, then you must set the Index Option resolution to minute, hour, or daily.
For more information about creating US Index Option subscriptions, see Index Options.
Accessing Data
For information about accessing US Equity Options data, see Handling Data.
Historical Data
You can get historical US Index Options data in an algorithm and the Research Environment.
Historical Data In Algorithms
To get historical US Index Options Universe data in an algorithm, call the History<OptionUniverse>
history
method with the canonical Index Option Symbol
. This method returns data on all of the tradable contracts, not just the contracts that pass your universe filter. If there is no data in the period you request, the history result is empty.
# DataFrame history_df = self.history(self.standard_option_symbol, timedelta(3)) # OptionUniverse objects history = self.history[OptionUniverse](self.standard_option_symbol, timedelta(3))
// OptionUniverse objects var history = History<OptionUniverse>(_standardOptionSymbol, TimeSpan.FromDays(3)).ToList();
For more information about Index Options Universe data in algorithms, see Historical Data.
Historical Data In Research
To get historical US Index Options Universe data in the Research Environment, call the History<OptionUniverse>
history
method with the canonical Option Symbol
. This method returns data on all of the tradable contracts, not just the contracts that pass your universe filter.
qb = QuantBook() index_symbol = qb.add_index('VIX').symbol option = qb.add_index_option(index_symbol) # or qb.add_index_option(index_symbol, "VIXW") history = qb.history(option.symbol, datetime(2020, 6, 1), datetime(2020, 6, 5))
var qb = new QuantBook();
var indexSymbol = qb.AddIndex("VIX").Symbol;
var option = qb.AddIndexOption(indexSymbol); // or qb.AddIndexOption(indexSymbol, "VIXW");
var history = qb.History<OptionUniverse>(option.Symbol, new DateTime(2020, 6, 1), new DateTime(2020, 6, 6));
foreach (var chain in history)
{
var endTime = chain.EndTime;
var filteredContracts = chain.Data
.Select(contract => contract as OptionUniverse)
.Where(contract => contract.Greeks.Delta > 0.3m);
foreach (var contract in filteredContracts)
{
var price = contract.Price;
var iv = contract.ImpliedVolatility;
}
}
For more information about historical Index Options Universe data in the Research Environment, see Universes.
Supported Assets
The following table shows the available Index Options:
Underlying Index | Underlying Ticker | Target Ticker | Standard Contracts | Weekly Contracts | Tradable on Expiry Day |
---|---|---|---|---|---|
NASDAQ-100 | NDX | ![]() | |||
NASDAQ-100 | NDX | NDXP | ![]() | ![]() | |
NASDAQ-100 | NDX | NQX | ![]() | ![]() | ![]() |
Russell 2000 | RUT | ![]() | |||
Russell 2000 | RUT | RUTW | ![]() | ![]() | |
S&P500 | SPX | ![]() | |||
S&P500 | SPX | SPXW | ![]() | ![]() | |
S&P500 | VIX | ![]() | |||
S&P500 | VIX | VIXW | ![]() |
For more information about each underlying Index, see Supported Indices.
Example Applications
The US Index Options Universe dataset enables you to accurately design strategies for Index Options. Examples include the following strategies:
- Buying VIX call Options to hedge against upcoming volatility
- Buying VIX put Options to capture the natural downward price movement in the VIX index
- Buying SPX put Options to protect against downward price movement in the S&P 500
Classic Algorithm Example
The following example algorithm subscribes to SPX put options that fall within delta range between -1 and -0.95, open interest range between 10 and 1000, and expire within seven days. Within this Option chain, the algorithm holds the put Option contract that has the minimum delta (closest to -1) during market hour to hedge the underlying intra-day movement completely. It avoid volatility from sentiment and only earns from inter-day movement from longer-term factors. When the contract expires, the algorithm rolls over to the next contract that meets this criteria.
from AlgorithmImports import *
class IndexOptionsUniverseAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2021, 1, 1)
self.set_end_date(2021, 2, 1)
self.set_cash(10000000)
# Asynchronous can use computational resources efficiently
self.universe_settings.asynchronous = True
# Subscribe to the underlying for the underlying position
# Set the data normalization mode to raw for strike price comparability
self.index = self.add_index("SPX").symbol
# Requesting option data and filter for the hedge candidates
option = self.add_index_option(self.index)
option.set_filter(self.option_filter)
self.option_symbol = option.symbol
# Set scheduled event to buy a hedge option contract at market open to eliminate the intra-day movement
self.schedule.on(
self.date_rules.every_day(self.index),
self.time_rules.after_market_open(self.index, 1),
self.buy_hedge_contract
)
# Set a scheduled event to sell the hedge contract before market close, since we want to earn from inter-day movement
# Leave 2 minutes contingency to fill
self.schedule.on(
self.date_rules.every_day(self.index),
self.time_rules.before_market_close(self.index, 2),
self.sell_hedge_contract
)
self.hedge = None
def option_filter(self, universe: OptionFilterUniverse) -> OptionFilterUniverse:
# Select the contracts with delta very close to -1 and high open interest
# This can effectively hedge most of the price change of the underlying and ensure the liquidity
# Make sure the contract is expiring close for its tradbility
return universe.include_weeklys().puts_only().expiration(2, 7).delta(-1, -0.95).open_interest(10, 1000)
def buy_hedge_contract(self) -> None:
chain = self.current_slice.option_chains.get(self.option_symbol)
if chain:
# Order the underlying if not hold, the order size should match the option contract
# Order only if option chain data ready for hedging
if not self.portfolio[self.index].invested:
self.market_order(self.index, self.securities[self.option_symbol].symbol_properties.contract_multiplier)
# Get the contract with delta closest to -1 (lowest possible delta)
contract = sorted(chain, key=lambda x: x.greeks.delta)[0]
self.hedge = contract.symbol
# Buy 1 deep ITM put with delta close to -1 to eliminate the intraday movement
self.market_order(self.hedge, 1)
def sell_hedge_contract(self) -> None:
# Check if any hedge contract position, if so, liquidate before market close to expose to underlying overnight movement
if self.hedge:
self.liquidate(self.hedge)
self.hedge = None
public class IndexOptionsUniverseAlgorithm : QCAlgorithm
{
private Symbol _index, _optionSymbol, _hedge;
public override void Initialize()
{
SetStartDate(2021, 1, 1);
SetEndDate(2021, 2, 1);
SetCash(10000000);
// Asynchronous can use computational resources efficiently
UniverseSettings.Asynchronous = True;
// Subscribe to the underlying for the underlying position
// Set the data normalization mode to raw for strike price comparability
_index = AddIndex("SPX").Symbol;
// Requesting option data and filter for the hedge candidates
var option = AddIndexOption(_index);
_optionSymbol = option.Symbol;
option.SetFilter(OptionFilter);
// Set scheduled event to buy a hedge option contract at market open to eliminate the intra-day movement
Schedule.On(
DateRules.EveryDay(_index),
TimeRules.AfterMarketOpen(_index, 1),
BuyHedgeContract
);
// Set a scheduled event to sell the hedge contract before market close, since we want to earn from inter-day movement
// Leave 2 minutes contingency to fill
Schedule.On(
DateRules.EveryDay(_index),
TimeRules.BeforeMarketClose(_index, 2),
SellHedgeContract
);
}
private OptionFilterUniverse OptionFilter(OptionFilterUniverse universe)
{
// Select the contracts with delta very close to -1 and high open interest
// This can effectively hedge most of the price change of the underlying and ensure the liquidity
// Make sure the contract is expiring close for its tradbility
return universe
.IncludeWeeklys()
.PutsOnly()
.Expiration(2, 7)
.Delta(-1m, -0.95m)
.OpenInterest(10, 1000);
}
private void BuyHedgeContract()
{
if (CurrentSlice.OptionChains.TryGetValue(_optionSymbol, out var chain))
{
// Order the underlying if not hold, the order size should match the option contract
// Order only if option chain data ready for hedging
if (!Portfolio[_index].Invested)
{
MarketOrder(_index, Securities[_optionSymbol].SymbolProperties.ContractMultiplier);
}
// Get the contract with delta closest to -1 (lowest possible delta)
var contract = chain.MinBy(x => x.Greeks.Delta);
_hedge = contract.Symbol;
// Buy 1 deep ITM put with delta close to -1 to eliminate the intraday movement
MarketOrder(_hedge, 1);
}
}
private void SellHedgeContract()
{
// Check if any hedge contract position, if so, liquidate before market close to expose to underlying overnight movement
if (_hedge != None)
{
Liquidate(_hedge);
_hedge = None;
}
}
}
Framework Algorithm Example
The following example algorithm demonstrating a Gamma Scalping strategy through framework algorithm. It filters SPX options with expiration between 30 to 90 days and open interest between 50 and 1000, due to liquidity concern and lower Gamma fluctuation. Assuming a significant upward trend is expecting, it orders a long straddle strategy from the strike price with Delta-neutral and the highest Gamma to earn the highest profit from upward underlying movement.
from AlgorithmImports import *
class IndexOptionsUniverseFrameworkAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2023, 1, 1)
self.set_end_date(2023, 12, 31)
self.set_cash(100000)
# Asynchronous can use computational resources efficiently
self.universe_settings.asynchronous = True
# Universe selection that select based on liquidity of the option contracts (by open interest)
self.add_universe_selection(IndexOptionsUniverseSelectionModel())
# Custom alpha model that use Delta and Gamma to signal insights
self.add_alpha(OptionDeltaGammaAlphaModel())
# To maintain long-short size equal, use a PCM that order a single contract
self.set_portfolio_construction(SingleSharePortfolioConstructionModel())
class IndexOptionsUniverseSelectionModel(OptionUniverseSelectionModel):
def __init__(self) -> None:
# Daily update with the select_option_chain_symbols function
super().__init__(timedelta(1), self.select_option_chain_symbols)
def select_option_chain_symbols(self, dt: datetime) -> List[Symbol]:
# Select only SPX options as our focus, which is a highly traded volatile index with great upward momentum
return [Symbol.create("NVDA", SecurityType.OPTION, Market.USA)]
def filter(self, universe: OptionFilterUniverse) -> OptionFilterUniverse:
# To ensure the liquidity and tradability, make sure the option is not 0-DTE and have a fair open interest
# A longer TTM will have lower Gamma thus more stable in the local delta-neutral position
return universe.include_weeklys().expiration(30, 90).open_interest(50, 1000)
class OptionDeltaGammaAlphaModel(AlphaModel):
def __init__(self) -> None:
# A day count variable to control the alpha model only trade once a day
self._day = -1
def update(self, algorithm: QCAlgorithm, slice: Slice) -> List[Insight]:
insights = []
if self._day != slice.time.day:
# We open position for each option underlying as separate bet
for _, chain in slice.option_chains.items():
# For theta-neutral, select the same expiry for both call and put
expiry = min(x.expiry for x in chain)
contracts = [x for x in chain if x.expiry == expiry]
# Calculate delta and gamma per strike price group for later filtering, ensure both call and put available for option strategy
delta_gamma_symbols = []
strikes = set(x.strikes for x in contracts if len([y for y in contracts if y.strike == x.strike]) == 2)
for strike in strikes:
# Get both call and put for their aggregated delta and gamma
call = next(filter(lambda x: x.right == OptionRight.CALL and x.strike == strike), contracts)
put = next(filter(lambda x: x.right == OptionRight.PUT and x.strike == strike), contracts)
delta_gamma_symbols.append((call.greeks.delta + put.greeks.delta, call.greeks.gamma + put.greeks.gamma, call.symbol, put.symbol))
if len(delta_gamma_symbols) == 0:
continue
# We want a delta-neutral position, so it is likely to be ATM (like a long straddle)
# Less than 2d.p. difference is non-significant, which we can risk for better reward
# Assuming the market direction is up in most scenario, we try to get the strike providing max overall Gamma
# Make sure the aggregated gamma to be positive to bet on large uptrend
# So it will earn more when the price really go up higher and higher, but locally immune for small noise
filtered = [item for item in delta_gamma_symbols if round(item[0], 2) <= 0.01 and item[1] > 0]
if len(filtered) == 0:
continue
selected = sorted(filtered, key=lambda x: x[1])[0]
# Provide trade signal and roll the day count to let the position stay for the whole day
selected_call = selected[2]
selected_put = selected[3]
insights.extend([
Insight.price(selected_call, Expiry.END_OF_DAY, InsightDirection.UP),
Insight.price(selected_put, Expiry.END_OF_DAY, InsightDirection.UP)
])
self._day = slice.time.day
return insights
class SingleSharePortfolioConstructionModel(PortfolioConstructionModel):
def create_targets(self, algorithm: QCAlgorithm, insights: List[Insight]) -> List[PortfolioTarget]:
targets = []
for insight in insights:
if algorithm.securities[insight.symbol].is_tradable:
# Use a whole number target to order the exact number of share for size-matching
targets.append(PortfolioTarget(insight.symbol, insight.direction))
return targets
public class IndexOptionsUniverseFrameworkAlgorithm : QCAlgorithm
{
public override void Initialize()
{
SetStartDate(2024, 1, 1);
SetEndDate(2024, 3, 31);
SetCash(100000);
// Asynchronous can use computational resources efficiently
UniverseSettings.Asynchronous = True;
// Universe selection that select based on liquidity of the option contracts (by open interest)
AddUniverseSelection(new IndexOptionsUniverseSelectionModel());
// Custom alpha model that use Delta and Gamma to signal insights
AddAlpha(new OptionDeltaGammaAlphaModel());
// To maintain long-short size equal, use a PCM that order a single contract
SetPortfolioConstruction(new SingleSharePortfolioConstructionModel());
}
}
class IndexOptionsUniverseSelectionModel : OptionUniverseSelectionModel
{
// Daily update with the SelectOptionChainSymbols function
public IndexOptionsUniverseSelectionModel()
: base(TimeSpan.FromDays(1), SelectOptionChainSymbols) {}
private static IEnumerable<Symbol> SelectOptionChainSymbols(DateTime utcTime)
{
// Select only SPX options as our focus, which is a highly traded volatile index with great upward momentum
return new[] {QuantConnect.Symbol.Create("SPX", SecurityType.IndexOption, Market.USA)};
}
protected override OptionFilterUniverse Filter(OptionFilterUniverse filter)
{
// To ensure the liquidity and tradability, make sure the option is not 0-DTE and have a fair open interest
// A longer TTM will have lower Gamma thus more stable in the local delta-neutral position
return filter
.IncludeWeeklys()
.Expiration(30, 90)
.OpenInterest(50, 1000);
}
}
class OptionDeltaGammaAlphaModel : AlphaModel
{
// A day count variable to control the alpha model only trade once a day
private int _day = -1;
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice slice)
{
var insights = new List<Insight>();
if (_day != slice.Time.Day)
{
// We open position for each option underlying as separate bet
foreach (var kvp in slice.OptionChains)
{
var chain = kvp.Value;
// For theta-neutral, select the same expiry for both call and put
var expiry = chain.Min(x => x.Expiry);
var contracts = chain.Where(x => x.Expiry == expiry).ToList();
// Calculate delta and gamma per strike price group for later filtering, ensure both call and put available for option strategy
var deltaGammaSymbols = new List<(decimal, decimal, Symbol, Symbol)>();
var strikes = contracts.Select(x => x.Strike)
.Where(x => contracts.Count(y => y.Strike == x) == 2)
.Distinct();
foreach (var strike in strikes)
{
// Get both call and put for their aggregated delta and gamma
var call = contracts.Single(x => x.Right == OptionRight.Call && x.Strike == strike);
var put = contracts.Single(x => x.Right == OptionRight.Put && x.Strike == strike);
deltaGammaSymbols.Add((call.Greeks.Delta + put.Greeks.Delta, call.Greeks.Gamma + put.Greeks.Gamma, call.Symbol, put.Symbol));
}
if (deltaGammaSymbols.Count == 0)
{
continue;
}
// We want a delta-neutral position, so it is likely to be ATM (like a long straddle)
// Less than 2d.p. difference is non-significant, which we can risk for better reward
// Assuming the market direction is up in most scenario, we try to get the strike providing max overall Gamma
// Make sure the aggregated gamma to be positive to bet on large uptrend
// So it will earn more when the price really go up higher and higher, but locally immune for small noise
var filtered = deltaGammaSymbols.Where(item => Math.Round(item.Item1, 2) <= 0.01m && item.Item2 > 0).ToList();
if (filtered.Count == 0)
{
continue;
}
var selected = filtered.OrderByDescending(item => item.Item2).First();
// Provide trade signal and roll the day count to let the position stay for the whole day
var selectedCall = selected.Item3;
var selectedPut = selected.Item4;
insights.AddRange(new[] {
Insight.Price(selectedCall, Expiry.EndOfDay, InsightDirection.Up),
Insight.Price(selectedPut, Expiry.EndOfDay, InsightDirection.Up)
});
_day = slice.Time.Day;
}
}
return insights;
}
}
class SingleSharePortfolioConstructionModel : PortfolioConstructionModel
{
public override IEnumerable<PortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
{
var targets = new List<PortfolioTarget>();
foreach (var insight in insights)
{
if (algorithm.Securities[insight.Symbol].IsTradable)
{
// Use a whole number target to order the exact number of share for size-matching
targets.Add(new PortfolioTarget(insight.Symbol, (int) insight.Direction));
}
}
return targets;
}
}
Data Point Attributes
The US Index Options Universe dataset provides OptionFilterUniverse
, OptionUniverse
, and OptionChain
objects.
OptionFilterUniverse Attributes
OptionFilterUniverse
objects have the following attributes:
OptionUniverse Attributes
OptionUniverse
objects have the following attributes:
OptionChain Attributes
OptionChain
objects have the following attributes: