---
title: "Index Options"
description: "Some of the Future Options data you can get include prices, open interest, Greeks, and universe selection data."
canonical: https://www.quantconnect.com/docs/v2/writing-algorithms/historical-data/asset-classes/index-options
markdown: https://www.quantconnect.com/docs/v2/writing-algorithms/historical-data/asset-classes/index-options.md
language: python
source: https://github.com/QuantConnect/Documentation/tree/master/03%20Writing%20Algorithms/20%20Historical%20Data/04%20Asset%20Classes/09%20Index%20Options
modified: 2026-10-30
index: https://www.quantconnect.com/llms.txt
---

# Index Options

[Documentation](https://www.quantconnect.com/docs/v2.md) > [Writing Algorithms](https://www.quantconnect.com/docs/v2/writing-algorithms.md) > Historical Data > Asset Classes > Index Options

> Code examples are shown in Python. Append `?lang=csharp` to this URL for the C# version of this page.

## Introduction

This page explains how to get historical data for Index Options. Some of the data you can get include prices, open interest, Greeks, and universe selection data.

## Trades

To get historical [trade data](https://www.quantconnect.com/docs/v2/writing-algorithms/securities/asset-classes/index-options/handling-data.md#02-Trades), call the `history` method with the `TradeBar` type and a security's `Symbol`. This method returns a DataFrame with columns for the open, high, low, close, and volume.

```python
class IndexOptionTradeBarHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        self.set_end_date(2024, 12, 31)
        # Get the Symbol of a security.
        index = self.add_index('SPX')
        symbol = sorted(self.option_chain(index.symbol), key=lambda c: c.open_interest)[-1].symbol
        # Get the 5 trailing daily TradeBar objects of the security in DataFrame format.
        history = self.history(TradeBar, symbol, 5, Resolution.DAILY)
```

| expiry | strike | type | symbol | time | close | high | low | open | volume |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-12 15:15:00 | 0.05 | 0.07 | 0.05 | 0.07 | 5773.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-13 15:15:00 | 0.05 | 0.10 | 0.05 | 0.05 | 3052.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-16 15:15:00 | 0.03 | 0.10 | 0.03 | 0.10 | 6423.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-17 15:15:00 | 0.03 | 0.05 | 0.03 | 0.05 | 6085.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-18 15:15:00 | 0.85 | 1.00 | 0.03 | 0.03 | 10551.0 |

```python
# Calculate the daily returns.
daily_returns = history.close.pct_change().iloc[1:]
```

```python
expiry      strike  type  symbol                    time
2024-12-20  4000.0  1     SPX 32NKZCP89T8EM|SPX 31  2024-12-13 15:15:00     0.000000
                                                    2024-12-16 15:15:00    -0.400000
                                                    2024-12-17 15:15:00     0.000000
                                                    2024-12-18 15:15:00    27.333333
```

If you intend to use the data in the DataFrame to create `TradeBar` objects, request that the history request returns the data type you need. Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame. To get a list of `TradeBar` objects instead of a DataFrame, call the `history[TradeBar]` method.

```python
# Get the 5 trailing daily TradeBar objects of the security in TradeBar format.
history = self.history[TradeBar](symbol, 5, Resolution.DAILY)
# Iterate through the TradeBar objects and access their volumes.
for trade_bar in history:
    t = trade_bar.end_time
    volume = trade_bar.volume
```

Request minute, hour, or daily resolution data. Otherwise, the history request won't return any data.

## Quotes

To get historical [quote data](https://www.quantconnect.com/docs/v2/writing-algorithms/securities/asset-classes/index-options/handling-data.md#03-Quotes), call the `history` method with the `QuoteBar` type and a security's `Symbol`. This method returns a DataFrame with columns for the open, high, low, close, and size of the bid and ask quotes. The columns that don't start with "bid" or "ask" are the mean of the quote prices on both sides of the market.

```python
class IndexOptionQuoteBarHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        self.set_end_date(2024, 12, 31)
        # Get the Symbol of a security.
        index = self.add_index('SPX')
        symbol = sorted(self.option_chain(index.symbol), key=lambda c: c.open_interest)[-1].symbol
        # Get the 5 trailing minute QuoteBar objects of the security in DataFrame format.
        history = self.history(QuoteBar, symbol, 5, Resolution.MINUTE)
```

| expiry | strike | type | symbol | time | askclose | askhigh | asklow | askopen | asksize | bidclose | bidhigh | bidlow | bidopen | bidsize | close | high | low | open |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-18 15:11:00 | 1.45 | 9.8 | 1.15 | 1.15 | 11.0 | 0.90 | 0.90 | 0.3 | 0.90 | 87.0 | 1.175 | 5.350 | 0.725 | 1.025 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-18 15:12:00 | 1.40 | 9.6 | 1.40 | 1.45 | 10.0 | 0.85 | 0.95 | 0.7 | 0.90 | 63.0 | 1.125 | 5.275 | 1.050 | 1.175 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-18 15:13:00 | 1.40 | 4.9 | 1.40 | 1.40 | 122.0 | 0.80 | 0.90 | 0.3 | 0.85 | 104.0 | 1.100 | 2.900 | 0.850 | 1.125 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-18 15:14:00 | 1.25 | 4.9 | 1.25 | 1.40 | 59.0 | 0.60 | 0.80 | 0.3 | 0.80 | 95.0 | 0.925 | 2.850 | 0.775 | 1.100 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-18 15:15:00 | 1.05 | 4.9 | 1.00 | 1.25 | 48.0 | 0.60 | 0.85 | 0.3 | 0.60 | 10.0 | 0.825 | 2.875 | 0.650 | 0.925 |

```python
# Calculate the spread at each minute.
spread = history.askclose - history.bidclose
```

```python
expiry      strike  type  symbol                    time
2024-12-20  4000.0  1     SPX 32NKZCP89T8EM|SPX 31  2024-12-18 15:11:00    0.55
                                                    2024-12-18 15:12:00    0.55
                                                    2024-12-18 15:13:00    0.60
                                                    2024-12-18 15:14:00    0.65
                                                    2024-12-18 15:15:00    0.45
dtype: float64
```

If you intend to use the data in the DataFrame to create `QuoteBar` objects, request that the history request returns the data type you need. Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame. To get a list of `QuoteBar` objects instead of a DataFrame, call the `history[QuoteBar]` method.

```python
# Get the 5 trailing minute QuoteBar objects of the security in QuoteBar format.
history = self.history[QuoteBar](symbol, 5, Resolution.MINUTE)
# Iterate through each QuoteBar and calculate the dollar volume on the bid.
for quote_bar in history:
    t = quote_bar.end_time
    bid_dollar_volume = quote_bar.last_bid_size * quote_bar.bid.close
```

Request minute, hour, or daily resolution data. Otherwise, the history request won't return any data.

## Slices

To get historical [Slice](https://www.quantconnect.com/docs/v2/writing-algorithms/key-concepts/time-modeling/timeslices.md) data, call the `history` method without passing any `Symbol` objects. This method returns `Slice` objects, which contain data points from all the datasets in your algorithm. If you omit the `resolution` argument, it uses the resolution that you set for each security and dataset when you created the subscriptions.

```python
class SliceHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 1)
        self.set_end_date(2024, 12, 31)
        # Add some securities and datasets.
        index = self.add_index('SPX')
        contract = sorted(self.option_chain(index.symbol), key=lambda c: c.open_interest)[-1]
        self.add_option_contract(contract.symbol)
        # Get the historical Slice objects over the last 5 days for all the subcriptions in your algorithm.
        history = self.history(5, Resolution.DAILY)
        # Iterate through each historical Slice.
        for slice_ in history:
            # Iterate through each TradeBar in this Slice.
            for symbol, trade_bar in slice_.bars.items():
                close = trade_bar.close
```

## Open Interest

To get historical [open interest data](https://www.quantconnect.com/docs/v2/writing-algorithms/securities/asset-classes/index-options/handling-data.md#05-Option-Contracts), call the `history` method with the `OpenInterest` type and a security's `Symbol`. This method returns a DataFrame with a single column.

```python
class IndexOptionOpenInterestHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        self.set_end_date(2024, 12, 31)
        # Get the Symbol of a security.
        index = self.add_index('SPX')
        symbol = sorted(self.option_chain(index.symbol), key=lambda c: c.open_interest)[-1].symbol
        # Get the 5 trailing daily OpenInterest objects of the security in DataFrame format.
        history = self.history(OpenInterest, symbol, 5, Resolution.DAILY)
```

| expiry | strike | type | symbol | time | openinterest |
| --- | --- | --- | --- | --- | --- |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-12 23:00:00 | 306249.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-15 23:00:00 | 305821.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-16 23:00:00 | 301048.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-17 23:00:00 | 299501.0 |
| 2024-12-20 | 4000.0 | 1 | SPX 32NKZCP89T8EM\|SPX 31 | 2024-12-18 23:00:00 | 294504.0 |

```python
# Calculate the daily change in open interest.
oi_delta = history.openinterest.diff().iloc[1:]
```

```python
expiry      strike  type  symbol                    time
2024-12-20  4000.0  1     SPX 32NKZCP89T8EM|SPX 31  2024-12-15 23:00:00    -428.0
                                                    2024-12-16 23:00:00   -4773.0
                                                    2024-12-17 23:00:00   -1547.0
                                                    2024-12-18 23:00:00   -4997.0
Name: openinterest, dtype: float64
```

If you intend to use the data in the DataFrame to create `OpenInterest` objects, request that the history request returns the data type you need. Otherwise, LEAN consumes unnecessary computational resources populating the DataFrame. To get a list of `OpenInterest` objects instead of a DataFrame, call the `history[OpenInterest]` method.

```python
# Get the 5 trailing daily OpenInterest objects of the security in OpenInterest format.
history = self.history[OpenInterest](symbol, 5, Resolution.DAILY)
# Iterate through the TradeBar objects and access their volumes.
for oi in history:
    t = oi.end_time
    open_interest = oi.value
```

## Daily Option Chains

To get historical [daily Option chain data](https://www.quantconnect.com/docs/v2/writing-algorithms/universes/index-options.md#08-Navigate-Daily-Option-Chains), call the `history` method with the Option `Symbol` object. The data this method returns contains information on all the currently tradable contracts, not just the contracts that pass your filter. If you pass `flatten=True`, this method returns a DataFrame with columns for the data point attributes.

```python
class IndexOptionDailyOptionChainHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 23)
        self.set_end_date(2024, 12, 31)
        # Add an Index Option universe.
        option = self.add_index_option('SPX')
        # Get the trailing 5 daily Option chains in DataFrame format.
        history = self.history(option.symbol, 5, flatten=True)
```

| time | symbol | close | delta | gamma | high | impliedvolatility | low | open | openinterest | rho | theta | underlying | value | vega | volume |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 2024-12-12 | SPX YOGZ798QKLRI\|SPX 31 | 5879.60 | 1.000000 | 0.000000 | 5897.45 | 0.000000 | 5865.00 | 5866.45 | 5280.0 | 0.051183 | -0.030094 | SPX: ¤6,084.20 | 5879.60 | 0.000000 | 9.0 |
| 2024-12-12 | SPX YP8JPVHGPQ9A\|SPX 31 | 5878.95 | 1.000000 | 0.000000 | 5898.45 | 0.000000 | 5864.30 | 5866.55 | 68.0 | 0.203532 | -0.029968 | SPX: ¤6,084.20 | 5878.95 | 0.000000 | 0.0 |
| 2024-12-12 | SPX YQ70D5ADE4VI\|SPX 31 | 5876.70 | 1.000000 | 0.000000 | 5892.70 | 0.000000 | 5862.20 | 5863.70 | 42.0 | 0.392162 | -0.029810 | SPX: ¤6,084.20 | 5876.70 | 0.000000 | 0.0 |
| 2024-12-12 | SPX YQYKVRJ3J9DA\|SPX 31 | 5873.70 | 1.000000 | 0.000000 | 5889.75 | 0.000000 | 5856.85 | 5860.50 | 35.0 | 0.541633 | -0.029685 | SPX: ¤6,084.20 | 5873.70 | 0.000000 | 0.0 |
| 2024-12-12 | SPX YRP5YAENLMPA\|SPX 31 | 5873.15 | 0.999970 | 0.000000 | 5888.45 | 1.651861 | 5857.20 | 5859.35 | 1.0 | 0.680416 | -0.032476 | SPX: ¤6,084.20 | 5873.15 | 0.004516 | 0.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2024-12-18 | SPX 32RLQ2G18QHCE\|SPX 31 | 4740.75 | -0.999435 | 0.000002 | 4751.20 | 0.268745 | 4727.55 | 4738.45 | 0.0 | -44.100768 | 1.613144 | SPX: ¤6,050.31 | 4740.75 | 0.077120 | 0.0 |
| 2024-12-18 | SPX 32XJE2QF1AJ7Y\|SPX 31 | 5445.85 | -0.675418 | 0.000079 | 5466.35 | 0.753369 | 5424.20 | 5445.05 | 28.0 | -101.357524 | -0.721391 | SPX: ¤6,050.31 | 5445.85 | 21.834520 | 0.0 |
| 2024-12-18 | SPX 337HSSRKH55N2\|SPX 31 | 5001.30 | -0.997608 | 0.000006 | 5012.40 | 0.139114 | 4990.20 | 4999.30 | 3.0 | -214.914871 | 1.611553 | SPX: ¤6,050.31 | 5001.30 | 0.638336 | 0.0 |
| 2024-12-18 | SPX 33HG7ISPWZS26\|SPX 31 | 4583.80 | -0.992253 | 0.000017 | 4595.60 | 0.118943 | 4574.20 | 4582.00 | 10.0 | -303.860365 | 1.514502 | SPX: ¤6,050.31 | 4583.80 | 2.232716 | 0.0 |
| 2024-12-18 | SPX 33REM8TVCUEHA\|SPX 31 | 4193.20 | -0.975511 | 0.000043 | 4205.90 | 0.111797 | 4185.60 | 4191.90 | 8.0 | -379.486892 | 1.404206 | SPX: ¤6,050.31 | 4193.20 | 6.947694 | 0.0 |

```python
# Select the 2 contracts with the greatest volume each day.
most_traded = history.groupby('time').apply(lambda x: x.nlargest(2, 'volume')).reset_index(level=1, drop=True).volume
```

```python
time        symbol
2024-12-12  SPX YOGZ79IHUCOE|SPX 31     20200.0
            SPX 32NKZCPHP4626|SPX 31    18417.0
2024-12-13  SPX YOGZ79IHUCOE|SPX 31      7109.0
            SPX 32OCJVBQ3CMGE|SPX 31     5930.0
2024-12-14  SPX YOGZ79IHUCOE|SPX 31     17443.0
            SPX 32NKZCRZNW3RI|SPX 31    16458.0
2024-12-17  SPX YOGZ7C245MVI|SPX 31     32796.0
            SPX 32NKZCS1BFG9A|SPX 31    30285.0
2024-12-18  SPX YOGZ79IHUCOE|SPX 31     14183.0
            SPX YOGZ7C245MVI|SPX 31     13335.0
Name: volume, dtype: float64
```

To get the data in the format of `OptionUniverse` objects instead of a DataFrame, call the `history[OptionUniverse]` method.

```python
# Get the historical OptionUniverse data over the last 30 days.
history = self.history[OptionUniverse](option.symbol, timedelta(30))
# Iterate through each daily Option chain.
for option_universe in history:
    t = option_universe.end_time
    # Select the contract with the most volume.
    most_traded = sorted(option_universe, key=lambda contract: contract.volume)[-1]
```

## Indicators

To get historical [indicator](https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/key-concepts.md) values, call the `indicator_history` method with an indicator and the security's `Symbol`.

```python
class IndexOptionIndicatorHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        self.set_end_date(2024, 12, 31)
        # Get the Symbol of the Option contract.
        underlying = self.add_index('SPX').symbol
        symbol = sorted(self.option_chain(underlying), key=lambda c: c.open_interest)[-1].symbol
        # Get the 21-day SMA values of the contract for the last 5 trading days.
        history = self.indicator_history(SimpleMovingAverage(21), symbol, 5, Resolution.DAILY)
```

To organize the data into a DataFrame, use the `data_frame` property of the result.

```python
# Organize the historical indicator data into a DataFrame to enable pandas wrangling.
history_df = history.data_frame
```

|  | current | rollingsum |
| --- | --- | --- |
| 2024-12-12 15:15:00 | 0.617857 | 12.975 |
| 2024-12-13 15:15:00 | 0.580952 | 12.200 |
| 2024-12-16 15:15:00 | 0.535714 | 11.250 |
| 2024-12-17 15:15:00 | 0.477381 | 10.025 |
| 2024-12-18 15:15:00 | 0.472619 | 9.925 |

```python
# Get the maximum of the SMA values.
sma_max = history_df.current.max()
```

The `indicator_history` method resets your indicator, makes a history request, and updates the indicator with the historical data. Just like with regular history requests, the `indicator_history` method supports time periods based on a trailing number of bars, a trailing period of time, or a defined period of time. If you don't provide a `resolution` argument, it defaults to match the resolution of the security subscription.

To make the `indicator_history` method update the indicator with an [alternative price field](https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/automatic-indicators.md#07-Alternative-Price-Fields) instead of the close (or mid-price) of each bar, pass a `selector` argument.

```python
# Get the historical values of an indicator over the last 30 days, applying the indicator to the security's volume.
history = self.indicator_history(indicator, symbol, timedelta(30), selector=Field.VOLUME)
```

Some indicators require the prices of multiple securities to compute their value (for example, the [indicators for the Greeks and implied volatility](https://www.quantconnect.com/docs/v2/writing-algorithms/securities/asset-classes/index-options/greeks-and-implied-volatility/indicators.md)). In this case, pass a list of the `Symbol` objects to the method.

```python
class IndexOptionMultiAssetIndicatorHistoryAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 12, 19)
        self.set_end_date(2024, 12, 31)
        # Get the Symbol of the underlying asset.
        underlying = self.add_index('SPX').symbol
        # Get the Option contract Symbol.
        option = sorted(self.option_chain(underlying), key=lambda c: c.open_interest)[-1].symbol
        # Get the Symbol of the mirror contract.
        mirror = Symbol.create_option(
            underlying, option.id.market, option.id.option_style,
            OptionRight.CALL if option.id.option_right == OptionRight.PUT else OptionRight.PUT,
            option.id.strike_price, option.id.date
        )
        # Create the indicator.
        indicator = ImpliedVolatility(
            option, self.risk_free_interest_rate_model, DividendYieldProvider(underlying),
            mirror, OptionPricingModelType.FORWARD_TREE
        )
        # Get the historical values of the indicator over the last 60 trading minutes.
        history = self.indicator_history(indicator, [underlying, option, mirror], 60, Resolution.MINUTE)
        # Get the average IV value.
        iv_avg = history.data_frame.current.mean()
```

## Examples

The following examples demonstrate some common practices for trading Index Options with historical data.

### Example 1: Standard-Weekly Contracts Cointegration

The following example analyzes the cointegration relationship between the front-month ATM SPX and SPXW calls. By measuring their spread divergence, we trade mean reversal on their spread convergence.

```python
from sklearn.linear_model import LinearRegression

class IndexOptionHistoricalDataAlgorithm(QCAlgorithm):
    _threshold = 2
    _cointegration_vector = [0, 0]
    _spread_mean = 0
    _spread_sd = 1
    _spx_contract = None
    _spxw_contract = None

    def initialize(self) -> None:
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 12, 31)
        self.set_cash(10000000)
        self._index = Symbol.create("SPX", SecurityType.INDEX, Market.USA)
        self._spx = Symbol.create_canonical_option(self._index)
        self._spxw = Symbol.create_canonical_option(self._index, "SPXW", Market.USA, "?SPXW")

        # Select the Index Options to analyze and trade by week.
        self.schedule.on(self.date_rules.week_start(), self.time_rules.at(9, 15), self.select_contract)

    def on_data(self, slice: Slice) -> None:
        if not (self._spx_contract and self._spxw_contract):
            return
        spxw = slice.quote_bars.get(self._spxw_contract)
        spx = slice.quote_bars.get(self._spx_contract)
        if not (spxw and spx):
            return
        # Obtain the current spread to see if there is any price divergence to trade.
        spread = self.calculate_spread(spxw.close, spx.close, self._cointegration_vector)
        z = (spread - self._spread_mean) / self._spread_sd
        # If the spread diverges above or below the threshold, trade to bet on mean reversion.
        if z > self._threshold and not self.portfolio[self._spx_contract].is_long:
            spx_qty = int(10 * self._cointegration_vector[1])
            spxw_qty = -10
            self.market_order(self._spx_contract, spx_qty)
            self.market_order(self._spxw_contract, spxw_qty)

        elif z < -self._threshold and not self.portfolio[self._spxw_contract].is_long:
            spx_qty = int(-10 * self._cointegration_vector[1])
            spxw_qty = 10
            self.market_order(self._spx_contract, spx_qty)
            self.market_order(self._spxw_contract, spxw_qty)

        # If prices converge, exit positions.
        if ((self.portfolio[self._spx_contract].is_long and z < 0) or
            (self.portfolio[self._spxw_contract].is_long and z > 0)):
            self.liquidate()

    def select_contract(self) -> None:
        self._spx_contract = None
        self._spxw_contract = None

        # Obtain the SPX ATM call contract since it is the most liquid to trade with.
        spx_contracts = [x for x in self.option_chain(self._spx) if x.expiry <= self.time + timedelta(30)]
        if not spx_contracts:
            return

        # Select the furthest expiry within the next 30 days
        expiry = max(x.expiry for x in spx_contracts)
        self._spx_contract = sorted(
            [x for x in spx_contracts
             if x.expiry == expiry and x.right == OptionRight.CALL],
            key=lambda x: abs(x.strike - x.underlying_last_price)
        )[0].symbol

        # Obtain the SPXW contract with the same strike, right, and expiry.
        spxw_contract = next(
            (
                x for x in self.option_chain(self._spxw)
                if (x.expiry == expiry and
                    x.right == OptionRight.CALL and
                    x.strike == self._spx_contract.id.strike_price)
            ),
            None
        )
        if not spxw_contract:
            return
        self._spxw_contract = spxw_contract.symbol

        # Subscribe to the contracts we will trade
        self.add_index_option_contract(self._spx_contract)
        self.add_index_option_contract(self._spxw_contract)

        # Obtain the historical data and find their cointegration relationship.
        history = self.history(QuoteBar, [self._spx_contract, self._spxw_contract], 1000).close.droplevel([0, 1, 2]).unstack(0)

        # Run linear regression to determine cointegration relationship
        self._cointegration_vector = self.calculate_cointegration_vector(history)

        residual = self.calculate_spread(history[self._spxw_contract], history[self._spx_contract], self._cointegration_vector)

        self._spread_mean = np.mean(residual)
        self._spread_sd = np.std(residual)

    def calculate_cointegration_vector(self, history):
        # Prepare log price series
        y = np.log(history[self._spxw_contract])
        x1 = np.log(history[self._spx_contract])

        # Linear regression for cointegration
        lr = LinearRegression().fit(x1.to_frame(), y)

        return [lr.intercept_, lr.coef_[0]]

    def calculate_spread(self, y, x1, cointegration_vector):
        return np.log(y) - (cointegration_vector[0] + np.log(x1) * cointegration_vector[1])
```
