OptionSpace.ai

Python package

From a question to a DataFrame.

Fetch fitted option data, inspect the returned observations and create an interactive chart with the same saved API credential.

Before your first query

Complete installation and login and run uv run optionspace whoami in your research folder. Examples below use package version 0.8.0 and dates that must be included in your access.

There are two different places to enter commands:

1. Connect and discover available data

import optionspace as osp

client = osp.Client()
print(client.entitlements())
print(client.symbols().head())

Client() reads your saved credential. It does not open an input prompt and does not require a key string in your code. symbols() lists product coverage; your key's permitted dates and frequency may be narrower.

Before selecting an expiry or intraday time, inspect what is available for the session:

print(client.snapshots("SPY", "2025-04-10"))
print(client.expirations("SPY", "2025-04-10", snapshot="EOD"))

2. Fetch one fitted smile

smile = client.smile(
    "SPY", "2025-04-10",
    expiration="2025-04-17",
    snapshot="EOD",
)
print(smile.head())
print(smile.attrs["snapshot_time"])

The result is a pandas DataFrame of option observations. Identity columns describe the symbol, root, date, snapshot, expiry, strike and side. Additional context lives in smile.attrs, including the resolved snapshot and data source.

Read the units: fitted IV is a decimal, so 0.20 means 20% volatility. Quote fields (bid, ask and their implied vols) for a session are served from the next session's 09:30 ET open; until then they are absent, and an absent field is not a zero. Use the returned extrapolation flags when interpreting the fitted wings. Every field is described in the field reference.

3. Open your first chart

Add this code after the smile query:

from pathlib import Path
import webbrowser
from optionspace import charts

fig = charts.smile_chart(smile, theme="light")
output = Path("spy-smile.html").resolve()
fig.write_html(str(output), include_plotlyjs=True)
webbrowser.open(output.as_uri())

An interactive chart opens in your browser. The HTML bundles the figure's JavaScript, so you can reopen it offline. This path does not launch Chromium, Playwright or a PNG exporter. Creating another figure from the same fetched frame makes no new API request.

Example OptionSpace fitted smile chart for SPY, with call and put quote bands and the fitted curve.
Example recorded study output. Your returned fields and quote bands depend on your access.

4. Extend the question

Follow ATM volatility during the session

atm = client.atm_intraday(
    "SPY", "2025-04-10", expiration="2025-04-17"
)
fig = charts.atm_chart(atm)
fig.write_html("spy-atm.html", include_plotlyjs=True)

Compare smiles at three times

times = ["09:32", "12:45", "15:57"]
smiles = [
    client.smile("SPY", "2025-04-10",
                 expiration="2025-04-17", snapshot=t)
    for t in times
]
fig = charts.compare_chart(smiles)
fig.write_html("spy-comparison.html", include_plotlyjs=True)

This example makes three smile requests. The selected times must be available to your key's snapshot frequency; use client.snapshots() to check first.

Query exact-expiry skew and its previous-session comparison

skew = client.expiry_metrics(
    "SPY", "2025-04-10",
    expiration="2025-04-17",
    compare_previous=True,
)
print(skew)

Put a stored-delta exposure in context

distribution = client.exposure_distribution(
    "SPY", "2025-04-10",
    snapshot="09:45", put_call="put",
    delta=0.15, dte=1, lookback_years=1,
)
print(distribution)

These methods return structured dictionaries with values, identities, coverage and limitations. Delta-based selection uses stored contract deltas. Read the response status and sample coverage before using a percentile or z-score. A request with insufficient observations may not have a usable statistic.

Choosing the right method

Method Use it for
symbols() Discover symbol coverage
snapshots(symbol, date) Find times available to your key
expirations(symbol, date) Discover listed expiries and roots
smile(symbol, date, expiration=...) One expiry at one snapshot
chain(symbol, date, snapshot=...) Option observations across expiries at one time
atm_intraday(symbol, date, expiration=...) One expiry's ATM path through a session
term_structure(symbol, date) ATM values across listed expiries; one request
tenor_history(symbol, tenor_days, start, end) History for a supported constant-maturity tenor
rates(start, end) Implied-rate history
expiry_metrics(...) Metrics for a listed expiry, optionally compared with the previous session
exposure_distribution(...) Historical context for a stored-delta-selected exposure

Every method's full signature, each argument with its type, default and meaning, what it returns and what it costs: see the reference.

Charts, movies and usage

Chart themes are light, dark and institutional. A chart is an editable Plotly figure. Interactive HTML works without a separate image exporter; PNG/SVG/PDF exports require the chart export dependencies and a compatible Chrome installation.

The package also provides DayMovie for session replays. Fetching a day's observations is a larger operation than fetching one smile: each smile is one request, so count the snapshots before you start. Video export additionally requires FFmpeg. See the Python toolkit examples for an actual output.

A term structure or movie makes several underlying calls. Reusing an already fetched frame or reopening a saved HTML file makes no new request. Each research result says what it cost in result["credits"]. Before a large one, pass estimate=True to get credits_estimate and credits_cap without running it, or max_credits=N to have it refused, unrun (MaxCreditsExceeded), if it could cost more. client.prices() returns the price list: what each call costs now. A research call that runs long becomes a background job on the server; the client waits for it (up to Client(job_timeout=1800) seconds) and returns the same result. If it stops waiting, JobTimeout carries the job_id; client.job(job_id) collects the answer within an hour. Check uv run optionspace whoami for authoritative account usage.

Need a second account or a notebook-specific environment? See profiles and environments.

Five-minute earnings research

With a key entitled to five-minute or finer data, request a denser history:

earnings = client.earnings_history(
    "NVDA", "2026-09-16", count=8, granularity="5m"
)
charts.earnings_runup_chart(earnings).show()

history = client.clean_atm_history(
    "NVDA", "2026-04-01", "2026-09-16", granularity="5m"
)
charts.clean_atm_chart(history).show()

Omitting granularity uses end-of-day observations. Five-minute paths retain their timestamps and respect early closes. The earnings comparison remains anchored at the final pre-report observation. Unavailable or excluded observations stay as gaps.

For a stock, the clean series removes only the next scheduled earnings report (when it falls within 90 days), because the earnings calendar lists one date ahead. Later reports inside a long tenor stay in the clean value. Where the removal cannot be vouched for, the clean value equals the unadjusted one and its error bar is empty. See the dataset catalog.

Follow a position minute by minute

test = client.earnings_backtest(
    "NVDA", "2026-09-16", count=8, granularity="1m"
)
charts.backtest_path_chart(test).show()

This changes the observation frequency, not the entry/exit rules or fills. It requires one-minute access. The path begins at entry and ends at exit; unavailable observations remain gaps. Hover for actual timestamps on the trading-time axis.