# Changelog
Source: https://docs.backquant.com/api/v2/discovery/changelog
GET /changelog
Returns the v2 release log plus the lifecycle status of v1. Customers integrating against either version use this to decide when to schedule re-integration work — `breaking: true` on any release is the signal to test, and `v1_status` flips to `deprecated` / `sunset` long before v1 is ever turned off.
Response is static — no I/O, sub-millisecond — so polling it from a CI job daily is free.
Static release log with per-version `breaking` flags. Customers integrating against the API use this to decide when to schedule re-integration work - `breaking: true` on any release is the signal to test.
# Endpoint catalog
Source: https://docs.backquant.com/api/v2/discovery/endpoints
GET /endpoints
Introspects the running FastAPI sub-app and returns one entry per route with path, HTTP methods, tag, summary, description, and the parameter list. Each parameter carries its name, location (`query` / `path` / `header`), required flag, type, default, numeric bounds (if any), and `enum` array when the parameter is Literal-typed.
Derived from in-memory route metadata — no Redis or DB I/O. Requires `X-API-Key`. For totally anonymous discovery use `/v2/openapi.json` (FastAPI built-in).
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
# Active expiries
Source: https://docs.backquant.com/api/v2/discovery/expiries
GET /expiries
Currently-tradeable expiry tokens for a symbol with DTE and an ISO date when the token is parseable. Use this to populate filter UIs without pulling the heavier /options/expiry-summary.
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
# API metadata
Source: https://docs.backquant.com/api/v2/discovery/meta
GET /meta
Static metadata about the v2 API. Supported symbol and exchange catalogs, the full error code list with HTTP status mapping, the auth scheme, the rate-limit ladder, the response envelope shape, and links to the OpenAPI spec / Swagger UI / ReDoc / endpoint catalog. Cheap (in-memory, no I/O).
Requires `X-API-Key` like every other v2 route — anonymous discovery is via `/v2/openapi.json` (FastAPI standard, no auth).
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
# Service status
Source: https://docs.backquant.com/api/v2/discovery/status
GET /status
Operational status. Each (symbol × category) cell is classified as `healthy`, `degraded`, `unhealthy`, or `unavailable` based on cache age. The `overall_status` is the worst case across the grid. Thresholds in the response reflect the underlying compute cadence.
Operational status: per-symbol-per-category freshness with healthy/degraded/unhealthy classification + an overall aggregate.
## See also
# Symbol catalog
Source: https://docs.backquant.com/api/v2/discovery/symbols
GET /symbols
Lists every symbol covered by v2 with the data categories that are currently live, the freshness of each category, the spot price, the count of active expiries, and the full set of supported endpoints. Recommended first call for new integrations.
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
# Dealer flow (time series)
Source: https://docs.backquant.com/api/v2/gex/dealer-flow
GET /gex/dealer-flow
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
# Dealer flow by level
Source: https://docs.backquant.com/api/v2/gex/dealer-flow-by-level
GET /gex/dealer-flow/by-level
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
# Delta flow (customer)
Source: https://docs.backquant.com/api/v2/gex/delta-flow
GET /gex/delta-flow
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
# GEX by expiry
Source: https://docs.backquant.com/api/v2/gex/expiry-profile
GET /gex/expiry-profile
Net / call / put gamma per expiration date with days-to-expiry. Useful for spotting where dealer exposure rolls off.
DTE-axis filters:
* `dte_min` / `dte_max` — bound the DTE window.
* `weekly_only=true` — exclude monthly + quarterly anchors (defined as the last Friday of the month). Tokens we cannot parse are kept rather than silently dropped.
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
# Gamma flow
Source: https://docs.backquant.com/api/v2/gex/gamma-flow
GET /gex/gamma-flow
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
# GEX history
Source: https://docs.backquant.com/api/v2/gex/history
GET /gex/history
Time-series of total GEX (and net-local-5pct, call/put walls when available). Use `?limit` (≤1000) to cap the page size and `?before=` to walk backwards into older history.
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
# GEX levels
Source: https://docs.backquant.com/api/v2/gex/levels
GET /gex/levels
All-expiry HVL/walls plus 0DTE-specific levels in a single call. Use `?include=` to opt in to optional sections — `spot,candles,zones,max_pain,expected_move,ranked`. Filter by exchange with `?exchanges=deribit,bybit,okx,binance`.
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
# Max pain
Source: https://docs.backquant.com/api/v2/gex/max-pain
GET /gex/max-pain
The strike at which option writers profit most at expiry. Use `?expiry=0dte` for the cheapest path (read straight from the 0DTE levels cache), `?expiry=all` for a per-expiry table covering every active expiration, or pass a specific expiry token (e.g. `28MAR25`) to also receive the full pain curve by strike.
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
# Stress history (Postgres)
Source: https://docs.backquant.com/api/v2/gex/stress-history
GET /gex/stress-history
Persistent record of HVL / call wall / put wall / net GEX, suitable for backtests or regime analysis. Window is bounded to 30 days (720 hours) so a query never blows out the read pool.
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
# Strike × expiry heatmap
Source: https://docs.backquant.com/api/v2/gex/strike-expiry-heatmap
GET /gex/strike-expiry-heatmap
Rows are strikes, columns are expiries (sorted ascending DTE).
Filters apply BEFORE downsampling so the cap parameters operate on the already-narrowed grid:
* `dte_min` / `dte_max` — drop expiries outside the DTE window.
* `moneyness_min` / `moneyness_max` — drop strikes outside the moneyness window (e.g. `0.8..1.2` keeps ATM ±20%).
* `max_strikes` — cap on strikes returned (kept closest to spot).
* `max_expiries` — cap on expiries (kept nearest by 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
# GEX by strike
Source: https://docs.backquant.com/api/v2/gex/strike-profile
GET /gex/strike-profile
Gamma exposure aggregated by strike. `?expiry=all` (default) covers every expiry, `?expiry=0dte` filters to today's expiry only, and an explicit expiry token (e.g. `28MAR25`) returns just that expiration.
Strike-axis filters (intersected when more than one is set):
* `strike_min` / `strike_max` — absolute bounds.
* `moneyness_min` / `moneyness_max` — strike/spot ratio bounds, e.g. `0.9..1.1` keeps the ATM ±10% slab.
* `spot_window_pct` — symmetric window in percent (e.g. `5` for ±5% of spot). Combined by intersection with the others.
When any strike filter narrows the set, `ranked_top_10` is rebuilt from the surviving strikes — the cached pre-filter ranking is not reused.
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).
## See also
# Strike × time heatmap
Source: https://docs.backquant.com/api/v2/gex/time-heatmap
GET /gex/time-heatmap
Time-series of greek exposures by strike. Pick the greek (`gex|dex|vex|charm|vanna`) and bound by DTE / OI / IV. Without filters this serves the pre-computed cache (cheap); with filters it reconstructs from raw snapshots (more work, but still bounded to the last ~48 ticks).
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
# Health check
Source: https://docs.backquant.com/api/v2/health
GET /health
Lightweight liveness/readiness probe for external monitors. Reports overall status plus per-subsystem (redis, db) flags. Auth-free — uses the same `check_connection_health()` Fly.io's load balancer hits at `/health`. Rate limit: **60/minute per IP**.
Status values: `ok` (all subsystems healthy), `degraded` (one subsystem failing), `unhealthy` (cache + DB both unreachable).
Auth-free liveness probe for monitors. Returns subsystem status (Redis + Postgres).
## See also
# Liquidation distribution
Source: https://docs.backquant.com/api/v2/liquidation/distribution
GET /liquidation/distribution
Per-price-level long and short liquidation volumes, with a leverage tier estimate based on distance from current price and cumulative-volume series for visualizing concentration.
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
Source: https://docs.backquant.com/api/v2/liquidation/heatmap
GET /liquidation/heatmap
Estimated liquidation levels normalized to a 0–100 scale, gridded by price bin and time bucket. Includes the OHLC candles plotted alongside so the heatmap can be rendered standalone.
Estimated liquidation levels normalized to a 0–100 scale, gridded by price bin and time bucket. Includes OHLC candles for standalone rendering.
# Multi-symbol GEX levels
Source: https://docs.backquant.com/api/v2/multi/gex-levels
GET /multi/gex/levels
Multi-symbol GEX levels in one round-trip. Returns a `results` map keyed by symbol with the same shape per-entry as /v2/gex/levels. Symbols with no current data return null in `results` and appear in `missing` rather than failing the whole request. Up to 8 symbols per request. Includes: `ranked` (top-10 strikes by |GEX|), `max_pain` (0DTE only), `expected_move` (1σ / 2σ implied range), `zones` (gamma flip zones).
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
# Options chain
Source: https://docs.backquant.com/api/v2/options/chain
GET /options/chain
Per-contract data (greeks, OI, IV, bid/ask) plus aggregate stats. Filters supported:
* `expiries` — CSV of tokens (e.g. `28MAR25,25APR25`).
* `dte_min` / `dte_max` — days-to-expiry bounds.
* `oi_min` / `oi_max` — open interest bounds.
* `delta_min` / `delta_max` — applied to |delta| (0..1).
* `strike_min` / `strike_max` — absolute strike bounds.
* `moneyness_min` / `moneyness_max` — strike / spot ratio bounds, e.g. `0.9..1.1` keeps strikes within ±10% of spot. Useful for cross-symbol screens since it normalises the strike grid.
* `option_type` — `call`, `put`, or `both`.
Two layers of projection:
* `fields` (top-level): comma-sep subset of `aggregates,available_expiries,contracts` — drops whole sections of the response, e.g. `?fields=contracts` to skip aggregates.
* `include` (per-contract): comma-sep subset of `bid_ask,gex,greeks,iv,oi,volume` — keeps only the named sub-sections of each contract dict. Core identity fields (`strike,expiry,dte,type`) are always present. Default is every section, matching v1 shape.
Payload is capped at `?max_contracts=` (default 2000, hard ceiling 5000). On overflow the response is truncated to the closest-to-spot subset and `truncated=true` is set with `total_matching_count` so the client knows to narrow the filter.
Response is cached for 30s keyed by the exact filter combination, so repeated calls with identical filters are sub-millisecond.
Filtered chain with two-layer projection (`?fields=` top-level, `?include=` per-contract) and a payload cap (`?max_contracts`). Cached 30s per filter combination.
## See also
# Expected move
Source: https://docs.backquant.com/api/v2/options/expected-move
GET /options/expected-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
# Per-expiry summary
Source: https://docs.backquant.com/api/v2/options/expiry-summary
GET /options/expiry-summary
One row per active expiry with the metrics most useful for chain-wide screening:
**Pre-computed every 30s** — ATM IV, 25Δ skew, put/call 25Δ IVs, PCR, call/put/total OI, net GEX.
**Added by v2 (OPEX enrichment, on-the-fly):**
* `type` — `daily | weekly | monthly | quarterly` (Deribit token parsed; quarterlies are last Fridays of Mar/Jun/Sep/Dec).
* `is_anchor` — true for monthly + quarterly anchors.
* `expiry_date` — ISO date when the token parses cleanly.
* `notional_oi_usd` — `total_oi × spot_price` (each contract is 1 unit of underlying on Deribit-style chains).
* `max_pain` — `{strike, value}` from a per-expiry max-pain pass over the latest chain snapshot. Cached 30s, shared with `/v2/gex/max-pain` and `/v2/options/opex`.
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).
## See also
# Futures term structure
Source: https://docs.backquant.com/api/v2/options/futures/term-structure
GET /options/futures/term-structure
Deribit-listed dated futures annualized basis vs spot, by maturity. Useful as a complement to the IV term structure for assessing carry in the calendar.
Deribit-listed dated futures annualized basis vs spot, by maturity. Useful as a complement to the IV term structure for assessing carry.
## See also
# 3D greek surface
Source: https://docs.backquant.com/api/v2/options/greeks/3d-surface
GET /options/greeks/3d/surface
Z = sum(greek × OI) per (strike, expiry) cell. Useful for 3D visualizations or for spotting where a particular greek is concentrated across the term structure.
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
# Greek profile
Source: https://docs.backquant.com/api/v2/options/greeks/by-greek
GET /options/greeks/{greek}
Net greek exposure per strike. `greek` is one of `delta` (DEX), `theta`, `vanna`, `charm`, `vega`.
Filters:
* `exchanges` — comma-separated venue filter.
* `moneyness_min` / `moneyness_max` — strike/spot ratio bounds, e.g. `0.9..1.1` keeps ATM ±10% exposure only.
* `dte_max` — re-aggregates from the raw chain to limit contributing contracts to `DTE ≤ dte_max`. The default cached greeks profile spans every active expiry; this lets you isolate near-term exposure. Cached 30s per `(symbol, greek, dte_max, exchanges)` so repeated requests stay cheap.
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
# Greek surface (strike × time)
Source: https://docs.backquant.com/api/v2/options/greeks/surfaces
GET /options/greeks/surfaces/{greek}
Time-evolving surface for `charm` or `vega` — strike on one axis, time on the other. Already pre-computed for the pro terminal; exposed here for quant tooling that wants the same view.
Time-evolving surface for `charm` or `vega`. Already pre-computed for the BackQuant terminal; exposed here for quant tooling.
## See also
# Greek TRACE - forward (time × price) projection
Source: https://docs.backquant.com/api/v2/options/greeks/trace
GET /options/greeks/trace/{greek}
Black-Scholes forward projection of dealer **gamma**, **charm**, or **vanna** across a (time × price) grid, holding today's chain constant and decaying τ toward each contract's expiry.
Returns a 2D `field` of cell values, the `time_axis_ms` and `price_axis` it's keyed on, and the contracts it consumed. Intended for desk-grade dealer-positioning visualisations (the BackQuant Pro Terminal's TRACE panel runs the same math client-side).
**Sign convention:** +1 for calls, −1 for puts — positive cell = call book dominates the put book at that (time, price).
**Units:**
* `gamma` → `$ per 1% spot move` (γ × S² × OI × 0.01).
* `charm` → 1/year (∂Δ/∂τ × OI).
* `vanna` → per IV unit (∂Δ/∂σ × OI).
**Performance:** the field is computed on cache miss in ~50–100 ms for typical grids. Cached for 30 s per `(symbol, full-param-hash)` so identical repeated calls are essentially free.
**What-if vol shifts:** `vol_shift_pct` multiplies every contract's σ by `1 + vol_shift_pct/100` before evaluation. Lets you build vol-up / vol-down surfaces without re-fetching.
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 is aggressor sold
minus bought per side. A positive cell means the signed book leans
supportive at that `(time, price)`; negative means the reverse.
## Units
| `greek` | Unit | Formula per cell (std qty = OI; flow qty = sold−bought) |
| -------------- | ------------------- | ------------------------------------------------------------ |
| `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
# Greek TRACE summary
Source: https://docs.backquant.com/api/v2/options/greeks/trace-summary
GET /options/greeks/trace/{greek}/summary
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
# All-expiry smile curves
Source: https://docs.backquant.com/api/v2/options/iv/curves
GET /options/iv/curves
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 vs realized vol history
Source: https://docs.backquant.com/api/v2/options/iv/iv-rv
GET /options/iv/iv-rv
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
# Skew (25Δ / 10Δ + butterfly)
Source: https://docs.backquant.com/api/v2/options/iv/skew
GET /options/iv/skew
Delta-interpolated risk-reversal and butterfly per active expiry. `skew_25d` = 25Δ put IV − 25Δ call IV (positive = put premium, fear bid); `butterfly_25d` = (25Δ put IV + 25Δ call IV) / 2 − ATM IV (smile curvature / wing pricing). Computed from real delta-bracketed options, not strike proxies.
Delta-interpolated risk-reversal and butterfly per active expiry. `skew_25d` = 25Δ put IV − 25Δ call IV.
## See also
# Single-expiry smile
Source: https://docs.backquant.com/api/v2/options/iv/smile
GET /options/iv/smile
Implied volatility by strike for one expiry — the cheap path when you only need a single tenor. Pass `?expiry=` as a Deribit-style token (e.g. `28MAR25`); omit it to receive the default front-month smile from `iv:smile:{symbol}:default`. The lookup is case-insensitive on the token; if neither cased form exists, this returns 404 with the available tokens hinted in the error.
IV by strike for one expiry. Lighter than `/iv/curves` when you only need one tenor.
## See also
# IV surface
Source: https://docs.backquant.com/api/v2/options/iv/surface
GET /options/iv/surface
2D implied-volatility grid: rows are strikes, columns are expiries (sorted by ascending DTE). Useful for vol-surface visualisations and identifying skew/term-structure shape at a glance.
2D implied-volatility grid: rows are strikes, columns are expiries (sorted by ascending DTE).
## See also
# Term structure
Source: https://docs.backquant.com/api/v2/options/iv/term-structure
GET /options/iv/term-structure
At-the-money implied volatility for each active expiration.
Filters:
* `exchanges` — comma-separated venue filter.
* `dte_max` — drop tenors with DTE > dte_max (focus on the front of the curve).
* `historical_compare_days=N` — also include the constant-maturity ATM IV from N days ago, sourced from the `iv_history` Postgres table. Returned as a separate `historical` block under `compare`. Different shape from the live curve — historical is by DTE-bucket (7/14/30/60/90/180/365) since that's how the worker stores it; the client can interpolate onto the live tenor grid for visualisation.
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
# OI by expiry
Source: https://docs.backquant.com/api/v2/options/oi/by-expiry
GET /options/oi/by-expiry
Open interest aggregated by expiration with calls and puts split.
DTE-axis filters:
* `dte_min` / `dte_max` — bound the DTE window.
* `weekly_only=true` — exclude monthly + quarterly anchors (last-Friday-of-month). Useful when monthly OI dominates the picture and you want to see the weekly distribution alone.
Open interest aggregated by expiration with calls and puts split. `?weekly_only=true` excludes monthly + quarterly anchors.
## See also
# OI history
Source: https://docs.backquant.com/api/v2/options/oi/history
GET /options/oi/history
Time-series of total / call / put open interest. Use `?limit` to cap page size and `?before=` to walk backwards into older history (the cursor is the timestamp of the oldest row in the previous page, returned as `next_cursor`).
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.
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
# OPEX calendar
Source: https://docs.backquant.com/api/v2/options/opex
GET /options/opex
Curated calendar of upcoming options expirations with everything needed for OPEX-day workflows in one call:
* Token + ISO date + DTE + classification (daily / weekly / monthly / quarterly) + `is_anchor` flag.
* OI block: `call_oi`, `put_oi`, `total_oi`, `pcr`, `notional_oi_usd` (= total_oi × spot, since each crypto options contract is 1 unit of underlying).
* 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), and `max_pain` (`{strike, value}` from the shared per-expiry cache used by `/v2/gex/max-pain` and `/v2/options/expiry- summary`).
`next_anchor` points at the closest monthly/quarterly. Filter with `?types=monthly,quarterly` for the institutional-roll view or `?horizon=` to bound the look-ahead window.
Curated calendar of upcoming expirations with type classification, anchor flag, OI, IV, gamma blocks (walls + max-pain), and `next_anchor` pointer.
## See also
# Put / call ratio
Source: https://docs.backquant.com/api/v2/options/pcr
GET /options/pcr
OI-weighted put/call ratio. `?granularity=intraday` returns the rolling intraday series; `daily` returns one value per UTC day.
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`** - rolling intraday series, refreshed every 30s. Use for live dashboards.
* **`?granularity=daily`** - one value per UTC day, sourced from the historical store. Use for time-series studies and backtests.
Filter the daily mode with `?days=N` (1–365) to control window length.
## See also
# Premium tide
Source: https://docs.backquant.com/api/v2/options/premium-tide
GET /options/premium-tide
Net premium and notional volume from Deribit-side trades, split into call and put flow. Use `?horizon=0dte` for today's tape or `weekly` for the rolling weekly aggregate.
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.
# Probability density
Source: https://docs.backquant.com/api/v2/options/probability/density
GET /options/probability/density
Breeden-Litzenberger probability density derived from option prices. Returns PDF, normalized PDF, CDF (above and below spot), and distribution stats (mean, std, mode, skew, kurtosis, 25/50/75 percentile prices) per expiry.
Filters:
* `expiry` — slice to a single expiry token (case-insensitive).
* `dte_max` — drop expiries with DTE > dte_max so near-term clients can avoid carrying the long tail.
* `confidence_band` — when set to `0.68` or `0.95` (the most common choices), each expiry payload gets a `confidence_band` object with `lower` / `upper` price levels containing that much implied probability mass, computed by interpolating the CDF.
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
# Probability surface
Source: https://docs.backquant.com/api/v2/options/probability/surface
GET /options/probability/surface
Risk-neutral probability density across the full strike × expiry grid as a single matrix, suitable for 3D visualisations or surface-fit applications. Built from the same Breeden-Litzenberger extraction as /options/probability/density but pre-aligned onto a unified strike axis so cross-expiry comparisons are direct.
Risk-neutral PDF across the full strike × expiry grid as a single matrix, suitable for 3D visualizations.
## See also
# Volatility risk premium
Source: https://docs.backquant.com/api/v2/options/vrp
GET /options/vrp
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
# Tape imbalance
Source: https://docs.backquant.com/api/v2/tape/imbalance
GET /tape/imbalance
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
# 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 and Binance.
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.
* **4 venues** ingested continuously (see [Venue coverage](#venue-coverage)).
* **5-year retention** in the persistence layer. Backfill of history
before `2026-05-17` is available on request via our archive partner.
* **Sub-second freshness** on the WebSocket. REST `/recent` reflects
the latest trade as soon as it lands in Postgres (\~200ms after the
venue WS emits it).
* **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.
## Venue coverage
| Venue | Source | Latency to tape | Coverage |
| ----------- | ------------------------------------------------------------------------------------- | --------------- | --------------------------------------------------------------- |
| **Deribit** | Native public WebSocket | \< 200 ms | Full BTC + ETH chains, every trade |
| **Bybit** | Native public WebSocket (`publicTrade.{coin}.option`) | \< 200 ms | Full BTC + ETH option chains |
| **OKX** | Native public WebSocket (`option-trades` channel, `BTC-USD` + `ETH-USD` instFamilies) | \< 200 ms | Every trade on listed BTC + ETH instruments |
| **Binance** | REST polling: top-30 most-active BTC + ETH contracts every 30 s | \~ 30 s | Top-volume strikes only (\~95% of total Binance options volume) |
Binance polls the most-active contracts rather than every instrument
because the Binance options public WS endpoint is not reachable from
our infrastructure. The hot-contract list refreshes every 5 minutes by
24-hour volume.
## Coins
* `BTC` and `ETH` are fully covered across all four venues.
* `SOL` and `HYPE` are accepted by the API schema (some venues are
starting to list them) but coverage is currently thin to non-existent.
Filter for those at your own risk.
## Tape vs derived GEX
| Surface | Live? | History? | Positioning |
| ------------------------------------- | ------------------ | ------------------------ | ------------------------------------------- |
| **Tape** (this section) | Yes, multi-venue | Yes, 5y cursor-paginated | N/A (raw trades) |
| **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) for flow vs std.
If you need deep history of GEX, IV or levels (not the raw tape),
contact us for a custom engagement using replayed chain snapshots.
## Quick start
```bash curl - recent trades theme={null}
curl https://api.backquant.com/v2/tape/recent?symbol=BTCUSDT&limit=5 \
-H "X-API-Key: $BQ_API_KEY"
```
```python Python - WebSocket theme={null}
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())
```
Need a working demo client? Grab
[`ws_tape_demo.py`](https://github.com/backquant/backquant-terminal/blob/main/scripts/v2_ws_demo/ws_tape_demo.py)
from the repo. Single file; needs `pip install websockets`.
## See also
# Tape — recent trades
Source: https://docs.backquant.com/api/v2/tape/recent
GET /tape/recent
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
# Tape — aggregate stats
Source: https://docs.backquant.com/api/v2/tape/stats
GET /tape/stats
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={null}
{
"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.0", "computed_at": "...", "source": [...] }
}
```
## See also
# Tape strike heat
Source: https://docs.backquant.com/api/v2/tape/strike-heat
GET /tape/strike-heat
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
# Tape — full filterable history
Source: https://docs.backquant.com/api/v2/tape/tape
GET /tape
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
# Tape — live WebSocket
Source: https://docs.backquant.com/api/v2/tape/websocket
Channel-multiplexed live stream of every options trade across Deribit, Bybit, OKX and Binance, 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}` | GEX levels snapshot (HVL, walls, spot) | When levels update (\~30 s) |
| `status.heartbeat` | Server liveness + per-client queue stats | Every 15 s |
`{coin}` is `BTC` or `ETH`. `{venue}` is `deribit`, `bybit`, `okx`, or
`binance` (case-insensitive on subscribe; the server normalises to
lowercase). `{SYMBOL}` is `BTCUSDT`, `ETHUSDT`, `SOLUSDT`, or
`HYPEUSDT`. See [Tape overview → Venue coverage](/api/v2/tape/overview#venue-coverage)
for what each venue ships.
**Levels caveat:** `gex.levels.*` pushes a **std** (textbook) levels
snapshot. REST [`/v2/gex/levels`](/api/v2/gex/levels) defaults to
**flow**. See [Positioning](/concepts/positioning).
Max **48 channels per connection**.
## Protocol
### Server → client envelope
Every server frame is a JSON object with an `event` field:
```json theme={null}
{ "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", "hvl": ...,
"call_resistance": ..., "put_support": ...,
"spot_price": ..., "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={null}
{ "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={null}
{
"venue": "deribit", // "deribit" | "bybit" | "okx" | "binance"
"coin": "BTC", // "BTC" | "ETH"
"instrument": "BTC-30JUN26-100000-C",
"trade_id": "12345",
"direction": "buy", // "buy" | "sell" (taker side)
"option_type": "call", // "call" | "put"
"strike": 100000.0,
"expiry_date": "2026-06-30",
"amount": 1.5, // contracts
"price": 0.0525, // venue-quoted (BTC for Deribit/OKX, USDT for Bybit/Binance)
"mark_price": 0.0530, // venue mark at trade time (when published)
"index_price": 67234.10, // spot index at trade time (when published)
"iv": 65.4, // implied vol % (when published)
"premium_usd": 5293.60, // canonical USD premium = price × amount × (index_price OR 1)
"is_block_trade": false,
"ts_ms": 1748480000000
}
```
`premium_usd` is the field to rank, filter or score by — venue quotation differences are already normalised away.
## 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.
## Per-tier connection caps
| Tier | Concurrent WS connections per API key |
| ----------- | ------------------------------------- |
| API Monthly | 2 |
| API Yearly | 4 |
| Enterprise | 10 (customisable) |
Exceeding the cap on connect returns close code `1008` with `Connection cap reached for tier 'X' (max N)`.
## 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={null}
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
# Whale prints
Source: https://docs.backquant.com/api/v2/tape/whale
GET /tape/whale
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
# 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
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={null}
curl https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT \
-H "X-API-Key: bq_live_your_api_key_here"
```
```python Python theme={null}
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={null}
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={null}
bq_live_<32-character-token>
```
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={null}
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Missing X-API-Key header"
},
"meta": {
"version": "2.0",
"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
Make your first API call.
Python, TypeScript, and curl recipes.
Per-tier limits and backoff strategy.
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 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** | Pulled from Deribit / Bybit / OKX / Binance, computed by our worker |
| **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 / OI history** | **30 seconds** | Append-only history of options trade flow |
| **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 |
## Reading freshness from a response
Every successful response includes:
```json theme={null}
{
"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={null}
{
"service": "BackQuant API v2",
"version": "2.0",
"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
Every meta field documented in detail.
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
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={null}
{
"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.0",
"timestamp": "2026-04-30T12:00:00Z",
"request_id": "req_a1b2c3..."
}
}
```
## All error codes
| Code | HTTP status | When it fires |
| --------------------- | --------------- | ----------------------------------------------------------------------------------------- |
| `UNAUTHORIZED` | 401 | Missing, invalid, or revoked `X-API-Key` header. |
| `FORBIDDEN` | 403 | Key is valid but the subscription tier doesn't allow this resource. |
| `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-tier rate limit hit. Retry after `meta.rate_limit.reset` or the `Retry-After` header. |
| `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={null}
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"))
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={null}
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);
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`** — Read `Retry-After` header (or
`meta.rate_limit.reset`) and wait. The
[`/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)
* `X-Request-ID` — the same UUID as `meta.request_id`
## See also
Per-tier limits and how to think about backoff.
Avoiding `UNAUTHORIZED` errors.
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`**: dealer size is aggressor
sold minus bought from the multi-venue options tape, not textbook OI
sign. Walls and HVL can shift when flow 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={null}
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
**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 Deribit, Bybit, OKX, and Binance,
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 is multi-venue aggressor **sold minus bought**
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={null}
{
"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 all four 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
How live GEX and TRACE sign dealer size, and when history stays std.
The strike where option writers profit most. Different metric from
GEX walls but often near them.
Vanna, charm, vega: what they measure and how to use them alongside
GEX.
Estimated hedge pressure across chain snapshots (gamma, charm, total).
Why monthly/quarterly expirations dominate the GEX picture for a
few days each month.
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
tape (sold minus bought per call/put side). 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 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`** (tape sold−bought size).
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={null}
{
"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` — 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
Gamma is the special case. Start there before reading the higher
order greeks.
How profiles and TRACE sign dealer size.
Forward time × price projection of dealer greeks.
Vanna and charm shape how the implied PDF evolves.
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 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) | Full 2D IV grid (strike × expiry) | 3D vol-surface viz |
| [`/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) | Single-expiry smile | 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 full strike × expiry grid of implied volatilities. Best rendered as
a 3D surface or contour plot. Returns:
* `x_grid` — strikes
* `y_grid` — expiry tenors (DTE)
* `z_grid` — IV values at each (strike, expiry) cell
* `min_iv` / `max_iv` — for color scaling
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.
## 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 the same data
for *one* expiry. 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` — 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
The Breeden-Litzenberger PDF derived from the IV surface.
GEX is computed using the same per-contract greeks as IV — the
suites are complementary.
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 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={null}
{
"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={null}
{
"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={null}
{
"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
The other major source of expiry-day price magnets.
Anchor expirations have the strongest max-pain signal.
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
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={null}
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={null}
{
"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={null}
{
"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` — 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
The data this bundles is the same as `/v2/gex/levels` per symbol.
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
**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={null}
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={null}
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
The pinning strike on each expiry — a key OPEX-day signal.
The walls / HVL / support levels OPEX positioning concentrates in.
Term structure flattening / steepening around OPEX is a big signal.
# 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
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) | Dealer size = aggressor **sold minus bought** per call/put side from the multi-venue options tape | Best match to how price behaves when tape exists (BTC, ETH) |
| **`std`** | Textbook convention: call OI positive, put OI negative | Backtests, comparisons to public GEX, or symbols without 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:
1. **Chain** - multi-venue options chain (Deribit, Bybit, OKX, Binance) for greeks and OI.
2. **Tape (flow only)** - multi-venue aggressor trades for the same coins. Size is coin-normalized so venues can be summed.
GEX dollar unit is unchanged: **γ × S² × qty × 0.01** (USD impact per 1%
spot move), where `qty` is flow sold-bought or std signed OI.
Details also appear under `meta.extra.aggregation` (`chain_venues`,
`flow_venues`, `flow_method`, `gex_scale`, `note`).
## Coin coverage
| Symbol | Flow tape | Default behaviour |
| ----------------- | --------- | --------------------------------------------------------- |
| BTCUSDT, ETHUSDT | Yes | Flow when tape is non-empty |
| SOLUSDT, HYPEUSDT | No | Auto-fallback to **std** (`no_aggressor_tape_for_symbol`) |
If flow is empty for BTC/ETH, the API also falls back to std and sets a
reason such as `flow_tape_empty` or `flow_qty_all_zero`.
## Live vs history
| Surface | Positioning |
| ------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Levels, strike profile, expiry profile, heatmaps, TRACE, multi levels, greek profiles | **flow** default |
| `/v2/gex/history`, `/v2/gex/stress-history` | **std** only (stored series) |
| WebSocket `gex.levels.{SYMBOL}` | **std** snapshot (REST levels stay flow by default) |
| 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/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
# Probability density
Source: https://docs.backquant.com/concepts/probability-density
Risk-neutral PDF / CDF via Breeden-Litzenberger — the market's implied price distribution
**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={null}
{
"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={null}
{
"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 surface that underpins this density extraction.
A simpler scalar measure of "where price wants to be" at expiry.
Vanna and charm shape how the PDF evolves between now and expiry.
# Rate limits
Source: https://docs.backquant.com/concepts/rate-limits
Per-tier limits, response headers, and backoff strategy
Rate limits are enforced per API key based on your subscription tier.
Exceeding your limit returns a `429` response with the
`RATE_LIMIT_EXCEEDED` error code and a `Retry-After` header.
## Limits by tier
| Tier | Requests per minute | Annual savings |
| -------------- | ------------------- | ---------------------- |
| **Monthly** | 60 | — |
| **Yearly** | 120 | \~15% off monthly × 12 |
| **Enterprise** | Unlimited | Custom |
[Get or upgrade your key at backquant.com/api-access](https://backquant.com/api-access).
## Rate limit headers
Every response — success **or** error — includes:
| Header | Description |
| ----------------------- | ----------------------------------------- |
| `X-RateLimit-Limit` | Your 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) |
The same triple is also echoed inside `meta.rate_limit` in the response
body, so browser-based dashboards can read it without inspecting
headers.
```bash theme={null}
curl -I "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT" \
-H "X-API-Key: YOUR_API_KEY"
```
```text theme={null}
HTTP/2 200
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1714478160
```
## What happens at the limit
1. The 121st request in a minute (on the Yearly tier) returns:
```json theme={null}
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 32 seconds.",
"details": {
"retry_after_seconds": 32,
"limit": "120 per 1 minute"
}
},
"meta": { ... }
}
```
2. HTTP status is `429`.
3. `Retry-After: 32` header tells you exactly how long to wait.
4. `X-RateLimit-Remaining: 0` confirms you're out for the window.
## Strategy: how to think about throttling
**Cache aggressively at your end.** The data refreshes every 30s
server-side anyway. If you're showing GEX levels in a dashboard,
polling once every 30 seconds (= 2 req/min per panel) gives you the
freshest possible data without burning quota. Polling every second is
wasted — you'd see the same `computed_at` 30 times in a row.
**Use multi-symbol bundling.** Hitting
[`/v2/multi/gex/levels`](/api/v2/multi/gex-levels) once for all four
symbols counts as **one** request, not four. For desk dashboards
watching the universe, this is the difference between 240/min (4
symbols × 60s) and 60/min on the Monthly tier.
**Use composable `?include=`.** `/v2/gex/levels?include=ranked,max_pain,
expected_move,zones` returns four blocks of data in one request that
would otherwise take four separate calls.
**Project payloads with `?fields` and `?include`.** Heavy endpoints
like `/v2/options/chain` support both top-level (`?fields=contracts`)
and per-row (`?include=oi,iv`) projection. Lighter responses = faster
parsing client-side and lower egress on our side.
**Implement exponential backoff on 429.** Wait `Retry-After` seconds,
then if you 429 again, wait 2× as long. Don't hammer.
```python theme={null}
import time, requests
def call_with_backoff(url, headers, max_attempts=4):
for i in range(max_attempts):
r = requests.get(url, headers=headers)
if r.status_code != 429:
return r
delay = int(r.headers.get("Retry-After", 60))
time.sleep(delay * (2 ** i)) # 60s, 120s, 240s, 480s
raise RuntimeError("rate-limit retries exhausted")
```
**Monitor `X-RateLimit-Remaining`.** When it drops below \~10% of your
limit, throttle yourself rather than waiting for a 429. Better to slow
down voluntarily than to handle errors after the fact.
## Endpoint cost notes
Most endpoints are pure cache reads (\~1ms server-side). A handful do
on-the-fly computation:
* `/v2/options/chain` with novel filter combos — first call computes,
subsequent calls (same filters) hit a 30s cache
* `/v2/gex/max-pain?expiry=all` — computes per-expiry max-pain across
the chain, cached 30s
* `/v2/options/greeks/{greek}?dte_max=N` — recomputes from raw chain
for the DTE-filtered slice, cached 30s
* `/v2/options/greeks/3d/surface` — strike × expiry matrix per greek,
cached 30s
All four count as one request against your rate limit, regardless of
whether they hit cold or warm cache.
## Need more throughput?
If 120 req/min isn't enough — typically when you're powering a paid
product downstream, or running >100 concurrent dashboards — contact
**[dev@backquant.com](mailto:dev@backquant.com)** about Enterprise. Enterprise removes the
per-minute limit and includes:
* Dedicated capacity
* Custom symbol additions (e.g. SUI, AVAX)
* Historical bulk export (CSV / Parquet)
* SLA + priority support
## See also
Full error code list with recommended retry strategies.
The cheapest way to watch the universe.
Why polling faster than 30s wastes quota.
# Response format
Source: https://docs.backquant.com/concepts/response-format
The standard envelope every BackQuant API response uses
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={null}
{
"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.0",
"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={null}
{
"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.0",
"timestamp": "2026-04-30T12:00:00.123Z",
"request_id": "req_a1b2c3d4..."
}
}
```
See [Errors](/concepts/errors) for the full list of error codes.
## Fields
`true` if the request succeeded, `false` if it failed. **Always
check this field first.**
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.
Present when `success` is `false`.
Machine-readable error code. See [Errors](/concepts/errors).
Human-readable description.
Optional structured payload — e.g. `RATE_LIMIT_EXCEEDED` returns
`retry_after_seconds`; `VALIDATION_ERROR` returns the failing
field list.
Metadata included in **every** response — success or error.
API version that served the response. Always `"2.0"` for v2 routes.
ISO 8601 wall-clock when the response was built.
Per-request UUID, propagated from `X-Request-ID` header. Include
this when reporting issues — we trace by it in our logs and Sentry.
Symbol the response is about, when applicable
(e.g. `BTCUSDT`).
Current spot price for the symbol at the time of computation.
Useful for normalising returns or sizing.
ISO 8601 of when the underlying cache value was last written by
our worker.
`now - computed_at` in seconds. Useful for staleness alarms in
your code.
Upstream venue list the data flowed through, e.g.
`["deribit", "bybit", "okx", "binance"]`. For provenance.
Echo of the `?exchanges=` filter when one was applied.
Confirms what your filter actually narrowed to.
`{limit, remaining, reset}` echo of the rate-limit headers.
Echoed in body too so browser-based dashboards can read it
without inspecting headers.
Open dict for endpoint-specific metadata
(e.g. `filter_hash` on `/options/chain`,
`expiry_count` on `/options/opex`).
## Pydantic schemas
The 10 most-traffic 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`
* Plus `ChangelogResponse`
The other 28 endpoints are documented with examples and the same
envelope shape.
## See also
Every error code with HTTP status mapping.
Rate-limit headers and tier-based throttling.
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 where each venue's data shows up in the API.
BackQuant ingests crypto options data from four exchanges and folds them into one canonical surface. This page describes which venues feed which endpoints, and how to filter by venue when relevant.
## Venues at a glance
| Venue | Tape | Chain / IV / GEX inputs | Source |
| ----------- | ----------- | ------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Deribit** | ✅ live WS | ✅ primary | Public WebSocket + REST chain. The deepest BTC + ETH books in the market. |
| **Bybit** | ✅ live WS | ✅ multi-venue OI roll-up | Public V5 WebSocket (`publicTrade.{coin}.option`) + chain REST. |
| **OKX** | ✅ live WS | ✅ multi-venue OI roll-up | Public V5 WebSocket (`option-trades`, `instFamily=BTC-USD` / `ETH-USD`). |
| **Binance** | ✅ REST poll | Limited (no live WS on options) | `eapi.binance.com/eapi/v1/trades` polled for the top-30 most-active BTC + ETH contracts every 30 seconds. |
### Coins
`BTC` and `ETH` are fully covered across all four venues. `SOL` and `HYPE` are valid symbols in the API schema (some venues have started listing them) but live coverage is currently thin.
## Where venues show up
| Endpoint family | Per-venue filter? | Default behaviour |
| ---------------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------- |
| [Tape REST](/api/v2/tape/overview) | ✅ `?venues=deribit,bybit,...` | All four venues unioned |
| [Tape WebSocket](/api/v2/tape/websocket) | ✅ per-channel subscription `tape.{coin}.{venue}` | Subscribe to `tape.{coin}.agg` for the union |
| [GEX](/api/v2/gex/levels) — strike profile / levels / heatmaps | Deribit-primary; multi-venue OI roll-up | Multi-venue blended view |
| [IV](/api/v2/options/iv/surface) — surface / term / skew / smile | Deribit-derived | Single, Deribit-rooted view |
| [Greeks profiles](/api/v2/options/greeks/by-greek) | Deribit-derived | Deribit-rooted |
| [Open interest](/api/v2/options/oi/by-expiry) | Multi-venue blended | Union of all venues |
When a metric is documented as "multi-venue blended," it means OI from every venue that lists the coin is rolled into one weighted view. Bid/ask depth is Deribit-only — the other venues don't make the chain pricing usable at retail latency.
## Why Binance isn't on the live WS
The public Binance options WebSocket host (`nbstream.binance.com/eoptions/...`) is not reachable from our infrastructure, despite the REST chain on `eapi.binance.com` working normally.
We therefore poll Binance's REST trade endpoint every 30 seconds for the most-actively-traded BTC and ETH contracts (hot list refreshed every 5 minutes by 24-hour volume). In practice this captures \~95 % of Binance options premium because volume concentrates in the top 20-30 strikes. Tail strikes that print rarely will be missed.
We're tracking the issue and will move Binance to the live WS as soon as the upstream path becomes usable.
## See also
# 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={null}
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={null}
curl -s https://api.backquant.com/v2/health | jq
```
## API metadata + supported symbols
```bash theme={null}
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={null}
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={null}
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={null}
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={null}
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={null}
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={null}
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={null}
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={null}
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={null}
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={null}
x-ratelimit-limit: 120
x-ratelimit-remaining: 87
x-ratelimit-reset: 1714478160
```
## Save the OpenAPI spec for SDK codegen
```bash theme={null}
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
The same patterns wired through `requests`.
The same patterns wired through `fetch`.
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={null}
export BACKQUANT_API_KEY=bq_live_your_api_key_here
```
```python theme={null}
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={null}
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={null}
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={null}
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={null}
# 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={null}
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={null}
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={null}
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
Same patterns in TypeScript / Node.js.
Pure-curl one-liners for each common workflow.
Robust error handling with backoff.
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={null}
export BACKQUANT_API_KEY=bq_live_your_api_key_here
```
```typescript theme={null}
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={null}
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={null}
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={null}
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={null}
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={null}
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={null}
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
Same patterns in Python.
Pure-curl one-liners.
Robust error handling.
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. The same data that powers the BackQuant terminal is
available programmatically, ready to plug into your trading systems,
research workflows, and dashboards.
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={null}
https://api.backquant.com/v2/
```
## Get started
Get your API key and authenticate your first request.
Make your first API call in under five minutes.
Browse the full endpoint set with interactive try-it-out.
Python, TypeScript, and curl recipes for common workflows.
## What's covered
The API is focused on the data crypto options traders actually
trade against. Everything is computed by us from raw exchange chains
(Deribit + Bybit + OKX + Binance) and refreshed every 30 seconds.
| 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, optional live GEX levels channel |
| **Probability** | Breeden-Litzenberger PDF/CDF + 3D probability surface + confidence-band interpolation |
| **Open interest** | By expiry, time-series with cursor pagination, put/call ratio (intraday + daily) |
| **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:
Gamma exposure, dealer positioning, walls and support levels.
How live GEX and TRACE sign dealer size.
Why monthly and quarterly expirations matter.
The strike where option writers profit most.
Breeden-Litzenberger explained for traders.
Surface, term structure, skew, smile.
DEX, vanna, charm, vega — what they measure.
# 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.
Sign in at [**backquant.com/api-access**](https://backquant.com/api-access)
and create a key. Your key looks like:
```text theme={null}
bq_live_<32-character-token>
```
Hit the composable GEX levels endpoint for BTC. Replace
`YOUR_API_KEY` with your actual key:
```bash curl theme={null}
curl "https://api.backquant.com/v2/gex/levels?symbol=BTCUSDT" \
-H "X-API-Key: YOUR_API_KEY"
```
```python Python theme={null}
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={null}
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());
```
Every response uses the same envelope. A successful call returns:
```json theme={null}
{
"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.0",
"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.
Most endpoints support filters. Here's the same call with the
composable `?include=` parameter and an exchange filter:
```bash theme={null}
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` - venue filter
* `?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).
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={null}
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
The single most-asked concept question. Read this before integrating.
Full envelope schema, every meta field explained.
Per-tier limits, response headers, retry strategy.
Python, TypeScript, curl recipes for common workflows.
Daily/weekly/monthly/quarterly classification, anchor expirations.
All 38 endpoints, with interactive try-it-out.
# Long-Term Trend & Valuation Model
Source: https://docs.backquant.com/tradingview/long-term-trend-valuation
A two-layer framework that separates 'where we are in the cycle' from 'what to do about it' - built for long-horizon decisions on assets with strong boom-bust structures.
Open the Long-Term Trend & Valuation Model on TradingView. Invite-only.
## Overview
This invite-only tool is a **two-layer framework** designed to simplify
long-horizon decision-making:
* **Valuation Engine** - measures how extended price is relative to its own
regime. Outputs a single oscillator centered on zero, bounded by
configurable overbought / oversold thresholds.
* **Trend Model** - aggregates several independent long-term subsystems
into a composite **strength score**, mapped to a long / cash / short
stance.
The separation of "where we are in the cycle" from "what to do about it"
makes this model particularly suited to assets with strong boom-bust
structures (like crypto). Each layer answers one question, and they're
designed to be read **together**, not as standalone signals.
## Valuation Engine
The valuation oscillator is a **weighted blend of six standardized
components**. Each component measures a different facet of "extension"
relative to the asset's own historical regime, then gets normalized so
that all signals sit on the same scale before they're combined.
### How it is built
| Stage | What happens |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Standardization** | Each component is converted to a z-score over a configurable lookback (default `1100` bars), so different scales become comparable. |
| **Weighted blend** | Components are combined with user-controllable weights so you can emphasize the lenses you trust most. |
| **Optional smoothing** | A moving average (`SMA`, `EMA`, `RMA`, `HULL`, `WMA`, `DEMA`, `TEMA`, `LINREG`, `ALMA`, `T3`) can be applied to filter whipsaws. Default is `RMA` over `45` bars. |
| **Bounded scaling** | A `tanh`-based compression keeps the oscillator readable across cycles - extreme outliers don't break the chart. |
| **Volatility-aware re-expansion** | After compression, the signal is re-expanded by rolling standard deviation, so "overbought / oversold" stays meaningful when volatility regimes change. |
| **Threshold logic** | Thresholds can be **fixed** (default ±1.5) or **dynamic** (k·σ bands around the rolling mean or around zero). |
### Reading the oscillator
| State | Meaning |
| ---------------------- | ---------------------------------------------------------- |
| **Above 0** | Supportive regime - backdrop favors continuation. |
| **Below 0** | Deterioration / risk aversion regime. |
| **Tagging upper band** | Optimistic stretch - late-cycle risk, take-profit context. |
| **Tagging lower band** | Pessimistic stretch - washouts, early-base context. |
### Configurable inputs
| Input | Default | What it changes |
| ---------------------------- | ------------- | ------------------------------------------------------------------------------------ |
| **Calculation Source** | `close` | Price source used everywhere downstream. |
| **Z-Score Lookback** | `1100` | The master regime lookback. Longer = smoother / longer-term framing. |
| **Per-component lookbacks** | various | Each component has its own length - defaults are tuned for daily/weekly horizons. |
| **Per-component weights** | `1.0`–`1.5` | Relative importance of each component in the final score. Set any to `0` to disable. |
| **Smooth Valuation** | on | Apply a moving-average filter to the final composite. |
| **MA Type / Period / Sigma** | RMA / 45 / 2 | Smoothing kernel and length (Sigma only used by ALMA). |
| **Normalization Length** | `90` | Window used by the volatility-aware compression. |
| **Re-expand Cap** | `2.0` σ units | Maximum width of the post-compression swing. |
| **Alpha mode** | `Manual` | Compression strength - set manually, or auto-mapped so 1σ→0.76, 2σ→0.95, or 3σ→0.99. |
| **Scale by Volatility** | on | Toggles the compression / re-expansion stage. |
### Threshold modes
* **Fixed thresholds** - default ±`1.5`, configurable.
* **Dynamic (`k · σ`)** - thresholds become `mean ± k · σ` over a rolling
window (default `100` bars, `k = 1.5`). Optionally **center at zero**
to use `±k · σ` regardless of mean drift.
### Built-in summary table
A right-corner summary table prints each component's current z-score plus
a 🟢 / 🔴 status emoji, the **Final Score**, and the overall valuation
emoji. Toggle on/off via *Show Summary Table*. The table works in light
or dark mode (controlled by the *Light or Dark Mode* input).
## Trend Model
The trend layer combines **seven independent long-term subsystems**.
Each subsystem produces its own long / short / neutral vote on the
prevailing regime, and the votes are averaged into a single composite
score in the range `[-1, +1]`.
The subsystems are deliberately diverse - each looks at trend from a
different mathematical lens (price-anchor alignment, persistence checks,
macro regime confirmation, higher-timeframe overlays, etc.). You can
toggle any subsystem on or off independently.
### Subsystem toggles
| Toggle | Default |
| ------------------- | ------- |
| Use Component One | on |
| Use Component Two | on |
| Use Component Three | on |
| Use Component Four | on |
| Use Component Five | on |
| Use Component Six | on |
| Use Component Seven | on |
Disabling a subsystem reweights the composite among the remaining
active subsystems - so you can trim the model down to only the lenses
you trust without throwing the score off scale.
### Interpretation
| Composite (`tpi`) | Stance |
| ----------------------------------------- | -------------- |
| Above **Long Threshold** *(default `0`)* | Long |
| Below **Short Threshold** *(default `0`)* | Short |
| Between thresholds | Neutral / Cash |
A colored signal line is plotted on the **price chart** (overlay),
optional bar coloring is available, and a right-corner table reports
the current strength (%), rate of change, and position state.
### Configurable inputs
| Input | Default | What it changes |
| --------------------------------- | ------------------ | ------------------------------------------------------ |
| **Show Signal Line** | on | Plots a colored MA on price reflecting current regime. |
| **Show Trend Table** | on | Right-corner table with strength %, RoC, and position. |
| **Show Background Coloring** | off | Tints the chart background by regime. |
| **Show Bar Color** | off | Repaints candles by regime. |
| **Long / Short Thresholds** | `0` / `0` | Where the composite must cross to flip stance. |
| **Long / Short / Neutral Colors** | green / red / gray | Visual palette. |
## How to Use
### Cycle framing
Read **valuation** and **trend** as a 2×2 matrix - the combination tells
you what stage of the cycle the asset is in.
| Valuation | Trend Composite | Cycle Context |
| ----------------- | --------------- | ----------------------------------------------------------- |
| Deep negative | Neutral / Short | **Accumulation context** - bases are built here. |
| Positive | Strong Long | **Participate in expansion** - trend is doing the work. |
| Extended positive | Weakening | **Late cycle caution** - start trimming, tighten risk. |
| Negative | Short | **Distribution / unwind** - risk-off, capital preservation. |
### Workflow examples
* **Regime allocation** - increase exposure when trend is Long *and*
valuation is rising; reduce when trend is Short *and* valuation is
falling.
* **Signal gating** - only run shorter-term entry systems in the
composite trend's direction.
* **Sizing overlay** - scale smaller near upper-band stretches; scale
larger after valuation resets near zero.
* **DCA context** - accumulate while valuation is negative but
*stabilizing* (slope flattening), not while it's still falling.
* **Cross-asset rotation** - compare valuations across multiple assets
on the daily timeframe and rotate to where conditions are most
favorable.
## Alerts
Built-in alert conditions:
### Valuation alerts
| Alert | Fires when |
| --------------------------- | -------------------------------------------- |
| **Overbought Enter / Exit** | Valuation crosses into / out of the OB zone. |
| **Oversold Enter / Exit** | Valuation crosses into / out of the OS zone. |
| **Bullish Zero Cross** | Valuation crosses above 0. |
| **Bearish Zero Cross** | Valuation crosses below 0. |
### Trend alerts
| Alert | Fires when |
| ------------------------- | ------------------------------------------------------ |
| **Long-Term Trend Long** | Composite crosses above the long threshold. |
| **Long-Term Trend Short** | Composite crosses below the short threshold. |
| **Long-Term Trend Flip** | Composite crosses the short threshold (regime change). |
## Strengths
* **Separates cycle context from trend stance** - two questions, two
answers, no conflation.
* **Multi-component voting** - both layers use diverse subsystems so no
single component can drag the model off course.
* **Volatility-aware scaling** - keeps "extreme" meaningful across
changing regimes.
* **Per-component toggles + weights** - you can tune the model to your
preferred lenses without rewriting the script.
* **Clear visuals and alerts** - encourages long-horizon discipline
rather than minute-to-minute reactivity.
## Final Notes
The Long-Term Trend & Valuation Model is **not designed to catch every
swing**. It is built to keep you aligned with the dominant trend, help
manage risk around cycle extremes, and provide a consistent language
for allocation decisions across timeframes and assets.
# Oscillator Suite
Source: https://docs.backquant.com/tradingview/oscillator-suite
A coordinated set of momentum, money flow, confluence, divergence, and reversal modules - built to turn live market noise into a readable sequence of conditions.
Open the Oscillator Suite on TradingView.
## What This Suite Does
Oscillator Suite is built for one job: **turn live market noise into a
readable sequence of conditions.** Not "one signal." Not "one oscillator." A
coordinated set of modules that track momentum, money pressure, agreement
between them, and exhaustion - so you can see when moves have real backing,
when they're fading, and when reversal conditions are worth treating as a
serious event.
The suite ships with **six interlocking modules**, all in one pane:
1. **Momentum Ribbon** - the core oscillator + signal line + crossover events.
2. **Modified Money Flow Index (MFI)** - pressure overlay with adaptive
thresholds.
3. **Confluence Zones** - bands that fill when momentum and money flow agree.
4. **Reversal Signals** - Major (`ℝ`) and Minor (cross) reversal events,
filtered by volume.
5. **Divergences** - auto-drawn divergence lines on extended momentum.
6. **Momentum Velocity** - a slower, structural momentum lens.
7. **Bar Coloring** - projects the suite's read directly onto candles.
## Why This Suite Feels Different in Live Markets
This suite is designed to show the *why* behind the candle:
* Is the move being **driven** or just **drifting**?
* Is participation **accumulating** or **exiting**?
* Are components telling the **same story**, or is the market split?
* When a reversal appears, is it a **real shift** or a random wiggle?
When components **converge**, you get higher clarity. When they **diverge**,
you get a warning before price makes it obvious.
## How to Read the Suite in Order
If you want the indicator to feel "alive" instead of confusing, use this
order of operations:
1. **Money Flow** - for pressure and participation
2. **Momentum Ribbon** - for direction and shift timing
3. **Confluence** - to measure agreement and regime quality
4. **Reversals** - to mark turning points inside those regimes
5. **Divergences** - for early "engine weakness" warnings
6. **Bar Coloring** - to project the whole read onto price
## Core Modules
### Momentum Ribbon - Timing Engine
The Momentum Ribbon is the oscillator at the center of the suite. It's a
normalized momentum read with two visible lines: the raw **signal**
(`sig`) and a smoothed **signal-of-signal** (`sgD`). The fill between them
flips color on every cross, and circle markers print at every crossover.
**Controls you'll touch:**
| Input | Default | What it changes |
| --------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Calculation Period** | `7` | Lookback for the oscillator base. Higher = smoother / fewer signals. Lower = faster / more signals. |
| **Signal Line type** | `SMA` | Smoothing kernel applied to the ribbon's signal-of-signal. Choose between `SMA`, `EMA`, `WMA`, `LINREG`, `T3`, `ALMA`, `DEMA`, `TEMA`, `RMA`. |
| **Smoothing length** | `3` | The smoothing length for the signal-line kernel. Higher = cleaner shifts. Lower = earlier shifts. |
| **Positive / Negative colors + transparency** | green / red / 40 | The ribbon fill colors and opacity. |
**What you'll see on chart:**
* A **filled ribbon** above and below zero, colored bullish or bearish.
* **Circle markers** at every momentum crossover (signal vs signal-of-signal).
* **Black inner-line outlines** so the ribbon is readable on any background.
**In live markets:** This is what you watch when price is chopping. The
ribbon will often show momentum actually resolving even if price looks
messy for a few candles.
### Modified Money Flow Index (MFI) - Pressure Layer
A modified Money Flow Index that uses an **adaptive threshold** rather than
fixed 80/20 levels. The script tracks rolling averages of bullish and
bearish MFI readings, so "strong pressure" is judged relative to the
asset's own recent participation - not a textbook constant.
**Controls:**
| Input | Default | What it changes |
| ---------------------- | ----------- | -------------------------------------------------------- |
| **Calculation Period** | `35` | MFI lookback. Higher = smoother. Lower = more reactive. |
| **Smoothing Period** | `6` | Extra smoothing applied to the modified MFI. |
| **Money Flow Colors** | green / red | Colors used in the MFI fill and inside confluence zones. |
**What you'll see on chart:**
* A line plotted between the ribbon and the zero level, colored by sign.
* **Stronger fills** (less transparent) where MFI exceeds its own bullish or
bearish running average - the script highlights *when pressure is
actually elevated*, not just present.
* A faint fill between MFI and zero when readings are weak, so you don't
mistake "barely positive" for accumulation.
The Money Flow wave is your context filter. It exists to separate:
| Condition | What it means |
| --------------------------- | ----------------------------------------------- |
| Real buy-side pressure | A push that has genuine participation behind it |
| Weak participation | A move that looks bullish but lacks support |
| Heavy distribution | A selloff with real selling pressure |
| Selling running out of fuel | A dip where pressure is fading |
### Confluence Zones - Regime Detection
The bands at ±50 / ±60 fill with color based on how Momentum and MFI agree.
You pick the fill style:
| Zone Type | What it shows |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **None** | Bands plotted in gray, no fills. |
| **Confluence Zones** *(default)* | Both bands fill bullish when momentum > 0 and MFI > 0; both fill bearish when both \< 0; muted/transparent when split. |
| **Overbought / Oversold Strength** | Band opacity scales with how extreme momentum is - the more stretched the read, the more saturated the fill. |
**Reading it:**
| Zone state | Meaning |
| ---------------------- | --------------------------------------------------------------- |
| **Bullish Confluence** | Momentum is constructive **and** pressure supports it |
| **Bearish Confluence** | Momentum is bearish **and** selling pressure supports it |
| **Mixed / faded** | Components disagree - expect chop, fakeouts, low follow-through |
**This is how you stop forcing trades.** When confluence is strong, you
can hold with more confidence. When confluence fades, you tighten
expectations and demand better structure or confirmation.
### Reversal Signals - Turning Points
Reversal events are filtered by **volume expansion** combined with momentum
and money flow conditions - they're not raw oscillator extremes. The suite
prints two tiers:
| Tier | Marker | Triggered when |
| --------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Major** | `ℝ` label at ±65 + soft background tint | Volume expands strongly **and** momentum is past the reversal threshold **and** money flow agrees with the direction (above bullish-MFI average / below bearish-MFI average). |
| **Minor** | Cross marker at ±65 | Lighter volume expansion **and** momentum past ±20 **and** the volume's own RSI confirms direction. |
**Reversal Factor (`1`–`10`, default `4`)** scales the strictness:
* **Lower** → more reversal events, more sensitive.
* **Higher** → fewer events, but each is more strongly filtered.
**Live-Market Mindset:**
* A reversal print during **heavy opposing pressure** is often just a pause.
* A reversal print when **money flow pressure is weakening** or shifting is
a different animal.
* A reversal print as **confluence transitions** is where dips and tops
become actionable ideas.
### Divergences - Early Warning
Divergences here only evaluate **when momentum is past the divergence
threshold** (default `20`). That filter is intentional - it stops the
indicator from spamming low-quality divergence lines in mid-range chop.
**Controls:**
| Input | Default | What it changes |
| --------------------------------- | ----------- | ---------------------------------------------------------------------------------- |
| **Divergence Threshold** | `20` | Lower = more (shorter-term) divergences. Higher = fewer (longer-term) divergences. |
| **Show Divergences** | on | Toggle the auto-drawn lines. |
| **Bull / Bear Divergence Colors** | green / red | Line colors. |
**What gets drawn:** when the ribbon is in extended territory and crosses
its own signal line, the script compares the most recent momentum extreme
against the previous extreme (in the same regime). If price has made a new
extreme but momentum hasn't, a line is drawn between the two.
**What divergence is used for here:**
* Spot **engine weakness** when price attempts to extend but momentum does
not match.
* Warn you early so you can manage risk **before** the obvious reversal
candle shows up.
* Help you identify when a trend is losing quality, especially when
confluence begins fading.
**Divergence → Confirmation Sequence:**
1. Momentum ribbon shifts
2. Money flow eases or flips
3. Confluence transitions
4. Reversal marker appears
### Momentum Velocity - Structural Layer
A second momentum lens that's slower and more "structural." It's a
weighted-price for-loop momentum read normalized over a longer lookback
(default `100` bars) - useful for confirming whether the broader momentum
environment is actually supporting what the ribbon is doing.
**Visual feedback:** plotted as columns at the bottom of the pane in a
**graduated color scale** - light tints at low magnitude, vivid green or
red as readings get extreme. So you can read background regime strength at
a glance without reading numbers.
**How traders use this in practice:**
* As a **permission layer** to avoid fighting stronger background pressure.
* To confirm when momentum shifts are **likely to hold**, not just flip
for a bar.
* To spot when short-term momentum is turning inside a larger supportive
environment.
### Bar Coloring - Speed Layer
Bar coloring projects the suite's current read directly onto candles so
you can process conditions without staring at the panel. Pick the mode
that matches how you trade:
| Mode | Bars colored when… |
| -------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **None** | No bar coloring. |
| **Momentum direction** | Ribbon signal is above / below its own smoothed signal. |
| **Momentum above/below midline** | Ribbon signal is above / below zero. |
| **MFI above/below midline** | Money flow is above / below zero. |
| **Confluence (Mom + MFI)** | Both momentum and MFI agree (both bullish or both bearish). |
| **Strong Confluence Only** *(default)* | Confluence **plus** money flow exceeds its own running bullish/bearish average. The strictest mode. |
| **Momentum Velocity** | Bars colored by the structural-layer reading. |
## Practical Playbooks
Without chasing.
* Start with confluence - get aggressive only when agreement is present
* Use the ribbon to time entries on momentum shifts
* Confirm with money flow pressure
* When confluence fades, manage tighter
That is not blind.
* Let price pull back while you watch money flow
* Heavy selling = you are early
* Easing pressure = you are getting close
* Best dips show up during confluence transitions
Without guessing.
* Watch for momentum weakening while price extends
* Divergence is your first warning
* Confluence fading + money flow shifting = high-interest reversal
* This sequence catches "strong reversals"
## Settings That Matter
### Calculation Period (Momentum)
| Lower | Higher |
| ----------------------------------- | ---------------------------------------- |
| Faster, more reactive, more signals | Smoother, fewer signals, cleaner regimes |
### Signal Line Type + Smoothing
Pick a kernel that matches asset behavior - `SMA` / `EMA` for general use,
`LINREG` or `T3` for cleaner regime tracking, `ALMA` / `DEMA` / `TEMA` for
faster response without raw whipsaw.
| More smoothing | Less smoothing |
| -------------------------- | ----------------------------- |
| Cleaner shifts, less noise | Earlier shifts, more activity |
### MFI Calculation + Smoothing
| Lower | Higher |
| -------------------- | ------------------------------------------- |
| Faster pressure read | Clearer accumulation/distribution structure |
### Divergence Threshold
| Lower | Higher |
| ------------------------------------- | ------------------------------------------ |
| More divergence events (shorter-term) | Fewer events (more selective, longer-term) |
### Reversal Factor
| Lower | Higher |
| -------------------- | ------------------------------------------------ |
| More reversal events | Fewer, stronger events through heavier filtering |
## Alerts
Built-in alert conditions you can wire into TradingView:
| Alert | Fires when |
| ---------------------------------- | --------------------------------------------------------------- |
| **Ribbon Long / Short** | Momentum signal crosses above / below its smoothed signal-line. |
| **Ribbon Cross Up / Down Midline** | Momentum signal crosses zero in either direction. |
| **Money Flow Long / Short** | Modified MFI crosses zero in either direction. |
| **Reversal Major Long / Short** | A major (`ℝ`) reversal prints. |
| **Reversal Minor Long / Short** | A minor (cross-marker) reversal prints. |
## How to Know You Are Reading It Right
When you get comfortable, you will start noticing the suite produces
**states**, not random prints:
| State | What it looks like |
| --------------------- | ------------------------------------------------------------- |
| **Strong Bullish** | Momentum drives, pressure supports, candles align |
| **Bullish Weakening** | Momentum begins fading, pressure cools, divergence may warn |
| **Mixed** | More fakeouts, fewer clean runs, demand stronger confirmation |
| **Transition** | Where the best reversals and dip catches often appear |
| **Strong Bearish** | Downside pressure is real, short-side regimes behave cleaner |
If you trade based on **states** instead of isolated signals, the suite
stops being "an indicator" and becomes a **live market interpreter**.
# TradingView Indicators
Source: https://docs.backquant.com/tradingview/overview
BackQuant's suite of invite-only TradingView indicators for discretionary execution and study.
BackQuant ships a curated set of TradingView indicators built around the
same philosophy as the terminal API: **read conditions, not isolated
signals.** Each script is designed to work standalone, but the real edge
comes from stacking them so that momentum, pressure, structure, and
volatility tell a coherent story.
All BackQuant TradingView indicators are **invite-only** and require an
active **yearly BackQuant subscription** for access. Once subscribed,
invites are granted to your TradingView username and the scripts appear
under *Indicators - Invite-only scripts* inside TradingView.
## Available Indicators
Coordinated momentum, money flow, confluence, divergence, reversal,
and bar-coloring modules in a single pane.
Modular execution overlay: trend, impulse, stop loss, RSI screener,
market structure, FVGs, volumetric order blocks, S/R, and reversal bands.
Two-layer cycle framework: a valuation oscillator plus a composite
long-term trend score for allocation decisions.
See every published BackQuant script directly on TradingView.
## Getting Access
Pick up a yearly plan at [backquant.com](https://backquant.com/). The
yearly tier is what unlocks the TradingView indicator bundle.
Enter your TradingView username in your BackQuant account settings.
Access is **auto-granted** as soon as it's saved - no manual approval
or email needed.
The indicators will be visible under *Indicators - Invite-only scripts*
in your TradingView chart.
## How These Docs Are Written
Indicator pages are written for **live-market use**, not as a list of
inputs. Every page covers the same five things:
The single job the indicator was built for, stated up front.
What every component reads from the tape, and what it shows on chart.
How to interpret outputs as a sequence of conditions, not a checklist
of signals.
The inputs that genuinely change behavior, with the direction each one
pushes the indicator.
A **Practical Playbooks** section also appears on each indicator page,
showing the trade scenarios the script is designed for - trend
participation, dip catching, top selling, exhaustion fading, and so on.
## Support
Questions about access, invites, or how to use a specific indicator on a
specific market? Reach the team at **[dev@backquant.com](mailto:dev@backquant.com)** or in the
BackQuant Discord.
# Trading Module
Source: https://docs.backquant.com/tradingview/trading-module
A modular overlay combining Trend, Impulse, Stop Loss, RSI screener, market structure, FVGs, volumetric order blocks, volumetric S/R, and reversal bands. Built for discretionary execution and study.
Open the Trading Module on TradingView. Current version: **v2.0.1**.
## What This Script Is
The Trading Module is a single overlay that bundles the components of a real
discretionary process: a directional **bias filter**, a **timing engine**, a
**risk framework**, a **multi-symbol watchlist**, and a **price-action layer**.
Every module is independently switchable, so you can run it as a clean
trend-follower, a structure-driven execution chart, or a watchlist scanner -
without juggling multiple scripts.
It is intentionally **not** a "buy/sell arrow" tool. The script's job is to
keep five questions answered at all times:
1. What direction am I biased toward? *(Trend)*
2. Is now a moment worth engaging? *(Impulse)*
3. Where is the idea wrong? *(Stop Loss)*
4. What else in my universe is doing the same thing? *(Screener)*
5. Where on the chart actually matters? *(Structure, FVGs, Order Blocks, S/R)*
## Module Map
Direction filter - 5 selectable engines from a composite multi-factor
model down to a simple EMA cross.
Timing engine - flags expansion / pressure events with `𝕃` (engage)
and `ℂ` (cash) labels.
Three structural risk frameworks - volatility, fixed-percent, or
bar-to-bar invalidation.
10-slot multi-symbol multi-timeframe RSI watchlist, gradient-colored
by strength.
Independent swing and internal pivot tracking with BOS / MSB / MSB+ tagging.
Pivot-anchored OB zones with internal buy/sell volume split and
relevance %.
Multi-timeframe imbalance boxes with mid-line and right-extension.
High-volume pivot-based support/resistance with touch counting and
auto-cleanup on break.
Volatility-and-percentile bands for top finding, dip hunting, and
exhaustion detection.
## Trend Models
The Trend Model is your bias filter. The script ships five engines so you can
pick the one whose behavior matches the asset and timeframe you trade. Only
one is active at a time.
| Model | What It Reads | Best For |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| **Universal Trend+** | A composite of five families - RSI regime, smoothed Rate-of-Change, fast/slow EMA spread, a normalized T3 oscillator, and a DEMA-ATR band - each emitting its own long/short/neutral vote, then averaged. | The default if you want one trend read that is hard to fake out by any single indicator failing. |
| **EMA Cross** | Fast vs slow EMA. Color flips with the cross. | Simple, responsive, but expects you to handle range-bound chop yourself. |
| **DEMA ATR** | A double-EMA midline with an ATR envelope; the line only shifts color when it actually breaks structure, not on every wiggle. | Cleaner than a basic cross in choppy assets. |
| **Relative Strength Overlay** | A "for-loop" RSI scoring system that ranks the current RSI against a band of historical readings and flips on persistent strength shifts. | A "strength state" trend rather than a moving-average state. |
| **Hull Trend** | A long-period Hull moving average, painted by its own slope. | Smooth, lag-reduced trend backdrop for higher timeframes. |
Treat the trend model as a **regime filter**, not a signal. Long bias =
filter shorts, look for impulse longs. Short bias = the inverse.
## Impulse Models
Impulse is the timing layer. It is decoupled from trend on purpose - a market
can be trending without currently *impulsing*, and impulses can occur
counter-trend (where they're useful as warnings rather than entries).
Both impulse engines share the same scoring core: a current oscillator value
is **compared against many of its own historical bars at once** and counted
into a single score. The score crosses a long threshold to print `𝕃`, and
crosses a short threshold to print `ℂ` (cash). This makes them more
selective than a raw oscillator cross.
| Model | Underlying Read |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **BBPct FL Impulse** | Bollinger %B normalized into the for-loop score. Reacts to where price sits inside its volatility envelope, not just its slope. |
| **DM Impulse Enhanced** | Built on Directional Movement. Cleaner read on directional pressure, less reactive to single bar volatility spikes. |
**Labels you'll see:**
* `𝕃` (long) - the impulse score flipped above the long threshold.
* `ℂ` (cash) - the impulse score crossed under the short threshold; treat as
exit / stand-aside, not necessarily an active short.
Counter-trend impulses are not invalid signals - they're early-warning
events. Use them to take profits or tighten stops, not to fight the trend
model.
## Stop Loss Frameworks
Three independent risk-overlay styles. None of them are "the" stop - they're
visual frameworks for structuring invalidation.
| Mode | What It Plots | When To Use |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| **Dynamic** | Two volatility bands above and below price, scaled by RMA of True Range × 1.5. Expands in volatile regimes, tightens when range contracts. | Default for most trend or impulse setups - it adapts. |
| **Fixed** | Three pairs of percentage bands at ±1%, ±2.5%, and ±4% from the bar's reference price. | Rule-based / mechanical risk sizing where you want flat percentages instead of vol-aware ones. |
| **Bar-to-Bar** | Marks the prior bar's low (on up bars) or high (on down bars) as the immediate invalidation. | Tight intra-trend management - "if last bar gets violated, the move is broken." |
Stops belong where the **trade idea is wrong**, not where you start to feel
pain. If you take entries from impulses, your stop has to be wide enough to
survive impulse-volatility, not just last-bar noise.
## RSI Screener
A built-in multi-symbol, multi-timeframe RSI watchlist that draws as a stacked
overlay on the right side of your chart - no extra panes, no second tab.
**What you control:**
* **10 symbol slots** with sensible crypto majors as defaults; each slot can
be swapped to any TradingView ticker.
* **Per-slot timeframe** (empty = current chart timeframe). Mix HTF and LTF
reads in the same screener.
* **RSI length** and a configurable **midline threshold** (default 50) used
as the bull/bear divider.
* **Gradient coloring**: when enabled, each row interpolates between long and
short colors based on its RSI value (20 → 80 range), so you read strength
by hue rather than just reading numbers.
* **Label size** and X/Y offsets for fitting the screener cleanly into your
layout.
**How to read it:**
* Read the **midline crosses**, not the OB/OS extremes - RSI > midline =
bull regime, \< midline = bear regime.
* Stack a higher-timeframe slot on top for regime, lower-timeframe slots
below for timing within that regime.
* Use the screener as **context**, not a trade trigger.
Each slot is a `request.security()` call. Heavy use on lower-end machines
may impact load time - disable slots you don't actively watch.
## Market Structure
Tracks pivots and prints **structural events** as labels on the chart.
Two independent layers: **Swing** (slower, macro) and **Internal** (faster,
intra-trend). Each has its own lookback.
**Events plotted:**
| Event | Meaning |
| ---------------------------------- | -------------------------------------------------------------------------------------- |
| **BOS** *(Break of Structure)* | A continuation event - trend extends past the most recent swing in the same direction. |
| **MSB** *(Market Structure Break)* | A directional shift - the structure breaks against the prior trend. |
| **MSB+** | A higher-confidence MSB filter for users who want fewer, stronger shift signals. |
**Configurable per layer:**
* Display mode: `All` / `MSB` / `MSB+` / `BOS` / `None` - show only what you care about.
* Independent lookback: swing default `50`, internal default `5`.
* Independent bull/bear colors and line styles (Solid / Dashed / Dotted).
**Use it for:**
* **Swing layer** → confirm the larger regime (does the trend model align
with structural reality?).
* **Internal layer** → time entries inside the swing regime.
## Volumetric Order Blocks
Order Blocks (OBs) are pivot-anchored zones that mark where the last
opposite-direction candle pushed the move that created a structural pivot.
The Trading Module's OB engine adds **volume context** so you can tell apart
"a textbook OB" from "an OB the market actually traded heavily through."
**Anatomy of each OB box:**
| Component | What it shows |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Top / Bottom of zone** | The OB price range itself. |
| **Mid-line** | A horizontal mid-price reference for partial reactions. |
| **Internal Buy/Sell metric (`Internal Buy/Sell Activity`)** | Two stacked sub-zones inside each OB box that grow as later bars print up vs down inside the zone. Lets you see at a glance whether buyers or sellers are actually engaging it. |
| **Volume label + relevance %** | Total volume that built the OB, plus what percentage of the visible OB stack that volume represents. The OB carrying more weight is doing more work. |
| **Right-extension box** | The OB projected forward so you can see future tests at a glance. |
**Detection & filtering:**
* **Filtering** - restrict OB creation to break events of a specific type
(`None`, `BOS`, `MSB`, `MSB+`).
* **Mitigation** - choose how an OB gets considered "used up": `Absolute`
(full body breach) or `Middle` (mid-line breach).
* **Hide Overlap** - when two OBs overlap, the engine keeps either the
more recent or earlier one (default: keep previous), so the chart doesn't
clutter with stacked duplicates.
* **OB count** - how many active OBs to keep on chart at once.
* **Swing OBs** - optional separate set of OBs anchored to swing structure
rather than internal structure.
* **Grayscale mode** - desaturate the OBs if you'd rather use color for
other modules.
An OB with a high relevance % and an internal metric that **agrees** with
the OB direction (buy metric dominant inside a bull OB) is doing real
work. An OB whose internal metric disagrees with its direction is already
losing its edge before price even returns.
## Fair Value Gaps (FVGs)
Three-bar imbalance boxes - places where price moved fast enough that the
candle in the middle did not overlap with its neighbors. The script draws
them as zones and adds a mid-line for partial fills.
**Controls:**
* **Enable** - toggle FVGs on/off (off by default).
* **Show Last** - cap of how many recent FVGs to display (default 5).
* **Timeframe** - detect FVGs on a different timeframe than your chart
(e.g. mark 1H FVGs on a 5m chart). Empty = chart timeframe.
* **Extend** - how many bars to project the FVG box into the future.
* **Bull/Bear color** - both default to a soft tint of your long color so
the chart doesn't fight your other modules; you can override.
**Interpretation:**
* An FVG is an **area of interest**, not a trade by itself. Many traders
use them as targets (price likes to revisit unfilled imbalances) or as
decision zones for entry/rejection.
* The engine **auto-removes** an FVG once price closes through it from
the wrong side - so what's on the chart is always still active.
## Volumetric Support & Resistance
A pivot-based S/R engine that only respects pivots **confirmed by elevated
volume**. Plain pivots are ignored - the level has to be earned.
**Detection logic:**
| Input | Effect |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| **Detection Sensitivity** *(default 5)* | The pivot lookback. Lower = more pivots, higher = stricter. |
| **Volume Multiplier** *(default 1.0×)* | The pivot's volume must be at least this multiple of average volume to qualify as a level. |
| **Analysis Period** *(default 100)* | Bars used for the volume baseline. |
| **Min Distance %** *(default 0.5%)* | Levels closer to an existing one are skipped, so the chart doesn't fill with near-duplicates. |
| **Max Levels** *(default 15)* | Hard cap on active levels. |
| **Remove Broken** | Auto-deletes levels once price closes through them by more than \~0.3 × ATR - keeps only "still active" zones. |
**What each box shows:**
* A **box around the level** with thickness scaled to ATR (so the zone is
visually proportionate to volatility).
* **Border thickness** - high-volume levels get a thicker border, so the
chart highlights the zones built on the most participation.
* **Touch counter** - every time price comes back into the zone, the touch
count goes up. Repeat tests are more meaningful than one-and-done levels.
* **Volume text** - optionally displayed inside the box (or beside it).
* **Right-extend** - toggle whether levels project into future bars.
Pair volumetric S/R with the impulse model: an `𝕃` printing **at** a
high-volume support that has multiple touches is much higher quality than
the same `𝕃` printing in open air.
## Reversal Bands
NEW in v2.0.1
A volatility-aware band system designed for **top finding, dip hunting, and
local exhaustion**. It combines two ideas:
1. **A percentile envelope** - the script tracks where the source has
historically traded within a configurable lookback (default 200 bars,
95th percentile) and uses that as a "stretched" anchor.
2. **A volatility-multiplied deviation band** - built off a baseline length
(default 50) and a volatility length (default 53), then scaled by a
multiplier (default 3.1×).
Together, they highlight **price behaving extremely relative to its own
recent regime** - not just "above an SMA."
**Inputs you'll touch most:**
| Input | What it changes |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **Percentile Lookback / Level** | How far back the percentile is measured, and how extreme a reading must be (95 = top 5% / bottom 5%). |
| **Baseline Length** | The smoothing of the central reference. Lower = faster, higher = cleaner. |
| **Volatility Length & Multiplier** | The width of the deviation envelope. Higher multiplier = fewer signals, more selective. |
| **Show Reversal Signals** | Toggle the in-band Dip / Top reversal signal markers on or off. |
**How to read it:**
* Price tagging the **upper band** in conditions of strong trend = late-cycle
caution / take-profit zone, not a blind short.
* Price tagging the **lower band** with weakening down-impulse = high-quality
dip-hunt context.
* Use it as a **filter on top of impulse signals**: an `ℂ` near the upper
band is more meaningful than an `ℂ` mid-range.
## Core Philosophy
This indicator is not "one model to rule them all." It exists to let you
build a process where each layer answers exactly one question:
| Layer | Decides |
| ------------------------------ | ---------- |
| **Trend** | bias |
| **Impulse** | timing |
| **Structure / OB / FVG / S/R** | location |
| **Stop Loss** | risk |
| **Screener** | focus |
| **Reversal Bands** | exhaustion |
If you only use one layer, you're discarding most of the edge the script is
designed to build. The strength is in **confluence and filtering** - multiple
modules agreeing on the same idea at the same place.
## Suggested Presets
* Trend: Universal Trend+ or DEMA ATR
* Impulse: BBPct FL or DM Enhanced
* Stop Loss: Dynamic
* Structure / FVG / OB: Off
* Screener: On (high TF)
* Trend: Hull Trend or Universal Trend+
* Impulse: On
* Market Structure: Swing + Internal
* FVG + Volumetric Order Blocks: On
* Stop Loss: Dynamic or Bar-to-Bar
* Screener: On (10 slots, mixed TFs)
* Minimal chart overlays
* Reversal Bands: On for context
* Drill into individual charts when alignment shows up
## Quick Input Map
| Group | Key inputs |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Main / Models** | Trend, Impulse, Stop Loss model selectors • Show Screener • Long/Short colors • Reversal Bands toggles |
| **Screener** | 10 symbol slots • per-slot timeframe + toggle • RSI length & midline • label size + offsets • gradient coloring |
| **Reversal Bands** | Percentile source, lookback, level • Deviation source, baseline length, volatility length & multiplier |
| **Volume Order Blocks** | Show on chart • OB count • Internal buy/sell metric • Swing OBs • Filtering (None / BOS / MSB / MSB+) • Mitigation (Absolute / Middle) • Grayscale |
| **Market Structure** | Swing & Internal mode (All / MSB / MSB+ / BOS / None) • per-layer lookback • bull/bear colors • line styles |
| **Fair Value Gaps** | Enable • Show Last (count) • Timeframe • Extend bars • bull/bear colors |
| **Volumetric S/R** | Sensitivity • Volume multiplier • Analysis period • Max levels • Min distance % • Remove broken • Right-extend • Volume text inside |
## Version History
### v2.0.1 - Latest
* Added **Reversal Bands** with volatility-adjusted percentile calculations.
* New reversal signals (Green for Dip, Red for Top).
* 7 new inputs for band sensitivity calibration.
* Bug fixes for calculation errors.
* Larger lookback without losing calculation time.
### v1.1.0
* Added **Hull Trend** model.
* Static screener color option (inverse of chart background).
* Volumetric S/R on/off toggle.
* Improved Market Structure inputs.
* Enhanced Order Block plotting with internal buy/sell volume metric.
* Significant runtime improvement.
* Temporary removal of `alerts()`.
## Final Notes
This is a full visual decision-support overlay for discretionary traders who
want trend, timing, structure, and watchlist scanning in one place. Use it
to build a repeatable process, then validate that process with proper
testing and journaling before risking real capital.
This is a heavy script when many modules are enabled - it draws live
objects and requests multiple symbols. Not every combination is optimal
for every market or timeframe; the modules are deliberately independent
so you can disable what you're not using.