# COT by market Source: https://docs.backquant.com/api/v2/cot/instrument GET /cot/instrument One futures market's Commitments of Traders positioning by trader category. Net contracts per trader category (dealers, asset managers, leveraged funds, other reportables and non-reportables for the financial futures report), the focus category's statistics, open interest and the smart-versus-fast money divergence. `key` comes from [/v2/cot/markets](/api/v2/cot/markets). Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # COT markets Source: https://docs.backquant.com/api/v2/cot/markets GET /cot/markets The futures markets covered by the COT endpoints and the report each reads. Keys for [/v2/cot/instrument](/api/v2/cot/instrument), with each market's asset class, the CFTC report it reads and the trader categories compared as smart and fast money. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # COT positioning across markets Source: https://docs.backquant.com/api/v2/cot/summary GET /cot/summary CFTC Commitments of Traders: each market's latest positioning, most extreme first. The latest weekly positioning of each market's primary trader category (leveraged funds for CME bitcoin and ether): net contracts, the weekly change, net as a share of open interest, where it sits in its own 1-year and 3-year range, and flags for extremes. `asset_class` defaults to `crypto`; `all` covers the 48 markets. Public CFTC data: positions as of Tuesday, published Friday 15:30 ET. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Changelog Source: https://docs.backquant.com/api/v2/discovery/changelog GET /changelog API version history with breaking-change flags. Returns the v2 release log plus the lifecycle status of v1. Static release log with per-version `breaking` flags. Use `breaking: true` as the signal to schedule client re-integration tests. Poll authenticated `GET /v2/changelog` for the canonical list (newest first). ## Recent versions | Version | Date | Summary | | - | - | - | | **2.13.0** | 2026-10-11 | [Constant-maturity vol](/api/v2/options/iv/constant-maturity): ATM and 25- / 10-delta risk reversals and butterflies from 7 days to 1 year, with static-arbitrage checks on every fitted smile. The consolidated options chain over the [WebSocket](/api/v2/tape/websocket), the [strike ladder](/api/v2/gex/ladder), a calibrated [options range](/api/v2/options/range), CFTC [COT positioning](/api/v2/cot/summary) and [SEC filings](/api/v2/filings/insider). Fixes [`/v2/options/iv/smile`](/api/v2/options/iv/smile) (404 on every expiry) and [premium tide](/api/v2/options/premium-tide) `0dte` (now today's session only). Bullish and Paradex join the options chain. | | **2.12.0** | 2026-10-01 | **Breaking:** every greek exposure is in dollars, one unit per greek (DEX $delta, vanna$ delta per vol point, charm $delta per day, vega$ per vol point), and vanna has the Black-Scholes sign. Gamma flip and walls follow one rule on every endpoint. See [Greeks](/concepts/greeks). | | **2.11.0** | 2026-09-07 | Dealer-flow positioning about 4x faster on the endpoints that were slowest (`flow` is the default, and it was scanning the whole options tape per request). **Breaking:** [`/v2/options/greeks/surfaces/{greek}`](/api/v2/options/greeks/surfaces) puts `exchange_breakdown` behind `?include=`. | | **2.10.1** | 2026-09-01 | `notional_volume` means premium paid on every symbol (HYPE carried underlying notional). Point-in-time chains recompute traded activity instead of serving pre-fix values, and a missing measurement reports `null` rather than a single-venue substitute. | | **2.10.0** | 2026-09-01 | **Breaking:** chain `volume` / `notional_volume` now cover all eight venues on BTC and ETH, and `trade_count` is populated for the first time. Fixes `bid` / `ask` on [`/v2/options/chain`](/api/v2/options/chain), which had been `0` on every contract. | | **2.9.0** | 2026-08-30 | Per-venue dealer flow on [`/v2/tape/flow-by-strike`](/api/v2/tape/flow-by-strike) via `?include=by_venue`, with a `venue_disagreement` statistic. Venues disagree on direction for about 70% of multi-venue contracts. | | **2.8.1** | 2026-08-30 | Responses \~17% smaller (float noise trimmed everywhere). **Breaking:** [`/v2/options/pcr`](/api/v2/options/pcr) intraday now honours `days` and puts `exchange_breakdown` behind `?include=`. | | **2.8.0** | 2026-08-28 | **Breaking:** point-in-time [`as_of`](/concepts/point-in-time) on nine endpoints including the raw options chain; time-heatmap filtered history now signed as-of each frame; market series report short windows. | | **2.7.1** | 2026-08-26 | [`GET /v2/options/venue-book`](/api/v2/options/venue-book) spread sorts rank tradeable spreads first and crossed books last. | | **2.7.0** | 2026-08-24 | Crypto market data and spot ETF flows: [candles](/api/v2/market/candles), [open interest](/api/v2/market/open-interest), [funding](/api/v2/market/funding), [CVD](/api/v2/market/cvd), [liquidations](/api/v2/market/liquidations), [ETF flows](/api/v2/etf/flows), and [`/v2/market/bundle`](/api/v2/market/bundle) returning them on one shared time axis. | | **2.6.11** | 2026-08-24 | Historical depth becomes a plan entitlement. Reported in `GET /v2/meta`, not yet enforced. | | **2.6.10** | 2026-08-24 | Dealer-flow served from a pre-computed aggregate instead of rebuilt per request. | | **2.6.9** | 2026-08-23 | Dealer-flow responses carry their own age; last good series retained six hours. | | **2.6.8** | 2026-08-22 | Dealer-flow no longer makes the first caller in a window wait out a rebuild. | | **2.6.7** | 2026-08-21 | Fixed: `GET /v2/gex/max-pain?expiry=0dte` computed across all expirations. | | **2.6.6** | 2026-08-21 | `429` responses send `Retry-After`; OpenAPI spec version matches response `meta.version`. | | **2.6.5** | 2026-08-20 | Fixed: `GET /v2/gex/strike-expiry-heatmap` returned 500 on every request. | | **2.6.4** | 2026-08-20 | `meta.extra.aggregation` wording no longer describes how the flow model is derived. | | **2.6.3** | 2026-08-19 | Fixed: `GET /v2/gex/stress-history` is now per symbol, not always BTC. | | **2.6.2** | 2026-08-18 | **Breaking:** consistency pass on [`GET /v2/options/oi/by-expiry`](/api/v2/options/oi/by-expiry); raw `exchange_breakdown` removed. | | **2.6.1** | 2026-08-18 | `?exchanges=` returns real per-venue numbers on every surface that accepts it. | | **2.6.0** | 2026-08-18 | **Breaking:** WebSocket `gex.levels` serves FLOW (not std); per-venue open interest on OI surfaces. | | **2.5.2** | 2026-08-17 | Thalex + Delta India + Delta Global venues. [`GET /v2/tape/flow-by-strike`](/api/v2/tape/flow-by-strike). | | **2.5.1** | 2026-08-17 | Thalex chain and tape always-on. | | **2.5.0** | 2026-08-04 | **Breaking:** entitlement failures return **403**; monthly request quotas; plan ladder. See [Rate limits](/concepts/rate-limits) and [Errors](/concepts/errors). | | **2.4.0** | 2026-07-31 | Derive venue; SOL/HYPE flow coverage notes. | | **2.3.0** | 2026-07-30 | Dealer / gamma / delta flow endpoints; tape analytics; WS GEX levels. | | **2.2.0** | 2026-07-30 | **Breaking:** default `positioning=flow`. | Venue IDs and tape units: [Venue coverage](/concepts/venues). ## See also > **[Venue coverage](/concepts/venues)** > **[Rate limits](/concepts/rate-limits)** > **[Flow by strike](/api/v2/tape/flow-by-strike)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Endpoint catalog Source: https://docs.backquant.com/api/v2/discovery/endpoints GET /endpoints Live catalog of every v2 route. Introspects the running FastAPI sub-app and returns one entry per route with path, HTTP methods, tag, summary,... Live introspection of every registered route in the API. For each endpoint you get the path, HTTP methods, tag, summary, description, and the full parameter list - including the `enum` array for any parameter that's Literal-typed (so you can validate user input client-side against exactly the same values the server accepts). The catalog is derived from the running FastAPI app at request time, not hard-coded. New routes appear here automatically. Use it as a lighter alternative to `/v2/openapi.json` when you want a flat catalog rather than the full OpenAPI 3 schema. ## See also > **[API metadata](/api/v2/discovery/meta)** > **[Symbol catalog](/api/v2/discovery/symbols)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Active expiries Source: https://docs.backquant.com/api/v2/discovery/expiries GET /expiries Active expiry tokens with DTE. Currently-tradeable expiry tokens for a symbol with DTE and an ISO date when the token is parseable. Currently-tradeable expiry tokens for a symbol with DTE and ISO date. Use this to populate filter UIs without parsing /options/expiry-summary. ## See also > **[OPEX calendar](/concepts/opex)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # API metadata Source: https://docs.backquant.com/api/v2/discovery/meta GET /meta API-level metadata. Static metadata about the v2 API. Static metadata about the API itself: name, version, the auth scheme, the rate-limit ladder, the response envelope shape, the full error code catalog with HTTP status mapping, and the supported symbol + exchange catalogs. No I/O - pure in-memory dict, sub-millisecond. Use this when you want a single programmatic source of truth for client-side validation: the supported-symbols list, the error-code table, and the auth requirements all come from one call so your client doesn't drift from the API contract. ## See also > **[Errors](/concepts/errors)** > **[Response format](/concepts/response-format)** > **[Rate limits](/concepts/rate-limits)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Service status Source: https://docs.backquant.com/api/v2/discovery/status GET /status v2 service status - per symbol, per category freshness. Operational status. Operational status: per-symbol-per-category freshness with healthy/degraded/unhealthy classification + an overall aggregate. ## Per-venue status Alongside the per-symbol grid, the response carries a `venues` block: one entry per venue with the state of each feed. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "venues": { "deribit": { "chain": "on", "tape": "on", "chain_symbols": ["BTCUSDT", "ETHUSDT"], "chain_contracts": 1910 }, "derive": { "chain": "off", "tape": "on", "chain_symbols": [], "chain_contracts": 0 } } ``` | Field | Meaning | | - | - | | `chain` | Options chain aggregation for that venue: `on`, `off` or `unknown` | | `tape` | Live trade tape for that venue: `on`, `off` or `unknown` | | `chain_symbols` | Which symbols the venue is currently live on | | `chain_contracts` | Instruments it contributed on the last cycle | Three states, and the difference matters: * **`on`** - enabled on the worker that owns that feed. * **`off`** - registered but disabled, or no collector running. * **`unknown`** - no fresh status was published. This is not a synonym for `off`: it means we cannot currently see the feed's state, and treating it as disabled would be a guess. `chain_contracts` is what separates a healthy venue from a quiet one. A venue reporting `on` with `0` contracts is enabled but delivering nothing, which a bare `on` would hide. > [!NOTE] > > Use this to interpret a zero in a per-venue response. A venue contributing `0` > open interest on [`by_venue`](/concepts/venues) is not necessarily switched off, > it may simply list no contracts on that surface right now. This endpoint tells > you which it is. ## See also > **[Data freshness](/concepts/data-freshness)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Symbol catalog Source: https://docs.backquant.com/api/v2/discovery/symbols GET /symbols Supported symbols + per-symbol availability. Lists every symbol covered by v2 with the data categories that are currently live, the freshness of each... The first call most integrators make. Returns every symbol the API supports with the categories of data currently live for each, the spot price, the count of active expiries, and the full set of endpoint paths that accept a `symbol` parameter. Use it to: * Discover which symbols are supported (`BTCUSDT`, `ETHUSDT`, `SOLUSDT`, `HYPEUSDT`) * Check at a glance whether a symbol has live cache data before hitting downstream endpoints * Build a dynamic UI that lists supported endpoints without hard-coding paths Powered by a single Redis `MGET` round-trip - sub-millisecond when warm. ## See also > **[Service status](/api/v2/discovery/status)** > **[Data freshness](/concepts/data-freshness)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # ETF AUM Source: https://docs.backquant.com/api/v2/etf/aum GET /etf/aum Assets under management per spot crypto ETF. Assets under management per fund. Available for BTC and ETH. As with [flows](/api/v2/etf/flows), check `meta.extra.days_since_latest` before treating the last point as current. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # ETF flows Source: https://docs.backquant.com/api/v2/etf/flows GET /etf/flows Daily creations and redemptions for spot crypto ETFs, per fund and in total, with running cumulatives. Daily creations and redemptions for spot crypto ETFs: per fund, in total, and as running cumulatives. `region=us` (default) covers the US spot ETFs. `region=hk` covers the Hong Kong listings and exists for BTC only. ## Read the freshness field ETF flows publish on trading days, so the newest date is normally the previous business day, and three days old across a weekend is entirely normal. Every response reports `meta.extra.latest_date` and `meta.extra.days_since_latest`. **Alert on `days_since_latest`**, not on the absence of an error: this data is cached with a generous expiry so that a collection outage degrades to serving the last known values rather than failing, which means a stale response looks exactly like a fresh one unless you check. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "meta": { "extra": { "region": "us", "latest_date": "2026-08-21", "days_since_latest": 1 } } } ``` If no data is available at all the endpoint returns `404` rather than an empty series, because an empty series reads as "there were no flows", which is a different claim. ## See also > **[ETF premium](/api/v2/etf/premium)** > **[ETF AUM](/api/v2/etf/aum)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # ETF premium Source: https://docs.backquant.com/api/v2/etf/premium GET /etf/premium Premium or discount of each spot ETF's market price to its net asset value. Premium or discount of each fund's market price to its net asset value. Available for BTC and ETH. As with [flows](/api/v2/etf/flows), check `meta.extra.days_since_latest` before treating the last point as current. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Institutional holders (SEC 13F) Source: https://docs.backquant.com/api/v2/filings/holders GET /filings/holders Top institutional holders of MSTR, COIN, HOOD and IBIT, with the change from the quarter before. From the latest quarterly 13F filings, due 45 days after quarter end; `period_end` names the quarter. `share_change` is null when the manager did not report the position the quarter before. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Insider trades (SEC Form 4) Source: https://docs.backquant.com/api/v2/filings/insider GET /filings/insider Form 4 transactions by officers, directors and 10% owners of COIN and HOOD. Newest first, with a readable `transaction` beside the SEC code. Purchases and sales are the discretionary trades; awards, option exercises and tax withholding are compensation mechanics, so `purchases_usd` and `sales_usd` count only non-derivative purchases and sales. MSTR is not included yet: its Form 4 lines do not record which share class they are in, and its directors buy the preferred series near \$100 par. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Dealer flow (time series) Source: https://docs.backquant.com/api/v2/gex/dealer-flow GET /gex/dealer-flow Dealer hedge flow time series (gamma + vanna + charm). Estimated dealer hedging flow across successive chain snapshots. Estimated **dealer** hedging pressure across successive chain snapshots: gamma, charm (and vanna when available), total, and cumulative. ## Formula (summary) * `gamma_flow` = spot move (%) × prior net GEX * `charm_flow` = −elapsed hours × prior net charm * `total_flow` = sum of components * `cumulative_flow` = running sum of `total_flow` Positive ≈ dealers buying the underlying to re-hedge. Negative ≈ selling. Uses `?positioning=flow|std` (default **flow**). Needs at least two chain snapshots; otherwise 404. This is **not** customer tape delta. For that, use [delta flow](/api/v2/gex/delta-flow). Full model notes: [Positioning](/concepts/positioning). ## See also > **[Gamma flow](/api/v2/gex/gamma-flow)** > **[Delta flow (customer)](/api/v2/gex/delta-flow)** > **[By level](/api/v2/gex/dealer-flow-by-level)** > **[Positioning models](/concepts/positioning)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Dealer flow by level Source: https://docs.backquant.com/api/v2/gex/dealer-flow-by-level GET /gex/dealer-flow/by-level Expected dealer hedge $ if spot walks to each strike. Pro DEALERFLOW-style level map: cumulative net GEX from spot out to each strike under FLOW/STD... Expected cumulative dealer hedge dollars if spot walks from the current price to each strike (dealer-flow ladder). Built from the signed strike GEX profile. Positive at a strike ≈ expected dealer **buying** if spot reaches that level. Negative ≈ selling. Default `positioning=flow`. See [Positioning](/concepts/positioning). ## See also > **[Strike profile](/api/v2/gex/strike-profile)** > **[Dealer flow series](/api/v2/gex/dealer-flow)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Delta flow (customer) Source: https://docs.backquant.com/api/v2/gex/delta-flow GET /gex/delta-flow Customer delta flow (HIRO-style) from options tape. Time-bucketed customer signed Black-Scholes delta notional from the multi-venue options tape... Time-bucketed **customer** signed Black-Scholes delta notional from the options tape (HIRO-style delta line), with cumulative net. * Coins: **BTC**, **ETH** * BS inputs use high-quality prints (Deribit for unit and IV consistency) * Distinct from [dealer-flow](/api/v2/gex/dealer-flow) (chain × price moves) Positive net ≈ customer buy-side delta. Negative ≈ customer sell-side delta. For multi-venue premium quadrants (not delta), use [tape imbalance](/api/v2/tape/imbalance) with `weight=flow`. ## See also > **[Tape imbalance](/api/v2/tape/imbalance)** > **[Dealer flow](/api/v2/gex/dealer-flow)** > **[Tape overview](/api/v2/tape/overview)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # GEX by expiry Source: https://docs.backquant.com/api/v2/gex/expiry-profile GET /gex/expiry-profile GEX by expiry. Net / call / put gamma per expiration date with days-to-expiry. Net / call / put gamma per expiration date with DTE. `?weekly_only=true` excludes monthly + quarterly anchors. Default **`positioning=flow`**. See [Positioning](/concepts/positioning). ## See also > **[What is GEX?](/concepts/gex)** > **[Flow vs std](/concepts/positioning)** > **[OPEX calendar](/concepts/opex)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Gamma flow Source: https://docs.backquant.com/api/v2/gex/gamma-flow GET /gex/gamma-flow Total gamma flow time series. Gamma component of dealer hedge flow only: each point is ΔS% × net_GEX under the selected positioning model, with session... Dealer gamma hedge series only: each point is `ΔS% × net_GEX` under the selected positioning model, plus a cumulative sum of those increments. Positive ≈ dealer buying pressure from gamma hedging on the move. Negative ≈ selling. Default `positioning=flow`. See [Positioning](/concepts/positioning). ## See also > **[Full dealer flow](/api/v2/gex/dealer-flow)** > **[Delta flow (customer)](/api/v2/gex/delta-flow)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # GEX history Source: https://docs.backquant.com/api/v2/gex/history GET /gex/history Total GEX history (cursor-paginated). Time-series of total GEX (and net-local-5pct, call/put walls when available). Cursor-paginated time-series of total GEX, net-local-5pct, call wall, put wall. **Positioning is std only.** Stored history was written under the textbook model. Live levels default to flow. See [Positioning](/concepts/positioning). ## See also > **[What is GEX?](/concepts/gex)** > **[Stress history](/api/v2/gex/stress-history)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Strike ladder Source: https://docs.backquant.com/api/v2/gex/ladder GET /gex/ladder Dealer exposure by strike and expiry for every greek, and the dealer position in contracts. The grid behind the terminal's HEATMAP: one metric per call (`gex`, `dex`, `vanna`, `charm`, `vega`, `oi` or `pos`), with a row per strike and a column per expiry, plus each strike's total over every expiry in scope. `pos` is the dealer position itself, in contracts: where dealers are longest and shortest, by strike and by expiry. With `positioning=flow` (the default for BTC and ETH) it is measured from the tape; see [How flow is measured](/concepts/positioning#how-flow-is-measured). ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -H "X-API-Key: $KEY" \ "https://api.backquant.com/v2/gex/ladder?symbol=BTCUSDT&metric=pos&expiry=week" ``` `expiry` takes `all`, `0dte`, `front`, `week`, `month`, ISO dates or tokens such as `30OCT26`. Every expiry in the response carries both forms. ## See also > **[Dealer positioning](/concepts/positioning)** > **[Strike profile](/api/v2/gex/strike-profile)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # GEX levels Source: https://docs.backquant.com/api/v2/gex/levels GET /gex/levels Composable GEX key levels. All-expiry HVL/walls plus 0DTE-specific levels in a single call. Composable HVL / call wall / put support / 0DTE levels in one call. Use `?include=` to opt into max-pain, expected move, ranked top-10, gamma flip zones, spot, candles. Default **`positioning=flow`**. Pass `?positioning=std` for textbook call/put OI signing. See [Positioning](/concepts/positioning). ## See also > **[What is GEX?](/concepts/gex)** > **[Flow vs std](/concepts/positioning)** > **[Strike profile](/api/v2/gex/strike-profile)** > **[Multi-symbol bundle](/concepts/multi-symbol)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Max pain Source: https://docs.backquant.com/api/v2/gex/max-pain GET /gex/max-pain Max-pain strike (per-expiry). The strike at which option writers profit most at expiry. The strike where option writers profit most at expiry. Use `?expiry=0dte` for the cheapest path, `?expiry=all` for a per-expiry table, or pass a specific expiry token to also receive the full pain curve. ## See also > **[Max pain explained](/concepts/max-pain)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Stress history (Postgres) Source: https://docs.backquant.com/api/v2/gex/stress-history GET /gex/stress-history Long-window GEX levels from Postgres (per symbol). Persistent record of HVL / call wall / put wall / net GEX, suitable for backtests or regime analysis. Long-window record of HVL / call wall / put wall / net GEX from the persistent store. Suitable for backtests or regime work. Bounded to 30 days. **Positioning is std only** (same as GEX history). Live REST levels default to flow. ## See also > **[What is GEX?](/concepts/gex)** > **[GEX history](/api/v2/gex/history)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Strike × expiry heatmap Source: https://docs.backquant.com/api/v2/gex/strike-expiry-heatmap GET /gex/strike-expiry-heatmap 2D strike × expiry GEX matrix. Rows are strikes, columns are expiries (sorted ascending DTE). 2D matrix of net GEX with rows=strikes, cols=expiries (sorted ascending DTE). `?max_strikes` and `?max_expiries` downsample around spot / front of curve. Default **`positioning=flow`**. See [Positioning](/concepts/positioning). ## See also > **[What is GEX?](/concepts/gex)** > **[Flow vs std](/concepts/positioning)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # GEX by strike Source: https://docs.backquant.com/api/v2/gex/strike-profile GET /gex/strike-profile GEX by strike. Gamma exposure aggregated by strike. Gamma exposure aggregated by strike. Filter by `?expiry=all|0dte|` and by strike window (`strike_min/max`, `moneyness_min/max`, `spot_window_pct`). Default **`positioning=flow`**. See [Positioning](/concepts/positioning). ## Venues Optional `?exchanges=` CSV filters the multi-venue OI roll-up. Allowed IDs: `deribit`, `bybit`, `okx`, `binance`, `derive`, `thalex`, `delta_india`, `delta`. Responses include `available_exchanges` with the full chain set so clients can render stable filter chips even when a venue contributed zero OI on this cycle. Underlying OI is **coin-unit** across venues. [Venue coverage](/concepts/venues). ## See also > **[What is GEX?](/concepts/gex)** > **[Flow vs std](/concepts/positioning)** > **[GEX levels](/api/v2/gex/levels)** > **[Flow by strike](/api/v2/tape/flow-by-strike)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Strike × time heatmap Source: https://docs.backquant.com/api/v2/gex/time-heatmap GET /gex/time-heatmap Strike × time greek heatmap. Time-series of greek exposures by strike. Time-series of greek exposures by strike. Pick the greek (`gex|dex|vex|charm|vanna`), bound by DTE / OI / IV. Default **`positioning=flow`**. See [Positioning](/concepts/positioning). ## See also > **[What is GEX?](/concepts/gex)** > **[Flow vs std](/concepts/positioning)** > **[Greeks beyond delta](/concepts/greeks)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Health check Source: https://docs.backquant.com/api/v2/health GET /health v2 health check (no auth). Lightweight liveness/readiness probe for external monitors. Auth-free liveness probe for monitors. Returns subsystem status (Redis + Postgres). ## See also > **[Data freshness](/concepts/data-freshness)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Liquidation distribution Source: https://docs.backquant.com/api/v2/liquidation/distribution GET /liquidation/distribution Liquidation histogram with leverage tiers. Per-price-level long and short liquidation volumes, with a leverage tier estimate based on distance from... Per-price-level long and short liquidation volumes with a leverage-tier estimate based on distance from spot. Use it as a histogram visualisation or as input to position-sizing logic - clusters of leverage near current price are where stop-cascades typically trigger. Leverage tiers are estimated from distance to spot: | Distance from spot | Tier | | - | - | | 0-1% | 100x (high) | | 1-2% | 50x (medium-high) | | 2-4% | 25x (medium) | | 4%+ | 10x (low) | Updated every 5 minutes server-side. ## See also > **[Liquidation heatmap](/api/v2/liquidation/heatmap)** > **[Data freshness](/concepts/data-freshness)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Liquidation heatmap Source: https://docs.backquant.com/api/v2/liquidation/heatmap GET /liquidation/heatmap Liquidation intensity heatmap (price × time). Estimated liquidation levels normalized to a 0-100 scale, gridded by price bin and time bucket. Estimated liquidation levels normalized to a 0-100 scale, gridded by price bin and time bucket. Includes OHLC candles for standalone rendering. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Market bundle Source: https://docs.backquant.com/api/v2/market/bundle GET /market/bundle Price, open interest and funding for one symbol and window in a single request, on one aligned time axis. Candles, open interest and funding for one symbol over one window, in a single request. Six series in one request: candles, open interest, funding, CVD on both the perpetual and spot aggregates, and liquidations. Use this instead of calling the individual endpoints. It is one round trip rather than six, and measured about **3x faster** than making those calls separately. Pick a subset with `series=` when you do not need all six. ## One shared time axis **Every series shares the response's single `timestamps` array.** Index `i` of any series corresponds to `timestamps[i]`, so you can zip them positionally without joining on time. This matters more than it sounds. The series come from different tables and do not all carry every bucket. Measured over a year at `1d`: candles 372, funding 364, liquidations 367, across the *same* first and last timestamp. Returned unaligned, a positional zip would pair one day's open interest with another day's price and every number would still look plausible. A bucket a series does not have is **`null` at that position**, never carried forward from the previous one. Filling it would invent a print that did not happen and bias any average computed over the column. `meta.extra.coverage` reports how many buckets each series actually filled, so sparsity is visible without counting nulls: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "data": { "timestamps": [1756080000000, 1756166400000, 1756252800000], "candles": { "close": [80332.7, 80383.1, 80401.2] }, "funding": { "rate_open": [0.0001, null, 0.0003] } }, "meta": { "extra": { "coverage": { "candles": 3, "funding": 2 }, "aligned": true } } } ``` > [!NOTE] > > The individual endpoints still return their own `timestamps`. Only the bundle > reindexes onto a shared axis, because doing that join is the reason it exists. > [!NOTE] > > **Quota is charged per series, not per request.** Three series cost three > units, exactly as three separate calls would, and the count is reported in > `meta.extra.quota_units_charged`. > > The bundle saves you round trips and alignment work, never allowance. Ask only > for the series you need. ## Partial results A series that cannot be served comes back as `null`, with its reason under `meta.extra.errors`. The rest of the bundle is unaffected. This is deliberate: one cold or unavailable series should not cost you the two that were ready. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "data": { "candles": { }, "open_interest": { }, "funding": null }, "meta": { "extra": { "series": ["candles", "open_interest", "funding"], "quota_units_charged": 3, "errors": { "funding": "No funding data for SOLUSDT" } } } } ``` ## Windows and cost Long windows are read from continuous aggregates rather than raw one-minute rows, so a year at `1d` is no more expensive than a day at `1m`. The source actually used is reported in `meta.extra.sources`. Windows are capped at one year, matching retention. Your plan's historical window applies on top of that: see `history_depth` and `your_plan` in [`/v2/meta`](/api/v2/discovery/meta). ## See also > **[Candles](/api/v2/market/candles)** > **[Rate limits](/concepts/rate-limits)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Candles Source: https://docs.backquant.com/api/v2/market/candles GET /market/candles OHLCV candles with buy/sell volume split, across the cross-venue aggregate. Open, high, low, close and volume, with the buy/sell split where the venue publishes it. Served from the cross-venue aggregate, so you do not need to pick an exchange. Long windows are read from continuous aggregates rather than raw one-minute rows, which means a year at `1d` costs no more than a day at `1m`. The source actually used is reported in `meta.extra.sources`. Windows are capped at one year (matching retention) and at 30,000 points. Your plan's historical window applies on top: see `history_depth` in [`/v2/meta`](/api/v2/discovery/meta). > [!TIP] > > Fetching open interest or funding over the same window? Use > [`/market/bundle`](/api/v2/market/bundle) instead: one round trip, and the > series arrive already aligned. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # CVD Source: https://docs.backquant.com/api/v2/market/cvd GET /market/cvd Cumulative volume delta: aggressor buy volume, sell volume, per-bucket delta and the running cumulative. Aggressor buy volume, sell volume, their per-bucket difference, and the running cumulative across the window. Derived from the same buy/sell columns [`/market/candles`](/api/v2/market/candles) already carries, so requesting candles and CVD over one window costs a **single** underlying read rather than two. ## On OHLC `delta` is exact: buy minus sell for that bucket. `open` and `close` are the running cumulative at the bucket's boundaries, also exact. > [!WARNING] > > `high` and `low` are returned as `null`, with `ohlc_complete: false`. > > They would require the path the cumulative took *inside* the bucket, and bucket > totals do not carry that. We return nulls rather than interpolating, because a > fabricated wick looks exactly like data and isn't. > > If you need true intra-bucket extremes, request a finer `interval` and > aggregate client-side. ## Reading the cumulative The cumulative is **relative to the start of the window**, not an all-time figure. It begins at zero on the first bucket returned. Compare shapes across windows, not levels: two requests with different `hours` will not produce the same absolute values, and neither is wrong. ## See also > **[Candles](/api/v2/market/candles)** > **[Market bundle](/api/v2/market/bundle)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Funding Source: https://docs.backquant.com/api/v2/market/funding GET /market/funding Aggregate perpetual funding rate over time. Aggregate perpetual funding rate, on the same interval grid as [`/market/candles`](/api/v2/market/candles). > [!NOTE] > > Funding settles on each venue's own schedule. Asking for a finer interval > resamples the settlements that exist; it does not create new ones. Same window rules as candles: capped at one year, long windows served from continuous aggregates, and your plan's historical window applies. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Liquidations Source: https://docs.backquant.com/api/v2/market/liquidations GET /market/liquidations Long and short liquidations over time, on the same interval grid as the other market series. Long and short liquidations over time, on the same interval grid as [`/market/candles`](/api/v2/market/candles), so they align with price, OI and CVD without resampling. This is the **time series**. For the strike-style views see [`/liquidation/heatmap`](/api/v2/liquidation/heatmap) and [`/liquidation/distribution`](/api/v2/liquidation/distribution). > [!TIP] > > Pulling liquidations alongside price and CVD? One call to > [`/market/bundle`](/api/v2/market/bundle) returns all six series aligned. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Open interest Source: https://docs.backquant.com/api/v2/market/open-interest GET /market/open-interest Aggregate perpetual open interest over time, on the same interval grid as candles. Aggregate perpetual open interest, on the same interval grid as [`/market/candles`](/api/v2/market/candles) so the two align without client-side resampling. Same window rules as candles: capped at one year, long windows served from continuous aggregates, and your plan's historical window applies. > [!TIP] > > Need price and funding over the same window too? One call to > [`/market/bundle`](/api/v2/market/bundle) returns all three. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Multi-symbol GEX levels Source: https://docs.backquant.com/api/v2/multi/gex-levels GET /multi/gex/levels GEX levels across multiple symbols (bundled). Multi-symbol GEX levels in one round-trip. Bundled multi-symbol read. Up to 8 symbols per request. Returns per-symbol entries plus a `served` / `missing` / `invalid` breakdown. Default **`positioning=flow`** (with the same per-symbol fallbacks as single-symbol levels). See [Positioning](/concepts/positioning). ## See also > **[Multi-symbol bundling](/concepts/multi-symbol)** > **[What is GEX?](/concepts/gex)** > **[Flow vs std](/concepts/positioning)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Options chain Source: https://docs.backquant.com/api/v2/options/chain GET /options/chain Filtered options chain. Per-contract data (greeks, OI, IV, bid/ask) plus aggregate stats. Filtered chain with two-layer projection (`?fields=` top-level, `?include=` per-contract) and a payload cap (`?max_contracts`). Cached 30s per filter combination. ## Where each number comes from A chain row mixes cross-venue and single-venue fields on purpose. Reading a single-venue field as a market-wide total is the most common way to misread this endpoint. | Field | Scope | | - | - | | `oi` | All eight venues, summed | | `bid`, `ask`, `mark` | **Deribit only.** Use [venue-book](/api/v2/options/venue-book) for consolidated best bid and offer | | `volume`, `notional_volume`, `trade_count` | All eight venues on **BTC and ETH**; Deribit only on SOL, Derive only on HYPE | The activity fields cover a rolling 24 hours and arrive under `?include=volume`. `volume` is contracts, `notional_volume` is **premium paid in USD**, and `trade_count` is prints. All three mean the same thing on every symbol; only the venue scope differs. Under `?as_of=`, these three are **recomputed** from the tape for the window ending at that snapshot (`meta.extra.activity_rebuilt` confirms it). `bid` and `ask` are not recoverable that way and stay `0` on chains persisted before 2026-09-01 - `meta.extra.quotes_note` says so. > [!WARNING] > > On the activity fields, **`null` is not `0`**. A `0` means measured and > nothing traded; a `null` means not measured. SOL reports > `trade_count: null`, because no venue publishes a per-contract trade count > for it, and BTC/ETH read `null` in the brief window where the cross-venue > measurement is unavailable. Full reasoning, including why SOL and HYPE are not blended, is in [Venues](/concepts/venues). ## See also > **[Which fields are cross-venue](/concepts/venues)** > **[Consolidated best bid/offer](/api/v2/options/venue-book)** > **[Greeks beyond delta](/concepts/greeks)** > **[Refresh cadences](/concepts/data-freshness)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Expected move Source: https://docs.backquant.com/api/v2/options/expected-move GET /options/expected-move Implied 1σ move. ATM-straddle-derived 1- and 2-sigma expected move (1d horizon). ATM-straddle-derived 1- and 2-sigma expected move (1d horizon). ## See also > **[Probability density](/concepts/probability-density)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Per-expiry summary Source: https://docs.backquant.com/api/v2/options/expiry-summary GET /options/expiry-summary Per-expiry IV / skew / PCR / OI / GEX (with OPEX enrichment). One row per active expiry with the metrics most useful for chain-wide screening:... One row per active expiry with ATM IV, 25Δ skew, PCR, OI, net GEX, plus OPEX enrichment (`type`, `is_anchor`, `expiry_date`, `notional_oi_usd`, `max_pain`). ## OI and notional | Field | Unit | | - | - | | `call_oi` / `put_oi` / `total_oi` | **Coin** of underlying (multi-venue sum) | | `notional_oi_usd` | `total_oi × spot_price` when spot is present | See [Venue coverage](/concepts/venues) for which venues feed the blend. ## See also > **[OPEX calendar](/concepts/opex)** > **[OI by expiry](/api/v2/options/oi/by-expiry)** > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Futures term structure Source: https://docs.backquant.com/api/v2/options/futures/term-structure GET /options/futures/term-structure Dated-futures annualized basis curve. Deribit-listed dated futures annualized basis vs spot, by maturity. Deribit-listed dated futures annualized basis vs spot, by maturity. Useful as a complement to the IV term structure for assessing carry. ## See also > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # 3D greek surface Source: https://docs.backquant.com/api/v2/options/greeks/3d-surface GET /options/greeks/3d/surface Strike × expiry surface for any greek. Z = sum(greek × OI) per (strike, expiry) cell. Z = sum(greek × OI) per (strike, expiry) cell for any greek. Useful for 3D visualizations or for spotting where exposure concentrates across the term structure. ## See also > **[Greeks beyond delta](/concepts/greeks)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Greek profile Source: https://docs.backquant.com/api/v2/options/greeks/by-greek GET /options/greeks/{greek} Greek exposure profile by strike. Net greek exposure per strike. Net greek exposure per strike. `greek` is one of `delta` (DEX), `theta`, `vanna`, `charm`, `vega`. Optional `?dte_max` recomputes from the raw chain limited to short-DTE contracts. Default **`positioning=flow`**. See [Positioning](/concepts/positioning). ## See also > **[Greeks beyond delta](/concepts/greeks)** > **[Flow vs std](/concepts/positioning)** > **[What is GEX?](/concepts/gex)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Greek surface (strike × time) Source: https://docs.backquant.com/api/v2/options/greeks/surfaces GET /options/greeks/surfaces/{greek} Strike × time greek surface. Time-evolving surface for charm or vega - strike on one axis, time on the other. Time-evolving surface for `charm` or `vega`. Already pre-computed for the BackQuant terminal; exposed here for quant tooling. ## Per-venue breakdown `exchange_breakdown` gives the per-venue split **at every point on the surface**. It is roughly three quarters of the payload, so as of **2.11.0 it is opt-in**: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -H "X-API-Key: bq_live_your_key_here" \ "https://api.backquant.com/v2/options/greeks/surfaces/vega?symbol=BTCUSDT&include=exchange_breakdown" ``` Same flag and same default as [`/v2/options/pcr`](/api/v2/options/pcr). > [!WARNING] > > **Breaking in 2.11.0.** The response previously included `exchange_breakdown` > by default, which made it about 638 KB compressed against 124 KB without. If > you read that field, add `?include=exchange_breakdown` to restore it. The > surface itself is unchanged either way. ## See also > **[Greeks beyond delta](/concepts/greeks)** > **[The same flag on PCR](/api/v2/options/pcr)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Greek TRACE - forward (time × price) projection Source: https://docs.backquant.com/api/v2/options/greeks/trace GET /options/greeks/trace/{greek} Forward (time × price) greek projection - gamma / charm / vanna. Black-Scholes forward projection of dealer gamma, charm, or vanna across a (time ×... Forward Black-Scholes projection of dealer **gamma**, **charm**, **vanna**, or **delta\_change** across a `(time × price)` grid. Same view as BackQuant Pro TRACE, as raw data for your own renderer. Holding today's chain constant, the endpoint walks each grid point (future timestamp + future spot), recomputes per-contract τ and the requested greek, sums across the chain with positioning-weighted size, and returns the field. Default **`positioning=flow`**. Pass `?positioning=std` for textbook OI sign. See [Positioning](/concepts/positioning). ## Why use it A point-in-time greek profile only shows exposure *now*. TRACE shows how walls develop, decay, or migrate as time and price evolve. Common uses: * **Intraday flow planning** - `greek=gamma&expiry=0dte` shows where 0DTE dealer hedging concentrates as the session progresses. * **Charm bleed** - `greek=charm` exposes passive delta drift from time alone, even with spot frozen. * **Delta change** - `greek=delta_change` maps how dealer delta shifts relative to spot/now as the grid walks (useful for hedge path views). * **Vol-shock surfaces** - `vol_shift_pct=±10` for instant vol up/down without a re-fetch. * **Multi-day overlays** - `expiry=all&horizon_hours=168` across the next week's term structure. For pin zones / acceleration pockets without the full grid, use [TRACE summary](/api/v2/options/greeks/trace-summary). ## Sign convention Under **std**, calls contribute positive size and puts negative (same as the rest of the stack). Under **flow**, size comes from BackQuant's measured dealer model. A positive cell means the signed book leans supportive at that `(time, price)`; negative means the reverse. ## Units | `greek` | Unit | Formula per cell | | - | - | - | | `gamma` | \$ per 1% spot move | `Σ sign × γ × S² × qty × 0.01` | | `charm` | per year | `Σ sign × ∂Δ/∂τ × qty` | | `vanna` | per IV unit | `Σ sign × ∂Δ/∂σ × qty` | | `delta_change` | delta units | change in summed signed delta vs reference (spot now, τ now) | `field_units` is echoed on every response so renderers need not hardcode this. ## Cells, axes, and shape ``` field[t][p] ← cell value at time_axis_ms[t], price_axis[p] ``` `time_axis_ms` runs from "now" to `now + horizon_hours`. `price_axis` runs from `spot × (1 − range/100)` to `spot × (1 + range/100)`. Both are evenly spaced. `abs_field_max = max(|field_min|, |field_max|)` supports symmetric colour scales: `value / abs_field_max` clamped to `[−1, +1]`. ## What-if vol shifts `vol_shift_pct` adds a flat percent shift to every contract's IV. Example: `vol_shift_pct=5` multiplies each σ by `1.05` before evaluation. ## Performance Cached for **30 seconds** per `(symbol, full-param-hash)`. Cold compute is typically **50-100 ms** for default grids; payload is often **30-80 KB** of JSON. ## Caps | Param | Min | Max | Default | | - | - | - | - | | `time_steps` | 2 | 200 | 60 | | `price_steps` | 2 | 200 | 80 | | `price_range_pct` | >0 | 50 | 8 | | `horizon_hours` | >0 | 720 | 24 | | `vol_shift_pct` | −50 | +50 | 0 | | `min_oi` | 0 | - | 0 | Going past the caps returns a `VALIDATION_ERROR` with a 422. ## See also > **[TRACE summary](/api/v2/options/greeks/trace-summary)** > **[Flow vs std](/concepts/positioning)** > **[Greeks beyond delta](/concepts/greeks)** > **[Greek profile](/api/v2/options/greeks/by-greek)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Greek TRACE summary Source: https://docs.backquant.com/api/v2/options/greeks/trace-summary GET /options/greeks/trace/{greek}/summary TRACE field summary - pin zones, accel pockets, lean. Same projection as /greeks/trace/{greek} but returns an interpretability summary instead of the... Same forward projection as [TRACE](/api/v2/options/greeks/trace), but returns an interpretability summary instead of the full 2D field: * pin zones (high mean absolute field) * near-spot acceleration pockets * late vs early lean along spot * zero-contour samples at the horizon Supports `gamma`, `charm`, `vanna`, and `delta_change`. Default `positioning=flow`. ## See also > **[Full TRACE field](/api/v2/options/greeks/trace)** > **[Positioning models](/concepts/positioning)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Constant-maturity vol Source: https://docs.backquant.com/api/v2/options/iv/constant-maturity GET /options/iv/constant-maturity ATM vol, 25- and 10-delta risk reversals and butterflies at fixed tenors from 7 days to 1 year, with static-arbitrage checks. The vol desk's view of the surface: ATM, the 25- and 10-delta call and put vols, risk reversals and butterflies at 7, 14, 30, 60, 90, 120, 180, 270 and 365 days. Fixed tenors compare cleanly across time and across coins, where listed expiries roll. Each expiry's ATM and wings are read off its quoted smile at Black-76 forward-delta strikes; each tenor interpolates total variance linearly in time between the two fitted expiries around it. Nothing is extrapolated: a tenor or a wing the quotes do not reach is null. Every response also carries the static-arbitrage checks on each fitted smile (`butterfly_free`, `calendar_free`, `min_density_g`) and a summary in `arbitrage`. The full method is on [Vol surface methodology](/concepts/vol-surface). ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "tenors": [ { "tenor_days": 30, "atm_iv": 35.53, "iv_25c": 35.35, "iv_25p": 37.59, "iv_10c": 36.23, "iv_10p": 41.72, "rr_25": -2.24, "bf_25": 0.94, "rr_10": -5.49, "bf_10": 3.45, "bracket_dte": [26.15, 47.15] } ], "arbitrage": { "butterfly_free": true, "calendar_free": true, "violations": [] } } ``` Vols in percent. `rr` is call minus put, so it is negative when puts are bid; [`/v2/options/iv/skew`](/api/v2/options/iv/skew) reports put minus call. Current values only, refreshed with the surface about every 30 seconds. ## See also > **[Vol surface methodology](/concepts/vol-surface)** > **[Single-expiry smile](/api/v2/options/iv/smile)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # All-expiry smile curves Source: https://docs.backquant.com/api/v2/options/iv/curves GET /options/iv/curves Full IV smile per expiry. Merged-IV smile (OTM puts below spot, OTM calls above) for every active expiry, sorted by DTE. Full merged-IV smile (OTM puts below spot, OTM calls above) for every active expiry, sorted by DTE. ## See also > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # IV vs realized vol history Source: https://docs.backquant.com/api/v2/options/iv/iv-rv GET /options/iv/iv-rv Implied vs realized vol history. Daily implied (DVOL/ATM-IV) and 30d realized vol with the spread. Daily implied (DVOL/ATM-IV) and 30d realized vol with the spread. ## See also > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Skew (25Δ / 10Δ + butterfly) Source: https://docs.backquant.com/api/v2/options/iv/skew GET /options/iv/skew 25Δ / 10Δ skew + 25Δ butterfly per expiry. Delta-interpolated risk-reversal and butterfly per active expiry. Delta-interpolated risk-reversal and butterfly per active expiry. `skew_25d` = 25Δ put IV − 25Δ call IV. ## See also > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Single-expiry smile Source: https://docs.backquant.com/api/v2/options/iv/smile GET /options/iv/smile One expiry's implied-vol smile from the fitted surface, by strike and log-moneyness, with its ATM, risk reversals, butterflies and arbitrage checks. One expiry's smile, rebuilt from its SVI fit between its lowest and highest quoted strikes and never past them (`method: svi`), or its row of the surface grid where the smile did not fit (`method: interp`, mostly same-day expiries). Each point gives the strike, the log-moneyness against the expiry's own forward and the vol in percent. The response adds the expiry's ATM vol, 25- and 10-delta risk reversals and butterflies, the fit error `smile_rmse`, and its static-arbitrage checks: `butterfly_free` (non-negative density across the quoted strikes, with `min_density_g`) and `calendar_free` (its total variance is at or above the previous fitted expiry's). Both are null on an `interp` row. `expiry` takes a token (`30OCT26`) or an ISO date (`2026-10-30`); omitted, the nearest fitted expiry is returned. `available_expiries` lists every expiry you can ask for, including fitted ones past the grid's 180 days. `call_ivs` and `put_ivs` are deprecated and always null. ## See also > **[Vol surface methodology](/concepts/vol-surface)** > **[Constant-maturity vol](/api/v2/options/iv/constant-maturity)** > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # IV surface Source: https://docs.backquant.com/api/v2/options/iv/surface GET /options/iv/surface The fitted implied-vol surface: vol by tenor and strike, with each expiry's forward, SVI parameters and fit error. The fitted implied-vol surface out to 180 days. `z_grid[i][j]` is the vol in percent at tenor `y_grid[i]` (days, ascending) and strike `x_grid[j]`. Cells are null where the expiry never traded that far out; nothing is extrapolated to fill them. `expiry_forwards` gives each row's forward and how it was found, the smile method and fit error, the raw SVI parameters `svi` where it fitted, its years to expiry `t`, the log-moneyness span of its quotes `k_range`, its quoted ATM and wing vols (`market`) and its quote count. `long_expiries` carries the same for expiries past 180 days, which are fitted but not drawn on the grid. `delta_surface` is the same surface by delta. Calendar arbitrage is removed on the grid. How every step works, and what is checked, is on [Vol surface methodology](/concepts/vol-surface). ## See also > **[Vol surface methodology](/concepts/vol-surface)** > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Term structure Source: https://docs.backquant.com/api/v2/options/iv/term-structure GET /options/iv/term-structure ATM IV by tenor. At-the-money implied volatility for each active expiration. ATM IV per active expiration. Filter `?dte_max` for front-of-curve, or `?historical_compare_days=N` for today vs N days ago overlay. ## See also > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # OI by expiry Source: https://docs.backquant.com/api/v2/options/oi/by-expiry GET /options/oi/by-expiry Call/put OI per expiry. Open interest aggregated by expiration with calls and puts split. Open interest aggregated by expiration with calls and puts split. `?weekly_only=true` excludes monthly + quarterly anchors. ## Units | Field | Unit | | - | - | | Call / put / total OI | **Coin** of the underlying (BTC, ETH, …) | | Multi-venue totals | Sum of coin OI across venues on the same normalized instrument | USD notional ≈ `total_oi × spot`. When present on [`/v2/options/expiry-summary`](/api/v2/options/expiry-summary), `notional_oi_usd` is that product. Filter with `?exchanges=` where supported. Venue list: [Venue coverage](/concepts/venues). ## See also > **[OPEX calendar](/concepts/opex)** > **[Expiry summary](/api/v2/options/expiry-summary)** > **[Venue coverage](/concepts/venues)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # OI history Source: https://docs.backquant.com/api/v2/options/oi/history GET /options/oi/history Total OI history (cursor-paginated). Time-series of total / call / put open interest. Cursor-paginated time-series of total / call / put open interest for the underlying. Use cursor pagination (`?limit` + `?before=`) to walk backwards through history without missing rows or double-counting at page boundaries. The response carries `next_cursor` - pass it as `?before=` on the next call to fetch the previous page. OI series are in **coin units** of the underlying (same convention as [`/options/oi/by-expiry`](/api/v2/options/oi/by-expiry)). Useful for: * Detecting OI build-ups before major expirations * Backtest data feeds that need contiguous history * Spotting unwinds (sudden OI drops) coincident with price moves ## See also > **[OI by expiry](/api/v2/options/oi/by-expiry)** > **[Venue coverage](/concepts/venues)** > **[OPEX calendar](/concepts/opex)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # OPEX calendar Source: https://docs.backquant.com/api/v2/options/opex GET /options/opex OPEX calendar with per-expiry context. Curated calendar of upcoming options expirations with everything needed for OPEX-day workflows in one call:... Curated calendar of upcoming expirations with type classification, anchor flag, OI, IV, gamma blocks (walls + max-pain), and `next_anchor` pointer. ## See also > **[OPEX calendar](/concepts/opex)** > **[Max pain](/concepts/max-pain)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Put / call ratio Source: https://docs.backquant.com/api/v2/options/pcr GET /options/pcr Put/call ratio. OI-weighted put/call ratio. OI-weighted put/call ratio - the most-asked-for single sentiment indicator from options data. PCR > 1 means more put OI than call OI (typically defensive / fear bid); PCR \< 1 means call-heavy (typically risk-on). Two granularities: * **`?granularity=intraday`** (default) - rolling series at roughly a five-minute cadence. Use for live dashboards. * **`?granularity=daily`** - one value per UTC day where the daily rollup is available. That rollup is not currently populated, so this falls back to the intraday series and reports `granularity: "intraday-fallback"` in `meta.extra`. Check that field rather than assuming daily buckets. ## Window `?days=N` (1 to 365, default 30) bounds **both** granularities. > [!NOTE] > > Before 2.8.1 `days` was accepted on the intraday branch and silently ignored, > so a request for a week received the entire stored series regardless. It now > genuinely bounds the window. `meta.extra` reports `points_available` and > `points_returned` so a trimmed series is visible rather than assumed complete. ## Per-venue breakdown `exchange_breakdown` gives call and put open interest per venue **for every point in the series**. It is roughly four fifths of the payload, so as of **2.8.1 it is opt-in**: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -H "X-API-Key: bq_live_your_key_here" \ "https://api.backquant.com/v2/options/pcr?symbol=BTCUSDT&include=exchange_breakdown" ``` > [!WARNING] > > **Breaking in 2.8.1.** The intraday response previously included > `exchange_breakdown` by default, which made it about 2.5 MB. If you read that > field, add `?include=exchange_breakdown` to restore it. Everything else is > unchanged. ## See also > **[OI by expiry](/api/v2/options/oi/by-expiry)** > **[OI history](/api/v2/options/oi/history)** > **[Premium tide](/api/v2/options/premium-tide)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Premium tide Source: https://docs.backquant.com/api/v2/options/premium-tide GET /options/premium-tide Net premium & volume tilt. Net premium and notional volume from Deribit-side trades, split into call and put flow. Net premium and notional volume from Deribit-side trades, split into call and put flow. `?horizon=0dte|weekly` selects today's tape vs the rolling weekly aggregate. Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Probability density Source: https://docs.backquant.com/api/v2/options/probability/density GET /options/probability/density Risk-neutral PDF/CDF (per expiry). Breeden-Litzenberger probability density derived from option prices. Risk-neutral PDF/CDF derived from option prices via Breeden-Litzenberger. Optional `?confidence_band=0.68|0.95` returns interpolated price bands per expiry. ## See also > **[Breeden-Litzenberger explained](/concepts/probability-density)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Probability surface Source: https://docs.backquant.com/api/v2/options/probability/surface GET /options/probability/surface 3D probability surface (PDF over strike × expiry). Risk-neutral probability density across the full strike × expiry grid as a single matrix, suitable... Risk-neutral PDF across the full strike × expiry grid as a single matrix, suitable for 3D visualizations. ## See also > **[Breeden-Litzenberger explained](/concepts/probability-density)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Calibrated options range Source: https://docs.backquant.com/api/v2/options/range GET /options/range The move options price over 1h, 4h and 24h, rescaled to realised moves, with its out-of-sample record. Options overprice moves on average (the variance risk premium), so a range built from implied vol alone holds more often than it claims. This endpoint takes the move DVOL prices and rescales it on the last 30 days of hourly moves, so the 68% and 90% bands hold about that often. Each horizon returns the bands as prices around spot and the `record`: how often such bands held out of sample, with each day fitted only on the days before it. On BTC and ETH the bands held 68-69% and 89-90% at 1h, 4h and 24h in walk-forward tests. BTC and ETH (Deribit's DVOL). `include=past` adds the bands as they were drawn at the time, out of sample: hourly bands for the last week, 4-hourly for the last month and daily (from 00:00 UTC) for the last six months, within your plan's history window. Each row, in `past_columns` order, gives the start time, the price at the start, the 68% and 90% bands as prices, the price at the end and whether price ended inside each band. The latest band is still running, so its end price and held flags are null. Rows tile the timeline, so they chart directly behind candles. ## See also > **[Expected move](/api/v2/options/expected-move)** > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Cross-venue book Source: https://docs.backquant.com/api/v2/options/venue-book GET /options/venue-book Consolidated best bid and offer across every venue we collect, with the venue named on each side, plus how far apart venues are quoting the same contract. Best bid and best offer for each contract across **every venue we collect**, with the venue named on each side, plus how far apart those venues are quoting the same contract. This is not what [`/options/chain`](/api/v2/options/chain) returns. The chain's `bid` and `ask` come from the aggregated row, which is based on a single venue. This endpoint is the genuine best-of, and by construction its spread is never wider than any individual venue's. > [!NOTE] > > Read this as **execution quality**, not as a dislocation scanner. Across the > liquid band, cross-venue implied-vol dispersion sits *inside* the tightest > single venue's own spread roughly 70% of the time, so for the typical contract > there is nothing to capture. What the endpoint answers well is where to execute > and what it costs. ## Scope Computed only for contracts inside the liquid band: **0.85 to 1.15 moneyness, 2 or more days to expiry**. Outside that band implied vol stops carrying information. Deep-ITM contracts have almost no vega, so a trivial price difference reads as tens of vol points. Before the band was applied, the widest apparent "dislocation" in a live run was 52 vol points on a 60,000 strike with spot at 77,600, which is an artefact rather than a signal. Contracts quoted by a single venue are omitted, since every field here is a comparison. A symbol with no multi-venue coverage returns `404` rather than an empty list, because an empty list reads as "spreads are zero". ## Units All prices are **coin-denominated**, and the response states this in `price_unit`. Most venues publish option premium in USD or USDT; those are converted at ingest. Deribit quotes its linear (USDC-settled) assets in USD and is converted the same way, while its BTC and ETH options are inverse and already coin- denominated. Do not infer the unit from magnitude: comparing an unconverted USD premium against a coin premium is exactly how a spread of -195% gets published. ## Reading the fields **consolidated** `boolean` `true` only when **two different venues** make the top of book. When `false`, one venue is best on both sides, so the spread shown is that venue's own and there is no consolidation benefit to report. **top\_of\_book\_liquid** `boolean` `true` when both sides come from a deep venue (Deribit, Bybit, OKX, Binance). When `false` the best price is coming from a thinner venue. Still worth seeing, but not a number to lean on. **book\_crossed** `boolean` A negative spread: one venue bidding above another's offer. Reported, never hidden, but excluded from `median_spread_pct` so the headline is not dragged below what trading actually costs, and sorted after every tradeable spread so the first page is books you can act on. On a typical BTC chain a majority of in-band contracts are crossed, so ordering on the raw number would bury the executable ones. Use `crossed_only=true` to select them. **crossed** `boolean` The same idea on the implied-vol axis: best bid vol above best ask vol, evaluated only between deep venues. **mixed\_settlement** `boolean` The comparison spans both settlement conventions. Deribit's BTC and ETH options are inverse (coin-settled); the USDC venues are linear. Implied vol is comparable across the two to first order, but part of any gap flagged this way is numeraire rather than volatility. > [!WARNING] > > **A crossed market is an observation, not an opportunity.** Venue quote > timestamps are collection times rather than exchange quote times, so a stale > quote on the thinner venue cannot be ruled out. Measured crossings are small, > short-dated and consistently one-directional, which points at structural causes > (settlement, index, funding) rather than anything capturable. Treat `crossed` > and `book_crossed` as venue-quality signals. ## Coverage Venue coverage is not uniform, and the response tells you which venues actually contributed via `venues` per contract and `coverage.venues` overall. | | | | - | - | | Deribit | Prices, no per-side implied vol | | OKX | Per-side implied vol, no prices | | Bybit, Binance | Both | | Thalex, Delta | Prices, single mark vol | A venue takes part in whichever comparison it actually supplied inputs for. Venues missing an input are omitted rather than zero-filled, since a zero would read as a dislocation. BTC and ETH have the deepest coverage. SOL is thinner. Single-venue markets return no rows at all. ## See also > **[Options chain](/api/v2/options/chain)** > **[Venues](/concepts/venues)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Volatility risk premium Source: https://docs.backquant.com/api/v2/options/vrp GET /options/vrp Volatility Risk Premium. Difference between implied and realized vol; positive = options expensive. Difference between implied and realized vol. Positive = options expensive vs realized; sustained `vrp < 0` is usually a buy-vol signal. ## See also > **[IV suite](/concepts/iv-suite)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Flow by strike Source: https://docs.backquant.com/api/v2/tape/flow-by-strike GET /tape/flow-by-strike Cumulative multi-venue aggressor buying and selling by strike on still-open contracts, split into call buy, call sell, put buy and put sell. `GET /v2/tape/flow-by-strike` Real taker flow on **still-open** contracts (`expiry_date >= today UTC`), aggregated by strike into four quadrants: call buy, call sell, put buy, put sell. This is **cumulative inventory of aggressor prints behind live OI**, not a rolling lookback window. Use it to see where aggressors have been active by strike without pulling the full trade history. ## Who it's for | You need… | Use | | - | - | | Per-strike sold vs bought (calls and puts) | **This endpoint** | | Time-bucket buy/sell imbalance | [`/v2/tape/imbalance`](/api/v2/tape/imbalance) | | Ranked net premium by strike over a lookback | [`/v2/tape/strike-heat`](/api/v2/tape/strike-heat) | | Raw trades | [`/v2/tape`](/api/v2/tape/tape) / [`/v2/tape/recent`](/api/v2/tape/recent) | ## Coins `BTCUSDT` and `ETHUSDT` only. `SOLUSDT` / `HYPEUSDT` return `404` - they do not yet have a multi-venue aggressor tape of this shape. ## Query parameters | Param | Type | Default | Description | | - | - | - | - | | `symbol` | string | required | `BTCUSDT` or `ETHUSDT` | | `expiry` | `YYYY-MM-DD` | all open | Restrict to one live expiry | | `expiries` | CSV of `YYYY-MM-DD` | - | Combine several live expiries; **takes precedence** over `expiry` | | `venues` | CSV | all | Filter: `deribit`, `bybit`, `okx`, `binance`, `derive`, `thalex`, `delta_india`, `delta` | | `min_premium_usd` | number ≥ 0 | `0` | Drop prints below this USD premium (whale floor) | | `include` | string | - | `by_venue` adds the per-venue split and the disagreement statistic (see below) | Omit both `expiry` and `expiries` to include every still-open contract. ## Per-venue split (`?include=by_venue`) Dealer flow aggregated across eight venues answers *which way are dealers positioned*. It does not tell you **which venue is driving it**, and that turns out to matter more often than not. ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -H "X-API-Key: bq_live_your_key_here" \ "https://api.backquant.com/v2/tape/flow-by-strike?symbol=BTCUSDT&include=by_venue" ``` Adds a `by_venue` array parallel to `strikes`, so you can zip the two rather than join on strike: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "by_venue": [ { "strike": 30000, "venues": { "deribit": { "call_buy": 0.0, "call_sell": 41200.5, "put_buy": 0.0, "put_sell": 0.0, "net_size": 1413.10 }, "okx": { "...": "...", "net_size": 116.49 }, "bybit": { "...": "...", "net_size": -14.35 } } } ] ``` `net_size` is **sold minus bought**, the same convention the dealer flow model uses, so a venue's number here and its contribution there point the same way. ### Why this exists Deribit carries about **88% of BTC option premium**. That is why most feeds are Deribit-only, and why it looks sufficient. It is not. Measured over seven days of live data, restricted to contracts at least two venues traded \$5,000 or more of premium on: | | contracts | venues disagree on direction | | - | - | -: | | BTC | 391 | **271 (69.3%)** | | ETH | 250 | **172 (68.8%)** | On 57 of those BTC contracts and 41 of the ETH ones, **Deribit alone gives the opposite sign to the aggregate**. A worked example: > **BTC 86,000 call, 11 Sep** - aggregate **+38.35** > Deribit **-26.20** ($200k) · Bybit **+44.63** ($143k) · OKX **+12.72** (\$51k) A Deribit-only view reports dealers short that strike. They are long it, and the venues carrying the other half of the volume are the reason. ### `venue_disagreement` Returned in the body and in `meta.extra`: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "venue_disagreement": { "strikes_compared": 106, "strikes_disagreeing": 82, "disagreement_pct": 77.4 } ``` Strikes only one venue traded are **excluded**. One venue cannot disagree with itself, and counting them would drag the figure toward zero as coverage widens rather than as agreement rises. A venue with no flow on a strike is not counted as a participant either, since zero is not a direction. ### Cost The split is opt-in because it multiplies the row count, not because it is expensive: it is one extra `GROUP BY` column on a scan that already reads these rows. Measured at 880ms against the aggregated query's 961ms. ## Response shape ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "success": true, "data": { "coin": "BTC", "expiry": null, "expiries": null, "available_expiries": ["2026-08-18", "2026-08-19", "2026-08-21"], "available_venues": [ "binance", "bybit", "delta", "delta_india", "deribit", "derive", "okx", "thalex" ], "spot_price": 63426.5, "strikes": [ { "strike": 63000, "call_buy": { "premium": 120450.2, "size": 12.4, "trades": 38 }, "call_sell": { "premium": 98010.0, "size": 9.1, "trades": 22 }, "put_buy": { "premium": 210300.5, "size": 18.2, "trades": 55 }, "put_sell": { "premium": 175200.0, "size": 15.0, "trades": 41 } } ], "totals": { "call_buy": ..., "call_sell": ..., "put_buy": ..., "put_sell": ... }, "block_trades": 17, "ts": "2026-08-18T02:15:00+00:00" }, "meta": { "...": "..." } } ``` ### Quadrant fields Each of `call_buy` / `call_sell` / `put_buy` / `put_sell`: | Field | Unit | Meaning | | - | - | - | | `premium` | USD | Sum of `premium_usd` on matching prints | | `size` | **Coin-equivalent** | Sum of tape `amount` (not raw venue contracts) | | `trades` | count | Number of prints | Dealer-style net size on a call strike is typically `call_sell.size − call_buy.size` (passive/maker inventory from aggressor tape). See [Positioning](/concepts/positioning). ## Important properties * **Cumulative on open contracts** - not “last N hours.” Expired prints drop when `expiry_date` rolls past UTC today. * **Native taker side** - every venue stamps `direction` at ingest (Deribit, Bybit, OKX, Binance, Derive, Thalex, Delta India, Delta Global). This is not quote-rule inference. * **Cached \~90s** - single-flight compute; safe to poll for dashboards. ## Example ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/tape/flow-by-strike?symbol=BTCUSDT&expiry=2026-08-18" \ -H "X-API-Key: $BQ_API_KEY" ``` ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} # Whale floor + venue filter curl "https://api.backquant.com/v2/tape/flow-by-strike?symbol=BTCUSDT&min_premium_usd=25000&venues=deribit,bybit,delta_india" \ -H "X-API-Key: $BQ_API_KEY" ``` ## See also > **[Positioning (flow vs std)](/concepts/positioning)** > **[Venue coverage](/concepts/venues)** > **[Tape overview](/api/v2/tape/overview)** > **[Greek TRACE](/api/v2/options/greeks/trace)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Tape imbalance Source: https://docs.backquant.com/api/v2/tape/imbalance GET /tape/imbalance Time-bucketed aggressor imbalance (premium or HIRO-style delta). Server-side rollup of the options tape into time buckets. Server-side time-bucketed rollup of the options tape. One row per bucket so long windows stay small. | `weight` | Meaning | | - | - | | `flow` (default) | Multi-venue aggressive premium by call/put × buy/sell, plus passive | | `delta` | Customer BS delta notional (HIRO-style). Deribit-quality prints for IV/units | Each point includes `net` and `cumulative_net` so you can plot a running line without another pass. Coins: BTC, ETH. ## See also > **[Delta flow](/api/v2/gex/delta-flow)** > **[Strike heat](/api/v2/tape/strike-heat)** > **[Whale prints](/api/v2/tape/whale)** > **[Tape overview](/api/v2/tape/overview)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Tape overview Source: https://docs.backquant.com/api/v2/tape/overview Multi-venue crypto options trade tape: REST history, live WebSocket, and rollup analytics across Deribit, Bybit, OKX, Binance, Derive, Thalex, and Delta. The **Options Tape API** is BackQuant's multi-venue, real-time and historical record of every options trade we observe. The same canonical payload powers cursor-paginated REST history and a live WebSocket stream. Use it for scoring methodologies, flow dashboards, and execution analytics. ## What's in it * **Every trade**, normalised: venue / coin / instrument / strike / expiry / direction / amount / price / index price / IV (when present) / premium in USD / block-trade flag / ms timestamp. * **Eight venue IDs** ingested when live (see [Venue coverage](/concepts/venues)). * **5-year retention** in the persistence layer (with noted exceptions such as Derive HYPE live-only). Backfill of history before `2026-05-17` is available on request via our archive partner. * **Sub-second freshness** on the WebSocket for native WS venues. REST `/recent` reflects the latest trade soon after it lands in Postgres. * **Filters** for venue, instrument, option type, strike range, premium minimum, and derived tags (`0dte`, `atm`, `whale`). * **Analytics helpers**: time-bucket imbalance, strike heat, whale prints, and **[flow by strike](/api/v2/tape/flow-by-strike)** (cumulative aggressor map on open contracts). > **[REST tape](/api/v2/tape/tape)** > **[WebSocket](/api/v2/tape/websocket)** > **[Imbalance](/api/v2/tape/imbalance)** > **[Strike heat](/api/v2/tape/strike-heat)** > **[Whale prints](/api/v2/tape/whale)** > **[Flow by strike](/api/v2/tape/flow-by-strike)** > **[Customer delta flow](/api/v2/gex/delta-flow)** > **[Venue coverage](/concepts/venues)** ## Venue coverage | Venue | Source | Latency to tape | Coverage | | - | - | - | - | | **Deribit** | Native public WebSocket | \< 200 ms | Full BTC + ETH chains | | **Bybit** | V5 `publicTrade.{coin}.option` | \< 200 ms | Full BTC + ETH option chains | | **OKX** | V5 `option-trades` (`BTC-USD` / `ETH-USD`) | \< 200 ms | Listed BTC + ETH instruments | | **Binance** | REST poll of hot contracts (\~30 s) | \~ 30 s | Top-volume strikes (\~95% of Binance options premium) | | **Derive** | WS `trades.{coin}` | \< 200 ms when enabled | BTC/ETH/SOL/HYPE; **flag-gated**; HYPE live-only | | **Thalex** | Book channel trades | \< 200 ms | BTC + ETH; always-on | | **Delta India** | `all_trades` WS | \< 200 ms | BTC + ETH minis; always-on | | **Delta Global** | `all_trades` WS | \< 200 ms | BTC + ETH; thinner book | Filter with `?venues=deribit,bybit,okx,binance,derive,thalex,delta_india,delta`. See [Venue coverage](/concepts/venues) for units and settlement behaviour. ## Units on the tape | Field | Meaning | | - | - | | `amount` | **Coin-equivalent** size (normalized at ingest) | | `premium_usd` | Canonical USD premium - use this to rank and filter | | `price` | Venue-quoted price (units vary by venue; prefer `premium_usd`) | Cross-venue comparisons should use `amount` and `premium_usd`. See [Venue coverage](/concepts/venues) for how each venue is normalized. ## Coins * `BTC` and `ETH` are fully covered across the main venues. * `SOL` and `HYPE` appear in schema / Derive paths; treat coverage as partial unless you have verified live prints. ## Tape vs derived GEX | Surface | Live? | History? | Positioning | | - | - | - | - | | **Tape** (this section) | Yes, multi-venue | Yes, cursor-paginated | N/A (raw trades) | | **Flow by strike** | Yes (\~90s cache) | Cumulative on open contracts | Aggressor map for flow rebuilds | | **Live GEX / TRACE / greek profiles** | Yes, \~30s refresh | Rolling Redis window | **flow** default; `?positioning=std` opt-in | | **GEX history / stress-history** | N/A | Stored series | **std** only | | **WS `gex.levels.*`** | Yes (\~30s) | No | **std** snapshot | See [Positioning](/concepts/positioning). ## Quick start ```bash curl - recent trades theme={"theme":{"light":"min-light","dark":"min-dark"}} curl https://api.backquant.com/v2/tape/recent?symbol=BTCUSDT&limit=5 \ -H "X-API-Key: $BQ_API_KEY" ``` ```bash curl - flow by strike theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/tape/flow-by-strike?symbol=BTCUSDT" \ -H "X-API-Key: $BQ_API_KEY" ``` ```python Python - WebSocket theme={"theme":{"light":"min-light","dark":"min-dark"}} import asyncio, json, os, websockets URL = "wss://api.backquant.com/v2/ws/options?api_key=" + os.environ["BQ_API_KEY"] async def main(): async with websockets.connect(URL) as ws: welcome = json.loads(await ws.recv()) print("welcome:", welcome["subscription_tier"]) await ws.send(json.dumps({ "action": "subscribe", "channels": ["tape.BTC.agg"], })) async for raw in ws: msg = json.loads(raw) if msg.get("event") == "trade": t = msg["data"] print(t["venue"], t["instrument"], t["direction"], "$", t["premium_usd"]) asyncio.run(main()) ``` ## See also > **[Authentication](/authentication)** > **[Data freshness](/concepts/data-freshness)** > **[Rate limits](/concepts/rate-limits)** > **[Response envelope](/concepts/response-format)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Tape - recent trades Source: https://docs.backquant.com/api/v2/tape/recent GET /tape/recent Latest N trades (shorthand for /tape?limit=N&order=desc). Convenience endpoint for clients who just want the newest N trades across all venues without... Shorthand for the latest N trades across the requested venues, sorted newest-first. Equivalent to `GET /v2/tape?limit=N&order=desc`, with no cursor state to manage. Use this when you just want a sanity sample or a "what's been happening" dump. Use [`/v2/tape`](/api/v2/tape/tape) when you need filters or pagination. ## See also > **[Full tape with filters](/api/v2/tape/tape)** > **[Live WebSocket](/api/v2/tape/websocket)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Tape - aggregate stats Source: https://docs.backquant.com/api/v2/tape/stats GET /tape/stats Tape statistics for a window. Aggregate stats over a time window - useful for Methix-style scorers that don't need raw trades, just the summary numbers. Aggregate roll-up over a configurable window (up to 7 days). Returns overall + per-venue totals: trade count, total premium in USD, contract volume, call/put split, and buy-side vs sell-side premium. Use this when you don't need raw trades - just the headline numbers for a scoring model, dashboard, or alert. ## Example response shape ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "success": true, "data": { "window_hours": 24, "overall": { "trades": 59145, "premium_usd": 65332823.53, "contracts": 59627.75, "calls": 31431, "puts": 27714, "buy_premium_usd": 31252458.99, "sell_premium_usd": 34080364.54 }, "per_venue": { "deribit": { "trades": 14610, "premium_usd": 51521299.5, ... }, "bybit": { "trades": 44406, "premium_usd": 5759987.3, ... }, "okx": { "trades": 123, "premium_usd": 8051378.5, ... }, "binance": { "trades": 6, "premium_usd": 158.0, ... } } }, "meta": { "version": "2.7", "computed_at": "...", "source": [...] } } ``` ## See also > **[Full tape with filters](/api/v2/tape/tape)** > **[Live WebSocket](/api/v2/tape/websocket)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Tape strike heat Source: https://docs.backquant.com/api/v2/tape/strike-heat GET /tape/strike-heat Aggressor premium heat by strike. Net / absolute premium by strike over a lookback window from the multi-venue options tape. Aggressor premium by strike over a lookback window: buy, sell, net, total, and trade count. Ranked by absolute net premium. Coins: BTC, ETH. Optional venue filter and minimum premium. ## See also > **[Imbalance](/api/v2/tape/imbalance)** > **[Whale prints](/api/v2/tape/whale)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Tape - full filterable history Source: https://docs.backquant.com/api/v2/tape/tape GET /tape Multi-venue options tape - paginated trade history. Returns options trades from Deribit + Bybit + OKX + Binance (BTC + ETH today). Multi-venue options trade tape with cursor pagination. All filters compose with AND. Returns up to 5000 trades per page; pass the response's `next_cursor` back as `?before=` to walk older history. ## How pagination works * **`order=desc`** (default) - newest first. The returned `next_cursor` is the **oldest** trade's timestamp in the page. Pass it as `?before=` on the next call to walk backward. * **`order=asc`** - oldest first. The `next_cursor` is the **newest** trade's timestamp in the page. Pass it as `?after=` to walk forward. * When `has_more` is `false`, you've reached the end. Cursors are emitted with a `Z` (UTC) suffix so they survive being copied straight back into a URL query string. ## Derived tags The `tags` parameter accepts a CSV of: * **`0dte`** - `expiry_date` equals today (UTC). * **`whale`** - trades with `premium_usd` ≥ \$250 000 by default. Override with `premium_min_usd`; the larger of the two wins. * **`atm`** - strike within ±2 % of the trade's `index_price`. Trades without an index price are excluded. ## Coverage See [Tape overview → Venue coverage](/api/v2/tape/overview#venue-coverage) for per-venue freshness and listing notes. ## See also > **[Recent (shorthand)](/api/v2/tape/recent)** > **[Flow by strike](/api/v2/tape/flow-by-strike)** > **[Aggregate stats](/api/v2/tape/stats)** > **[Live WebSocket](/api/v2/tape/websocket)** > **[Venue coverage](/concepts/venues)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Tape - live WebSocket Source: https://docs.backquant.com/api/v2/tape/websocket Channel-multiplexed live stream of options trades across all tape venues, with API-key auth, per-tier connection caps and slow-consumer protection. `wss://api.backquant.com/v2/ws/options` Single connection, multiple subscriptions. The same canonical trade payload from the REST tape, pushed sub-second after the upstream venue emits it. ## Authentication Pass your API key on the WebSocket handshake - either of: * **Query parameter** - `wss://api.backquant.com/v2/ws/options?api_key=bq_live_...` (easiest from browsers). * **`Authorization: Bearer` header** - preferred from server-to-server clients. * **`X-API-Key` header** - accepted as a fallback for clients that can set arbitrary handshake headers. The handshake validates the key, subscription status and tier eligibility. Failures close the connection with a `1008` policy-violation code and a human-readable reason. ## Channel inventory | Channel | Payload | Frequency | | - | - | - | | `tape.{coin}.{venue}` | One trade per frame | Per trade (\~10-100/s in burst) | | `tape.{coin}.agg` | Same trade payload, aggregated across all venues for that coin | Per trade | | `gex.levels.{SYMBOL}` | FLOW-signed GEX levels (all-expiry + 0DTE HVL, walls, spot) | When levels update (\~30 s) | | `chain.{SYMBOL}.{EXPIRY}` | The consolidated options chain for one expiry: snapshot, then the contracts that changed | Per chain refresh (\~30 s) | | `chain.{SYMBOL}.all` | The same for every listed expiry | Per chain refresh (\~30 s) | | `status.heartbeat` | Server liveness + per-client queue stats | Every 15 s | `{coin}` is typically `BTC` or `ETH` (SOL/HYPE appear when tape exists). `{venue}` is one of `deribit`, `bybit`, `okx`, `binance`, `derive`, `thalex`, `delta_india`, `delta`, `bullish`, `paradex` (case-insensitive on subscribe; the server normalises to lowercase). `bullish` and `paradex` are accepted but quiet: their trades are not collected yet. `{SYMBOL}` is `BTCUSDT`, `ETHUSDT`, `SOLUSDT`, or `HYPEUSDT`. See [Venue coverage](/concepts/venues) for what each venue ships. **Levels note (changed in 2.6.0):** `gex.levels.*` pushes **flow**-signed levels - the same model and the same builder as REST [`/v2/gex/levels`](/api/v2/gex/levels), so the two surfaces always agree. Before 2.6.0 this channel pushed a **std** (textbook) snapshot while REST defaulted to flow. When the aggressor tape is unavailable for a symbol the payload falls back to the worker's std snapshot and says so: `positioning: "std"` plus a `fallback_reason`. Always read `positioning` before overlaying the numbers on anything else. See [Positioning](/concepts/positioning). On subscribing to `gex.levels.*` the server sends the **current levels immediately**, flagged `"snapshot": true`, then pushes again only when the levels move. Without that first frame you could not tell an idle book from a subscription that never took. Max **48 channels per connection**. ## Protocol ### Server → client envelope Every server frame is a JSON object with an `event` field: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "event": "welcome", "ts": "...", "subscription_tier": "enterprise", "client_id": "abc...", "max_channels": 48, "available_channels": [...] } { "event": "subscribed", "ts": "...", "channels": [...], "added": [...] } { "event": "unsubscribed", "ts": "...", "channels": [...], "removed": [...] } { "event": "trade", "ts": "...", "channel": "tape.BTC.deribit", "data": { ...trade payload... } } { "event": "levels", "ts": "...", "channel": "gex.levels.BTCUSDT", "data": { "symbol": "BTCUSDT", "spot_price": ..., "all_expiry": { "hvl": ..., "call_resistance": ..., "put_support": ..., "call_wall_2": null, "call_wall_3": null, "put_wall_2": null, "put_wall_3": null }, "odte": { "hvl": ..., "call_resistance": ..., "put_support": ... }, "positioning": "flow", "fallback_reason": null, "ts": "...", "positioning_note": "..." } } { "event": "heartbeat", "ts": "...", "channel": "status.heartbeat", "queue": { "size": 7, "capacity": 1000 }, "channels": ["tape.BTC.agg", ...] } { "event": "pong", "ts": "..." } { "event": "error", "ts": "...", "detail": "...", "invalid": [...], "available_channels": [...] } { "event": "slow_consumer", "ts": "...", "detail": "Queue full for >5s ..." } ``` ### Client → server commands ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "action": "subscribe", "channels": ["tape.BTC.agg", "gex.levels.BTCUSDT"] } { "action": "unsubscribe", "channels": ["tape.BTC.deribit"] } { "action": "ping" } ``` Each command receives a corresponding ack frame. ### Trade payload ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "venue": "deribit", "coin": "BTC", "instrument": "BTC-30JUN26-100000-C", "trade_id": "12345", "direction": "buy", "option_type": "call", "strike": 100000.0, "expiry_date": "2026-06-30", "amount": 1.5, "price": 0.0525, "mark_price": 0.0530, "index_price": 67234.10, "iv": 65.4, "premium_usd": 5293.60, "is_block_trade": false, "ts_ms": 1748480000000 } ``` | Field | Meaning | | - | - | | `venue` | `deribit` \| `bybit` \| `okx` \| `binance` \| `derive` \| `thalex` \| `delta_india` \| `delta` | | `amount` | **Coin-equivalent** size (normalized at ingest) | | `price` | Venue-quoted price (units vary by venue) | | `premium_usd` | Canonical USD premium - **rank and filter on this** | `amount` and `premium_usd` are comparable across venues. For Delta ingest details see [Venue coverage → Delta units](/concepts/venues#delta-exchange-units). ## Connection lifecycle 1. **Connect** with API key → server validates → counts connection against your per-tier cap. 2. **Receive `welcome`** → sanity-check `subscription_tier` and `available_channels`. 3. **Send `subscribe`** → server replies with `subscribed` ack + immediately starts streaming matching trades. 4. **Stream** - trades arrive on their channel; `status.heartbeat` arrives every 15 s if subscribed. 5. **Heartbeat liveness** - the server expects at least one client frame (subscribe / unsubscribe / ping) within 60 s. Otherwise it closes with code `1001`. 6. **Slow consumer** - each client has a 1000-message send buffer. If full for > 5 s the server sends a `slow_consumer` event then closes with code `1013`. Reduce your subscription set or process frames faster. 7. **Disconnect** → connection counter decrements; reconnect any time. ## Consolidated chain `chain.{SYMBOL}.{EXPIRY}` (for example `chain.BTCUSDT.30OCT26`) and `chain.{SYMBOL}.all` stream the options chain merged across every venue that lists each contract, the same chain [`/v2/options/chain`](/api/v2/options/chain) serves. Channel names are case-insensitive; an expiry that is not listed is refused with an `error` carrying `available_expiries`. On subscribe the server sends a snapshot; after each chain refresh (about every 30 seconds) it sends only the contracts whose quotes, implied vol, open interest or 24h volume changed, and the ids that left the chain: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "event": "chain", "channel": "chain.BTCUSDT.30OCT26", "ts": "...", "data": { "snapshot": false, "ts": "...", "spot": 82842.0, "price_unit": "coin", "changed": [ { "id": "30OCT26-90000-C", "...": "..." } ], "removed": [] } } ``` Each contract: | Field | Meaning | | - | - | | `id` | `{EXPIRY}-{STRIKE}-{C or P}`, stable across updates | | `expiry`, `strike`, `type`, `dte` | Contract terms; `dte` in days | | `oi` | Open interest summed across venues, in coins | | `iv` | Open-interest-weighted implied vol across venues, percent | | `mark`, `bid`, `ask` | Deribit's mark and top of book, in `price_unit` (coin for BTC/ETH, USD for SOL/HYPE) | | `best_bid`, `best_bid_venue`, `best_ask`, `best_ask_venue`, `quoting_venues` | The consolidated best bid and ask across venues, with the venue on each side; null outside the liquid band (about 15% from spot, 2+ days out, 2+ quoting venues) | | `delta`, `gamma`, `theta`, `vega`, `vanna`, `charm` | Per contract, same units as [`/v2/options/chain`](/api/v2/options/chain) | | `volume_24h`, `trade_count_24h` | From our cross-venue aggressor tape | Greeks ride along on every changed contract, but a greek moving on its own does not count as a change (they all move with spot every refresh). Quote sizes and per-venue rows are not in the chain we publish, so they are not streamed. Limits: up to 12 chain channels per connection, of which one may be `.all`. Sizes on BTC (October 2026): about 630 KB for the whole-chain snapshot and 400 KB per refresh; about 50 to 65 KB per expiry. ## Per-tier connection caps | Level | Concurrent WS connections | | - | - | | Starter | 1 | | Standard | 2 | | Pro | 5 | | Enterprise | up to 25 (custom) | Exceeding the cap on connect returns close code `1008` with a connection-cap message. WS usage does **not** draw down monthly REST quota - see [Rate limits](/concepts/rate-limits). ## Reconnect template The server has no replay - `$` cursor is used on the underlying Redis stream, so a reconnect resumes with whatever's live, not whatever you missed. For zero-gap consumers, also poll [`/v2/tape?after=`](/api/v2/tape/tape) on reconnect to fetch trades that arrived during the gap. ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} import asyncio, json, os, random, websockets URL = "wss://api.backquant.com/v2/ws/options?api_key=" + os.environ["BQ_API_KEY"] CHANNELS = ["tape.BTC.agg", "status.heartbeat"] async def run(): attempt = 0 while True: try: async with websockets.connect(URL, ping_interval=20) as ws: await ws.recv() # welcome await ws.send(json.dumps({"action": "subscribe", "channels": CHANNELS})) attempt = 0 async for raw in ws: msg = json.loads(raw) if msg.get("event") == "trade": handle(msg["data"]) except (websockets.ConnectionClosed, ConnectionError) as e: attempt += 1 backoff = min(60, 2 ** attempt) * (1 + random.uniform(-0.2, 0.2)) print(f"reconnect in {backoff:.1f}s ({e})") await asyncio.sleep(backoff) def handle(trade): print(trade["venue"], trade["instrument"], trade["direction"], f"${trade['premium_usd']:,.0f}") asyncio.run(run()) ``` Full working client: [`scripts/v2_ws_demo/ws_tape_demo.py`](https://github.com/backquant/backquant-terminal/blob/main/scripts/v2_ws_demo/ws_tape_demo.py). ## See also > **[REST tape with filters](/api/v2/tape/tape)** > **[Tape overview](/api/v2/tape/overview)** > **[GEX levels (REST, flow default)](/api/v2/gex/levels)** > **[Authentication](/authentication)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Whale prints Source: https://docs.backquant.com/api/v2/tape/whale GET /tape/whale Largest options prints in the window. Top trades by premium_usd across venues. Largest options prints by `premium_usd` in the window. Default floor \$100k. Returns full trade fields (venue, instrument, direction, strike, expiry, IV). Coins: BTC, ETH. ## See also > **[Full tape](/api/v2/tape/tape)** > **[Strike heat](/api/v2/tape/strike-heat)** Need higher limits or redistribution rights?Custom and Enterprise plans are available. [Contact us](mailto:dev@backquant.com) [View plans](https://backquant.com/api-access) # Authentication Source: https://docs.backquant.com/authentication Get an API key and authenticate your requests Every BackQuant API request requires an API key passed in the `X-API-Key` header. Keys are tied to your subscription tier and control your [rate limits](/concepts/rate-limits). ## Get an API key > **[Get your API key](https://backquant.com/api-access)** > > Sign up or sign in at **backquant.com/api-access** to create and manage > your keys. ## Pass your API key Include your key in the `X-API-Key` header on every request. ```bash curl theme={"theme":{"light":"min-light","dark":"min-dark"}} curl https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT \ -H "X-API-Key: bq_live_your_api_key_here" ``` ```python Python theme={"theme":{"light":"min-light","dark":"min-dark"}} import requests resp = requests.get( "https://api.backquant.com/v2/gex/levels", params={"symbol": "BTCUSDT"}, headers={"X-API-Key": "bq_live_your_api_key_here"}, ) data = resp.json() ``` ```typescript TypeScript theme={"theme":{"light":"min-light","dark":"min-dark"}} const resp = await fetch( "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT", { headers: { "X-API-Key": "bq_live_your_api_key_here" } }, ); const data = await resp.json(); ``` API keys follow this format: ```text theme={"theme":{"light":"min-light","dark":"min-dark"}} bq_live_<32-character-token> ``` > [!WARNING] > > Keep your API key secret. Do not commit it to source control or expose > it in client-side code. If your key is compromised, rotate it > immediately at [backquant.com/api-access](https://backquant.com/api-access). ## Endpoints that don't require auth A small set of endpoints are intentionally public so you can introspect the API before signing up: | Endpoint | Purpose | | - | - | | `/v2/openapi.json` | Full machine-readable OpenAPI 3 spec | | `/v2/docs` | Swagger UI - interactive playground | | `/v2/redoc` | ReDoc - alternative reference renderer | | `/v2/health` | Liveness/readiness probe for monitors | Every other route requires `X-API-Key`. ## Authentication errors A missing or invalid API key returns a `401` response with the `UNAUTHORIZED` error code: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "success": false, "error": { "code": "UNAUTHORIZED", "message": "Missing X-API-Key header" }, "meta": { "version": "2.7", "timestamp": "2026-04-30T12:00:00Z", "request_id": "req_..." } } ``` Common causes: * The `X-API-Key` header is missing from the request. * The key value has a leading/trailing space (common when copy-pasting). * The key was revoked at [backquant.com/api-access](https://backquant.com/api-access). * Your subscription lapsed (`FORBIDDEN` rather than `UNAUTHORIZED` in this case). The `request_id` field in the `meta` block is the value of the `X-Request-ID` header - include it when reporting auth issues so we can trace the specific call in our logs. ## Next steps > **[Quick start](/quickstart)** > > Make your first API call. > **[Code examples](/examples/python)** > > Python, TypeScript, and curl recipes. > **[Rate limits](/concepts/rate-limits)** > > Per-tier limits and backoff strategy. > **[Errors](/concepts/errors)** > > Every error code with handling examples. # Data freshness Source: https://docs.backquant.com/concepts/data-freshness How current the data is - and how to tell from the response * Every v2 response carries `computed_at` and `freshness_seconds` in `meta`, and GEX and options data refresh every 30 seconds. * On a healthy 30-second endpoint, `freshness_seconds` sits between 0 and 60; values consistently above 90 mean you should check `/v2/status`. * A large `freshness_seconds` on daily or historical series such as `/v2/options/vrp` is normal. * [`/v2/health`](/api/v2/health) always returns HTTP 200, so read the `status` in the body. Every v2 response carries `computed_at` and `freshness_seconds` in the `meta` block. This page explains what they mean and the cadence behind them. ## Refresh cadences | Category | Refresh | What's in it | | - | - | - | | **GEX** (levels, strike profile, expiry profile, max-pain, expiry summary, time heatmap, history) | **30 seconds** | The ten-venue options chain (see [Venues](/concepts/venues)), computed by our worker; `flow` positions re-read from the tape behind a 15-second cache | | **Strike ladder** (`/v2/gex/ladder`) | **30 seconds** (10-second response cache) | Same chain and `flow` positions as GEX | | **Options** (chain, IV surface / term / skew / smile / curves, greeks, probability density, OI by expiry, expected move) | **30 seconds** | Same compute pipeline as GEX - they share the snapshot | | **Premium tide** | **30 seconds** | 1-minute buckets of Deribit option premium and volume; `0dte` is today's session | | **OI history** | **30 seconds** | Append-only history of open interest | | **Options range** (`/v2/options/range`) | **1 minute** (implied vol), **10 minutes** (calibration) | The band widths follow DVOL each minute; the multipliers are refitted every 10 minutes on the last 30 days of hourly moves | | **COT** (`/v2/cot/*`) | **Weekly** | CFTC positions as of Tuesday, published Friday 15:30 ET; we load each release within hours | | **Insider trades** (`/v2/filings/insider`) | **Minutes** in US hours, slower overnight | SEC Form 4 filings, polled from EDGAR | | **13F holders** (`/v2/filings/holders`) | **Quarterly** | Filings are due 45 days after quarter end; `period_end` names the quarter | | **Liquidation heatmap** | **5 minutes** | Lower cadence; perp liquidations don't move that fast | | **IV-RV / VRP history** | **Daily** (after UTC midnight) | Daily snapshots, not intraday | | **Stress history** (`/v2/gex/stress-history`) | **Continuous** (Postgres-backed) | Persistent log of HVL / walls over time | | **Chain traded activity** (`volume`, `notional_volume`, `trade_count`) | **60 seconds**, over a rolling **24-hour** window | Measured from our cross-venue aggressor tape, not from a venue's own summary. See [Venues](/concepts/venues) for which symbols are blended | > [!NOTE] > > The activity fields refresh on their own 60-second cadence, independently of > the 30-second chain snapshot they ride in. They are also the only fields on > the chain that describe a **window** (the last 24 hours) rather than an > instant, so they move even when nothing about the contract has changed. > > If the measurement goes unavailable they become `null` rather than freezing > at their last value. A `null` there means not measured; a `0` means measured > and nothing traded. ## Reading freshness from a response Every successful response includes: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "meta": { "computed_at": "2026-04-30T11:59:48.000Z", "freshness_seconds": 12.0, ... } } ``` * `computed_at` - wall-clock when the underlying cache was last written * `freshness_seconds` - `now - computed_at`, in seconds For a healthy 30s endpoint, `freshness_seconds` should be in the range 0-60. Values consistently > 90 mean the worker is having trouble refreshing - check `/v2/status` for confirmation. ## Operational status [`/v2/status`](/api/v2/discovery/status) gives you a per-symbol per-category health grid: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "service": "BackQuant API v2", "version": "2.7", "current_time": "2026-04-30T12:00:00Z", "overall_status": "healthy", "symbols": { "BTCUSDT": { "gex": { "status": "healthy", "freshness_seconds": 12 }, "options": { "status": "healthy", "freshness_seconds": 18 }, "chain": { "status": "healthy", "freshness_seconds": 22 }, "liquidation": { "status": "healthy", "freshness_seconds": 280 } }, ... }, "thresholds": { "gex": { "healthy_max_seconds": 60, "degraded_max_seconds": 300 }, ... } } ``` Each cell is one of: * `healthy` - within the expected refresh window * `degraded` - behind schedule but still usable * `unhealthy` - dangerously stale; worker is having trouble * `unavailable` - no cache at all (cold start or extended outage) The `overall_status` is the worst case across the grid - pessimistic on purpose so monitoring alerts trip on the first real problem. ## When you don't care about freshness For one-off historical queries (`/v2/gex/stress-history`, `/v2/options/iv/iv-rv`, `/v2/options/vrp`), the data is daily/historical and `computed_at` reflects when the historical row was written. Don't panic if `freshness_seconds` is huge - that's normal for time-series. ## Public liveness probe [`/v2/health`](/api/v2/health) is a no-auth liveness probe for external monitors (UptimeRobot, Datadog synthetic, etc.). Returns `status: ok | degraded | unhealthy` based on Redis + Postgres reachability. Always HTTP 200 - the body has the truth, not the status code, so the envelope contract stays clean. ## Related concepts > **[Response format](/concepts/response-format)** > > Every meta field documented in detail. > **[Rate limits](/concepts/rate-limits)** > > Cadence considerations for polling - don't poll faster than the > refresh. # Errors Source: https://docs.backquant.com/concepts/errors Every error code, when it fires, and how to handle it * Failed requests return a non-2xx status and an `error` envelope, so check `success` before reading `data` and branch on `error.code`. * `UNAUTHORIZED` (401) means a missing or bad key, while `FORBIDDEN` (403) means your plan does not permit the resource. * On `RATE_LIMIT_EXCEEDED`, check `X-Quota-Remaining`: if it is `0`, your monthly quota is exhausted, so stop retrying. * Don't retry `NOT_FOUND`, back off exponentially on `UPSTREAM_ERROR`, and include `meta.request_id` when reporting `INTERNAL_ERROR`. When a request fails, the API returns a non-2xx HTTP status and an error envelope with a machine-readable `code` field, a human-readable `message`, and (sometimes) a structured `details` payload. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Invalid request parameters", "details": { "errors": [ { "type": "literal_error", "loc": ["query", "symbol"], "msg": "Input should be 'BTCUSDT', 'ETHUSDT', 'SOLUSDT' or 'HYPEUSDT'" } ] } }, "meta": { "version": "2.7", "timestamp": "2026-04-30T12:00:00Z", "request_id": "req_a1b2c3..." } } ``` ## All error codes | Code | HTTP status | When it fires | | - | - | - | | `UNAUTHORIZED` | 401 | Missing, malformed, revoked, or expired `X-API-Key`. **Not** “plan too low.” | | `FORBIDDEN` | 403 | Key is valid but the **plan does not permit** this resource (v2.5.0+). Do not treat as a bad key. | | `NOT_FOUND` | 404 | Symbol, expiry, or other resource not present in cache or DB. | | `VALIDATION_ERROR` | 400 / 422 | Bad query parameter - out of range, wrong type, or unknown enum value. | | `RATE_LIMIT_EXCEEDED` | 429 | Per-minute burst **or** monthly quota exceeded. Retry after `Retry-After` / wait for the window. | | `UPSTREAM_ERROR` | 502 / 503 / 504 | Cache (Redis) or database (Postgres) temporarily unreachable. Retry with backoff. | | `INTERNAL_ERROR` | 500 | Unexpected server-side failure. Include `meta.request_id` when reporting. | The full list is also available programmatically at [`/v2/meta`](/api/v2/discovery/meta) - useful if you want to keep your client's error map in sync with the API. ## Handling errors in code Always check `success` before reading `data`. Use `error.code` to branch your error handling. ```python Python theme={"theme":{"light":"min-light","dark":"min-dark"}} import requests import time def get_gex_levels(api_key, symbol="BTCUSDT", max_retries=3): for attempt in range(max_retries): resp = requests.get( "https://api.backquant.com/v2/gex/levels", params={"symbol": symbol}, headers={"X-API-Key": api_key}, ) body = resp.json() if body["success"]: return body["data"] code = body["error"]["code"] if code == "UNAUTHORIZED": raise ValueError( "Invalid API key. Get/rotate at https://backquant.com/api-access" ) if code == "RATE_LIMIT_EXCEEDED": retry_after = int(resp.headers.get("Retry-After", "60")) # Two different 429s. A burst clears within the minute; a monthly # quota 429 sends the seconds until the month rolls over, which # you must never sleep off - surface it instead. if resp.headers.get("X-Quota-Remaining") == "0": raise RuntimeError(body["error"]["message"]) print(f"Rate limited; sleeping {retry_after}s") time.sleep(retry_after) continue if code == "NOT_FOUND": # 404 = no cache for this symbol/expiry. Don't retry - handle as no-data. return None if code == "UPSTREAM_ERROR": # Redis/DB blip - exponential backoff sleep = 2 ** attempt print(f"Upstream error; sleeping {sleep}s") time.sleep(sleep) continue if code == "VALIDATION_ERROR": raise ValueError(f"Invalid parameters: {body['error']}") raise RuntimeError( f"Unexpected error: {body['error']} " f"(request_id={body['meta'].get('request_id')})" ) raise RuntimeError("Max retries exceeded") ``` ```typescript TypeScript theme={"theme":{"light":"min-light","dark":"min-dark"}} async function getGexLevels( apiKey: string, symbol = "BTCUSDT", maxRetries = 3, ): Promise { for (let attempt = 0; attempt < maxRetries; attempt++) { const resp = await fetch( `https://api.backquant.com/v2/gex/levels?symbol=${symbol}`, { headers: { "X-API-Key": apiKey } }, ); const body = await resp.json(); if (body.success) return body.data; const code = body.error.code; if (code === "UNAUTHORIZED") { throw new Error( "Invalid API key. Get/rotate at https://backquant.com/api-access", ); } if (code === "RATE_LIMIT_EXCEEDED") { const retryAfter = parseInt(resp.headers.get("Retry-After") ?? "60", 10); // Two different 429s. A burst clears within the minute; a monthly quota // 429 sends the seconds until the month rolls over, which you must // never sleep off - surface it instead. if (resp.headers.get("X-Quota-Remaining") === "0") { throw new Error(body.error.message); } console.log(`Rate limited; sleeping ${retryAfter}s`); await new Promise((r) => setTimeout(r, retryAfter * 1000)); continue; } if (code === "NOT_FOUND") { return null; // no data, don't retry } if (code === "UPSTREAM_ERROR") { const sleep = 2 ** attempt; console.log(`Upstream error; sleeping ${sleep}s`); await new Promise((r) => setTimeout(r, sleep * 1000)); continue; } if (code === "VALIDATION_ERROR") { throw new Error(`Invalid parameters: ${JSON.stringify(body.error)}`); } throw new Error( `Unexpected error: ${body.error.message} ` + `(request_id=${body.meta?.request_id})`, ); } throw new Error("Max retries exceeded"); } ``` ## Guidance per code **`UNAUTHORIZED`** - Check the `X-API-Key` header is present, the value has no leading/trailing space, and the key hasn't been revoked at [backquant.com/api-access](https://backquant.com/api-access). **`FORBIDDEN`** - Your subscription doesn't allow this resource. Usually a sign you've downgraded a paid tier or your subscription lapsed. **`NOT_FOUND`** - Don't retry. Either the symbol/expiry doesn't have cache yet (cold start, or unsupported symbol), or the path is wrong. **`VALIDATION_ERROR`** - Read `error.details.errors` for the failing parameter list. Common causes: passing `BTC` instead of `BTCUSDT`, unsupported `?greek=` value, out-of-range numeric param. **`RATE_LIMIT_EXCEEDED`** - Covers two different situations, and they need different handling. Tell them apart with `X-Quota-Remaining`: * **Per-minute burst** (`X-Quota-Remaining` above `0`). `Retry-After` is the seconds left in the current minute window. Sleep it off and retry. * **Monthly quota exhausted** (`X-Quota-Remaining` is `0`). `Retry-After` is the seconds until the quota resets at 00:00 UTC on the 1st, which can be weeks. **Do not sleep on this one** - stop retrying and surface `error.message`, which names your cap and where to upgrade. To hit either limit less often: [`/v2/options/chain`](/api/v2/options/chain) is the most expensive single endpoint - if you're polling it aggressively, batch with [multi-symbol bundles](/concepts/multi-symbol) or pull only the contracts you need with `?max_contracts`. **`UPSTREAM_ERROR`** - Transient. Exponential backoff (2s, 4s, 8s) usually clears it within \~30s. If it persists past a minute, check [`/v2/health`](/api/v2/health). **`INTERNAL_ERROR`** - Always include `meta.request_id` when reporting to [dev@backquant.com](mailto:dev@backquant.com). We trace by it. ## Always-present headers Even on errors, the response carries: * `X-RateLimit-Limit` - your tier's per-minute cap * `X-RateLimit-Remaining` - calls left in the current window * `X-RateLimit-Reset` - Unix timestamp when the window resets * `Retry-After` - seconds until safe to retry (**on 429 only**). Seconds left in the minute window for a burst 429; seconds until the month rolls over for a quota 429 - see the guidance above before sleeping on it * `X-Request-ID` - the same UUID as `meta.request_id` * `X-RateLimit-Fallback` - `true` only during degraded mode, otherwise absent (see [Rate limits](/concepts/rate-limits)) ## See also > **[Rate limits](/concepts/rate-limits)** > > Per-tier limits and how to think about backoff. > **[Authentication](/authentication)** > > Avoiding `UNAUTHORIZED` errors. > **[Data freshness](/concepts/data-freshness)** > > What `NOT_FOUND` actually means per endpoint. # FAQ Source: https://docs.backquant.com/concepts/faq Common gotchas, integration questions, and operational tips ## Data and freshness ### Why is `freshness_seconds` 30+ in my response? That's normal. The compute pipeline refreshes the cache on a 30-second cadence for GEX / IV / chain data, and 5 minutes for liquidation. A `freshness_seconds` value between 0 and 60 on a 30s-cadence endpoint is healthy. See [Data freshness](/concepts/data-freshness) for the full cadence table. ### Polling once a second is hammering the rate limit. What's the right frequency? Polling faster than **30 seconds** is wasted work - you'll see the same `computed_at` 30 times in a row. For dashboard panels, 30s is the sweet spot. For OPEX-day or volatile windows where you want every refresh, you could go to 15s, but anything below that returns duplicate data. See [Rate limits](/concepts/rate-limits) for tier budgeting. ### My `meta.freshness_seconds` is showing huge values (hours / days). Bug? Not a bug. The historical endpoints (`/v2/gex/stress-history`, `/v2/options/iv/iv-rv`, `/v2/options/vrp`) return *historical* time series - the `computed_at` reflects when each historical row was written, which can be days or weeks ago. For these endpoints, freshness isn't a staleness signal; it's a retention signal. ## Errors and gotchas ### I'm getting 404 on a symbol that should have data. What's wrong? A 404 on an authed v2 endpoint means "no cache available for this symbol/expiry/window right now," not "endpoint doesn't exist." Possible causes: 1. The symbol is supported but cold - call [`/v2/symbols`](/api/v2/discovery/symbols) and check `categories_live`. If your category isn't in the list, the worker hasn't populated it yet. 2. The symbol is unsupported. The supported set is `BTCUSDT`, `ETHUSDT`, `SOLUSDT`, `HYPEUSDT`. Anything else returns a `VALIDATION_ERROR` (422), not a 404. 3. You asked for a specific expiry token that doesn't exist. Call [`/v2/expiries?symbol=...`](/api/v2/discovery/expiries) for the active token list. ### Why does my client see `success: false` even when the data looks fine? You're probably reading `data` before checking `success`. The envelope **always** has `success` first - branch on it before touching `data` or `error`. See the [response format](/concepts/response-format). ### I just hit `RATE_LIMIT_EXCEEDED`. What now? Check the `Retry-After` response header (or the `error.details.retry_after_seconds` field in the body). Wait that many seconds, then retry. If it happens repeatedly, your polling cadence is too aggressive - see the rate-limits page or consider upgrading your tier at [backquant.com/api-access](https://backquant.com/api-access). ### What does `UPSTREAM_ERROR` mean? A transient failure reaching our cache or database - usually clears within 30 seconds. Use exponential backoff (2s, 4s, 8s) and your client should recover automatically. If it persists past a minute, hit [`/v2/health`](/api/v2/health) for the current subsystem status. ## Positioning (flow vs std) ### Why did my GEX levels change without a big move in spot? Live GEX defaults to **`positioning=flow`**, which measures dealer size from observed trading rather than assuming it from open interest. Walls and HVL can shift when positioning rotates even if spot is quiet. Use `?positioning=std` for the older call-positive / put-negative model. See [Positioning](/concepts/positioning). ### SOL or HYPE returned `positioning: "std"` even though I asked for flow? Those coins do not have a full multi-venue aggressor tape yet. The API falls back to std and sets `meta.extra.positioning_fallback_reason` (for example `no_aggressor_tape_for_symbol`). BTC and ETH also fall back if the tape is empty. ### Is `/v2/gex/history` on the same model as live levels? No. History and stress-history are stored **std** series. Live levels, strike profile, TRACE, and greek profiles default to **flow**. Do not mix them in one chart without noting the model. ### Dealer-flow vs delta-flow: which is HIRO-style customer flow? * **`/v2/gex/delta-flow`** and tape **`weight=delta`**: customer signed delta notional from the options tape. * **`/v2/gex/dealer-flow`** / **`gamma-flow`**: estimated **dealer** hedge from successive chain snapshots (spot moves × prior GEX/charm). Positive dealer-flow ≈ dealers buying the underlying to re-hedge. Positive delta-flow ≈ net customer buy-side delta. ### WebSocket `gex.levels.BTCUSDT` vs REST `/v2/gex/levels`? REST rebuilds with default **flow** (unless you pass `positioning=std`). The WebSocket levels channel pushes a **std** levels snapshot when it updates (\~30s). Use REST when you need flow; use WS for a light std pulse. ## Pagination, filters, and projection ### How does cursor pagination work? History endpoints (`/v2/gex/history`, `/v2/options/oi/history`) accept `?limit` (page size) and `?before=` (cursor). Each response includes `next_cursor` - pass it as the next call's `?before` to walk backwards. Iterate until `timestamps` comes back empty. ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} cursor = None while True: params = {"symbol": "BTCUSDT", "limit": 200} if cursor: params["before"] = cursor r = get("/v2/gex/history", params).json() if not r["data"]["timestamps"]: break process(r["data"]) cursor = r["data"]["next_cursor"] ``` ### What's the difference between `?fields=` and `?include=` on `/v2/options/chain`? * **`?fields=`** is **top-level** projection. It drops whole sections of the response: `?fields=contracts` returns just contracts and skips `aggregates` and `available_expiries`. * **`?include=`** is **per-row** projection on contracts. It keeps only the named sub-sections of each contract dict: `?include=oi,iv` returns `strike, expiry, dte, type, oi, iv` per row and drops the greeks, bid/ask, etc. You can use both in the same call. ### How do I filter by strike around spot without knowing spot price? Use **moneyness bounds** (`?moneyness_min=0.9&moneyness_max=1.1`) - the server resolves them against current spot for you. Or `?spot_window_pct=5` for a symmetric ±5% window. Both work on every endpoint that accepts strike-axis filters. ## OPEX, expiries, dates ### Why are expiry tokens like `28MAR25` and not ISO dates? That's the convention the underlying exchange (Deribit) uses, so it's what the worker stores natively. Every endpoint that returns an expiry token also includes the parsed `expiry_date` (ISO YYYY-MM-DD) when it parses cleanly, so you can render either format in your UI. ### How do I find the next OPEX day? Hit [`/v2/options/opex?symbol=BTCUSDT`](/api/v2/options/opex) and read `data.next_anchor`. It points at the closest monthly or quarterly expiration with its expiry token, ISO date, and DTE. ### Are weekly / monthly / quarterly classifications consistent across symbols? Yes - the classification is calendar-based (last Friday of month → monthly; last Friday of Mar/Jun/Sep/Dec → quarterly), not symbol-specific. So `28MAR25` is `quarterly` for BTC, ETH, SOL, and HYPE. ## Caching and SDKs ### Can I cache responses on my end? Yes, and you should. Server-side data refreshes every 30s anyway - caching responses for 15-30s on your end is standard practice and saves you rate-limit budget. Use the `meta.computed_at` field as your cache key so you can detect refreshes. ### Do you ship official SDKs? Not yet. The OpenAPI 3 spec at [`/v2/openapi.json`](/api/v2/discovery/endpoints) is comprehensive enough that codegen tools produce a usable client in any language. See [curl recipes](/examples/curl) for the codegen one-liners. ### How do I report a bug? Email **[dev@backquant.com](mailto:dev@backquant.com)** with: 1. The endpoint URL you hit 2. The `meta.request_id` from the response (lets us trace the exact call) 3. What you expected vs what you saw We trace by request ID so the more specific the better. ## Subscription and access ### How do I get an API key? [backquant.com/api-access](https://backquant.com/api-access) - sign up or sign in, create a key, copy. Same key works across all v2 endpoints; tier determines your rate limit. ### How do I upgrade my tier? Same place. Upgrades take effect immediately; the new rate limit applies on your next API call. ### Is my data redistributable? No - personal-use plans don't permit redistribution. If you're building a product on top of the API that resells the data, contact **[dev@backquant.com](mailto:dev@backquant.com)** for an enterprise discussion. See [Terms of Service](https://www.backquant.com/terms). # What is GEX? Source: https://docs.backquant.com/concepts/gex Gamma exposure, dealer positioning, walls and support levels - explained * GEX measures dealer hedging pressure: positive net gamma means dealers stabilize price, negative net gamma means they amplify moves. * Live GEX defaults to flow positioning; pass `?positioning=std` for the textbook model (call OI positive, put OI negative). * [`/v2/gex/levels`](/api/v2/gex/levels) returns all-expiry and 0DTE levels, including HVL, the gamma-flip regime line. * Pass `?exchanges=` to recompute levels from specific venues, since aggregating across venues can hide venue-specific positioning. **Gamma exposure (GEX)** measures how much underlying delta dealers are forced to hedge for every \$1 the underlying price moves. It's the single most useful concept for understanding why crypto sometimes pins to a strike, sometimes accelerates through it, and where the next reversal is likely to happen. ## The intuition in one paragraph When you buy an option, somebody else (a dealer / market maker) is the counterparty. To stay delta-neutral, the dealer hedges in the underlying. **Gamma** is the rate of change of delta with respect to the underlying price - so as price moves, the dealer's delta hedge has to be adjusted. **Net dealer gamma** at a given strike tells you how much hedging *pressure* exists at that level. Positive net gamma = dealers buy on dips and sell on rallies (stabilizing). Negative net gamma = dealers sell on dips and buy on rallies (amplifying). ## What we compute for you For every active option contract across the multi-venue chain (Deribit, Bybit, OKX, Binance, plus Derive / Thalex / Delta when live - see [Venue coverage](/concepts/venues)), we compute the dollar gamma exposure (USD impact per 1% spot move) and aggregate it per strike, per expiry, and per venue. You get the aggregated values directly; no reconstruction needed on your side. **Signing the book (positioning).** Live GEX defaults to **flow**: dealer size per contract comes from BackQuant's measured model from the options tape. Pass `?positioning=std` for the textbook model (call OI positive, put OI negative). Full rules, fallbacks, and history caveats: [Dealer positioning: flow vs std](/concepts/positioning). The [`/v2/gex/strike-profile`](/api/v2/gex/strike-profile) endpoint is the strike-level aggregation; [`/v2/gex/levels`](/api/v2/gex/levels) distils that further into the trader-actionable levels below. ## Key levels you'll see in the API The [`/v2/gex/levels`](/api/v2/gex/levels) endpoint gives you the trader-actionable levels distilled from the full strike profile: | Field | What it means | How traders use it | | - | - | - | | **HVL** (Hedging Volume Level) | The strike where cumulative net gamma crosses zero closest to spot | The "gamma flip" - above HVL dealers stabilize, below HVL they amplify. Treat as a regime line. | | **call\_resistance** | The strike with the largest *positive* gamma above spot | The wall dealers will defend by selling into rallies. Common reversal zone. | | **call\_wall\_2 / call\_wall\_3** | Second / third strongest call walls above spot | Where price *might* pin if it breaks call\_resistance. | | **put\_support** | The strike with the largest positive gamma below spot | Dealers buy on dips into this level. Common bounce zone. | | **put\_wall\_2 / put\_wall\_3** | Second / third strongest put supports below spot | Successive support levels if put\_support breaks. | | **odte\_hvl / odte\_call\_resistance / odte\_put\_support** | Same three levels but computed from 0DTE-only options | Intraday-magnet levels; matters most in the last few hours of the trading day. | ## How to read them in practice **Spot above HVL with strong call\_resistance overhead** - expect mean-reverting price action, dampened vol, dealers selling into rallies toward the call wall. Good environment for premium selling. **Spot below HVL with strong put\_support below** - expect amplified moves on the way down, but a snap-back if price reaches put\_support. Dealers will start buying. **Spot crossing HVL** - regime change. The stabilizing/destabilizing balance flips. Watch for vol expansion right at the cross. **0DTE levels different from all-expiry levels** - common in the last hour before close. The 0DTE levels usually win for the close itself; the all-expiry levels matter more for the next session. ## What our `/v2/gex/levels` endpoint returns Out of the box, you get all-expiry walls + 0DTE walls: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "all_expiry": { "hvl": 67000, "call_resistance": 70000, "call_wall_2": 72500, "call_wall_3": 75000, "put_support": 64000, "put_wall_2": 60000, "put_wall_3": 58000 }, "odte": { "hvl": 67500, "call_resistance": 68000, "put_support": 66500 } } ``` Add `?include=ranked,max_pain,expected_move,zones` to also get: * `ranked` - top 10 strikes by absolute GEX (where the action concentrates) * `max_pain` - see the [max pain page](/concepts/max-pain) * `expected_move` - 1σ / 2σ implied range from ATM straddle * `gamma_flip_zones` - multiple-zero-crossing detail when the chain has more than one regime line ## Filtering by venue Aggregating across venues smooths noise but can also hide venue-specific positioning (e.g. Deribit dealers vs retail-heavy Binance options). Use `?exchanges=deribit` (or any subset) to recompute levels from a specific venue's chain only. ## Filtering by strike window Far-OTM strikes can have huge GEX from a small cluster of contracts and distort the picture. Use `?moneyness_min=0.9&moneyness_max=1.1` to clamp the analysis to ATM ±10%, or `?spot_window_pct=5` for a symmetric ±5% window. Both work on [`/v2/gex/strike-profile`](/api/v2/gex/strike-profile) and [`/v2/gex/strike-expiry-heatmap`](/api/v2/gex/strike-expiry-heatmap). ## Related concepts > **[Flow vs std positioning](/concepts/positioning)** > > How live GEX and TRACE sign dealer size, and when history stays std. > **[Max pain](/concepts/max-pain)** > > The strike where option writers profit most. Different metric from > GEX walls but often near them. > **[Greeks beyond delta](/concepts/greeks)** > > Vanna, charm, vega: what they measure and how to use them alongside > GEX. > **[Dealer flow](/api/v2/gex/dealer-flow)** > > Estimated hedge pressure across chain snapshots (gamma, charm, total). > **[OPEX calendar](/concepts/opex)** > > Why monthly/quarterly expirations dominate the GEX picture for a > few days each month. > **[Multi-symbol bundle](/concepts/multi-symbol)** > > Get GEX levels for BTC + ETH + SOL + HYPE in a single call. # Glossary Source: https://docs.backquant.com/concepts/glossary Quick-reference for every term used across the docs Alphabetical. If you're new to options analytics, start with [What is GEX?](/concepts/gex) and the [IV suite](/concepts/iv-suite) overviews instead - those build the mental model. Use this page as a lookup once concepts land. ## A **Anchor expiry** - A monthly or quarterly options expiration. Heavier OI than weeklies; institutional positioning concentrates here. See [OPEX calendar](/concepts/opex). **ATM IV** - At-the-money implied volatility. The IV of options whose strike equals current spot. Used as the "headline" vol for an expiry. ## B **Backwardation (vol)** - Term structure where front-month IV is higher than back-month IV. Common during stress. See [IV suite](/concepts/iv-suite). **Breeden-Litzenberger** - The technique that extracts a probability distribution from option prices. The basis of [`/v2/options/probability/density`](/api/v2/options/probability/density). **Butterfly** - `(25Δ put IV + 25Δ call IV) / 2 − ATM IV`. Measures the curvature of the smile / how richly priced the wings are. ## C **Call resistance** - The strike with the largest positive net gamma above spot. Often a reversal level - dealers sell into rallies toward this strike. See [What is GEX?](/concepts/gex). **Call wall** - Synonym for call resistance. `call_wall_2` and `call_wall_3` are the second and third strongest above spot. **Cardle (candle)** - OHLCV bar. The terminal renders 30m candles by default; the API ships them alongside GEX levels when `?include=candles` is set. **Charm** - `∂Delta/∂Time`. Drives end-of-day and pre-OPEX delta hedging flows. See [Greeks beyond delta](/concepts/greeks). **`computed_at`** - ISO timestamp in `meta` showing when the underlying cache was last written by our worker. Pair with `freshness_seconds`. **Confidence band** - A price range containing X% of implied probability mass for an expiry. Returned by [`/v2/options/probability/density?confidence_band=0.68|0.95`](/api/v2/options/probability/density). **Contango (vol)** - Term structure where back-month IV is higher than front-month IV. The "normal" regime in low-vol periods. ## D **Dealer flow** - Estimated dealer hedge pressure across successive chain snapshots (gamma, charm, optional vanna components + cumulative). See [`/v2/gex/dealer-flow`](/api/v2/gex/dealer-flow). Distinct from customer **delta flow**. **Delta flow** - Customer signed Black-Scholes delta notional from the options tape (HIRO-style customer line). See [`/v2/gex/delta-flow`](/api/v2/gex/delta-flow). **DEX** - Dollar delta exposure. Net delta dealers carry per strike, in USD terms. See [Greeks beyond delta](/concepts/greeks). **DTE** - Days to expiration. An integer count. ## E **Expected move** - The 1σ implied price range for the next 24 hours, derived from ATM straddle pricing. Returned by [`/v2/options/expected-move`](/api/v2/options/expected-move) or as part of `/v2/gex/levels?include=expected_move`. **Expiry token** - Deribit-style date encoding, e.g. `28MAR25` = March 28 2025. Every endpoint that returns one also returns `expiry_date` (ISO) when parseable. ## F **Flow positioning** - Dealer size signed from multi-venue aggressor trading activity rather than an OI assumption. Default on live GEX and TRACE. See [Positioning](/concepts/positioning). **`freshness_seconds`** - `now − computed_at`. Useful for staleness alarms. See [Data freshness](/concepts/data-freshness). ## G **Gamma** - Rate of change of delta with respect to spot. The greek that drives dealer hedging behaviour around walls. **Gamma flip** - The strike where cumulative net dealer gamma crosses zero, closest to spot. Above the flip, dealers stabilise; below, they amplify. Reported as `hvl` in the levels response. **GEX** - Gamma exposure. The flagship analytic. See [What is GEX?](/concepts/gex). ## H **HVL** - Hedging Volume Level. Same as the gamma flip - the regime line for dealer behaviour. **HYPEUSDT** - Hyperliquid's HYPE token. One of the four supported symbols (`BTCUSDT`, `ETHUSDT`, `SOLUSDT`, `HYPEUSDT`). ## I **IV** - Implied volatility. The volatility level the market is pricing into options. **IV rank** - Where current IV sits between its 52-week low and high. Not currently exposed in v2 (planned). **IV-RV spread** - `IV − RV`. Positive = options expensive vs realised move; negative = cheap. Returned by [`/v2/options/iv/iv-rv`](/api/v2/options/iv/iv-rv). ## M **Max pain** - The strike where option *writers* profit most at expiry. See [Max pain](/concepts/max-pain). **Moneyness** - `strike / spot`. `0.9` = 10% OTM (for calls) / 10% ITM (for puts). Used as a strike filter on most endpoints (`?moneyness_min=0.9&moneyness_max=1.1`). ## N **Net GEX** - Sum of call GEX and put GEX at a strike (or aggregated across the chain). Positive net = stabilising; negative = amplifying. **Notional OI (USD)** - `total_oi × spot_price`. The dollar size of the position behind the OI count. Surfaced on every OPEX expiration. ## O **0DTE** - Zero days to expiration. Options expiring today. Many endpoints have a 0DTE variant of their levels (`odte_hvl`, `odte_call_resistance`, etc.). **OI** - Open interest. The number of contracts outstanding. **OPEX** - Options expiration. See [OPEX calendar](/concepts/opex). ## P **PCR** - Put / call ratio. OI-weighted ratio of put OI to call OI. PCR > 1 = defensive; PCR \< 1 = call-heavy. See [`/v2/options/pcr`](/api/v2/options/pcr). **PDF (probability density function)** - The implied probability distribution of the underlying at a future expiry. See [Probability density](/concepts/probability-density). **Pin risk** - The likelihood of price pinning to a strike on expiry day. Highest when max pain and gamma walls cluster near spot. **Positioning** - How live GEX/TRACE sign dealer size: `flow` (default) or `std`. See [Positioning](/concepts/positioning). **Premium tide** - Net options premium and notional volume tilt. See [`/v2/options/premium-tide`](/api/v2/options/premium-tide). **Put support** - The strike with the largest positive net gamma below spot. Often a bounce level - dealers buy on dips into this strike. **Put wall** - Synonym for put support. `put_wall_2` and `put_wall_3` are the second and third strongest below spot. ## Q **Quarterly expiry** - Last Friday of March, June, September, or December. The heaviest OI bucket of the year. ## R **Risk reversal** - `25Δ call IV − 25Δ put IV`. Positive = call premium; negative (the more common case) = put premium / fear bid. Surfaced on [`/v2/options/iv/skew`](/api/v2/options/iv/skew). **RV** - Realised volatility. The vol that actually happened over a trailing window (typically 30 days). ## S **Skew** - The asymmetry between OTM put IV and OTM call IV. Crypto typically prices puts richer (positive skew). See [IV suite](/concepts/iv-suite). **Smile** - The shape of IV when plotted by strike. ATM is the trough; OTM puts and OTM calls are the wings. **Source** - `meta.source` lists the upstream venues each response touched (`["deribit", "bybit", "okx", "binance"]`). Provenance. **`spot_price`** - Current spot price for the symbol, snapshotted at the time of computation. Returned in `meta` on every endpoint. **Stress history** - Long-window record of HVL and walls from the persistent store. Used for backtests. [`/v2/gex/stress-history`](/api/v2/gex/stress-history). ## T **Term structure** - The curve of ATM IV plotted against expiry tenor. Slope tells you the market's vol forecast across time. [`/v2/options/iv/term-structure`](/api/v2/options/iv/term-structure). **Theta** - Time decay. Premium that bleeds out of an option as time passes. Useful as a rough estimate of dealer income from selling premium. See [Greeks beyond delta](/concepts/greeks). **TRACE** - Forward (time × price) projection of dealer greek exposure from today's chain. Greeks: `gamma`, `charm`, `vanna`, `delta_change`. See [`/v2/options/greeks/trace`](/api/v2/options/greeks/trace). ## V **Vanna** - `∂Delta/∂IV`. Drives "vol-rallies-the-tape" hedging flows. See [Greeks beyond delta](/concepts/greeks). **Vega** - Sensitivity to IV moves. Where dealer P\&L is concentrated when vol shifts. **VRP** - Volatility risk premium. The persistent gap by which implied vol exceeds realised vol. See [`/v2/options/vrp`](/api/v2/options/vrp). ## W **Whale print** - Large options trade by `premium_usd`. REST helper: [`/v2/tape/whale`](/api/v2/tape/whale). **Weekly expiry** - A Friday expiration that is *not* the last Friday of the month. Lighter OI than monthlies, more responsive to short-term positioning. # Greeks beyond delta Source: https://docs.backquant.com/concepts/greeks DEX, theta, vanna, charm, vega - what they measure and how to use them * The greek profile endpoints give you dealer-aggregated delta (DEX), theta, vega, vanna and charm exposure per strike. * Profiles default to `positioning=flow`; pass `?positioning=std` for textbook call/put OI sign. * Cached profiles span every expiry, so pass `?dte_max` to recompute from short-DTE contracts only. The greek profile endpoints in v2 give you the **dealer-aggregated greek exposure per strike** for the four underlyings. Same idea as [GEX](/concepts/gex) but for the higher-order greeks that drive hedging flows the gamma profile alone misses. Profiles default to **`positioning=flow`**, BackQuant's measured dealer model. Pass `?positioning=std` for textbook call/put OI sign. Forward (time × price) maps live under [TRACE](/api/v2/options/greeks/trace). ## The five exposure profiles | Greek | Endpoint | What dealer holds | What it drives | | - | - | - | - | | **Delta (DEX)** | [`/v2/options/greeks/delta`](/api/v2/options/greeks/by-greek) | Net directional exposure per strike | Forced delta hedging in the underlying | | **Theta** | [`/v2/options/greeks/theta`](/api/v2/options/greeks/by-greek) | Time-decay USD/day per strike | The premium dealers earn passively | | **Vega** | [`/v2/options/greeks/vega`](/api/v2/options/greeks/by-greek) | IV sensitivity per strike | P\&L impact of vol moves | | **Vanna** | [`/v2/options/greeks/vanna`](/api/v2/options/greeks/by-greek) | ∂Delta/∂IV per strike | Delta hedging adjustment when vol moves | | **Charm** | [`/v2/options/greeks/charm`](/api/v2/options/greeks/by-greek) | ∂Delta/∂Time per strike | EOD / pre-expiry delta drift | All five share the same shape: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "greek": "vanna", "strikes": [60000, 62500, 65000, 67500, 70000, ...], "net_exposure": [-12000, -8000, 5000, 22000, 14000, ...], "call_exposure": [...], "put_exposure": [...], "total_exposure": 81000 } ``` ## DEX - the workhorse Net delta exposure tells you how much underlying dealers must hold *right now* to be neutral at each strike. Large positive DEX above spot \= dealers long deltas there = they'll sell into rallies. Large negative DEX below spot = they're short = they'll buy on dips. DEX is essentially the *integral* of GEX across price. Use both: **GEX** for "where will dealers turn the boat?", **DEX** for "how big is their current position?" ## Vanna - the vol-rallies-the-tape effect Vanna is **dDelta/dVol**. Positive vanna means: when IV rises, dealer delta increases (they need to buy underlying); when IV falls, dealer delta decreases (they sell). The classic "vol crush rallies the underlying" pattern is vanna in action. In crypto, vanna concentrates around 25Δ strikes (where vega is biggest). Watch for cliffs in the vanna profile near current spot - those are levels where a vol move triggers hedging flows that move price. ## Charm - the time-bleed delta drift Charm is **dDelta/dTime**. As an option ages with no underlying move, its delta drifts (calls toward 0 or 1, puts toward 0 or -1). Dealers hedge that drift, which creates predictable end-of-day flows. Watch this in the last hour before close (especially before OPEX days) - large charm at strikes near spot means dealers will be adjusting deltas just from time passing, even if price doesn't move. ## Vega - vol P\&L geography Where dealers will gain or lose if IV moves. Useful as a complement to the [IV surface](/concepts/iv-suite) - high vega at a strike means a vol move there has outsized impact on the dealer book. ## Filtering the greek profiles Each greek endpoint accepts: * `?exchanges=deribit,bybit,okx,binance,derive,thalex,delta_india,delta` - venue filter * `?moneyness_min=0.9&moneyness_max=1.1` - focus on ATM ±10% * `?dte_max=7` - recompute from raw chain limited to short-DTE contracts (cached 30s per `(symbol, greek, dte_max)` so repeated requests stay sub-millisecond) The `?dte_max` filter is particularly useful - the cached profiles span *every* expiry, but for intraday workflows you usually only care about 0DTE / weekly contributions. ## Surfaces - strike × time Two endpoints expose the *evolution* of exposure over time: * [`/v2/options/greeks/surfaces/charm`](/api/v2/options/greeks/surfaces) - strike × time charm surface * [`/v2/options/greeks/surfaces/vega`](/api/v2/options/greeks/surfaces) - strike × time vega surface These are useful when you want to see where exposure is *building* over the day, not just the current snapshot. ## 3D - strike × expiry for any greek [`/v2/options/greeks/3d/surface?greek=gamma`](/api/v2/options/greeks/3d-surface) returns a `Z` matrix of `Σ(greek × OI)` per `(strike, expiry)` cell for any greek. Best for 3D visualisation; the matrix is downsampled around spot when the cell count exceeds 5000 to keep payload bounded. ## Related concepts > **[What is GEX?](/concepts/gex)** > > Gamma is the special case. Start there before reading the higher > order greeks. > **[Flow vs std](/concepts/positioning)** > > How profiles and TRACE sign dealer size. > **[TRACE](/api/v2/options/greeks/trace)** > > Forward time × price projection of dealer greeks. > **[Probability density](/concepts/probability-density)** > > Vanna and charm shape how the implied PDF evolves. > **[OPEX calendar](/concepts/opex)** > > Charm matters most pre-OPEX as time-decay flows accelerate. # The IV suite Source: https://docs.backquant.com/concepts/iv-suite Surface, term structure, skew, smile, IV-RV, VRP - what each one tells you * The IV endpoints cover every standard slice of the surface: fitted surface, constant maturity, term structure, skew, curves, smile, IV-RV and VRP. * Use `/v2/options/iv/constant-maturity` to track ATM, risk reversals and butterflies at fixed tenors without the jumps from expiries rolling. * Watch the sign: `rr_25` on constant maturity is call minus put, while `/iv/skew` reports put minus call. * Use `/v2/options/iv/smile` instead of `/curves` when you only need one expiry. The implied-volatility (IV) endpoints in v2 give you every standard slice and aggregate of the IV surface. Each one answers a different question. ## At a glance | Endpoint | What it shows | Use it for | | - | - | - | | [`/v2/options/iv/surface`](/api/v2/options/iv/surface) | Fitted IV grid (tenor × strike) with each expiry's SVI fit | 3D vol-surface viz, your own pricing | | [`/v2/options/iv/constant-maturity`](/api/v2/options/iv/constant-maturity) | ATM, 25Δ / 10Δ RR and BF at 7 days to 1 year, arbitrage-checked | Vol desk monitoring at fixed tenors | | [`/v2/options/iv/term-structure`](/api/v2/options/iv/term-structure) | ATM IV per expiry | Calendar / term carry, contango vs backwardation | | [`/v2/options/iv/skew`](/api/v2/options/iv/skew) | 25Δ / 10Δ skew + 25Δ butterfly per expiry | Risk-reversal, fear gauge | | [`/v2/options/iv/curves`](/api/v2/options/iv/curves) | Full smile per expiry, multi-expiry | Vol surface comparison across tenors | | [`/v2/options/iv/smile`](/api/v2/options/iv/smile) | One expiry's fitted smile, with RR, BF and arbitrage checks | Cheaper than `/curves` when you only need one tenor | | [`/v2/options/iv/iv-rv`](/api/v2/options/iv/iv-rv) | Daily IV vs realised vol history | Vol-selling timing | | [`/v2/options/vrp`](/api/v2/options/vrp) | Volatility risk premium (IV − RV history) | Mean-reversion signal on premium richness | ## Surface The fitted grid of implied volatilities out to 180 days. Best rendered as a 3D surface or contour plot. Returns: * `x_grid` - strikes * `y_grid` - expiry tenors (DTE), listed expiries only * `z_grid` - `z_grid[i][j]` is the IV at tenor `y_grid[i]` and strike `x_grid[j]`; null where the expiry never traded that far out * `min_iv` / `max_iv` - for color scaling * `expiry_forwards` / `long_expiries` - each expiry's forward and SVI fit How the surface is fitted and checked is on [Vol surface methodology](/concepts/vol-surface). Most useful when you can see the whole shape - local "wings" up means fat-tail pricing, "smirk" with low call-side IV means upside-skew positioning. ## Term structure ATM implied vol per expiry, sorted by DTE. The shape tells you the market's vol forecast across time: * **Upward-sloping (contango)** - far-month vol higher than front-month. Normal for low-vol regimes; the market is pricing in eventual normalisation. * **Downward-sloping (backwardation)** - front higher than back. Common during stress, expirations or regime breaks. The market is pricing in *now* being noisier than *later*. * **Hump near anchor expirations** - higher IV at the upcoming monthly/quarterly than at adjacent weeklies, because of OPEX positioning. Filter `?dte_max=60` to focus on the front of the curve, or pass `?historical_compare_days=30` to also receive the constant-maturity ATM IV from 30 days ago - useful for showing "term structure shifted right" in dashboards. ## Constant maturity `/v2/options/iv/constant-maturity` reads ATM and the 25- and 10-delta wings off each expiry's quoted smile and interpolates them to fixed tenors: 7, 14, 30, 60, 90, 120, 180, 270 and 365 days. Poll it to track a 30-day risk reversal or a 3-month butterfly through time without the jumps that come from expiries rolling. `rr_25` there is call minus put, the desk convention; `/iv/skew` below reports put minus call. Each response says whether the fitted smiles are free of butterfly and calendar arbitrage, expiry by expiry. ## Skew `/v2/options/iv/skew` gives you per-expiry: * `skew_25d` = 25Δ put IV − 25Δ call IV (positive = downside premium) * `skew_10d` = 10Δ put IV − 10Δ call IV (tail skew) * `butterfly_25d` = (25Δ put IV + 25Δ call IV) / 2 − ATM IV (smile curvature) These are computed from real delta-bracketed options (interpolated to exact 25Δ / 10Δ), not strike proxies. Crypto skews are often more extreme than equities - `skew_25d` of +5-10 IV points on a near-term expiry is normal in jittery regimes, > 15 is signalling crash hedging. A *flattening* skew over time (positive going to zero) often precedes a local low. A *steepening* skew is fear bid. ## Curves and Smile [`/v2/options/iv/curves`](/api/v2/options/iv/curves) returns the full merged-IV smile (OTM puts below spot, OTM calls above) for *every* active expiry, sorted by DTE. Best for plotting all smiles on a single chart for visual comparison. [`/v2/options/iv/smile`](/api/v2/options/iv/smile) returns *one* expiry's smile from its SVI fit, by strike and log-moneyness, with its RR, BF and arbitrage checks. Lighter payload; use this if you only need the front month or a specific tenor. ## IV-RV history `/v2/options/iv/iv-rv` returns the daily history of: * `iv` - at-the-money implied volatility * `rv` - realised volatility (typically 30d) * `spread` - IV − RV Use the spread to decide when implied vol is rich relative to what's actually been realised. Sustained `spread > 0` is a sell-vol signal; sustained `spread < 0` is a buy-vol signal. ## VRP - the volatility risk premium `/v2/options/vrp` is the same as `iv-rv` but normalised. The vol risk premium is the *excess* IV traders demand over realised - historically positive on average (vol writers earn a premium). Sharp drops below zero are usually short-vol unwinds and worth tracking. ## Filtering knobs across the suite * `?days=30/90/365` on history endpoints (`/iv-rv`, `/vrp`) - window length * `?exchanges=deribit,bybit,okx,binance,derive,thalex,delta_india,delta` - venue filter (term structure recomputes from filtered breakdown) * `?dte_max=N` - front-of-curve focus * `?historical_compare_days=N` - overlay today's term structure with N days ago ## Related concepts > **[Probability density](/concepts/probability-density)** > > The Breeden-Litzenberger PDF derived from the IV surface. > **[What is GEX?](/concepts/gex)** > > GEX is computed using the same per-contract greeks as IV - the > suites are complementary. > **[OPEX calendar](/concepts/opex)** > > Term-structure humps near anchor expirations are an OPEX signal. # Max pain Source: https://docs.backquant.com/concepts/max-pain The strike where option writers profit most - and how to read it * Max pain is the strike that minimises the total payout to option buyers at expiry, where writers keep the most premium. * [`/v2/gex/max-pain`](/api/v2/gex/max-pain) takes `expiry=0dte`, `expiry=all`, or a specific expiry token that also returns the full `pain_curve`. * `value` is the total option-buyer payout at the max-pain strike, in USD. * A sharp U-shaped `pain_curve` signals a strong magnet; a flat curve, common on thin expiries, means no real magnet exists. **Max pain** is the strike at which the largest dollar amount of options expires worthless. It's the single number that summarizes "where do option *writers* (dealers + sellers) want price to be at expiry?" ## The intuition For each candidate strike, calculate the total dollar payout option *buyers* would receive if the underlying settled there at expiry. **Max pain is the strike that minimises that total payout** - the price where the most contracts expire worthless and writers keep the most premium. Dealers who sold those options have an interest in hedging price toward that strike as expiration approaches. Whether they actually have the muscle to drive price there is a separate question (usually no for liquid underlyings, sometimes yes for thin OPEX days near the strike), but the level itself is a meaningful magnet. ## The endpoints ### Quick: 0DTE max-pain [`/v2/gex/max-pain?symbol=BTCUSDT&expiry=0dte`](/api/v2/gex/max-pain) - returns the max-pain strike for today's expiry from the pre-computed 0DTE cache. Sub-millisecond response. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "expiry": "0dte", "max_pain": { "strike": 67500, "value": 12500000.0 } } ``` `value` is the total option-buyer payout at the max-pain strike, in USD. ### Per-expiry table [`/v2/gex/max-pain?symbol=BTCUSDT&expiry=all`](/api/v2/gex/max-pain) - the per-expiry max-pain table for every active expiration. Sorted by DTE. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "expiry": "all", "per_expiry": [ { "expiry": "30APR26", "dte": 0, "max_pain": { "strike": 67500, "value": 5e7 } }, { "expiry": "02MAY26", "dte": 2, "max_pain": { "strike": 67000, "value": 8e7 } }, { "expiry": "30MAY26", "dte": 30, "max_pain": { "strike": 65000, "value": 4e8 } }, ... ] } ``` Cached for 30s - repeated calls are sub-millisecond. ### Specific expiry with the full pain curve [`/v2/gex/max-pain?symbol=BTCUSDT&expiry=30MAY26`](/api/v2/gex/max-pain) - returns the max-pain strike for that expiry **plus** the full `pain_curve` (one entry per strike), so you can plot the pain function as a U-shape and visually see how steep the magnet is. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "expiry": "30MAY26", "dte": 30, "max_pain": { "strike": 65000, "value": 4e8 }, "pain_curve": [ { "strike": 60000, "pain": 7.2e8 }, { "strike": 62500, "pain": 5.5e8 }, { "strike": 65000, "pain": 4.0e8 }, { "strike": 67500, "pain": 4.3e8 }, { "strike": 70000, "pain": 5.1e8 }, ... ] } ``` A flat curve (similar pain values across many strikes) means weak magnet - dealers have no strong preference. A sharp U (one strike with much lower pain than its neighbours) means a strong magnet - that's the strike to watch into expiry. ## Where else max-pain shows up in v2 * **`/v2/options/opex`** - every expiration in the horizon comes with its `max_pain` block under `gamma`. * **`/v2/options/expiry-summary`** - every row carries `max_pain` for that expiry. * **`/v2/gex/levels?include=max_pain`** - the 0DTE max-pain alongside the gamma walls. All three share the same per-expiry max-pain cache (30s TTL) so the snapshot scan happens once per refresh cycle regardless of how many endpoints query it. ## How traders use it **Day before OPEX**: combine max-pain with [GEX levels](/concepts/gex). When max\_pain is between call\_resistance and put\_support, the levels reinforce each other and the pin is high-probability. **OPEX day**: the closer to expiry, the tighter the pin tends to get. Calculate the distance from spot to max\_pain and the implied move; if the implied move is smaller than the distance, the chain is pricing in a successful pin. **Cross-expiry**: an upcoming monthly's max\_pain that's far from spot is bullish/bearish positioning. The market is signalling where it expects to be at the next anchor. **Avoid**: treating max-pain as a forecast on illiquid expiries. Distant or thinly-traded expirations have noisy max-pain - the curve is flat, no real magnet exists. ## Related concepts > **[What is GEX?](/concepts/gex)** > > The other major source of expiry-day price magnets. > **[OPEX calendar](/concepts/opex)** > > Anchor expirations have the strongest max-pain signal. > **[Probability density](/concepts/probability-density)** > > The market's full implied price distribution for each expiry. # Multi-symbol bundling Source: https://docs.backquant.com/concepts/multi-symbol Get the universe in one round-trip via /v2/multi/gex/levels * [`/v2/multi/gex/levels`](/api/v2/multi/gex-levels) returns GEX levels for up to 8 symbols in one round-trip, keyed by symbol under `results`. * It defaults to `positioning=flow` with the same per-symbol fallbacks as single-symbol levels, so SOL and HYPE usually fall back to std. * A symbol with no live data appears in `missing` with a `null` result, but the other symbols still come back. * The `spot` and `candles` includes are not available here, so use the single-symbol endpoint when you need them. Desk dashboards routinely watch the entire crypto-options universe at once: BTC + ETH + SOL + HYPE. Hitting `/v2/gex/levels` four times means four HTTP round-trips. The multi endpoint collapses that into one. Default **`positioning=flow`**, with the same per-symbol fallbacks as single-symbol levels (SOL/HYPE usually fall back to std). See [Positioning](/concepts/positioning). ## The endpoint [`/v2/multi/gex/levels`](/api/v2/multi/gex-levels) takes a CSV of symbols (up to 8) and returns a `results` map keyed by symbol: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/multi/gex/levels?\ symbols=BTCUSDT,ETHUSDT,SOLUSDT,HYPEUSDT\ &include=ranked,max_pain,expected_move,zones" \ -H "X-API-Key: YOUR_API_KEY" ``` ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "results": { "BTCUSDT": { "all_expiry": { "hvl": 67000, "call_resistance": 70000, ... }, "odte": { "hvl": 67500, ... }, "spot_price": 67213.5, "computed_at": "2026-04-30T11:59:48Z", "ranked": { "all_expiry_top_10": [...], "odte_top_10": [...] }, "max_pain": { "strike": 67500, "value": 5e7 }, "expected_move": { "upper_1sd": 70000, "lower_1sd": 64000, ... } }, "ETHUSDT": { ... }, "SOLUSDT": { ... }, "HYPEUSDT": { ... } }, "requested": ["BTCUSDT", "ETHUSDT", "SOLUSDT", "HYPEUSDT"], "served": ["BTCUSDT", "ETHUSDT", "SOLUSDT", "HYPEUSDT"], "missing": [], "invalid": [] } ``` ## Why it's faster than N calls Server-side, the bundle endpoint batches every lookup into a single operation rather than serving them sequentially. The result: one HTTP round-trip instead of N, and lower upstream load on our side. For a desk dashboard polling on a 30s loop, that's a 4× reduction in round-trips with no client-side change beyond switching to the bundle endpoint. ## Partial results If one of the requested symbols has no live data (cold cache), it appears in `missing` and its `results[symbol]` is `null` - but the other symbols still come back. **The whole call doesn't fail** because of one stale symbol. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "results": { "BTCUSDT": { ... }, "ETHUSDT": { ... }, "SOLUSDT": null, "HYPEUSDT": { ... } }, "requested": ["BTCUSDT", "ETHUSDT", "SOLUSDT", "HYPEUSDT"], "served": ["BTCUSDT", "ETHUSDT", "HYPEUSDT"], "missing": ["SOLUSDT"], "invalid": [] } ``` `invalid` lists symbols that aren't in the supported set (BTCUSDT/ETHUSDT/SOLUSDT/HYPEUSDT) - your client got the symbol name wrong. ## Filters * `?exchanges=deribit,bybit,okx,binance,derive,thalex,delta_india,delta` - same venue filter as single-symbol; applies to every symbol in the bundle. * `?include=ranked,max_pain,expected_move,zones` - same composable includes. The single-symbol `spot` and `candles` includes are intentionally absent here to keep the bundled payload bounded (candles arrays would multiply payload by \~10×). ## Limits * **Up to 8 symbols** per request (you have 4 today; the cap leaves room for future expansion). * Duplicate symbols in the request are de-duplicated while preserving caller order. ## When to use it **Use it for**: dashboard refreshes, multi-symbol overlays, screeners that scan the universe. **Don't use it for**: single-symbol drilldowns. The per-symbol endpoints support more includes (`spot`, `candles`) that the multi endpoint omits. ## Related concepts > **[What is GEX?](/concepts/gex)** > > The data this bundles is the same as `/v2/gex/levels` per symbol. > **[Data freshness](/concepts/data-freshness)** > > The shared cache that makes the bundle cheap. # OPEX calendar Source: https://docs.backquant.com/concepts/opex Daily, weekly, monthly, quarterly - and why anchor expirations matter * Monthly and quarterly expirations are anchor expiries, with 5-10x the OI of weeklies and outsized influence on price into expiry. * Every expiration comes pre-classified with a `type` field and an `is_anchor` flag, so you don't parse tokens client-side. * [`/v2/options/opex`](/api/v2/options/opex) bundles OI, IV, gamma and `max_pain` per expiration; pass `types=monthly,quarterly` for anchors only. **OPEX** (options expiration) is the day options contracts expire. In crypto, contracts expire on multiple cadences: * **Daily expirations** - BTC and ETH have daily 0DTE contracts on Deribit * **Weekly expirations** - every Friday, smaller OI * **Monthly expirations** - the **last Friday** of each month, much larger OI * **Quarterly expirations** - last Friday of March, June, September, December - the heaviest of all The monthly and quarterly expirations are **anchor expiries** - institutional positioning concentrates in them, GEX clusters around their strikes, and they often dictate price action in the days leading up to expiry. ## Why this distinction matters Monthly/quarterly expirations have: * **5-10× the OI** of weeklies in the same week * **Roll activity** in the 3-5 days before - rolling positions to the next expiry can move spot * **Pin risk** from large dealer hedges unwinding on expiry day * **Vol regime shifts** as the GEX picture redistributes after the expiry-day delta unwind Weekly expirations matter for short-term gamma but have nowhere near the institutional weight. ## The classification BackQuant returns Every expiration in our responses comes pre-classified with a `type` field - you don't have to parse Deribit-style tokens or detect last-Friday-of-month rules client-side: | Type | Definition | Example | | - | - | - | | `quarterly` | Last Friday of Mar / Jun / Sep / Dec | `28MAR25`, `27JUN25`, `26SEP25`, `26DEC25` | | `monthly` | Last Friday of any other month | `25APR25` (April 30 is Wednesday → last Friday is the 25th), `30MAY25`, `25JUL25` | | `weekly` | Any other Friday | `07MAR25`, `14MAR25`, `21MAR25`, `04APR25`, `11APR25` | | `daily` | Any non-Friday weekday | `26MAR25` (Wednesday), `27MAR25` (Thursday) | `is_anchor: true` is shorthand for `monthly` or `quarterly` - the two that institutions watch. Tokens that don't parse cleanly are returned as `unknown` and never silently filtered out. ## The `/v2/options/opex` endpoint [`/v2/options/opex`](/api/v2/options/opex) bundles everything you need for an OPEX-day workflow into one call: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/options/opex?symbol=BTCUSDT&horizon=30" \ -H "X-API-Key: YOUR_API_KEY" ``` For each upcoming expiration within the horizon, you get: * **Identification**: token, ISO date, DTE, type, `is_anchor` flag * **OI block**: `call_oi`, `put_oi`, `total_oi`, `pcr`, **`notional_oi_usd`** (= total\_oi × spot) * **IV block**: `atm_iv`, `skew_25d`, `put_25d_iv`, `call_25d_iv` * **Gamma block**: `net_gex`, `call_resistance`, `put_support` (computed per-expiry from the strike × expiry heatmap), `max_pain` Plus a `next_anchor` pointer at the closest monthly/quarterly so you can highlight it in your UI. ## Filtering for the institutional view Want only the heavyweight expirations? Filter to anchors: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/options/opex?\ symbol=BTCUSDT\ &horizon=90\ &types=monthly,quarterly" \ -H "X-API-Key: YOUR_API_KEY" ``` You'll get back just the monthlies + quarterlies in the next 90 days, sorted by DTE. ## Same enrichment in `/v2/options/expiry-summary` If you already use [`/v2/options/expiry-summary`](/api/v2/options/expiry-summary), each row is enriched with the same `type`, `is_anchor`, `expiry_date`, `notional_oi_usd`, and `max_pain` fields. Plus top-level `anchor_count` and `type_counts` so you can render summary stats without iterating. ## How to use this in production **Pre-OPEX setup (T-5 to T-1)**: filter to next anchor, watch the gamma walls move as positions unwind/roll. Big call\_resistance moves upward = bullish positioning rolling forward. **OPEX day**: the `max_pain` strike on the anchor expiry is a magnet. Combined with [GEX levels](/concepts/gex), you get a tight pin prediction window for the close. **Post-OPEX**: the next anchor's expected\_move and OI tell you whether positioning has compressed (low IV, low OI = sell-vol environment) or expanded (high IV, high OI = vol expansion likely). ## Related concepts > **[Max pain](/concepts/max-pain)** > > The pinning strike on each expiry - a key OPEX-day signal. > **[What is GEX?](/concepts/gex)** > > The walls / HVL / support levels OPEX positioning concentrates in. > **[The IV suite](/concepts/iv-suite)** > > Term structure flattening / steepening around OPEX is a big signal. # Point-in-time (as_of) Source: https://docs.backquant.com/concepts/point-in-time Ask what the chain, dealer positioning and implied vol were at a past instant, with the snapshot used and its limits reported alongside. * Pass `as_of` to nine endpoints, including `/v2/options/chain` and `/v2/gex/levels`, to get the answer from the chain snapshot at that instant. * `as_of` snaps to the nearest snapshot at or before your time, never after, so align on `snapshot_ts`, not on what you requested. * Chain snapshots start 2026-04-16 and the aggressor tape 2026-05-17; between those dates, responses use `std` instead of `flow`. * Timestamps before the chain floor return `404`, future timestamps return `400`, and timestamps outside your plan's window are clamped. Most of this API answers *what is true now*. Nine endpoints also answer **what was true at a given moment**, so a signal can be evaluated against history rather than only observed live. ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -H "X-API-Key: bq_live_your_key_here" \ "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT&as_of=2026-08-20T14:30:00Z" ``` Omit `as_of` and nothing changes. Supply it and the endpoint reconstructs the answer from the chain snapshot in force at that instant. ## Where it works | Endpoint | | | - | - | | [`/v2/options/chain`](/api/v2/options/chain) | **The chain itself**, with every filter and projection | | [`/v2/gex/levels`](/api/v2/gex/levels) | Gamma flip, call wall, put support | | [`/v2/gex/strike-profile`](/api/v2/gex/strike-profile) | Dealer gamma by strike | | [`/v2/gex/dealer-flow`](/api/v2/gex/dealer-flow) | Dealer hedge flow series ending at that instant | | [`/v2/gex/gamma-flow`](/api/v2/gex/gamma-flow) | Gamma component of the same series | | [`/v2/options/iv/surface`](/api/v2/options/iv/surface) | Strike x expiry vol grid | | [`/v2/options/iv/term-structure`](/api/v2/options/iv/term-structure) | ATM IV by tenor | | [`/v2/options/iv/skew`](/api/v2/options/iv/skew) | Skew curve | | [`/v2/options/expected-move`](/api/v2/options/expected-move) | Expected move by expiry | Start with [`/v2/options/chain`](/api/v2/options/chain) if you would rather compute your own views: it returns the raw contracts as they stood, and every other endpoint above is derived from exactly that snapshot. The two flow endpoints differ from the rest. They return a **series across snapshots**, so `as_of` sets where that series ENDS rather than naming a single point. Each frame in it carries the tape as of that frame. For historical **price**, open interest, funding, CVD and liquidations, use [`/v2/market/*`](/api/v2/market/candles), which is built for time series. ## It snaps backward, never forward Chains are persisted roughly every five minutes, so an arbitrary timestamp will not land on one exactly. `as_of` resolves to the nearest snapshot **at or before** the time you asked for. Never after. Snapping forward would let a backtest see a chain from after the moment it is meant to be trading, which makes a strategy look better than it is. Snapping backward can only ever be slightly stale, which is honest and measurable. The response tells you exactly what you got: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "meta": { "extra": { "as_of": { "requested": "2026-08-20T14:30:00+00:00", "snapshot_ts": "2026-08-20T14:27:31+00:00", "lag_seconds": 149.0, "resolution": "nearest snapshot at or before the requested time" } } } ``` Align on `snapshot_ts`, not on what you requested. ## Flow positioning is rebuilt as of that moment Under the default `positioning=flow`, the dealer flow map is reconstructed from the aggressor tape **as it stood at that instant** rather than from today's tape. A given timestamp therefore returns the same numbers every time you ask, which is what makes the series usable for research. Values are rounded before they are returned. The same aggregate run twice can differ in its last bits, which is meaningless as a quantity and unacceptable in something you are meant to be able to re-run. ## Coverage Both sources are bounded by when ingestion began, not by a retention policy, and they do not start on the same day. | Source | Available from | Governs | | - | - | - | | Chain snapshots | **2026-04-16** | Everything below `as_of` | | Aggressor tape | **2026-05-17** | `positioning=flow` only | An `as_of` between those two dates has a chain but no tape. The response is signed with the textbook `std` convention instead and says so: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "flow_unavailable_before": "2026-05-17", "flow_coverage_note": "positioning=flow needs the aggressor tape, which begins 2026-05-17. ..." ``` Earlier than the chain floor returns `404` rather than the oldest snapshot we happen to hold. ## Nothing is borrowed from live An `as_of` response never contains a current value wearing a past timestamp. On [`/v2/gex/levels`](/api/v2/gex/levels), `max_pain` and `expected_move` are recomputed from the snapshot rather than read from the live caches that back them normally. Where an honest answer is not possible, the section is omitted and named: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "unavailable_sections": { "sections": ["candles", "spot"], "reason": "Price history comes from the perps candle series, not the options snapshot. Use /v2/market/candles for historical price rather than a value back-dated from a chain." } ``` `include=spot` and `include=candles` come from the perpetual candle series, a different source with its own history. Back-dating a price from an options snapshot would be inventing one. ## Your plan's window applies `as_of` is bounded by the same history entitlement as `days` and `hours`. A timestamp outside your window is **clamped, never rejected**, and reported: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "history_clamp": { "requested": "2025-08-20T14:30:00+00:00", "allowed": "2026-07-29T00:00:00+00:00", "unit": "as_of", "max_history_days": 30, "enforced": true } ``` See [Rate limits](/concepts/rate-limits) for the ladder. ## Practical notes * **A timestamp far in the past is not an error, it is a 404** when history does not reach it. Check the coverage table before assuming a gap. * **Repeated requests are cheap.** Answers are cached on the resolved snapshot, so sweeping a window pays for each snapshot once no matter how many distinct timestamps you send. * **Future timestamps are rejected** with `400`. Omit `as_of` for the current value. ## See also > **[Flow vs std](/concepts/positioning)** > **[Rate limits](/concepts/rate-limits)** > **[Market data](/api/v2/market/candles)** > **[Data freshness](/concepts/data-freshness)** # Dealer positioning: flow vs std Source: https://docs.backquant.com/concepts/positioning How GEX and TRACE sign dealer exposure, multi-venue aggregation, and when each model applies * Live GEX and TRACE default to `flow`, BackQuant's measured dealer model; pass `?positioning=std` for the textbook call-positive, put-negative convention. * Read `meta.extra.positioning` for the model that actually ran, and `meta.extra.positioning_fallback_reason` when flow fell back to std. * SOL and HYPE fall back to std without aggressor tape, and `/v2/gex/history` and `/v2/gex/stress-history` are std only. * Don't mix customer flow (`/v2/gex/delta-flow`) with dealer hedge flow (`/v2/gex/dealer-flow`). Dealer exposure needs a **sign** at every contract: is the desk long or short that option's gamma? Two models are available on the live GEX and TRACE surfaces. ## Models | Mode | Meaning | When to use | | - | - | - | | **`flow`** (default) | BackQuant's measured dealer model, derived from observed trading rather than an open-interest assumption | Best match to how price behaves where the model is available (BTC, ETH) | | **`std`** | Textbook convention: call OI positive, put OI negative | Backtests, comparisons to public GEX, or symbols without usable tape | Pass `?positioning=flow` or `?positioning=std`. Omit the param to get **flow**. Every response that rebuilds live exposure reports the effective model in: * `data.positioning` when present * `meta.extra.positioning` and `meta.extra.positioning_requested` * `meta.extra.positioning_fallback_reason` when flow was requested but std ran instead ## Aggregation Live signed surfaces use: Both models read the same multi-venue options chain for greeks and **coin-unit OI**: Deribit, Bybit, OKX, Binance, Derive (when enabled), Thalex, Delta India, Delta Global, Bullish and Paradex. See [Venue coverage](/concepts/venues) for what each venue contributes. GEX is reported in **USD impact per 1% move in spot**, so magnitudes are comparable across symbols and across both models. `meta.extra.aggregation` echoes which venues were combined for the response. Bullish and Paradex contribute open interest but their trades are not collected yet, so they count in `std` and not in `flow`. ## Coin coverage | Symbol | Flow tape | Default behaviour | | - | - | - | | BTCUSDT, ETHUSDT | Yes (multi-venue) | Flow when tape is non-empty | | SOLUSDT, HYPEUSDT | When Derive (or other) tape is live | Falls back to **std** if no aggressor tape (`no_aggressor_tape_for_symbol`) | If flow is empty for BTC/ETH, the API falls back to std and sets `meta.extra.positioning_fallback_reason` (for example `flow_tape_empty` or `flow_qty_all_zero`). ## Live vs history | Surface | Positioning | | - | - | | Levels, strike profile, expiry profile, heatmaps, TRACE, multi levels, greek profiles, `/v2/gex/ladder` | **flow** default | | `/v2/gex/history`, `/v2/gex/stress-history` | **std** only (stored series) | | WebSocket `gex.levels.{SYMBOL}` | **flow** default, same as REST levels | | Max pain, IV, PCR, OI, expected move, probability | Not a signed dealer model | ## Customer flow vs dealer hedge flow Do not mix these up: | Endpoint family | Side | Source | | - | - | - | | `/v2/gex/delta-flow`, tape `weight=delta` | **Customer** signed delta from trades | Options tape | | `/v2/tape/flow-by-strike` | **Aggressor** activity by strike | Options tape | | `/v2/gex/gamma-flow`, `/v2/gex/dealer-flow` | **Dealer** hedge estimate from chain × moves | GEX snapshots | Positive on dealer-flow ≈ estimated dealer **buying** the underlying to re-hedge. Positive on delta-flow ≈ net **customer** buy-side delta notional. ## Related > **[What is GEX?](/concepts/gex)** > **[Venue coverage](/concepts/venues)** > **[Flow by strike](/api/v2/tape/flow-by-strike)** > **[Greek TRACE](/api/v2/options/greeks/trace)** # Probability density Source: https://docs.backquant.com/concepts/probability-density Risk-neutral PDF / CDF via Breeden-Litzenberger - the market's implied price distribution * [`/v2/options/probability/density`](/api/v2/options/probability/density) returns the risk-neutral PDF and CDF for every active expiry, extracted from option prices. * Pass `?confidence_band=0.68` or `?confidence_band=0.95` to get `lower` and `upper` price bounds per expiry. * Treat it as risk-neutral, not a real-world forecast, and expect noisier tails where far-OTM strikes are sparse. **Risk-neutral probability density** is the full implied probability distribution of the underlying's price at a future expiry, extracted from option prices. It tells you not just where the market expects price to land (the mean / mode) but the full *shape* of expectations - including skew toward downside crashes or upside squeezes. ## The intuition Breeden and Litzenberger (1978) showed that the shape of an option chain - specifically the way call prices respond to strike - implicitly encodes the market's probability distribution for the underlying at expiry. We do the heavy lifting on our side and return a clean, normalised PDF over the strike grid for every active expiry. You get the distribution directly. No surface-fitting, no numerical differentiation, no boundary-handling. Plot it, integrate it, sample percentiles from it. ## What you get from `/v2/options/probability/density` For every active expiry, the [endpoint](/api/v2/options/probability/density) returns: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "expiries": { "30MAY26": { "strikes": [60000, 62500, 65000, 67500, 70000, 72500, 75000, ...], "pdf": [0.001, 0.003, 0.012, 0.025, 0.018, 0.008, 0.002, ...], "pdf_normalized": [...], "cdf_below": [0.05, 0.12, 0.30, 0.55, 0.78, 0.92, 0.99, ...], "cdf_above": [0.95, 0.88, 0.70, 0.45, 0.22, 0.08, 0.01, ...], "mean": 67200, "mode": 67500, "std": 4100, "skewness": -0.18, "kurtosis": 3.6, "prob_25": 64500, "prob_50": 67500, "prob_75": 70200 }, ... }, "spot_price": 67213.5 } ``` | Field | Meaning | | - | - | | `pdf` | Probability density per strike (un-normalized - sum × strike spacing ≈ 1) | | `pdf_normalized` | PDF divided by total mass, sums to 1 over the strike grid | | `cdf_below` | Cumulative probability `P(price < strike)` - monotone non-decreasing | | `cdf_above` | Survival function `P(price > strike)` = 1 − cdf\_below | | `mean` | Expected price at expiry under the risk-neutral measure | | `mode` | Most likely single price (peak of the PDF) | | `std` | Standard deviation in price units | | `skewness` | Distribution skew - negative = downside-skewed (crash risk priced in) | | `kurtosis` | Tail-fatness - > 3 means fat tails, \< 3 means thin | | `prob_25 / 50 / 75` | The 25th / 50th / 75th percentile prices | ## Confidence bands - the easiest way to use this Pass `?confidence_band=0.68` (≈ 1σ) or `?confidence_band=0.95` (≈ 2σ) and each expiry payload gets an extra block: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "30MAY26": { ..., "confidence_band": { "confidence": 0.68, "lower": 63100, "upper": 71200, "lower_percentile": 0.16, "upper_percentile": 0.84 } } } ``` `lower` and `upper` are the price levels containing 68% (or 95%) of the implied probability mass, computed by interpolating the CDF. Use these directly as range bounds in dashboards or mean-reversion strategies. ## Filtering * `?expiry=28MAR25` - slice to a single expiry token (case-insensitive) * `?dte_max=30` - drop expiries beyond N days, focus on the front ## How traders use it **Identifying skew**: a `skewness` of -0.5 on the next monthly means the market is paying up for downside protection. Often a contrarian buy signal when extreme. **Sizing risk**: a `confidence_band` at 95% gives you the implied 2σ range. Position sizing that assumes a tighter range is taking a vol view; sizing that assumes wider is hedged. **Strike selection**: looking at the PDF for the next monthly, you can see where the "shoulders" of the distribution are - strikes with elevated PDF density that aren't the mode. These are often the strikes where dealer hedging clusters and worth knowing for execution. **Comparing expiries**: the term structure of `std` (per-expiry vol) and `skewness` (per-expiry crash risk) tells you whether the market is pricing volatility expansion or compression over the next few weeks. ## What it isn't * **Not a forecast.** Risk-neutral probabilities differ from real-world probabilities by the risk premium. A 70% RN probability of upside doesn't mean a 70% real-world chance. * **Not infinitely smooth.** The underlying chain can be sparse for far-OTM strikes, so the tails of the PDF are noisier than the body. Use the body for sizing decisions; treat the tails as informational. * **Not free.** It's computationally heavy. The endpoint is cached at the worker layer - refreshed every 30s, not real-time. ## See also * **3D surface**: [`/v2/options/probability/surface`](/api/v2/options/probability/surface) returns the same PDF aligned across all expiries on a unified strike grid, suitable for 3D visualisation. ## Related concepts > **[The IV suite](/concepts/iv-suite)** > > The IV surface that underpins this density extraction. > **[Max pain](/concepts/max-pain)** > > A simpler scalar measure of "where price wants to be" at expiry. > **[Greeks beyond delta](/concepts/greeks)** > > Vanna and charm shape how the PDF evolves between now and expiry. # Rate limits Source: https://docs.backquant.com/concepts/rate-limits Per-tier burst and monthly quotas, response headers, and backoff strategy * Limits apply per user, shared across all your API keys, as a per-minute burst cap plus a UTC calendar-month request quota. * Exceeding either returns `429` with `RATE_LIMIT_EXCEEDED`; if `X-Quota-Remaining` is `0`, stop retrying rather than sleeping on `Retry-After`. * WebSocket sessions count only against the concurrent connection cap, not your monthly REST quota. * History requests beyond your plan's window are never rejected; `meta.extra.history_clamp` reports what you asked for and what was served. Rate limits are enforced per **user** (shared across that user's API keys) based on subscription level. Exceeding a limit returns `429` with `RATE_LIMIT_EXCEEDED`. As of API **v2.5.0** there are two dimensions: 1. **Per-minute burst** - short-window ceiling (headers `X-RateLimit-*`). 2. **Monthly request quota** - UTC calendar-month budget (headers `X-Quota-*`). Sustained polling at the burst rate will exhaust the month early. WebSocket sessions count against the **concurrent connection** cap only. They do **not** draw down the monthly REST quota. ## Limits by level | Level | Monthly requests | Burst / min | API keys | WS connections | | - | -: | -: | -: | -: | | **Starter** | 10,000 | 30 | 1 | 1 | | **Standard** | 250,000 | 60 | 2 | 2 | | **Pro** | 1,000,000 (fair use) | 120 | 5 | 5 | | **Enterprise** | Uncapped monthly | 600 | custom | up to 25 | The starter burst is 30/min rather than a rate matched to its monthly allowance: a single question in an AI client fires several tool calls in quick succession, and a tight burst cap would fail mid-answer. The monthly allowance is the number that defines the plan. Plan mapping notes (2.5.0): * Crypto API plans `api_only_monthly` / `api_only_yearly` → **standard**. * Terminal Yearly (`yearly`) includes **starter** API access. * Bundle `api_monthly` maps to **pro** (120/min). Legacy `api` is also treated as pro. [Get or upgrade access at backquant.com/api-access](https://backquant.com/api-access). ## Response headers Every authenticated REST response includes: | Header | Description | | - | - | | `X-RateLimit-Limit` | Per-minute burst cap | | `X-RateLimit-Remaining` | Calls left in the current minute window | | `X-RateLimit-Reset` | Unix timestamp when the minute window resets | | `Retry-After` | Seconds until safe to retry (**on 429 only**) | | `X-Quota-Limit` | Monthly request cap | | `X-Quota-Used` | Requests counted this UTC month | | `X-Quota-Remaining` | Monthly remaining | | `X-Quota-Period` | Period id `YYYYMM` (UTC) | | `X-RateLimit-Fallback` | `true` only while the limiter is in degraded mode (see below) | The per-minute triple is also echoed in `meta.rate_limit` when present. `GET /v2/meta` returns the full per-level ladder under `rate_limits`. > [!NOTE] > > `X-RateLimit-Fallback: true` means our rate-limit store was briefly > unreachable and a stricter per-machine counter took over, so > `X-RateLimit-Limit` will read lower than your plan's normal cap. It is rare > and self-healing, and the header is absent the rest of the time. If you > surface your remaining budget to users, read the limit from the header rather > than hardcoding your plan's number, or it will look wrong during one of these > windows. ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -I "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT" \ -H "X-API-Key: YOUR_API_KEY" ``` ## What happens at the limit 1. Burst or monthly exhaustion returns HTTP **429** with `RATE_LIMIT_EXCEEDED`. Monthly messages name the monthly cap. 2. Respect `Retry-After`, but check `X-Quota-Remaining` first. On a burst 429 it is the seconds left in the minute. On a monthly 429 (`X-Quota-Remaining: 0`) it is the seconds until the month rolls over, so stop retrying and surface the message rather than sleeping on it. 3. Do not spin-retry without backoff. ## Auth vs entitlement | Status | Meaning | | - | - | | **401** `UNAUTHORIZED` | Missing, malformed, revoked, or expired key | | **403** `FORBIDDEN` | Key is valid but the **plan does not permit** this use | | **429** | Rate / quota exceeded | Clients that treat every 401 as “refresh credentials” must treat **403** as “upgrade / wrong plan,” not as a bad key. API access requires subscription status **`active`**. `trialing` grants the terminal but **not** the API until the trial converts. ## Historical depth Separate from request limits, your plan sets how far back history endpoints may reach. | Plan | Window | | - | -: | | Evaluation (`sandbox`) | 30 days | | Terminal Yearly, Crypto API monthly | 30 days | | Crypto API annual | 90 days | | Terminal + API monthly | 180 days | | Terminal + API annual | 365 days | | Enterprise | Full history | A request beyond your window is **clamped, never rejected**. You receive the data you are entitled to, plus a `meta.extra.history_clamp` block saying what was asked for and what was served: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "history_clamp": { "requested": 365, "allowed": 30, "unit": "days", "max_history_days": 30, "enforced": true } } ``` Call [`GET /v2/meta`](/api/v2/discovery/meta) to see the full ladder and, under `your_plan`, the window that applies to your own key. > [!WARNING] > > **Your window is what you may REQUEST, not a promise the data exists.** Options > history (used by [`as_of`](/concepts/point-in-time) and the GEX endpoints) > begins when ingestion did, and market series depth varies by interval: coarser > intervals are stored for longer. `GET /v2/meta` publishes `data_coverage` > alongside this ladder so you can size a backtest against both numbers, and a > short market window is reported on the response as `meta.extra.available_hours`. > [!NOTE] > > For existing paid plans the clamp is currently **reported but not > enforced**: `meta.history_depth_enforced` is `false` and your request is > served in full. Evaluation keys are the exception and are enforced today, > so a trial reflects the plan being trialled. Check whether > `history_clamp` appears in your traffic now, and you will know in advance > whether enforcement affects you. ## Strategy **Cache aggressively.** Chain/GEX snapshots refresh \~30s server-side. Polling once per 30s per panel is enough. **Use multi-symbol bundling.** [`/v2/multi/gex/levels`](/api/v2/multi/gex-levels) for several symbols counts as **one** request. **Use composable `?include=` / `?fields`.** Fewer round trips, lighter payloads. **Prefer WebSocket for tape.** Live prints over WS avoid burning monthly REST quota on `/tape/recent` polling. ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} import time, requests def get_with_backoff(url, headers, max_tries=5): delay = 1.0 for _ in range(max_tries): r = requests.get(url, headers=headers, timeout=30) if r.status_code != 429: return r wait = int(r.headers.get("Retry-After", delay)) time.sleep(wait) delay = min(delay * 2, 60) return r ``` ## See also > **[Errors](/concepts/errors)** > **[Meta](/api/v2/discovery/meta)** > **[Authentication](/authentication)** # Response format Source: https://docs.backquant.com/concepts/response-format The standard envelope every BackQuant API response uses * Every response uses the same envelope, so check `success` before reading `data` or `error`. * The `meta` block is present on every response, success or error; include `meta.request_id` when reporting issues. * Use `meta.computed_at` and `meta.freshness_seconds` to check how current the data is. * Don't assert equality on `meta.version`, because the minor version bumps whenever a public default or response contract changes. Every response from the BackQuant API uses the same envelope structure, whether the request succeeds or fails. **Always check the `success` field before reading `data` or `error`.** ## Success response When a request succeeds, `success` is `true` and the result is in `data`. The `meta` block carries everything you need to know about the provenance and freshness of the data. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "success": true, "data": { "all_expiry": { "hvl": 67000, "call_resistance": 70000, "put_support": 64000 }, "odte": { "hvl": 67500, "call_resistance": 68000, "put_support": 66500 } }, "meta": { "version": "2.7", "timestamp": "2026-04-30T12:00:00.123Z", "request_id": "req_a1b2c3d4...", "symbol": "BTCUSDT", "spot_price": 67213.5, "computed_at": "2026-04-30T11:59:48.000Z", "freshness_seconds": 12.0, "source": ["deribit", "bybit", "okx", "binance"] } } ``` ## Error response When a request fails, `success` is `false` and the reason is in `error`. The `meta` block keeps the same shape so logging code doesn't have to special-case errors. ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "success": false, "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Try again in 60 seconds.", "details": { "retry_after_seconds": 60, "limit": "120 per 1 minute" } }, "meta": { "version": "2.7", "timestamp": "2026-04-30T12:00:00.123Z", "request_id": "req_a1b2c3d4..." } } ``` See [Errors](/concepts/errors) for the full list of error codes. ## Fields **success** `boolean` *required* `true` if the request succeeded, `false` if it failed. **Always check this field first.** **data** `object` The response payload. Present when `success` is `true`. The shape varies by endpoint - see the [API reference](/api/v2/gex/levels) for each endpoint's schema. The top 10 most-traffic endpoints have Pydantic-typed schemas in the OpenAPI spec; the rest are documented with examples. **error** `object` Present when `success` is `false`. **properties** **error.code** `string` *required* Machine-readable error code. See [Errors](/concepts/errors). **error.message** `string` *required* Human-readable description. **error.details** `object` Optional structured payload - e.g. `RATE_LIMIT_EXCEEDED` returns `retry_after_seconds`; `VALIDATION_ERROR` returns the failing field list. **meta** `object` *required* Metadata included in **every** response - success or error. **properties** **meta.version** `string` *required* The v2 contract version that served the response - currently `"2.7"`. This is `major.minor`, and it **changes**: the minor bumps whenever a public default or response contract changes (see the [changelog](/api/v2/discovery/changelog)). Treat it as informational, or compare the major. Do not assert equality on the full string, or a routine release will break your client. **meta.timestamp** `string` *required* ISO 8601 wall-clock when the response was built. **meta.request\_id** `string` Per-request UUID, propagated from `X-Request-ID` header. Include this when reporting issues - we trace by it in our logs and Sentry. **meta.symbol** `string` Symbol the response is about, when applicable (e.g. `BTCUSDT`). **meta.spot\_price** `number` Current spot price for the symbol at the time of computation. Useful for normalising returns or sizing. **meta.computed\_at** `string` ISO 8601 of when the underlying cache value was last written by our worker. **meta.freshness\_seconds** `number` `now - computed_at` in seconds. Useful for staleness alarms in your code. **meta.source** `array` Upstream venue list the data flowed through, e.g. `["deribit", "bybit", "okx", "binance"]`. For provenance. **meta.exchanges\_filtered** `array` Echo of the `?exchanges=` filter when one was applied. Confirms what your filter actually narrowed to. **meta.rate\_limit** `object` `{limit, remaining, reset}` echo of the rate-limit headers. Echoed in body too so browser-based dashboards can read it without inspecting headers. **meta.extra** `object` Open dict for endpoint-specific metadata (e.g. `filter_hash` on `/options/chain`, `expiry_count` on `/options/opex`). ## Pydantic schemas Twelve of the 62 v2 endpoints have full Pydantic response schemas in the OpenAPI spec. Use them with codegen tools (`openapi-python-client`, `openapi-typescript-codegen`, etc.) to get typed client SDKs: * `GexLevelsResponse`, `GexStrikeProfileResponse`, `MaxPainResponse` * `ChainResponse`, `ExpirySummaryResponse`, `OpexResponse` * `IvTermStructureResponse`, `IvSkewResponse`, `ProbabilityDensityResponse` * `MultiGexLevelsResponse`, `TraceResponse` * Plus `ChangelogResponse` Every other endpoint is documented with examples and the same envelope shape. ## See also > **[Errors](/concepts/errors)** > > Every error code with HTTP status mapping. > **[Rate limits](/concepts/rate-limits)** > > Rate-limit headers and tier-based throttling. > **[Data freshness](/concepts/data-freshness)** > > What `computed_at` and `freshness_seconds` mean per endpoint. # Venue coverage Source: https://docs.backquant.com/concepts/venues Which exchanges BackQuant ingests for options chains, GEX, IV, levels and the tape - plus how to filter by venue. * BackQuant folds options from multiple exchanges into one surface; filter with `?exchanges=` on GEX endpoints where supported, or `?venues=` on the tape. * On `/options/chain`, `oi` is summed across venues, but `bid`, `ask` and `mark` come from Deribit only. * `?exchanges=` re-aggregates per-venue data, so exposures sum back to the total, but filtered IV is that venue's own mark IV. * On the activity fields, `null` means not measured, while `0` means measured with nothing traded. BackQuant ingests crypto options from multiple exchanges and folds them into one canonical surface. Filterable venue IDs on public v2 are: `deribit` · `bybit` · `okx` · `binance` · `derive` · `thalex` · `delta_india` · `delta` · `bullish` · `paradex` ## Venues at a glance | Venue ID | Tape | Chain / OI / GEX | How we ingest | Notes | | - | - | - | - | - | | **`deribit`** | ✅ live WS | ✅ primary depth | Public WS + REST | Deepest BTC/ETH books; primary bid/ask for IV | | **`bybit`** | ✅ live WS | ✅ multi-venue OI | V5 `publicTrade.{coin}.option` + chain REST | BTC/ETH | | **`okx`** | ✅ live WS | ✅ multi-venue OI | V5 `option-trades` (`BTC-USD` / `ETH-USD`) | Size coin-normalized at ingest | | **`binance`** | ✅ REST poll | ✅ multi-venue OI | `eapi` trades on a hot list (\~30s) | No live options WS from our infra; top-volume strikes (\~95% of venue premium) | | **`derive`** | ✅ live WS | ✅ when enabled | WS `trades.{coin}` + ticker cache | **Flag-gated on compute** (`DERIVE_*`); BTC/ETH/SOL/HYPE. **HYPE tape is live-only** (no Timescale history) | | **`thalex`** | ✅ live WS | ✅ always-on | Book channel `book.{INSTR}.none.10.100ms` `trades[]` | BTC/ETH; USD-settled premium | | **`delta_india`** | ✅ live WS | ✅ always-on | `api.india.delta.exchange` + `socket.india.delta.exchange` | BTC/ETH minis; see [units](#delta-exchange-units) | | **`delta`** | ✅ live WS | ✅ always-on | `api.delta.exchange` + `socket.delta.exchange` | Global Delta book (thinner than India) | | **`bullish`** | Off (collector paused) | ✅ always-on (since 2026-09-29) | Option ladder snapshot (REST) | BTC only, USDC-settled. Trades are not collected yet, so Bullish is not in the tape or in `flow` positioning | | **`paradex`** | Off (collector paused) | ✅ always-on (since 2026-09-29) | `markets/summary` snapshot (REST) | BTC and ETH, USD-settled. Trades are not collected yet, so Paradex is not in the tape or in `flow` positioning | ### Coins | Coin | Chain / GEX | Flow tape | | - | - | - | | **BTC**, **ETH** | Full multi-venue (all ten IDs) | The eight tape venues (Bullish and Paradex are OI only) | | **SOL**, **HYPE** | When Derive / Deribit linear HYPE are enabled | FLOW falls back to `std` if no aggressor tape | ## Where venues show up | Endpoint family | Per-venue filter? | Default behaviour | | - | - | - | | [Tape REST](/api/v2/tape/overview) | ✅ `?venues=...` | Union of all tape venues | | [Tape WebSocket](/api/v2/tape/websocket) | ✅ `tape.{coin}.{venue}` or `.agg` | Subscribe `tape.{coin}.agg` for the union | | [Flow by strike](/api/v2/tape/flow-by-strike) | ✅ `?venues=...` | Cumulative aggressor map on open contracts | | [GEX](/api/v2/gex/levels) / strike profiles / heatmaps | ✅ `?exchanges=...` where supported | Multi-venue OI roll-up; Deribit-rooted pricing | | [IV](/api/v2/options/iv/surface) | Per-venue quotes where a venue quotes the contract | Blended OI-weighted across quoting venues | | [Open interest](/api/v2/options/oi/by-expiry) | Multi-venue, with an explicit `by_venue` split | **Coin-unit OI** summed across venues | When a metric is “multi-venue blended,” OI from every venue that lists the coin is summed on normalized instrument names. Bid/ask depth for IV remains Deribit-primary. Strike-profile payloads advertise `available_exchanges` with the full chain set (including Thalex / Derive / both Delta IDs) even on thin books, so UI filters stay stable. ## Which fields are cross-venue, and which are one venue's A single row of [`/options/chain`](/api/v2/options/chain) deliberately mixes both kinds. Reading a single-venue field as a market-wide total is the most common way to misread this API, so the split is explicit: | Field | Scope | Why | | - | - | - | | `oi` | **All eight venues**, summed | Positioning is the whole market's, and OI is coin-native everywhere so it sums without conversion | | `bid`, `ask`, `mark` | **Deribit only** | Deribit is the reference book. For consolidated best bid and offer with the venue named on each side, use [`/options/venue-book`](/api/v2/options/venue-book) | | `volume`, `notional_volume`, `trade_count` | **All eight venues on BTC and ETH.** Deribit only on SOL, Derive only on HYPE | Measured over a rolling 24h from our aggressor tape, which spans every venue for BTC and ETH but not for SOL or HYPE | `volume` is contracts, `notional_volume` is **premium paid in USD**, and `trade_count` is prints. All three mean the same thing on every symbol; only the venue scope differs. > [!NOTE] > > Before 2026-09-01, `notional_volume` on HYPE carried *underlying* notional > (contracts x spot) rather than premium, because that is what Derive > publishes. It is now converted, so the field is comparable across symbols. > If you stored HYPE values before that date they are not comparable with > current ones. ### Why SOL and HYPE are not blended Publishing a "cross-venue" total that quietly omits a venue would be worse than publishing one venue's number and saying so. Our tape does not span venues for these two, so they carry the source venue's own figures and are labelled here rather than blended into something that looks complete. ### `null` is not `0` On those three activity fields the distinction is load-bearing: * **`0`** means measured, and nothing traded in the window. * **`null`** means not measured. SOL reports `trade_count: null`, because no venue publishes a per-contract trade count for it. Collapsing the two is how a contract that traded thousands of times can read as untraded. On BTC and ETH all three read `null` in the rare window where the cross-venue measurement is briefly unavailable (recovery is about 60 seconds). We report it missing rather than substituting one venue's figure, which would answer a cross-venue question with a number 10-20x smaller and give you no way to tell. ### Under `as_of` Point-in-time chains **recompute** the activity fields for the 24 hours ending at the resolved snapshot, from the same cross-venue tape, so history does not serve figures that were only ever single-venue. `meta.extra.activity_rebuilt` confirms it ran. `bid` and `ask` are different. On chains persisted before 2026-09-01 they are `0` because they were never recorded, and no recomputation can recover them. `meta.extra.quotes_note` says so on every affected response, so those zeros are never mistaken for an unquoted market. ### Counting prints and notional together A cross-venue trade count is dominated by retail flow. Over 24 hours on BTC, one venue contributed 194,063 of 255,369 prints but only $7.8M of $46M in premium, while Deribit contributed 6,060 prints and \$30M. Neither number alone describes the activity; read them together. ## What `?exchanges=` actually does The filter **re-aggregates the surface from the per-venue breakdown**. It is not a scaling of the blended number, so `?exchanges=deribit,bybit` returns exactly what those two books contribute, and the parts add back up to the unfiltered response. Two quantities behave differently, and the distinction matters when you compare a filtered result against the total: * **Exposures** (GEX, DEX, charm, vega) are `greek x open interest`, so they are linear in OI. A venue's share of the open interest is exactly its share of the exposure, and the per-venue parts sum back to the total. * **Implied volatility does not split.** IV is a quote, not a quantity. A venue-filtered IV surface or expected move reports **that venue's own mark IV**, never a share of the cross-venue blend. Unfiltered, the blend is OI-weighted across the venues quoting that contract. A venue that does not quote a contract is left out of that contract's IV rather than counted as zero. Unknown venue names are ignored, and a filter naming only unknown venues returns every venue instead of an empty response. > [!NOTE] > > A venue returning **zero** is not the same as a venue being **off**. Zero > means it lists no contracts on that surface right now; off means the feed is > disabled. `GET /v2/status` reports `chain` and `tape` per venue as > `on` / `off` / `unknown` so you can tell which you are looking at. ## Per-venue open interest: `by_venue` Open-interest surfaces carry an explicit split so you never have to infer one from repeated filtered calls: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} "by_venue": { "deribit": { "call_oi": 100.0, "put_oi": 70.0, "total_oi": 170.0, "notional_oi_usd": 10914460.0 }, "bybit": { "call_oi": 20.0, "put_oi": 15.0, "total_oi": 35.0, "notional_oi_usd": 2247330.0 } } ``` * Open interest is **coin-native** on every venue, so the cross-venue sum is meaningful without conversion. `notional_oi_usd` is `total_oi x spot` using the same spot the response reports. * **All eight venues are always present**, zero-filled, so your parsing never has to handle a missing key. * Delta India and Delta Global are **independent books** and are summed, never deduplicated. * `by_venue_unattributed` is `null` when the split reconciles with the response totals, which is the normal case. A non-null value means some open interest could not be attributed to a venue, and is shown rather than quietly dropped. Available on [OI by expiry](/api/v2/options/oi/by-expiry), [expiry summary](/api/v2/options/expiry-summary) and [GEX strike profile](/api/v2/gex/strike-profile). On the strike profile the split covers the strikes actually returned, so it tracks any strike or moneyness filter you applied. Requesting a single `expiry=` token returns whole-expiry venue OI with a `by_venue_note` saying so, because no finer per-(strike, expiry) split is published. ## Per-venue dealer flow: `include=by_venue` Open interest by venue tells you where the **stock** sits. Flow by venue tells you who is **moving it**, and the two answer different questions. [`/v2/tape/flow-by-strike?include=by_venue`](/api/v2/tape/flow-by-strike) splits each strike's aggressor flow across the venues that traded it, with `net_size` as sold minus bought. ### Why one venue is not enough Deribit carries roughly **88% of BTC option premium**. That share is why most data feeds are Deribit-only, and why a single-venue view looks like it should be sufficient. Measured over seven days of live data, restricted to contracts at least two venues traded \$5,000 or more of premium on, so thin-venue noise cannot flatter the result: | | contracts | venues disagree on direction | | - | - | -: | | BTC | 391 | **271 (69.3%)** | | ETH | 250 | **172 (68.8%)** | On 57 of those BTC contracts and 41 of the ETH ones, **Deribit alone gives the opposite sign to the aggregate**. > **BTC 86,000 call, 11 Sep** - aggregate **+38.35** > Deribit **-26.20** ($200k) · Bybit **+44.63** ($143k) · OKX **+12.72** (\$51k) A Deribit-only view reports dealers short that strike. They are long it. The 12% of premium that sits elsewhere is not a rounding error on the answer, it is frequently the whole of it. Every response carries `venue_disagreement`, so you can see how much the venues are splitting on the window you actually requested rather than taking the figures above on trust. ## Delta Exchange units Delta lists **mini** options. The public API always exposes **coin** size and USD premium already normalized. You do not need to re-apply `contract_value` on the public fields. | What you see on the API | Meaning | | - | - | | Chain OI (`oi` / totals) | **Coin** of BTC or ETH | | Tape `amount` | **Coin** | | Tape `premium_usd` | USD premium for that print | | Upstream `contract_value` | BTC `0.001` / ETH `0.01` coin per contract (ingest only) | Ingest mapping (for transparency): ```text theme={"theme":{"light":"min-light","dark":"min-dark"}} amount = size × contract_value premium_usd = price × amount ``` where upstream `size` is contracts and upstream `price` is USD per coin. Use the API’s `amount` and `premium_usd` as-is. Thalex is USD-settled: `premium_usd = price × amount` with `amount` already coin-equivalent. ## Settlement and expiry labels | Venue class | Settle time (UTC) | | - | - | | Deribit-style CEX dailies (Deribit, Bybit, OKX, Binance, Thalex, …) | **08:00** | | Delta India / Delta Global | **12:00** same calendar day | Public expiry tokens (e.g. `18AUG26`) follow the **Deribit-session** calendar used across GEX, OPEX countdowns, and TRACE: * Countdowns and 0DTE / front-expiry selection use the **08:00 UTC** cliff. * After that session roll, remaining Delta inventory for that calendar day is **rolled into the next session expiry** in the aggregated chain so strike / GEX / OI surfaces stay on one expiry ladder. * Filter and response tokens stay Deribit-style; you do not get a separate public “India 12:00” expiry id. ## Why Binance isn't on the live options WS The public Binance options WebSocket host is not reachable from our infrastructure. We poll REST trades for the most-active BTC/ETH contracts (\~30s). The hot list refreshes about every 5 minutes by 24h volume. Low-volume strikes may be missed. ## See also > **[Tape overview](/api/v2/tape/overview)** > **[Flow by strike](/api/v2/tape/flow-by-strike)** > **[Positioning](/concepts/positioning)** > **[Data freshness](/concepts/data-freshness)** # Vol surface methodology Source: https://docs.backquant.com/concepts/vol-surface How the IV surface, smiles and constant-maturity vols are built, what is checked, and what is not * Every v2 implied-vol number comes from one surface per coin, rebuilt about every 30 seconds from the multi-venue chain. * Each expiry gets its own forward and an SVI smile, used only within 2 vol points RMSE; otherwise the expiry is published as `interp`. * The grid covers listed expiries out to 180 days, and cells with no honest value are null rather than invented. * Calendar arbitrage is removed on the grid, while fitted smiles are only checked, with failures listed in `arbitrage.violations`. Every implied-vol number in v2 starts from one surface per coin, rebuilt about every 30 seconds from the multi-venue options chain. This page is the full recipe, so you can reproduce it or decide where you disagree with it. ## Inputs * **The chain.** One row per contract across every venue that lists it (see [Venues](/concepts/venues)). A contract quoted on several venues carries their implied vols blended by open interest. * **Out of the money only.** Puts below the forward, calls above, the average of the two at the money. ITM options add no information that the OTM side and parity do not already give, and their marks are noisier. * **Coordinates.** Log-moneyness `k = ln(K / F)` against each expiry's own forward, and total variance `w = sigma^2 T`, with `T` in years (calendar days / 365). ## Forwards Each expiry gets its own forward. One forward for the whole surface is only right when basis is zero, and crypto basis is not (BTC's long-dated expiries sit several percent over spot). 1. **Put-call parity.** `F = K + (C - P)` per strike in the strike currency, undiscounted, then the median across strikes. Needs at least two call/put pairs; a per-strike solve more than 50% from spot is a bad mark and is dropped first. Against Deribit's listed BTC futures the parity forward came within 0.12% at every tenor inside 180 days. 2. **Delta.** Where parity has too few pairs, the strike where the call-equivalent delta crosses 0.5. 3. **Spot.** A forward from either method must sit within `0.5% + 30% x T` of spot (the floor is mark noise, the rate is carry); outside it the next method is tried, and if neither passes the expiry is read against spot. `expiry_forwards[].method` (`parity`, `delta` or `anchor`) on [`/v2/options/iv/surface`](/api/v2/options/iv/surface) says which one each expiry used. ## Smiles Each expiry's smile is fitted with raw SVI: ``` w(k) = a + b ( rho (k - m) + sqrt((k - m)^2 + sigma^2) ) ``` * **Window.** Strikes within four ATM standard deviations (`4 x ATM vol x sqrt(T)`, at least 0.10 in log-moneyness). The deep wings carry no liquidity, and their marks are what wreck a naive fit. * **Weights.** Square root of open interest, so a dead wing mark cannot drag the fit. * **Honesty gate.** A fit is used only if it lands within 2 vol points RMSE of the quotes it was fitted to. Otherwise the expiry is published as `interp`: its quotes joined in total variance, linear in `k`. In practice that is same-day expiries, whose smile is a kink SVI cannot draw. The fitted parameters, the RMSE, the quote count and the log-moneyness span of the expiry's quotes (`svi`, `smile_rmse`, `n_quotes`, `k_range`) are on every expiry of the surface. Rebuild any smile yourself from them. Note that `k_range` can be wider than the fit window: between the window and the outermost quote the curve is the fit's continuation, which `smile_rmse` does not measure. ## The grid `z_grid[i][j]` is the vol in percent at tenor `y_grid[i]` (days) and strike `x_grid[j]`. Rows are real listed expiries out to 180 days; no tenor is invented between them. * **Strike window.** Forward plus or minus 40%, tightened to the strikes most expiries actually quote. * **No invented wings.** A fitted row is drawn at most half its own quoted span (and never more than 0.25 in log-moneyness) past its outermost quote, and never above 1.5 times its highest quoted vol. An `interp` row reaches 0.10 past its quotes. Past that, the cell is null. * **Unpriceable cells.** A cell whose out-of-the-money option is worth less than a millionth of the forward has no vol worth reporting and is null. A row left less than 35% filled is dropped whole. * **Range.** Vols outside 5% to 250% are dropped as broken marks, never clipped to the limit. Expiries past 180 days are fitted the same way and published in `long_expiries` with the same fields, but not drawn on the grid. They are there for the 6-month to 1-year constant-maturity tenors. ## Static arbitrage Two conditions make a surface arbitrage-free: no calendar arbitrage (total variance at a fixed moneyness never falls as maturity rises) and no butterfly arbitrage (every smile implies a non-negative density). **On the grid**, calendar arbitrage is removed: each cell is raised to the running maximum of total variance down the maturity axis, the smallest change that restores the condition. Where that would push a cell past what its own expiry could plausibly be worth (1.5 times its highest quoted vol), the cell becomes null instead, so a same-day wing mark is never carried forward into a later expiry. `n_calendar_lifted` counts the cells raised. **On the fitted smiles**, both conditions are checked on every response of [`/v2/options/iv/constant-maturity`](/api/v2/options/iv/constant-maturity) and [`/v2/options/iv/smile`](/api/v2/options/iv/smile), across each expiry's quoted strikes (`k_range`): * `butterfly_free`: Durrleman's condition `g(k) >= 0` everywhere in that range, where ``` g(k) = (1 - k w' / (2 w))^2 - (w'^2 / 4) (1 / w + 1 / 4) + w'' / 2 ``` The risk-neutral density is proportional to `g`, so a negative `g` is a butterfly that pays you to own it. `min_density_g` is the lowest value found. * `calendar_free`: the smile's total variance is at or above the previous fitted expiry's at every strike both quote. Failures are listed in `arbitrage.violations`; nothing is hidden or quietly repaired. On live BTC on 2026-10-11, all 13 fitted expiries (1 day to 474 days) passed both. **What is not done.** Each expiry is fitted on its own (raw SVI, not a joint SSVI surface), so the checks above are tests, not constraints of the fit. Butterfly repair is not applied to `interp` rows. A projection onto the nearest convex smile exists, and was measured on the live chains: on SOL it doubled the 90th-percentile repricing error to remove butterflies worth fractions of a cent, so it stays off until it can be bounded by each option's value. ## Constant maturity [`/v2/options/iv/constant-maturity`](/api/v2/options/iv/constant-maturity) reports ATM and the 25- and 10-delta wings at 7, 14, 30, 60, 90, 120, 180, 270 and 365 days. Per expiry: * **ATM** is the vol at the forward (`k = 0`). * **A wing** is the vol at the strike with that Black-76 forward delta, `N(d1) = delta` with `d1 = (-k + w(k) / 2) / sqrt(w(k))`. Puts are read on the call-equivalent axis, so the 25-delta put sits at call delta 0.75. * **The vol is read off the quoted smile**, not the SVI fit: total variance linear in `k` between the neighbouring quotes. Checked on live BTC, SVI matched the quotes at both 25-delta wings on most expiries but missed the December put wing by about 0.7 vol, which is most of a risk reversal. * **A wing the quotes do not reach is null.** That vol was never quoted. Per tenor, each point's total variance is interpolated linearly in time between the two fitted expiries that bracket it (sticky delta), then turned back into vol. There is no extrapolation: a tenor before the first fitted expiry or after the last is null. `bracket_dte` names the two expiries behind each tenor, and `expiries[].n_quotes` shows how deep each one's book is, so you can see when a long tenor leans on a thin expiry. Conventions, in vol points: | Field | Definition | | - | - | | `rr_25`, `rr_10` | call vol minus put vol at that delta (negative when puts are bid) | | `bf_25`, `bf_10` | average of the call and put vols minus ATM | [`/v2/options/iv/skew`](/api/v2/options/iv/skew) reports skew as put minus call. It is the same quantity with the opposite sign. ## Freshness The surface, smiles and constant-maturity vols share one build, about every 30 seconds; `meta.computed_at` is that build's time. These are current values: there is no constant-maturity history. ## See also > **[The IV suite](/concepts/iv-suite)** > **[Venues](/concepts/venues)** > **[Data freshness](/concepts/data-freshness)** > **[Constant-maturity vol](/api/v2/options/iv/constant-maturity)** # curl recipes Source: https://docs.backquant.com/examples/curl Single-shot curl one-liners for the most common v2 workflows Set your key once and reuse the variable across requests: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} export BACKQUANT_API_KEY=bq_live_your_api_key_here ``` All examples below assume that variable is set. Pipe through `jq` if you want pretty JSON. ## Health probe (no auth) ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s https://api.backquant.com/v2/health | jq ``` ## API metadata + supported symbols ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s https://api.backquant.com/v2/meta \ -H "X-API-Key: $BACKQUANT_API_KEY" | jq '.data | {name, version, supported_symbols, rate_limits}' ``` ## GEX levels with everything One call, all the optional sections: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/gex/levels?\ symbol=BTCUSDT\ &include=ranked,max_pain,expected_move,zones,spot,candles" \ -H "X-API-Key: $BACKQUANT_API_KEY" | jq '.data | {hvl: .all_expiry.hvl, max_pain, expected_move}' ``` ## OPEX calendar - anchors only, next 90 days ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/options/opex?\ symbol=BTCUSDT\ &horizon=90\ &types=monthly,quarterly" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | jq '.data.expirations[] | {expiry, type, dte, oi: .oi.total_oi, max_pain: .gamma.max_pain.strike}' ``` ## Multi-symbol GEX in one call ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/multi/gex/levels?\ symbols=BTCUSDT,ETHUSDT,SOLUSDT,HYPEUSDT\ &include=ranked,max_pain,expected_move" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | jq '.data.results | to_entries[] | {symbol: .key, hvl: .value.all_expiry.hvl, max_pain: .value.max_pain.strike}' ``` ## Filter chain to ATM ±10% with greeks only ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/options/chain?\ symbol=BTCUSDT\ &moneyness_min=0.9\ &moneyness_max=1.1\ &include=oi,iv,greeks\ &max_contracts=200" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | jq '{contracts: (.data.contracts | length), truncated: .data.truncated, total: .data.total_matching_count}' ``` ## Per-expiry max-pain ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/gex/max-pain?symbol=BTCUSDT&expiry=all" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | jq '.data.per_expiry' ``` ## Probability density with 1σ band ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/options/probability/density?\ symbol=BTCUSDT\ &confidence_band=0.68" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | jq '.data.expiries | to_entries[] | {expiry: .key, lower: .value.confidence_band.lower, upper: .value.confidence_band.upper}' ``` ## IV term structure with 30-day historical overlay ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/options/iv/term-structure?\ symbol=BTCUSDT\ &historical_compare_days=30" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | jq '{current: .data, compare_30d_ago: .data.compare}' ``` ## 30-day stress history (Postgres-backed) ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s "https://api.backquant.com/v2/gex/stress-history?\ symbol=BTCUSDT\ &days=30" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | jq '{points: (.data.timestamps | length), latest_hvl: .data.hvl[-1]}' ``` ## Watch rate-limit headers ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s -i "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT" \ -H "X-API-Key: $BACKQUANT_API_KEY" \ | grep -i "x-ratelimit" ``` ```text theme={"theme":{"light":"min-light","dark":"min-dark"}} x-ratelimit-limit: 120 x-ratelimit-remaining: 87 x-ratelimit-reset: 1714478160 ``` ## Save the OpenAPI spec for SDK codegen ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl -s https://api.backquant.com/v2/openapi.json -o backquant-v2-openapi.json # Generate a Python client (requires `openapi-python-client`): openapi-python-client generate --path backquant-v2-openapi.json # Generate a TypeScript client (requires `openapi-typescript-codegen`): openapi --input backquant-v2-openapi.json --output ./src/api-client ``` ## See also > **[Python recipes](/examples/python)** > > The same patterns wired through `requests`. > **[TypeScript recipes](/examples/typescript)** > > The same patterns wired through `fetch`. > **[Quick start](/quickstart)** > > Step-by-step from zero to first call. # Python recipes Source: https://docs.backquant.com/examples/python Copy-paste Python snippets for the most common v2 workflows These recipes use only the standard library + `requests`. No SDK required. Set your key in an env var so it doesn't get committed: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} export BACKQUANT_API_KEY=bq_live_your_api_key_here ``` ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} import os import requests API_KEY = os.environ["BACKQUANT_API_KEY"] BASE = "https://api.backquant.com" def get(path: str, params: dict | None = None) -> dict: """Tiny wrapper around requests with auth + error handling.""" r = requests.get( f"{BASE}{path}", params=params or {}, headers={"X-API-Key": API_KEY}, timeout=15, ) body = r.json() if not body["success"]: raise RuntimeError( f"{body['error']['code']}: {body['error']['message']} " f"(request_id={body['meta'].get('request_id')})" ) return body ``` ## Watch GEX levels for the universe Single call, four symbols, all the includes: ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} resp = get("/v2/multi/gex/levels", { "symbols": "BTCUSDT,ETHUSDT,SOLUSDT,HYPEUSDT", "include": "ranked,max_pain,expected_move,zones", }) for sym, data in resp["data"]["results"].items(): if data is None: print(f"{sym}: no data") continue print(f"{sym}: HVL={data['all_expiry']['hvl']} " f"Call wall={data['all_expiry']['call_resistance']} " f"Put support={data['all_expiry']['put_support']} " f"Max pain={data['max_pain']['strike']}") ``` ## Build the OPEX calendar widget Show the next 30 days of expirations with their OI and walls, sorted by DTE: ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} resp = get("/v2/options/opex", {"symbol": "BTCUSDT", "horizon": 30}) print(f"{'EXPIRY':<12} {'TYPE':<10} {'DTE':>4} {'OI':>10} {'NOTIONAL($M)':>14} {'CALL WALL':>10} {'PUT SUPP':>10}") for e in resp["data"]["expirations"]: notional_m = (e["oi"]["notional_oi_usd"] or 0) / 1e6 print( f"{e['expiry']:<12} {e['type']:<10} {e['dte']:>4} " f"{e['oi']['total_oi']:>10.0f} {notional_m:>14.1f} " f"{e['gamma']['call_resistance'] or 0:>10.0f} " f"{e['gamma']['put_support'] or 0:>10.0f}" ) next_anchor = resp["data"]["next_anchor"] if next_anchor: print(f"\nNext anchor: {next_anchor['expiry']} ({next_anchor['type']}, " f"{next_anchor['dte']}d away)") ``` ## Filter the chain to ATM ±10% with greeks only Cuts payload by \~80% vs the full chain: ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} resp = get("/v2/options/chain", { "symbol": "BTCUSDT", "moneyness_min": 0.9, "moneyness_max": 1.1, "option_type": "both", "include": "greeks,iv,oi", # per-row projection "max_contracts": 200, }) for c in resp["data"]["contracts"]: print(f"{c['strike']:>7} {c['expiry']:>10} {c['type']:>5} " f"OI={c['oi']:>5} IV={c['iv']:>5.1f} Δ={c['delta']:>+.2f} " f"Γ={c['gamma']:>+.4f}") if resp["data"]["truncated"]: print(f"\n⚠ Result truncated: matched {resp['data']['total_matching_count']}, " f"returned {len(resp['data']['contracts'])}.") ``` ## Implied price band for risk sizing Get the 1σ price band for the next monthly OPEX expiry: ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} # Find the next monthly anchor opex = get("/v2/options/opex", { "symbol": "BTCUSDT", "horizon": 60, "types": "monthly,quarterly", }) next_monthly = next((e for e in opex["data"]["expirations"] if e["is_anchor"]), None) if next_monthly: expiry = next_monthly["expiry"] # Pull the probability band for that expiry pdf = get("/v2/options/probability/density", { "symbol": "BTCUSDT", "expiry": expiry, "confidence_band": 0.68, }) band = pdf["data"]["expiries"][expiry]["confidence_band"] print(f"{expiry} 1σ implied range: ${band['lower']:.0f} - ${band['upper']:.0f}") ``` ## Paginate the GEX history Walk backwards through the time-series using cursor pagination: ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} all_points = [] cursor = None for _ in range(10): # safety cap params = {"symbol": "BTCUSDT", "limit": 200} if cursor: params["before"] = cursor resp = get("/v2/gex/history", params) page = resp["data"] all_points.extend(zip(page["timestamps"], page["total_gex"])) if not page["timestamps"]: break cursor = page["next_cursor"] print(f"Pulled {len(all_points)} history points, oldest={all_points[-1][0]}") ``` ## Backtest setup - historical levels Pull 30 days of HVL / walls / net GEX for a regime study: ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} resp = get("/v2/gex/stress-history", {"symbol": "BTCUSDT", "days": 30}) import pandas as pd df = pd.DataFrame({ "ts": pd.to_datetime(resp["data"]["timestamps"]), "spot": resp["data"]["spot_price"], "hvl": resp["data"]["hvl"], "call_wall": resp["data"]["call_wall"], "put_wall": resp["data"]["put_wall"], "net_gex": resp["data"]["net_gex"], }) df["above_hvl"] = df["spot"] > df["hvl"] df["distance_to_call_wall_pct"] = (df["call_wall"] - df["spot"]) / df["spot"] * 100 print(df.tail()) print(f"\n% of time above HVL: {df['above_hvl'].mean():.1%}") ``` ## Health check before each polling cycle For long-running dashboards: ```python theme={"theme":{"light":"min-light","dark":"min-dark"}} import time def health_ok() -> bool: """Auth-free health probe.""" r = requests.get(f"{BASE}/v2/health", timeout=5) return r.json()["data"]["status"] in ("ok", "degraded") while True: if not health_ok(): print("API unhealthy, sleeping 30s") time.sleep(30) continue # do your work levels = get("/v2/gex/levels", {"symbol": "BTCUSDT", "include": "max_pain,expected_move"}) # ... render dashboard ... time.sleep(30) ``` ## See also > **[TypeScript recipes](/examples/typescript)** > > Same patterns in TypeScript / Node.js. > **[curl recipes](/examples/curl)** > > Pure-curl one-liners for each common workflow. > **[Errors](/concepts/errors)** > > Robust error handling with backoff. > **[Rate limits](/concepts/rate-limits)** > > Stay under your tier without hitting 429. # TypeScript recipes Source: https://docs.backquant.com/examples/typescript Copy-paste TypeScript / Node.js snippets for the most common v2 workflows These recipes use the standard `fetch` API. No SDK required. Set your key in an env var: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} export BACKQUANT_API_KEY=bq_live_your_api_key_here ``` ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}} const API_KEY = process.env.BACKQUANT_API_KEY!; const BASE = "https://api.backquant.com"; interface ApiResponse { success: boolean; data?: T; error?: { code: string; message: string; details?: unknown }; meta: { version: string; request_id?: string; [k: string]: unknown }; } async function get( path: string, params: Record = {}, ): Promise> { const qs = new URLSearchParams( Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])), ); const url = `${BASE}${path}${qs.toString() ? `?${qs}` : ""}`; const r = await fetch(url, { headers: { "X-API-Key": API_KEY } }); const body: ApiResponse = await r.json(); if (!body.success) { throw new Error( `${body.error!.code}: ${body.error!.message} ` + `(request_id=${body.meta.request_id})`, ); } return body; } ``` ## Watch GEX levels for the universe ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}} const resp = await get("/v2/multi/gex/levels", { symbols: "BTCUSDT,ETHUSDT,SOLUSDT,HYPEUSDT", include: "ranked,max_pain,expected_move,zones", }); const results = (resp.data as { results: Record }).results; for (const [sym, data] of Object.entries(results)) { if (data === null) { console.log(`${sym}: no data`); continue; } console.log( `${sym}: HVL=${data.all_expiry.hvl} ` + `Call wall=${data.all_expiry.call_resistance} ` + `Put support=${data.all_expiry.put_support} ` + `Max pain=${data.max_pain.strike}`, ); } ``` ## OPEX calendar widget ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}} const resp = await get("/v2/options/opex", { symbol: "BTCUSDT", horizon: 30 }); const data = resp.data as { expirations: any[]; next_anchor: any }; console.table( data.expirations.map((e) => ({ expiry: e.expiry, type: e.type, dte: e.dte, oi: e.oi.total_oi, notional_m: ((e.oi.notional_oi_usd ?? 0) / 1e6).toFixed(1), call_wall: e.gamma.call_resistance, put_support: e.gamma.put_support, })), ); if (data.next_anchor) { console.log( `Next anchor: ${data.next_anchor.expiry} (${data.next_anchor.type}, ` + `${data.next_anchor.dte}d away)`, ); } ``` ## Filter the chain to ATM ±10% with greeks only ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}} const resp = await get("/v2/options/chain", { symbol: "BTCUSDT", moneyness_min: 0.9, moneyness_max: 1.1, option_type: "both", include: "greeks,iv,oi", max_contracts: 200, }); const data = resp.data as { contracts: any[]; truncated: boolean; total_matching_count: number; }; console.table(data.contracts); if (data.truncated) { console.warn( `Result truncated: matched ${data.total_matching_count}, ` + `returned ${data.contracts.length}.`, ); } ``` ## 1σ implied price band for the next monthly OPEX ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}} const opex = await get("/v2/options/opex", { symbol: "BTCUSDT", horizon: 60, types: "monthly,quarterly", }); const expirations = (opex.data as any).expirations; const nextAnchor = expirations.find((e: any) => e.is_anchor); if (nextAnchor) { const pdf = await get("/v2/options/probability/density", { symbol: "BTCUSDT", expiry: nextAnchor.expiry, confidence_band: 0.68, }); const band = (pdf.data as any).expiries[nextAnchor.expiry].confidence_band; console.log( `${nextAnchor.expiry} 1σ implied range: $${band.lower.toFixed(0)} - $${band.upper.toFixed(0)}`, ); } ``` ## Paginate GEX history ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}} const allPoints: [string, number][] = []; let cursor: string | undefined; for (let i = 0; i < 10; i++) { const params: Record = { symbol: "BTCUSDT", limit: 200, }; if (cursor) params.before = cursor; const resp = await get("/v2/gex/history", params); const page = resp.data as { timestamps: string[]; total_gex: number[]; next_cursor: string | null }; page.timestamps.forEach((t, idx) => allPoints.push([t, page.total_gex[idx]])); if (page.timestamps.length === 0) break; cursor = page.next_cursor ?? undefined; } console.log(`Pulled ${allPoints.length} history points`); ``` ## Backoff on rate-limit errors ```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}} async function getWithBackoff(path: string, params = {}, maxAttempts = 4): Promise> { for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await get(path, params); } catch (e: any) { if (e.message.startsWith("RATE_LIMIT_EXCEEDED")) { const wait = 60 * 2 ** attempt; // 60s, 120s, 240s, 480s console.warn(`Rate limited, sleeping ${wait}s`); await new Promise((r) => setTimeout(r, wait * 1000)); continue; } throw e; } } throw new Error("Max rate-limit retries exceeded"); } ``` ## See also > **[Python recipes](/examples/python)** > > Same patterns in Python. > **[curl recipes](/examples/curl)** > > Pure-curl one-liners. > **[Errors](/concepts/errors)** > > Robust error handling. > **[Rate limits](/concepts/rate-limits)** > > Per-tier limits + backoff strategy. # Introduction Source: https://docs.backquant.com/introduction Professional-grade options + GEX analytics for crypto markets BackQuant is a professional market-data API focused on **crypto options analytics**: gamma exposure (GEX), implied volatility surfaces, dealer positioning, max-pain, OPEX calendar, and risk-neutral probability distributions, alongside price, open interest, funding, CVD, liquidations and spot ETF flows. The same data that powers the BackQuant terminal is available programmatically, ready to plug into your trading systems, research workflows, and dashboards. It is also available over [MCP](/mcp-server) for Claude and other AI clients. > **[Get your API key](https://backquant.com/api-access)** > > Sign up at **backquant.com/api-access**. This API is for personal-use plans. No redistribution is permitted - see our [Terms of Service](https://www.backquant.com/terms) for details. Custom and Enterprise plans are available; contact [dev@backquant.com](mailto:dev@backquant.com). ## Base URL ```text theme={"theme":{"light":"min-light","dark":"min-dark"}} https://api.backquant.com/v2/ ``` ## Get started > **[Authentication](/authentication)** > > Get your API key and authenticate your first request. > **[Quick start](/quickstart)** > > Make your first API call in under five minutes. > **[API reference](/api/v2/gex/levels)** > > Browse the full endpoint set with interactive try-it-out. > **[Code examples](/examples/python)** > > Python, TypeScript, and curl recipes for common workflows. ## What's covered The API is focused on the data crypto options traders actually trade against. Chains and GEX refresh about every 30 seconds from a multi-venue aggregator (Deribit, Bybit, OKX, Binance, plus Derive / Thalex / Delta India / Delta Global when live - see [Venue coverage](/concepts/venues)). | Category | What's in it | | - | - | | **GEX** | Composable levels (HVL / call wall / put support / max-pain / expected move / gamma flip zones), strike + expiry profiles, heatmaps, history, stress history, dealer/gamma/delta flow | | **Options chain** | Filtered chain with two-layer projection (`?fields` + `?include`), expiry summary table, OPEX calendar with daily/weekly/monthly/quarterly classification | | **IV analytics** | Surface, term structure, 25Δ/10Δ skew, butterfly, full smile per expiry, single-expiry smile, IV-RV history, VRP | | **Greeks** | DEX, theta, vanna, charm, vega: per-strike profiles, strike × time surfaces, 3D surface, TRACE field + summary | | **Tape** | Multi-venue REST + WebSocket, imbalance, strike heat, whale prints, **flow by strike**, optional live GEX levels channel | | **Probability** | Breeden-Litzenberger PDF/CDF + 3D probability surface + confidence-band interpolation | | **Open interest** | By expiry (coin-unit multi-venue), time-series with cursor pagination, put/call ratio (intraday + daily) | | **Market data** | OHLCV candles with buy/sell split, aggregate open interest, funding, CVD on both perp and spot, liquidations over time, plus a bundle returning all of them on one shared time axis | | **ETF flows** | Daily creations and redemptions per fund with running cumulatives, premium/discount to NAV, assets under management | | **Cross-venue book** | Best bid and offer across every venue with the venue named on each side, spread and quote dispersion per contract | | **Other** | Premium tide (0DTE + weekly), dated-futures basis, liquidation heatmap + distribution, multi-symbol GEX bundle | Live dealer exposure defaults to **flow** positioning (`?positioning=std` for textbook OI sign). See [Positioning](/concepts/positioning). ## Concepts before reference If you're new to options analytics or just want a refresher on what this data means in trader terms: > **[What is GEX?](/concepts/gex)** > > Gamma exposure, dealer positioning, walls and support levels. > **[Flow vs std](/concepts/positioning)** > > How live GEX and TRACE sign dealer size. > **[OPEX calendar](/concepts/opex)** > > Why monthly and quarterly expirations matter. > **[Max pain](/concepts/max-pain)** > > The strike where option writers profit most. > **[Probability density](/concepts/probability-density)** > > Breeden-Litzenberger explained for traders. > **[The IV suite](/concepts/iv-suite)** > > Surface, term structure, skew, smile. > **[Greeks beyond delta](/concepts/greeks)** > > DEX, vanna, charm, vega - what they measure. # What to ask it Source: https://docs.backquant.com/mcp-examples Example questions for the BackQuant MCP server, and which tool each one reaches for. The server is designed to be used in plain language. You do not name tools; your AI client picks them. These are questions that work well, grouped by what you are trying to find out. > [!NOTE] > > Everything here is **crypto**. BTC and ETH have the deepest coverage; SOL and > HYPE are supported where data exists. ## Start here > **"What is going on with BTC?"** > Price against the dealer-gamma levels, perpetual positioning and the options > tape, in one call. It names anything it could not fetch instead of quietly > returning less. > *(`get_brief`)* > **"Anything worth knowing on ETH right now?"** > *(`get_brief`)* Reach for a specific tool once the question names a metric; `get_brief` is for open questions. ## Perpetuals > **"Is open interest building on BTC?"** > *(`get_perps`, `view=oi`)* > **"What is funding doing, are shorts paying longs?"** > *(`get_perps`, `view=funding`)* > **"Show me spot versus perp CVD."** > Aggressor delta on both sides, which is where spot and perp demand diverge. > *(`get_perps`, `view=cvd`)* ## Dealer positioning and levels Good opening questions when you want a read on the market: > **"What are the gamma levels on BTC right now?"** > Returns the gamma flip, call wall and put support, with spot for context. > *(`get_gex_levels`)* > **"Compare BTC and ETH dealer positioning."** > Both markets side by side in one call, rather than asking twice. > *(`compare_symbols`)* > **"Which strikes carry the most gamma on ETH?"** > The strike-by-strike breakdown behind the headline levels. > *(`get_gex_strike_profile`)* ## What is actually trading > **"What has been trading on ETH today?"** > Recent prints and aggregates over them. > *(`get_tape`)* > **"Where did aggressors buy and sell calls on BTC this week?"** > Bought against sold, strike by strike, on still-open contracts. > *(`get_flow_by_strike`)* > **"Show me the biggest BTC options trades today."** > Whale prints, filtered by premium. > *(`get_tape`)* > **"Has dealer hedging pressure been building on BTC?"** > Hedging pressure over time rather than a single snapshot. > *(`get_dealer_flow`)* ## Volatility and what the market is pricing > **"What move is the market pricing for the next BTC expiry?"** > The implied move from ATM volatility for that expiry. > *(`get_expected_move`)* > **"What are the odds BTC finishes above 70k this month?"** > Market-implied probability of finishing above or below a level. > *(`get_probability`)* > **"How does ETH implied vol look across expiries?"** > The term structure, plus smile and skew cuts if you ask for them. > *(`get_iv`)* ## Structure and expiries > **"What expiries are listed for BTC?"** > Valid expiry tokens, useful before asking anything expiry-specific. > *(`list_expiries`)* > **"How much open interest rolls off at the next BTC expiry?"** > Upcoming expiries and the size expiring at each. > *(`get_opex`)* > **"Where is max pain for the front ETH expiry?"** > *(`get_max_pain`)* > **"Give me an overview of every BTC expiry."** > Dates, days to expiry, ATM vol and open interest in one table. > *(`get_expiry_summary`)* ## Over time > **"How has BTC gamma changed over the last two weeks?"** > A metric's history rather than its current value. > *(`get_history`)* > **"Has open interest been rising on ETH?"** > *(`get_oi`)* ## Spot ETF flows > **"Are the spot ETFs buying or selling BTC?"** > Daily net creations and redemptions across every US spot fund. > *(`get_etf_flows`)* > **"Which fund took the money in the last session?"** > The per-fund split for the latest settled day, not just the total. > *(`get_etf_flows`)* > **"How much has gone into the ETH ETFs since launch?"** > The running cumulative rather than a single day. > *(`get_etf_flows`)* ETF data is daily and publishes on trading days, so the most recent point can be a day or more old over a weekend or holiday. The response carries the date it belongs to, and a good answer will say which day it is quoting. ## Positioning reports and filings > **"How are hedge funds positioned in CME bitcoin futures?"** > Leveraged funds, asset managers and dealers, net, with the weekly change and > where it sits in three years of history. > *(`get_filings`)* > **"Are Coinbase insiders selling?"** > Form 4 buys and sells at COIN over recent months. > *(`get_filings`)* > **"Who are the biggest holders of IBIT, and who added last quarter?"** > The top 13F holders with the quarter-on-quarter change. > *(`get_filings`)* COT is weekly (positions as of Tuesday, released Friday) and 13F is quarterly with a lag of up to 45 days after the quarter, so these describe positioning as of the report date, not now. A good answer will quote that date. ## Getting better answers **Name the market.** "BTC" or "ETH" beats "bitcoin's options" for getting the right tool called first time. **Say the timeframe out loud.** "over the last two weeks" produces a history call; without it you get a snapshot of right now. **Ask for one thing at a time.** A question bundling levels, flow and vol becomes three tool calls, each drawing on your quota. Ask, read, follow up. **Use real expiry tokens.** If you want a specific expiry, ask for the list first, then reference one. The model can also do this itself in two steps. **Ask it to explain its answer.** The tools return numbers; the interpretation is your client's. "Why does that matter for spot?" usually produces a better read than asking for the interpretation up front. ## What it will not do * **Equities.** The API is crypto-only, so no SPX, NDX or single stocks. * **Trade for you.** It is read-only market data. There are no order tools. * **See your screen.** Unlike the AI panel in the terminal, MCP has no view of your workspace or panels. It only knows what it queries. * **Predict.** It reports positioning, flow and pricing. Any forecast is your client reasoning over those numbers, not a BackQuant output. # Limits and errors Source: https://docs.backquant.com/mcp-limits How MCP tool calls consume your plan, what happens when a limit is hit mid-conversation, and how to read the errors your AI client shows. MCP runs on the **same entitlements as the REST API**. There is no separate MCP allowance, and nothing here is charged differently: a tool call is a request. ## How a conversation consumes your plan | | | | - | - | | One tool call | One request against your monthly allowance | | One question | Often **several** tool calls | | A JSON-RPC batch | Charged **per `tools/call` inside it**, not once per request | | Per-minute burst | Shared with REST, same ceiling | | Inference | Not billed by us. Your AI client uses your own subscription | A single question routinely fires three to ten tool calls: the model may list expiries, fetch levels, then pull flow before answering. Budget by questions rather than by requests, and assume a handful of requests per question. See [Rate limits](/concepts/rate-limits) for the per-plan numbers. > [!NOTE] > > Because a burst of calls arrives inside one answer, the starter burst allowance > is set well above one call per question. Hitting the ceiling mid-answer is the > failure this protects against. ### Batching Most clients send one tool call per request and never think about this. If yours batches, two rules apply: * Each `tools/call` in the batch is charged separately, so a batch costs the same as sending its calls one at a time. * **A single batch may carry at most 50 `tools/call` messages.** Beyond that the whole batch is rejected with a JSON-RPC `Invalid Request` reading `too many tools/call in one batch; the limit is 50 per request`, and nothing in it runs. Split it and resend. Fifty is far above what a question needs (three to ten is typical), so this is a guard against runaway automation rather than a limit normal use meets. ## When something goes wrong Errors are returned to your AI client as readable text, so you generally see an explanation in the conversation rather than a status code. ### You are not connected The client shows an authorization prompt, or reports that the server requires sign-in. Reconnect from your client's settings. Tokens are tied to the BackQuant account that approved them, so this also appears after you revoke a connection. If you connect with an API key rather than OAuth, the same situation reads as `the access token is invalid or expired`. That means the key was mistyped, revoked, or belongs to a plan without API access. Check it at the API dashboard rather than reconnecting, since there is no sign-in step to repeat. ### Your plan does not include API access You are told during sign-in, before the connection completes, rather than by a confusing tool failure later. Terminal Monthly has no API access; a trial does not grant it either. See [the plan table](/mcp-server) for what qualifies. ### Your subscription is not active Same point in the flow as above. Access resumes when the subscription is active again; nothing needs reconnecting on our side. ### Monthly quota exhausted The tool returns the API's own message, which names the cap, and your client relays it mid-conversation. Nothing is broken; the allowance is spent for the calendar month. Quotas run per user, not per key, and reset at the start of the next month in UTC. ### Too many calls too quickly A burst above the per-minute ceiling is rejected until the window rolls. In practice this means waiting a few seconds and asking again. ### The data is not available Some answers do not exist for some markets: a symbol with no aggressor tape, an expiry that is not listed, a history window longer than what is stored. The tool says so rather than returning an empty result that reads as zero. ### A tool failed unexpectedly You will see the tool name and the error type. Nothing internal is exposed to the conversation. If it repeats, send us the tool name and roughly when it happened. ## Response size Every tool has a token budget, and responses are shaped to stay inside it. A raw options chain would be correct and useless: it would fill your client's context window and leave no room for reasoning. So tools return ranked, trimmed answers rather than complete dumps. Practically, that means asking for "the top strikes by gamma" works well, and asking for "every strike and every expiry" gets you a sensible subset. If you need the complete surface, use the [REST API](/api/v2/gex/strike-profile), which has no such budget. ## Coverage boundaries * **Crypto only.** Equities are not available through the API or MCP. * **BTC and ETH** have the fullest coverage, including multi-venue aggressor tape. SOL and HYPE are supported where data exists. * **Historical depth varies by dataset** and is shorter than live coverage. If a window comes back shorter than you asked for, that is the stored depth, not an error. # MCP server Source: https://docs.backquant.com/mcp-server Use BackQuant's crypto options, GEX and flow data directly inside Claude, Cursor or any MCP client. OAuth sign-in or an API key, 23 tools, no code required. BackQuant ships a remote **Model Context Protocol** server, so the same data behind the v2 API is available conversationally inside your own AI client. ``` https://mcp.backquant.com/mcp ``` `https://api.backquant.com/mcp` is the same server if your client prefers the API hostname. There are two ways in. Clients that speak OAuth (Claude, Cursor, VS Code) need nothing but your BackQuant login: sign in once, approve, done. Clients that only send a static header (the OpenAI Responses API, LangChain, n8n, your own scripts) use your existing API key as a bearer token. Either way your AI client calls the tools on your behalf and reads the answers back to you. > [!NOTE] > > The server carries **crypto only**. Equities are not exposed through the API or > MCP for upstream licensing reasons. ## What you need An active subscription that includes API access. That means any of: | Plan | Includes MCP | | - | - | | Crypto API (`api_only_monthly` / `api_only_yearly`) | Yes | | Terminal + API | Yes | | Terminal Yearly | Yes, at starter limits | | Terminal Monthly | No, this plan has no API access | Your subscription must be **active**. A trial does not grant API access, so it does not grant MCP either. If you connect without an eligible plan the server tells you that during sign-in rather than failing later with a confusing tool error. ## Connect with your login For **Claude**, **Cursor**, **VS Code** and anything else that speaks OAuth. If your client only sends a static header, skip to [clients that do not do OAuth](#clients-that-do-not-do-oauth) instead. **1. Add the server to your client** Point your MCP client at `https://mcp.backquant.com/mcp`. In **Claude** (Desktop or Web), add it under Settings, Connectors, Add custom connector. In **Cursor**, add it as a remote MCP server. Any client that speaks Streamable HTTP with OAuth will work the same way. **2. Sign in** Your client opens a BackQuant login page. Sign in with the account that holds your subscription and approve the connection. Clients that support **dynamic client registration** (RFC 7591) register themselves automatically, so there is nothing to configure by hand. **3. Ask something** Try *"What are the BTC gamma levels right now?"* or *"Where did aggressors buy calls on ETH today?"* The client picks the right tool and calls it. ## Clients that do not do OAuth Some clients never run the sign-in dance. They send one static header and expect that to be the whole story: the **OpenAI Responses API**, **LangChain**, **n8n**, and anything you write yourself. For those, pass your BackQuant API key as a bearer token. It is the same key you already create at [the API dashboard](https://backquant.com/api-dashboard), not a separate MCP credential, and it carries the same plan and quota. ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl https://mcp.backquant.com/mcp \ -H "Authorization: Bearer bq_live_your_key_here" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' ``` With the OpenAI Responses API, give the tool your MCP URL and put the same key in `authorization`. OpenAI does not perform the OAuth exchange itself, so the bearer path is the supported route there: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "type": "mcp", "server_label": "backquant", "server_url": "https://mcp.backquant.com/mcp", "authorization": "bq_live_your_key_here" } ``` > [!NOTE] > > Treat the key like any other secret. A bearer token is sent on every call, so > prefer an environment variable over pasting it into a shared workflow, and > rotate it from the dashboard if it is ever exposed. ## Local clients that only speak stdio Older MCP clients launch a local process and talk to it over stdin/stdout rather than calling a URL. Bridge them with `mcp-remote`, which runs locally and forwards to the server. It needs Node; there is nothing to install by hand, `npx` fetches it on first run. Add this to your client's MCP config (`claude_desktop_config.json` for Claude Desktop, `.cursor/mcp.json` for Cursor): ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "mcpServers": { "backquant": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.backquant.com/mcp"] } } } ``` That opens a browser once to sign in, then caches the token. To skip the browser entirely, hand the bridge your API key instead: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "mcpServers": { "backquant": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.backquant.com/mcp", "--header", "Authorization: Bearer bq_live_your_key_here" ] } } } ``` Restart the client after editing its config. If you already connect over a URL, you do not need any of this. ## Tools Twenty-three tools, grouped by what you would ask for. Each returns a shaped, compact answer rather than a raw dump, so a question costs a sensible number of tokens. **Start here** | Tool | Answers | | - | - | | `get_brief` | Everything at once for one market: price against the dealer-gamma levels, perpetual positioning, and what the options tape has been doing. One call instead of three, and it names the parts it could not fetch rather than going quiet on them. Best first move for an open question; go straight to a specific tool when the question names a metric. | **Positioning and levels** | Tool | Answers | | - | - | | `get_gex_levels` | Gamma flip (HVL), call wall, put support for a market | | `get_gex_strike_profile` | Dealer gamma broken down by strike | | `get_greek` | Dealer exposure for a single greek, in several cuts | | `get_max_pain` | The strike where the most option value expires worthless | | `compare_symbols` | Gamma levels for several assets side by side, in one call | **Flow and tape** | Tool | Answers | | - | - | | `get_tape` | Individual prints and aggregates over them. `hours` sets the lookback, 1 to 168, so "what traded in the last hour" and "biggest print this week" are both askable | | `get_flow_by_strike` | Where aggressors actually bought and sold, strike by strike. Pass `strike` to ask about one level directly, e.g. "is anyone buying calls at 100k"; if that strike is not open it answers for the nearest and says so | | `get_dealer_flow` | Dealer hedging pressure over time, measured from the tape | **Volatility and pricing** | Tool | Answers | | - | - | | `get_iv` | Implied volatility in whichever cut you need | | `get_expected_move` | The move the market is pricing for an expiry. `view=calibrated` (BTC, ETH) gives realistic 68% and 90% price bands for the next 1, 4 and 24 hours, rescaled to how far price has actually moved, with how often they have held | | `get_probability` | Market-implied odds of finishing above or below a price | | `get_basis` | The dated-futures curve, annualised basis by expiry. For perpetual basis use `get_perps`, which reports `perp_basis_bps` | **Structure and history** | Tool | Answers | | - | - | | `get_oi` | Open interest by expiry or over time | | `get_expiry_summary` | Every listed expiry with DTE, ATM IV and open interest | | `get_opex` | Upcoming expiries and how much OI rolls off at each | | `get_history` | How a positioning metric has changed over time | | `get_liquidation` | Where leveraged perp positions get liquidated | | `get_perps` | The perpetual-futures picture: open interest, funding, spot and perp CVD, liquidations, and the perpetual basis to spot. `view=` narrows it to one of those. | **Spot ETFs** | Tool | Answers | | - | - | | `get_etf_flows` | Daily net creations and redemptions for the US spot crypto ETFs, per fund and in total, plus the cumulative since launch | **Positioning reports and filings** | Tool | Answers | | - | - | | `get_filings` | Public regulatory data, weekly or quarterly. CFTC Commitments of Traders for CME bitcoin and ether and other futures markets (how leveraged funds, asset managers and dealers are net positioned, the weekly change and its 3-year percentile), SEC Form 4 insider trades at COIN and HOOD, and the top 13F holders of MSTR, COIN, HOOD and IBIT | **Discovery** | Tool | Answers | | - | - | | `list_symbols` | Which markets are covered and which have data right now | | `list_expiries` | Valid expiry tokens to pass to the other tools. You rarely need it first: any tool taking `expiry` also accepts `next` for the front expiry | ## What every response carries Alongside the numbers, tool results carry: | Field | What it tells you | | - | - | | `summary` | The answer in a sentence, for the model to relay. On every tool | | `units` | The scale of the numbers, so you never have to assume one | | `as_of` | When the data was computed, not when you asked. On a multi-source answer this is the OLDEST input, because a result is only as current as its stalest part | | `data_age_seconds` | How old it is, in seconds | | `venues` | Which exchanges the answer was built from | A few tools carry fewer of these, for reasons rather than by oversight. `list_symbols` and `list_expiries` report no `units`, because they return identifiers rather than measurements. `list_symbols` also reports no `as_of`: it lists which markets exist, and stamping it would describe when you asked rather than anything about the data. `get_history` and `get_etf_flows` report no `venues`, because a series spanning weeks or a fund's daily creations are not attributable to one set of exchanges. Two stamps mean exactly what they say, and are worth reading carefully: * `get_etf_flows` reports the **settlement date**, so `data_age_seconds` is measured in days. These figures settle daily and are a business day or more behind by design. That is the honest number: the cache behind them refreshes every few minutes, and reporting that would tell you three-day-old flows were two minutes old. * For bucketed series such as `get_perps`, `as_of` is the **newest bucket**, so it trails by up to one `interval`. A one-hour series reading 50 minutes old is current, not stale. The `interval` field tells you which. ### The tools do not share one snapshot Each tool answers from its own read, and the underlying feeds move at different speeds. The trade tape is continuous. Dealer gamma is snapshotted periodically. Spot ETF flows settle daily and are a business day old by design. So a single question can combine numbers computed minutes apart. That is normal and usually harmless, but it is yours to see rather than guess at: compare `as_of` across two results and the gap is explicit. When one answer draws on several sources, it also carries `source_skew_seconds` and a note naming which input lags: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "as_of": "2026-09-07T01:05:12+00:00", "data_age_seconds": 1785.7, "venues": ["binance", "bybit", "deribit", "okx"], "source_skew_seconds": 1785.7, "source_skew_note": "inputs span 1786s: /gex/dealer-flow is the oldest, /tape/stats the freshest. Not one snapshot." } ``` We do not publish a single snapshot ID across all 23 tools. It would imply the feeds were captured at one instant, and they are not. ## Limits MCP runs on the **same entitlements as the REST API**. There is no separate MCP quota: a tool call is a request, counted against your plan's monthly allowance and per-minute burst exactly like a REST call. See [Rate limits](/concepts/rate-limits). One question from a model often fires several tool calls in quick succession, which is why the starter burst allowance is set well above one call per question. ## How it relates to the AI panel They are complements, not substitutes. | | AI panel in the terminal | MCP | | - | - | - | | Requires | A terminal plan | An API plan | | Runs | In the BackQuant terminal | In your own AI client | | Data | Everything the terminal shows | Crypto v2 data | | Sees your screen | Yes, it reads your panels | No | The panel gives depth inside our product. MCP puts our data inside your workflow. ## Technical details For anyone implementing a client rather than using one: | | | | - | - | | Transport | Streamable HTTP | | Protocol revisions | `2025-06-18` (preferred), `2025-03-26` | | Authorization | OAuth 2.1, PKCE required (`S256`), or an API key as a bearer token | | Grants | `authorization_code`, `refresh_token` | | Discovery | `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource` | | Client registration | Dynamic, RFC 7591, at `/oauth/register` | | Issuer | `https://api.backquant.com` | Private-use redirect schemes (RFC 8252) are accepted, so desktop clients that use a custom scheme rather than a loopback URL connect without special-casing. > [!NOTE] > > Tokens are scoped to the BackQuant account that approved them. Revoking the > connection in your AI client, or the subscription lapsing, stops access at the > next call. ## See also > **[What to ask it](/mcp-examples)** > **[Limits and errors](/mcp-limits)** # Quick start Source: https://docs.backquant.com/quickstart Make your first v2 API call in under five minutes This guide takes you from "I have an account" to "I have a working integration" in five steps. **1. Get your API key** Sign in at [**backquant.com/api-access**](https://backquant.com/api-access) and create a key. Your key looks like: ```text theme={"theme":{"light":"min-light","dark":"min-dark"}} bq_live_<32-character-token> ``` **2. Make your first call** Hit the composable GEX levels endpoint for BTC. Replace `YOUR_API_KEY` with your actual key: ```bash curl theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT" \ -H "X-API-Key: YOUR_API_KEY" ``` ```python Python theme={"theme":{"light":"min-light","dark":"min-dark"}} import requests resp = requests.get( "https://api.backquant.com/v2/gex/levels", params={"symbol": "BTCUSDT"}, headers={"X-API-Key": "YOUR_API_KEY"}, ) print(resp.json()) ``` ```typescript TypeScript theme={"theme":{"light":"min-light","dark":"min-dark"}} const resp = await fetch( "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT", { headers: { "X-API-Key": "YOUR_API_KEY" } }, ); console.log(await resp.json()); ``` **3. Read the envelope** Every response uses the same envelope. A successful call returns: ```json theme={"theme":{"light":"min-light","dark":"min-dark"}} { "success": true, "data": { "all_expiry": { "hvl": 67000, "call_resistance": 70000, "call_wall_2": 72500, "call_wall_3": 75000, "put_support": 64000, "put_wall_2": 60000, "put_wall_3": 58000 }, "odte": { "hvl": 67500, "call_resistance": 68000, "put_support": 66500 } }, "meta": { "version": "2.7", "timestamp": "2026-04-30T12:00:00.123Z", "request_id": "req_a1b2c3...", "symbol": "BTCUSDT", "spot_price": 67213.5, "computed_at": "2026-04-30T11:59:48.000Z", "freshness_seconds": 12.0, "source": ["deribit", "bybit", "okx", "binance"] } } ``` Always check `success` before reading `data`. On failure you get `success: false` and an `error` object instead - see [Errors](/concepts/errors) for the full code list. The `meta` block tells you how stale the data is (`freshness_seconds`), which venues it came from (`source`), and the spot price at the time of computation (`spot_price`). Use `request_id` when reporting issues. **4. Add a filter or include** Most endpoints support filters. Here's the same call with the composable `?include=` parameter and an exchange filter: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/gex/levels?\ symbol=BTCUSDT\ &exchanges=deribit,bybit\ &include=ranked,max_pain,expected_move,zones" \ -H "X-API-Key: YOUR_API_KEY" ``` Now the response includes the top-10 ranked strikes by |GEX|, the 0DTE max-pain strike, the 1σ/2σ expected move from ATM straddles, and the gamma flip zones - in one call. Common filters you'll use across the API: * `?symbol=BTCUSDT|ETHUSDT|SOLUSDT|HYPEUSDT` - supported symbols * `?exchanges=deribit,bybit,okx,binance,derive,thalex,delta_india,delta` - venue filter (omit for all) * `?expiry=all|0dte|` - single expiry slice * `?dte_min` / `?dte_max` / `?weekly_only=true` - DTE-axis filter * `?moneyness_min` / `?moneyness_max` - strike/spot ratio bounds * `?positioning=flow|std` - dealer signing (default flow on live GEX) * `?include=...` - opt-in to optional response sections * `?fields=...` - top-level projection on heavy endpoints Live GEX defaults to flow positioning. Details: [Positioning](/concepts/positioning). **5. Try the OPEX calendar** For a more product-y example, get the next 30 days of options expirations with their classification, OI, walls, and max-pain in one call: ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}} curl "https://api.backquant.com/v2/options/opex?symbol=BTCUSDT&horizon=30" \ -H "X-API-Key: YOUR_API_KEY" ``` The response gives you a ranked calendar of upcoming expirations, each tagged `daily | weekly | monthly | quarterly`, with the next monthly/quarterly anchor highlighted under `next_anchor`. See [the OPEX concept page](/concepts/opex) for what these mean and how to use them. ## What to read next > **[What is GEX?](/concepts/gex)** > > The single most-asked concept question. Read this before integrating. > **[Response format](/concepts/response-format)** > > Full envelope schema, every meta field explained. > **[Rate limits](/concepts/rate-limits)** > > Per-tier limits, response headers, retry strategy. > **[Code examples](/examples/python)** > > Python, TypeScript, curl recipes for common workflows. > **[OPEX calendar](/concepts/opex)** > > Daily/weekly/monthly/quarterly classification, anchor expirations. > **[API reference](/api/v2/gex/levels)** > > All 62 endpoints, with interactive try-it-out.