Historical Options Data for Python Research: A Reproducible Example
Load one daily CSV, select a SPY options chain and check which observations are usable before building a research dataset. This example runs on the free sample with Python's standard library.
Run the example
The sample includes TSLA, KO, SPY and SPX/SPXW for July–December 2022 in the paid archive's 34-column CSV format. The example selects SPY calls observed on 25 August 2022, expiring on 16 September 2022, with strikes from $400 to $440 inclusive.
Save these two scripts in the same folder. Use Python 3.10 or newer. No additional packages are needed.
Download sample-fetch script · Download research example · Free sample ZIP (239 MB)
python download_options_sample.py
python options_research.py
The download script verifies the pinned ZIP checksum. The research script then checks the selected daily file against its manifest, including its hash, byte size, row count and column order. It reads the CSV directly inside the ZIP, so extraction is unnecessary. A different sample version stops the example rather than silently changing its inputs.
The selection in Python
selected = [r for r in rows
if r['underlying'] == 'SPY'
and r['quote_date'] == '2022-08-25'
and r['expiration'] == '2022-09-16'
and r['type'] == 'call'
and Decimal('400') <= number(r['strike']) <= Decimal('440')]
The script exposes each filter as a command-line argument. Symbols are matched exactly. It does not merge ticker histories or infer a contract's identity from its strike alone.
Actual results for this selection
Run on 9 November 2026 with Python 3.12.14, tutorial revision 1. These counts describe the selected sample rows, not an estimate of archive-wide completeness or accuracy.
| Check | Result |
|---|---|
| Rows in the daily sample CSV, across all included symbols | 32,418 |
| SPY calls matching the stated filters | 41 |
| Positive, non-crossed bid and ask | 41 of 41 |
| Midpoint IV with parity-derived forward, iv_flag = 0 | 41 of 41 |
| Quote midpoint range, per share | $0.805 to $21.78 |
| Midpoint IV range, decimal units | 0.159196 to 0.239328 |
Download the complete run result (JSON). The midpoint is a calculation from the reported bid and ask, not a trade price or an executable fill. An IV of 0.159196 means 15.9196% in percentage units.
Keep missing values and zero values distinct
The narrow chain above has two-sided quotes throughout. Widening the strike range demonstrates why the missing-value checks matter.
python options_research.py --strike-min 0 --strike-max 1000
For the same SPY observation date, expiry and call type, the wider selection contains 283 contracts. Of those, 75 have no bid, 172 have recorded zero volume, and 126 have no midpoint IV. These populations can overlap. There are 208 positive, non-crossed two-sided quotes. View the wider run result (JSON).
def number(cell):
return None if cell == '' else Decimal(cell)
bid, ask = number(row['bid']), number(row['ask'])
usable = bid is not None and ask is not None and 0 < bid <= ask
midpoint = (bid + ask) / 2 if usable else None
A blank bid or ask is unavailable. Filling the blank with zero would manufacture a midpoint. A recorded volume of zero remains zero and must not be dropped merely because it is false-like in Python. The example preserves both cases.
Read iv_flag with iv. Flags 0 and 1 identify solved midpoint IV, using respectively a parity-derived forward or the documented fallback forward. The wider run has 157 flag-0 rows, 75 flag-2 rows and 51 flag-3 rows. A usable two-sided quote alone does not guarantee a midpoint IV solution. Bid-side and ask-side IV are solved separately and must be checked independently. See the field definitions and current options README.
IV is a decimal. For the supplied Greeks, theta is per year, vega per 1.00 volatility and rho per 1.00 interest rate. Confirm these units before combining the data with another library.
Exact sample identity
The sample has 127 daily files. This tutorial reads one of them. Its checksum checks establish that the run used the stated bytes; they do not independently validate market prices.
- ZIP SHA-256
8517b069f4d41e44f950dc79a3cdab694359b0fae91664190011e39dd8e48b76- Manifest SHA-256
be929e8164c92a6c0368af2e0877eb077bbc6cdaa5cb27365de5d102e958e160- 2022-08-25_options.csv SHA-256
4eb2102efafc74d665cac5da8b938456bf2132843f2e4025d8a9e4ab5aada492
Keep the ZIP, scripts and result JSON together. The download script refuses a future sample with a different checksum. A revised tutorial requires a new run and updated results.
What this example does and does not establish
- This is an end-of-day chain inspection. It cannot reconstruct intraday executions, queue priority, spreads at an earlier decision time or strategy returns.
- Historical rows in this 2022 sample do not contain a quote timestamp. A common CSV date does not establish simultaneous capture of every field.
- Do not use a day's closing data to simulate a trade earlier that same day. Any backtest needs an explicit availability and execution-time rule, transaction costs and its own treatment of missing observations.
- This example does not validate every date or the entire archive. Historical corporate-action adjusted contracts and other coverage limitations are described in the methodology. An exact underlying match does not resolve those limitations.
- The options quality report comparing Cboe samples uses different specified files and dates. Its comparison rates do not describe this Python selection.
Next, compare CSV archives with APIs, inspect the other free samples, or review options coverage and purchase choices.