Source: https://databento.com/docs/examples/options/equity-options-introduction


# Equity options: Introduction

> **Info**
>
> This article introduces additional concepts to get started with equity options data
> on Databento. If you're new to Databento, see the [Quickstart](https://databento.com/docs/quickstart.md)
> guide.

## Overview

In this example, we'll introduce:
- The OPRA dataset
- Using instrument definitions to get symbols, strike prices, and expirations
- Options Clearing Corporation (OCC) symbology for raw symbols
- Using parent instrument symbology to fetch an option chain
- Stream live options trades

We'll also highlight any special conventions of equity options datasets on
Databento that differ from those of other asset classes.

## Finding an equity options dataset

To use equity options data on Databento, first identify the dataset that you want
from our [data catalog]($PLATFORM_HOSTNAME) and go to its detail page. Here,
you can find its dataset ID at the top left of the page.

![Dataset ID](https://databento.com/docs/equity-options-dataset-id.png.md)

For this example, we'll use the [OPRA]($PLATFORM_HOSTNAME/datasets/OPRA.PILLAR)
dataset, whose dataset ID is `OPRA.PILLAR`. You'll need to pass this in as the `dataset`
parameter of any API or client method.

## OPRA

OPRA provides consolidated last sale and national BBO across all US equity options exchanges.
Various types of options are covered, including:
- Single name stock options (e.g. AAPL, TSLA)
- ETF options (e.g. SPY, QQQ)
- Index options (e.g. SPX/SPXW, VIX)

> **Info**
>
> The "Pillar" suffix in the `OPRA.PILLAR` dataset ID is a reference to the SIAC Pillar
> SIP, the latest infrastructure behind the binary OPRA feed since 2021.

## Using instrument definitions to get symbols, strike prices, and expirations

A full list of instrument definitions can be fetched with the `definition` schema and
passing in `symbols="ALL_SYMBOLS"` to the [timeseries.get\_range](https://databento.com/docs/api-historical/timeseries/timeseries-get-range.md?lang=python)
method.

```python
import databento as db

client = db.Historical("$YOUR_API_KEY")
data = client.timeseries.get_range(
    dataset="OPRA.PILLAR",
    schema="definition",
    symbols="ALL_SYMBOLS",
    start="2024-08-09",
    limit=10000,
)
df = data.to_df()
print(df[["raw_symbol", "strike_price", "expiration"]])
```
```plaintext
                                                raw_symbol  strike_price                expiration
ts_recv
2024-08-09 10:30:00.446956866+00:00  BKNG  250117P01770000        1770.0 2025-01-17 00:00:00+00:00
2024-08-09 10:30:00.446956866+00:00                   BKNG           NaN                       NaT
2024-08-09 10:30:00.446972667+00:00  BLK   250117C00430000         430.0 2025-01-17 00:00:00+00:00
2024-08-09 10:30:00.446972667+00:00                    BLK           NaN                       NaT
2024-08-09 10:30:00.448575941+00:00  UNH   250117P00640000         640.0 2025-01-17 00:00:00+00:00
...                                                    ...           ...                       ...
2024-08-09 10:30:00.586069426+00:00  SPY   240814P00561000         561.0 2024-08-14 00:00:00+00:00
2024-08-09 10:30:00.586076435+00:00  SMCI  250117C01080000        1080.0 2025-01-17 00:00:00+00:00
2024-08-09 10:30:00.586081478+00:00  SMCI  250117C01440000        1440.0 2025-01-17 00:00:00+00:00
2024-08-09 10:30:00.586087572+00:00  QQQ   250331C00415000         415.0 2025-03-31 00:00:00+00:00
2024-08-09 10:30:00.586107573+00:00  SHY   250117P00085000          85.0 2025-01-17 00:00:00+00:00

[10000 rows x 3 columns]
```
Note that we used a timeseries method here and the results are indexed
in time. These are called **point-in-time instrument definitions**.
Many data providers treat instrument listings and definitions as a static
table, so this may initially feel unintuitive if you're new to this approach.
However, for options, this is important as new instruments are added intraday
as the underlying moves, and knowing the strike prices of these instruments
may result in lookahead effects.

## OCC symbology

Databento's raw symbols for equity options are based on OCC symbology,
also known as Options Symbology Initiative (OSI) symbology.
The [OPRA dataset specification](https://databento.com/docs/datasets/opra-pillar.md#symbology) provides an overview of this symbology format.

You can specify the symbology type to use for your input symbols with the
`stype_in` parameter. To demonstrate this, let's fetch the raw symbol
`SPY   241115P00525000` with `stype_in="raw_symbol"`: the SPY 525.00 put expiring on 2024-11-15.

```python
import databento as db

client = db.Historical("$YOUR_API_KEY")
data = client.timeseries.get_range(
    dataset="OPRA.PILLAR",
    schema="cmbp-1",
    stype_in="raw_symbol",
    symbols=["SPY   241115P00525000"],
    start="2024-08-09T09:30-04:00",
    end="2024-08-09T10:00-04:00",
)
df = data.to_df()
print(df[["symbol", "bid_px_00", "ask_px_00"]].head())
```
```plaintext
                                                    symbol  bid_px_00  ask_px_00
ts_recv
2024-08-09 13:30:00.025137089+00:00  SPY   241115P00525000      16.33      16.53
2024-08-09 13:30:00.167110712+00:00  SPY   241115P00525000      16.33      16.52
2024-08-09 13:30:00.182316856+00:00  SPY   241115P00525000      16.34      16.51
2024-08-09 13:30:00.238455572+00:00  SPY   241115P00525000      16.34      16.50
2024-08-09 13:30:00.340439305+00:00  SPY   241115P00525000      16.34      16.51
```
## Using parent symbology to fetch an option chain

Working with individual raw symbols can be tedious. Many users find it easier to
specify the underlying's symbol and fetch all option symbols for that underlying
in an **option chain** at once.

This can be done using our [parent symbology](https://databento.com/docs/conventions/symbology.md#parent)
by passing in `stype_in="parent"` to your request. For example, we can print all
tick-by-tick trades of options on AAPL for a given time range as follows:

```python
import databento as db

client = db.Historical("$YOUR_API_KEY")
data = client.timeseries.get_range(
    dataset="OPRA.PILLAR",
    schema="trades",
    stype_in="parent",
    symbols=["AAPL.OPT"],
    start="2024-08-09T09:30-04:00",
    end="2024-08-09T10:00-04:00",
)
df = data.to_df()
print(df[["symbol", "price"]])
```
```plaintext
                                                    symbol  price
ts_recv
2024-08-09 13:30:00.008689345+00:00  AAPL  240809C00217500   0.06
2024-08-09 13:30:00.021880156+00:00  AAPL  240816C00230000   0.10
2024-08-09 13:30:00.032886565+00:00  AAPL  240920P00175000   0.77
2024-08-09 13:30:00.036991612+00:00  AAPL  240809P00202500   0.08
2024-08-09 13:30:00.109699333+00:00  AAPL  240816C00215000   2.23
...                                                    ...    ...
2024-08-09 13:59:58.801315993+00:00  AAPL  240816C00207500   6.80
2024-08-09 13:59:58.962219427+00:00  AAPL  240809C00212500   1.24
2024-08-09 13:59:59.036621416+00:00  AAPL  240816C00240000   0.02
2024-08-09 13:59:59.124512360+00:00  AAPL  240809C00212500   1.24
2024-08-09 13:59:59.858997181+00:00  AAPL  240809C00220000   0.02

[22451 rows x 2 columns]
```
## Streaming live equity options trades

For this example, we will stream the [TCBBO schema](https://databento.com/docs/schemas/tbbo.md#consolidated-bbo-on-trade-tcbbo) for all AAPL options. This schema shows every trade event along with the NBBO immediately *before* the effect of each trade.

Over 99.9% of events on the OPRA feed are CMBP-1 updates. It's significantly easier to work with another schema like [TCBBO](https://databento.com/docs/schemas/tbbo.md#consolidated-bbo-on-trade-tcbbo) or [trades](https://databento.com/docs/schemas/trades.md).

While the OPRA feed does not disseminate the trade aggressor side for a trade (`side` will be always be `N`), you can use the NBBO before the trade executes to help make a determination on whether the trade was initiated by the buyer or seller.

> **Info**
>
> This example requires a live license to `OPRA.PILLAR`. Visit our [live data portal]($PLATFORM_HOSTNAME/portal/live-data#manage)
> to sign up.

```python
import databento as db

# Enable some basic logging
db.enable_logging("INFO")

# Create a live client and connect
live_client = db.Live("$YOUR_API_KEY", compression=db.Compression.ZSTD)

# Subscribe to the TCBBO schema for all active AAPL options
live_client.subscribe(
    dataset="OPRA.PILLAR",
    schema="tcbbo",
    symbols="AAPL.OPT",
    stype_in="parent",
)

# We add a print callback to view each record
live_client.add_callback(print)

# Start the live client to begin data streaming
live_client.start()

# Run the stream for 5 seconds before closing
live_client.block_for_close(timeout=5)
```
> **Info**
>
> If you do not see any output, it could be because the markets are closed. See the `start` parameter in [Live.subscribe](https://databento.com/docs/api-live/client/subscribe.md) for using [intraday replay](https://databento.com/docs/api-live/basics/intraday-replay.md).

```plaintext
SymbolMappingMsg(rtype=<RType.SYMBOL_MAPPING: 22>, publisher_id=0, instrument_id=16777235, ts_event=1769047399503117383, pretty_ts_event='2026-01-22T02:03:19.503117383Z', stype_in=None, stype_in_symbol='AAPL.OPT', stype_out=None, stype_out_symbol='AAPL  270115P00380000', start_ts=18446744073709551615, pretty_start_ts='2554-07-21T23:34:33.709551615Z', end_ts=18446744073709551615, pretty_end_ts='2554-07-21T23:34:33.709551615Z')
...
Cmbp1Msg(ts_recv=1769005800009884453, pretty_ts_recv='2026-01-21T14:30:00.009884453Z', rtype=<RType.TCBBO: 194>, publisher_id=OPRA.PILLAR.XPHL (35), instrument_id=16787902, ts_event=1769005800009683740, pretty_ts_event='2026-01-21T14:30:00.009683740Z', action='T', side='N', price=260000000, pretty_price=0.26, size=7, flags=LAST | TOB (192), ts_in_delta=0, bid_px_00=260000000, pretty_bid_px_00=0.26, ask_px_00=300000000, pretty_ask_px_00=0.3, bid_sz_00=13, ask_sz_00=3, bid_pb_00=35, ask_pb_00=35)
Cmbp1Msg(ts_recv=1769005800010490784, pretty_ts_recv='2026-01-21T14:30:00.010490784Z', rtype=<RType.TCBBO: 194>, publisher_id=OPRA.PILLAR.XISX (26), instrument_id=16787347, ts_event=1769005800010290179, pretty_ts_event='2026-01-21T14:30:00.010290179Z', action='T', side='N', price=4950000000, pretty_price=4.95, size=1, flags=LAST | TOB (192), ts_in_delta=0, bid_px_00=9223372036854775807, pretty_bid_px_00=NaN, ask_px_00=9223372036854775807, pretty_ask_px_00=NaN, bid_sz_00=0, ask_sz_00=0, bid_pb_00=0, ask_pb_00=0)
Cmbp1Msg(ts_recv=1769005800023091148, pretty_ts_recv='2026-01-21T14:30:00.023091148Z', rtype=<RType.TCBBO: 194>, publisher_id=OPRA.PILLAR.XISX (26), instrument_id=16787347, ts_event=1769005800022890199, pretty_ts_event='2026-01-21T14:30:00.022890199Z', action='T', side='N', price=5000000000, pretty_price=5, size=1, flags=LAST | TOB (192), ts_in_delta=0, bid_px_00=4900000000, pretty_bid_px_00=4.9, ask_px_00=5000000000, pretty_ask_px_00=5, bid_sz_00=2, ask_sz_00=1, bid_pb_00=26, ask_pb_00=26)
...
Cmbp1Msg(ts_recv=1769005800202002589, pretty_ts_recv='2026-01-21T14:30:00.202002589Z', rtype=<RType.TCBBO: 194>, publisher_id=OPRA.PILLAR.XBXO (33), instrument_id=16794630, ts_event=1769005800201802008, pretty_ts_event='2026-01-21T14:30:00.201802008Z', action='T', side='N', price=1920000000, pretty_price=1.92, size=1, flags=LAST | TOB (192), ts_in_delta=0, bid_px_00=1920000000, pretty_bid_px_00=1.92, ask_px_00=2030000000, pretty_ask_px_00=2.03, bid_sz_00=6, ask_sz_00=6, bid_pb_00=27, ask_pb_00=27)
Cmbp1Msg(ts_recv=1769005800215279430, pretty_ts_recv='2026-01-21T14:30:00.215279430Z', rtype=<RType.TCBBO: 194>, publisher_id=OPRA.PILLAR.XISX (26), instrument_id=16790992, ts_event=1769005800215078713, pretty_ts_event='2026-01-21T14:30:00.215078713Z', action='T', side='N', price=410000000, pretty_price=0.41, size=3, flags=LAST | TOB (192), ts_in_delta=0, bid_px_00=350000000, pretty_bid_px_00=0.35, ask_px_00=420000000, pretty_ask_px_00=0.42, bid_sz_00=3, ask_sz_00=6, bid_pb_00=37, ask_pb_00=37)
Cmbp1Msg(ts_recv=1769005800215680274, pretty_ts_recv='2026-01-21T14:30:00.215680274Z', rtype=<RType.TCBBO: 194>, publisher_id=OPRA.PILLAR.XISX (26), instrument_id=16780046, ts_event=1769005800215479700, pretty_ts_event='2026-01-21T14:30:00.215479700Z', action='T', side='N', price=480000000, pretty_price=0.48, size=6, flags=LAST | TOB (192), ts_in_delta=0, bid_px_00=460000000, pretty_bid_px_00=0.46, ask_px_00=910000000, pretty_ask_px_00=0.91, bid_sz_00=20, ask_sz_00=1, bid_pb_00=25, ask_pb_00=25)
```
## Special conventions for equity options on Databento

- **Empty levels and null prices.** Many option contracts are illiquid and may have an
  empty book, especially at the start of day. Empty levels will have null prices and
  zero depth. We represent null prices with type max, e.g. 2^63 - 1, as a sentinel value.
  Our client libraries will generally replace these sentinel values with a more idiomatic
  null representation--for example, if you pass `price_type="float"` (default) in to the
  [DBNStore.to\_df](https://databento.com/docs/api-historical/helpers/dbn-store-to-df.md?lang=python)
  method of the Python client, these sentinel values will be replaced with `NaN` in the resulting DataFrame.
- **Bandwidth.** Due to the significant size of OPRA data, your live feed may get backed
  up if your client is not reading fast enough or network bandwidth is limited--this is
  a result of TCP flow control. Hence, we recommend setting up [dedicated connectivity](https://databento.com/docs/architecture/dedicated-connectivity-guide.md)
  if you intend to consume a large number of symbols or the entire dataset in real-time.
- **Intraday listings.** Our instrument definitions are timestamped on both
  historical and live data. This point-in-time treatment is especially useful
  if you intend to subscribe to and trade new strikes as they get listed; to backtest
  this, and to avoid lookahead effects of knowing the underlying's future price changes.

## Numeric publisher IDs

The source of each trade and quote is identified by a numeric publisher ID.

> **See also**
>
> [US equity options volume by venue](https://databento.com/docs/examples/options/options-venues.md) to see how to
> identify exchange from publisher ID.
