Free Options Data
for Backtesting
Compare free samples, APIs, and hosted platforms, then verify expired contracts, quote history, Greeks, timestamps, and licensing before trusting a backtest.
TL;DR
Use samples to test a schema, a hosted platform to test a strategy, and an API free tier only for narrow contract studies.
Expect limited symbols, dates, resolutions, exports, or calls. Complete historical US option chains are rarely free to download.
Last-price fills, missing expired contracts, stale quotes, and vendor-computed Greeks can make an attractive result impossible to trade.
Rebuild the point-in-time chain first, then select contracts and model fills with a documented spread and slippage rule.
Choose free options data that fits the test
Define the market, underlyings, date range, interval, quote or trade requirement, and whether the strategy selects by delta, moneyness, or days to expiration. That specification determines whether a free route is usable.
Best for testing parsers and a short proof of concept. Confirm the sample contains bid, ask, expiration, strike, call/put, volume, open interest, and stable contract identifiers.
Often the most practical route because licensed data stays inside the platform. QuantConnect advertises options data and backtesting on its free plan, though compute and export limits still matter.
Useful for a narrow historical probe when the endpoint explicitly supports past dates. Verify expired-contract coverage, pagination, correction policy, quote fields, and whether the free key can request the dates you need.
Useful for evaluating columns, file size, symbology, and ingestion speed. A clean sample does not prove full-universe completeness, long history, corporate-action continuity, or export rights.
Control the biases that break options backtests
Rebuild the point-in-time contract universe
At each decision time, select only contracts that were listed and observable then. Retain expired, delisted, and adjusted contracts instead of joining a present-day chain backward. Record listing date, expiration, root, multiplier, deliverable, settlement, and adjustment status so corporate actions do not silently change the instrument.
Keep every market-data clock separate
A row can contain a fresh quote, an older last trade, prior-session open interest, and Greeks calculated later by a vendor. Store quote, trade, open-interest, underlying, analytics, and receipt times separately. The strategy may use a field only if its source time was available before the simulated decision.
Model contract selection before fills
If a rule chooses the nearest 30-delta call or 45-day contract, compute that choice from the contemporaneous chain and the model inputs available then. Do not use a vendor's revised Greek or a contract that became liquid later. Save the candidate set and rejection reasons with every signal.
Use executable fill assumptions
- Reject crossed, locked, stale, or zero-sided quotes unless the strategy defines a special rule.
- Model buys from the ask side and sells from the bid side, then test midpoint and adverse-slippage scenarios separately.
- Cap order size by displayed size or a documented participation rule.
- Include contract multiplier, commissions, exchange fees, assignment, exercise, and early-exercise logic.
- Do not fill after the last valid quote or outside the simulated market session.
Separate research, validation, and test periods
Tune contract filters, volatility rules, and slippage assumptions only on the research period. Freeze the strategy before validation and keep a final untouched test period. Report results by regime, liquidity bucket, expiration type, and spread cost—not only one aggregate return.
Python example: reject unrealistic option rows
Normalize the chain before selecting a contract. This local example keeps valid markets, computes a midpoint, and rejects crossed quotes.
import pandas as pd
df = pd.read_csv("options_quotes.csv")
df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True)
df = df.dropna(subset=["timestamp", "expiration", "strike", "bid", "ask"])
df = df[(df["bid"] >= 0) & (df["ask"] >= df["bid"])]
df["mid"] = (df["bid"] + df["ask"]) / 2
df = df.sort_values(["timestamp", "contract"]).drop_duplicates(
["timestamp", "contract"], keep="last"
)
print(df[["timestamp", "contract", "bid", "ask", "mid"]].tail())
| Step | Why it matters | Minimum check |
|---|---|---|
| Universe | Selection must use contracts listed at that timestamp. | Expiration, strike, side, listing history. |
| Quote | A last trade may be stale or outside the live spread. | Bid ≤ ask, age, size, exchange time. |
| Fill | Midpoint fills can overstate executable P&L. | Spread rule, slippage, fees, multiplier. |
Use QVeris to compare options-data capabilities
The exact QVeris Options Chain Tool is strongly relevant for inspecting the shape of a current chain: its public record includes calls and puts by expiration and strike with last price, bid, ask, volume, and open interest. It does not by itself prove point-in-time history, expired-contract retention, revision policy, or backtest licensing.
- Use the QVeris Playground to inspect one known symbol and expiration before mapping a schema.
- Ask explicitly for historical dates and expired contracts; never infer history from a current result.
- Save the tool ID, request, raw response, retrieval time, validation decision, and rejected assumptions.
FAQ
Use provider samples, a hosted platform with included data, or a documented API free tier for a narrow set of historical contracts.
It can validate a workflow or narrow hypothesis, but broad chains, expired contracts, quote history, and export rights are commonly limited or paid.
Use midpoint only as a sensitivity case. A base case should reflect side, spread, displayed size, slippage, fees, and quote age.
Removing them changes the historical opportunity set and creates survivorship bias, especially around illiquid or adjusted contracts.
References and next steps
QuantConnect plans
QuantConnect options documentation
Historical Option Data samples
Alpha Vantage historical options
