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:
- Terminal (PowerShell on Windows): installation,
uv run optionspace loginanduv run optionspace whoami. - Python file or notebook: the code examples on this page. Save a file such as
first_chart.pyin your research folder, then runuv run python first_chart.pythere.
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.
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.