CODE PLAYBOOK · PYTHON · PUBLIC ENDPOINT

Pull Crypto Options Open Interest and Max Pain in Python

Deribit's public options API returns hundreds of individual instrument rows per expiry — every strike, every call and put, as separate line items. This playbook pulls one endpoint that has already aggregated it into open interest by strike, put/call ratio, and max pain, so you don't have to write that aggregation code yourself.

~10 min readPython 3.8+No API key required

The problem: raw options data is hundreds of rows

If you've ever hit Deribit's public/get_book_summary_by_currency endpoint directly, you know the shape you get back: one row per instrument, named things like BTC-27MAR26-80000-C. For a single currency on a single day there can be 300-800 of these rows across a dozen expiries. To get anything useful out of it — open interest by strike, a put/call ratio, where max pain sits for the nearest expiry — you have to parse each instrument name into expiry/strike/type, bucket by strike, sum open interest on each side, and then run a max-pain calculation across every candidate strike for every expiry.

That parsing and aggregation is exactly what GET /v1/options/chain pre-computes. One request returns the underlying price, total call/put open interest, put/call ratios by OI and by volume, open interest bucketed by strike (filtered to a sane range around spot), a per-expiry summary, and max pain per expiry — already computed from the same Deribit book-summary data.

Prerequisites

This specific endpoint is public — no API key needed to call it. You'll need:

A free key from /signup isn't required for this endpoint, but it's worth grabbing anyway (200 calls/day free) if you plan to combine this with other parts of the API — derivatives screener, whale flows, on-chain metrics — since those apply per-key rate limits instead of the shared public-IP throttle.

The endpoint

GET https://api.smartmoneyapi.com/v1/options/chain?currency=BTC

Query parameter currency accepts BTC or ETH (defaults to BTC if omitted). The underlying data source is Deribit's public options API — this endpoint doesn't add any proprietary options data, it just parses Deribit's instrument-name strings (BTC-27MAR26-80000-C → expiry 27MAR26, strike 80000, type call) and aggregates the result so you don't have to.

FieldMeaning
underlying_priceCurrent BTC/ETH index price from Deribit
total_call_oi / total_put_oiSum of open interest across all calls / puts, all expiries
pcr_oiPut/call ratio by open interest — total_put_oi ÷ total_call_oi
pcr_volumePut/call ratio by 24h traded volume
oi_by_strikeArray of {strike, call_oi, put_oi}, filtered to strikes between 0.5x and 2.0x the underlying price
expiry_summaryPer-expiry object keyed by Deribit date code (e.g. "27MAR26") with call_oi, put_oi, pcr, max_pain, total_contracts
max_painPer-expiry object with the max-pain strike and total_pain_quote (USD notional)

A companion endpoint, GET /v1/options/history?currency=BTC&metric=pcr_oi&hours=24, is also public and returns a time series for pcr_oi, total_oi, or max_pain — useful once you want to chart how positioning is shifting rather than just look at a snapshot.

Python script

This fetches the BTC chain, prints the top-level summary, the nearest expiry's max pain strike, and the top 5 strikes by combined open interest:

python
import requests BASE = "https://api.smartmoneyapi.com" def get_options_chain(currency="BTC"): resp = requests.get(f"{BASE}/v1/options/chain", params={"currency": currency}, timeout=15) resp.raise_for_status() return resp.json() def main(): data = get_options_chain("BTC") print(f"Underlying price: {data['underlying_price']:,.2f}") print(f"PCR (open interest): {data['pcr_oi']}") print(f"PCR (volume): {data['pcr_volume']}") # Nearest expiry's max pain strike expiries = sorted(data["max_pain"].keys()) if expiries: nearest = expiries[0] mp = data["max_pain"][nearest] print(f"Nearest expiry ({nearest}) max pain strike: {mp['strike']:,.0f} " f"({mp['total_pain_quote']:,.0f} {mp['quote_currency']} notional)") # Top 5 strikes by combined call + put open interest ranked = sorted( data["oi_by_strike"], key=lambda s: s["call_oi"] + s["put_oi"], reverse=True, )[:5] print("\nTop 5 strikes by combined OI:") for row in ranked: total = row["call_oi"] + row["put_oi"] print(f" {row['strike']:>10,.0f} call={row['call_oi']:>9,.0f} " f"put={row['put_oi']:>9,.0f} total={total:>9,.0f}") if __name__ == "__main__": main()

JavaScript / fetch variant

Same call from Node or a browser console, using the built-in fetch:

javascript
const BASE = "https://api.smartmoneyapi.com"; async function getOptionsChain(currency = "BTC") { const res = await fetch(`${BASE}/v1/options/chain?currency=${currency}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } (async () => { const data = await getOptionsChain("BTC"); console.log(`Underlying price: ${data.underlying_price}`); console.log(`PCR (open interest): ${data.pcr_oi}`); console.log(`PCR (volume): ${data.pcr_volume}`); const expiries = Object.keys(data.max_pain).sort(); if (expiries.length) { const nearest = expiries[0]; const mp = data.max_pain[nearest]; console.log(`Nearest expiry (${nearest}) max pain strike: ${mp.strike} (${mp.total_pain_quote} ${mp.quote_currency})`); } const ranked = [...data.oi_by_strike] .sort((a, b) => (b.call_oi + b.put_oi) - (a.call_oi + a.put_oi)) .slice(0, 5); console.log("Top 5 strikes by combined OI:"); ranked.forEach(r => console.log(` ${r.strike} call=${r.call_oi} put=${r.put_oi}`)); })();
Building more than an options dashboard?

This same key unlocks funding heatmaps, whale positioning across 8 chains, and liquidation data — one API instead of a dozen exchange integrations.

Get your free API key →

Expected output

A trimmed real response for currency=BTC looks like this (fields shortened for readability — oi_by_strike and expiry_summary normally contain many more entries):

json
{ "currency": "BTC", "underlying_price": 61234.5, "total_call_oi": 152340.0, "total_put_oi": 98211.0, "total_call_volume": 4210.5, "total_put_volume": 3012.2, "pcr_oi": 0.6446, "pcr_volume": 0.7154, "oi_by_strike": [ {"strike": 55000.0, "call_oi": 1200.0, "put_oi": 3400.0}, {"strike": 60000.0, "call_oi": 8900.0, "put_oi": 6100.0}, {"strike": 65000.0, "call_oi": 7200.0, "put_oi": 2800.0} ], "expiry_summary": { "27MAR26": {"call_oi": 45210.0, "put_oi": 30120.0, "pcr": 0.666, "max_pain": 60000.0, "total_contracts": 812} }, "max_pain": { "27MAR26": {"strike": 60000.0, "total_pain_quote": 1234567.0, "quote_currency": "USD"} }, "updated": 1753300000 }

Running the Python script above against a response shaped like this prints something like:

text
Underlying price: 61,234.50 PCR (open interest): 0.6446 PCR (volume): 0.7154 Nearest expiry (27MAR26) max pain strike: 60,000 (1,234,567 USD notional) Top 5 strikes by combined OI: 60,000 call= 8,900 put= 6,100 total= 15,000 65,000 call= 7,200 put= 2,800 total= 10,000 55,000 call= 1,200 put= 3,400 total= 4,600

What max pain actually is (and isn't)

Max pain is a purely mechanical open-interest calculation: for a given expiry, the code walks every candidate strike price and sums the dollar value of contracts that would finish in-the-money on each side (calls below the candidate, puts above it, distance × OI). The candidate strike where that total payout is lowest — where the largest total dollar amount of open contracts would expire worthless — is the max pain strike.

That's it. It is a snapshot of where existing open interest happens to sit, computed the same way every time from the same book-summary data. It is not a prediction that price will move to that strike, and it is not evidence of market maker manipulation or any other directional claim. Treat it the same way you'd treat any other open-interest statistic: descriptive of current positioning, not predictive of where price goes next.

What to build next

All of this lives behind one API key across 66+ endpoints covering derivatives, on-chain, and whale data — instead of separately integrating Deribit, half a dozen chain explorers, and multiple exchange APIs.

Start free — 200 calls/day, no card

Get live whale flow, funding, open interest and on-chain data across 3 exchanges from one API. Free tier, no credit card, upgrade any time.

Start free →
Try the live API console → (no account needed)