API reference
Four market-analysis endpoints, one header, no SDK. Start with a free key by email, then use the same key for paid access when you need the daily limit removed.
New to the terms on this page? Learn explains day types, volume-price analysis and how to read a probability from zero, and Guides walks through max pain and gamma exposure by hand. This page assumes you just want to call things.
Base URL and authentication
Every endpoint lives under https://api.quantdata.uk, and every one of them takes a single header:
Machine-readable contract:
https://api.quantdata.uk/openapi.json. Import that OpenAPI document when an
agent platform supports Actions or tool schemas; this page remains the human-readable reference.
X-API-Key: qd_9f3c...
Keys always begin with qd_. There is no OAuth flow, no bearer token exchange and no
session cookie — the header is the entire authentication story, which is deliberate: it means
any AI agent that can make an HTTP request can use the API without an integration project.
Requests are HTTPS only. Treat a key like a password; if one leaks, email us and we will rotate
it.
Start with one anonymous look, then get a free key
Omit the header and one source can receive 1 complete first-look
response per UTC day. That first response has the same analytical fields and no watermark.
Its free_tier.next_action points to the free-key request.
For a useful trial, request a free qd_ key with an email address. No login,
password, card or GUI is required:
curl -s -X POST https://api.quantdata.uk/v1/access/free-key \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","marketing_opt_in":false,"source":"docs"}'
The endpoint returns 202 and delivery status, never the raw key:
HTTP/1.1 202 Accepted
Content-Type: application/json
Cache-Control: no-store
{
"status": "delivery_started",
"delivery": "email",
"message": "Check that inbox for your qd_ API key.",
"free_key": {
"price_usd": 0,
"card_required": false,
"account_required": false,
"calls_per_utc_day": 10,
"shared_across": ["brooks", "weis", "maxpain", "gamma"],
"auth": { "header": "X-API-Key", "prefix": "qd_" }
},
"marketing_updates": "not_subscribed"
}
The raw key is sent only to that inbox. marketing_opt_in is optional and defaults
to false; set it to true only when the email owner explicitly asks for product updates. A true
value records a pending request only. The recipient must reply SUBSCRIBE to the
delivery email before any marketing updates begin, so another person cannot subscribe an inbox
they do not control. The delivery email is a service message, not marketing consent.
A free key provides 10 successful responses per UTC day, shared across
Brooks, Weis, Max Pain and Gamma. Both the anonymous and free-key counters reset at 00:00 UTC.
Their responses carry free_tier.access_kind, calls remaining, reset time and the
correct next action. When either allowance runs out, the API returns 429
FREE_QUOTA_EXHAUSTED with Retry-After and
quota_resets_at. The anonymous error offers request_free_api_key;
the free-key error carries checkout for a person who needs uninterrupted access. A source
address is hashed rather than stored for the anonymous counter, as the
privacy page spells out. The same four endpoints are also a remote MCP
server — per-client connect instructions on the MCP page.
Or connect over MCP
The same four endpoints are also a remote MCP server, for Claude Desktop, ChatGPT, Cursor, VS Code, Zed and anything else that speaks the Model Context Protocol. There is nothing to install — point the client at:
https://api.quantdata.uk/mcp
Transport is streamable HTTP. The MCP tools are the same four APIs behind another transport.
No authentication follows the same no-key rules as the HTTP API: one anonymous first-look
response per source, or a shared 100-per-platform daily pool when
the call arrives through a recognised AI-platform fetcher. The MCP access tool
quantdata_request_free_api_key issues a free key for an email address without a
GUI. Unlike the HTTP endpoint, it returns the key in the tool result so the assistant can keep
working in the same turn, and emails a copy to that address; the email states that an assistant
requested it and how to have it disabled. Keys issued this way expire after 30 days and are
never a key that already belonged to that address. Send X-API-Key as a request header
for the shared 10-response UTC-day allowance or paid access. The tool
descriptions carry the same coverage limits and caveats as this page.
What costs money, and what does not
| Access | What it covers | Price |
|---|---|---|
| Free web pages — no key, no signup |
The Max Pain and GEX pages at
/max-pain/{ticker} and /gex/{ticker}, the
methodology page and the guides.
Web pages, not API endpoints — nothing to call, nothing to authenticate.
| $0 |
| Free API key — email delivery, no login or card | All four endpoints, sharing 10 successful responses per UTC day. Requested from the HTTP endpoint, the raw key is delivered only to the submitted inbox. Requested through the MCP tool, it is also returned to the assistant so it can continue without a copy-paste step, and expires after 30 days. | $0 |
| Brooks-only paid API | /v1/brooks/{symbol} and /v1/brooks/backtest.
No trial; personalized AI-workspace prompt delivered after checkout.
| $9.90/mo |
| Full paid API | /v1/brooks/{symbol}, /v1/weis/{symbol},
/v1/maxpain/{symbol}, /v1/gamma/{symbol} | $149/mo |
Without a key, one source gets 1 first-look response per UTC day. The free key is the intended evaluation path after that.
Brooks-only is $9.90 a month with no trial. Full access is
$149 a month, every endpoint, with the first 3 days free. In either case,
use the same email as the free key and that key is upgraded in place; otherwise a new
qd_ key is shown after checkout. The research behind the endpoints is written up on the
Brooks Daily Bias and Weis Wave
pages.
Keys bought under the older two-product pricing keep working at their original price. If yours
covers one product and you now need the other, email
quantdata@quantdata.uk — we move existing subscribers across by
hand rather than charge them twice. That legacy case is the only thing that returns
403 rather than 401, so you can tell “wrong key” apart
from “key does not cover this endpoint” without guessing.
Quickstart
# Brooks price-action events in the freshest window
curl -H "X-API-Key: qd_9f3c..." \
https://api.quantdata.uk/v1/brooks/ES=F
# Weis Wave state, US day session
curl -H "X-API-Key: qd_9f3c..." \
https://api.quantdata.uk/v1/weis/ES=F
# Same instrument, Asian session (24h instruments only: =F futures, =X spot FX)
curl -H "X-API-Key: qd_9f3c..." \
"https://api.quantdata.uk/v1/weis/ES=F?session=asia"
# Full bar stream, including pre/post-market; matches the published Weis reference-rate window
# Requires a source with real extended-hours volume; otherwise returns 422 NO_EXTENDED_VOLUME
curl -H "X-API-Key: qd_9f3c..." \
"https://api.quantdata.uk/v1/weis/ES=F?session=full"
# Max pain for every expiration inside 45 days
curl -H "X-API-Key: qd_9f3c..." \
https://api.quantdata.uk/v1/maxpain/AAPL
# Dealer gamma positioning, with per-strike detail
curl -H "X-API-Key: qd_9f3c..." \
"https://api.quantdata.uk/v1/gamma/SPY?by_strike=true" Endpoints
GET /v1/brooks/{symbol}
Rolling Brooks price-action events in the current window, each with the pre-registered
outcome rate measured for that window; range breakouts additionally carry a calibrated
per-event failure estimate. Part of the paid plan; use a key for ongoing calls. Definitions
and validation are described on the Brooks Events page.
The same response carries two further blocks: day_type, the five-class
day-type distribution from the model trained for the current window — the day session
(mode=day_session, published accuracy 66%/80% against a 37% baseline) and
the Asian window (mode=asia_session, trained natively on Asian windows,
pre-registered and confirmed zero-shot on NQ, with its own accuracy figures on its own
label taxonomy — the two sets are not comparable; the gap window has no validated model
and returns available: false) — and shape, a plain arithmetic
description of the bars already printed in the window.
| Parameter | In | Type | Default | Notes |
|---|---|---|---|---|
symbol | path | string | required | Ticker in any supported format. Normalised server-side — see symbol formats below. |
window | query | rth | gap | asia | freshest bars | rth is the market's own day session — 09:30–16:00 New York for US
symbols, local hours for Hong Kong and A-shares. gap
covers 16:00–21:30 Beijing — the stretch between the Asian close and the US
open. asia covers 09:00–16:00 Beijing. gap and
asia are limited to 24-hour instruments — futures
(=F), spot FX (=X) and crypto; anything else returns
400. Omit the parameter to follow whichever window has the freshest
bars.
|
{
"symbol": "ES=F",
"window": "gap",
"window_is_live": true,
"session": "2026-08-06",
"bars": 41,
"quote": {
"open": 6362.25, "high": 6371.5, "low": 6355.0,
"last": 6368.75, "prev_close": 6360.5, "change_pct": 0.13
},
"source": "leandata",
"volatility_base": {
"avg_daily_range_5d": 62.75,
"definition": "mean high-low range of the last 5 completed US regular sessions"
},
"events": [
{
"type": "range_breakout",
"direction": "up",
"at": "2026-08-06T18:05:00+08:00",
"bars_since": 6,
"breakout_level": 6366.0,
"range": { "low": 6358.25, "high": 6366.0 },
"outcome_so_far": { "failed_within_10_bars": null, "resolved": false, "bars_observed": 6 },
"conditional_estimate": {
"p_fail_within_10_bars": 0.58,
"meaning": "Of past breakouts whose own rolling context looked like this one, this share closed back through the breakout level within 10 bars."
},
"reference": {
"failure_rate_pct": {
"within_5_bars": { "rate_pct": 50.4, "n": 3918 },
"within_10_bars": { "rate_pct": 62.8, "n": 3916 },
"within_20_bars": { "rate_pct": 72.4, "n": 3882 }
}
}
}
],
"day_type": {
"available": false,
"why": "No day-type model has been trained or validated for the gap window. The day session has its own model (mode=day_session) and the Asian window has its own (mode=asia_session); the gap window is covered by the shape block and the event reads instead.",
"window_note": "Two validated day-type reads exist, each from a model trained on its own window ..."
},
"shape": {
"bars_measured": 41,
"net_move_pct_of_range": 61.2,
"close_position_in_range": 0.83,
"one_way_share": 0.58,
"deepest_pullback_pct_of_range": 24.5,
"high_made_at": 0.92,
"low_made_at": 0.04,
"direction_changes": 3,
"range_tightness": 0.74,
"how_to_read": "Arithmetic over the last 41 five-minute bars — what this stretch did, not what comes next ...",
"not_a_forecast": "Measured description of bars that have already printed. It carries no accuracy figure because nothing is being predicted, and it is not the day-type classification ..."
},
"reference_note": "Measured on ES futures 5-minute bars, 2010-2026, pre-registered.",
"disclaimer": "Educational analytics, not investment advice."
} | Field | Type | Meaning |
|---|---|---|
symbol | string | The normalised symbol the read was computed on. May differ from what you sent. |
window | string | The window the events were built on: rth, gap or asia. An unrecognised value such as eth returns 400 rather than quietly falling back. |
window_is_live | boolean | Check this before saying "now". true means the window is open and this read covers the window in progress. false means you are reading the last completed one; a window_note spells it out. |
session | string | The window-local trading date the read covers, YYYY-MM-DD. |
bars | integer | 5-minute bars inside the window so far. A complete US day session is 78; the gap window holds 66. |
quote | object | open, high, low, last, prev_close, change_pct for the window — so you can verify the read describes the stretch you think it does. |
source | string | Which market-data vendor served the bars. |
volatility_base | object | The 5-day average daily range every event definition is scaled by, with its definition spelled out. |
events | array | Each classical event detected in this window today (first occurrence per type), with direction, timing, live outcome status, the pre-registered reference rates for this window, and — on range_breakout — the calibrated conditional_estimate. An empty array means nothing has fired yet; that is a finding, not an error. |
day_type | object | The calibrated five-class day-type distribution for the session in progress. available is true only in the rth window; in gap and asia it is false with a why, because the model reads a session from its open and those windows have none. When available: bars_read, probs (five classes, descending, summing to 1), meaning, accuracy, and below 18 bars a provisional_note. This block is a prediction about an unfinished day and carries its measured accuracy with it. |
shape | object | Arithmetic over the bars already printed in this window: bars_measured, net_move_pct_of_range, close_position_in_range, one_way_share, deepest_pullback_pct_of_range, high_made_at, low_made_at, direction_changes, range_tightness, plus how_to_read and not_a_forecast. Present in every window including gap, and absent only when fewer than 10 bars have printed. It measures a stretch that already happened, so it has no accuracy figure and is not the day-type read. |
window_note | string (optional) | Present whenever window_is_live is false. Names the completed window being returned so it is not reported as live action. Relay it. |
window_progress_note | string (optional) | Present when the window just opened: the first range needs 10 completed bars plus a breakout bar, so no range event can exist earlier. |
coverage_note | string (optional) | Present on symbols other than ES, NQ and QQQ: the reference rates and the conditional model were measured and validated there, so elsewhere they are context, not claims. Relay it. |
symbol_note | string (optional) | Present when the ticker resolves but is a known confusion trap — for example GOLD, which is a US-listed equity (Gold.com, Inc.) and not the metal — for the metal use GC=F or GLD. |
disclaimer | string | Always present. Surface it; do not strip it. |
The example above is a gap read, so its day_type block is
unavailable by construction. In the rth window the same block looks like this:
{
"day_type": {
"available": true,
"mode": "day_session",
"bars_read": 54,
"probs": {
"TrendUp": 0.34,
"Reversal": 0.22,
"TrendingRange": 0.21,
"Range": 0.2,
"TrendDown": 0.03
},
"meaning": "Calibrated probability distribution over the five day types this session
could resolve into. Report the distribution, not just the top class — the
second script is often very much alive.",
"accuracy": "Measured on held-out S&P 500 E-mini years the model never trained on: at
18 bars (90 minutes in) top-1 53.5% and top-2 68.6%; over a complete
session 66% and 80%. The majority class covers 37% of days — quote that
baseline alongside any accuracy figure. Calibration error is under 10% ..."
}
}
Two different kinds of statement live in one response, and they should not be merged.
day_type estimates how an unfinished day resolves, so it is scored: 66% top-1
and 80% top-2 over a complete session, 53.5% and 68.6% at the 18-bar mark, against a 37%
majority-class baseline, on held-out ES years. shape measures bars that have
already printed, so there is nothing to score and no accuracy figure to borrow — it is not
a day type, and it says nothing about what comes next.
Only our own labels and probabilities are returned. No course material, slide titles or third-party text is ever exposed through this endpoint.
GET /v1/weis/{symbol}
Weis Wave structure and volume-price events for the instrument. Part of the paid plan; use a key for ongoing calls.
| Parameter | In | Type | Default | Notes |
|---|---|---|---|---|
symbol | path | string | required | Must be an instrument whose data source carries volume. Same formats as /v1/brooks. |
session | query | rth | asia | full | rth | rth is the US day session. asia is accepted only for 24-hour
instruments (=F, =X), otherwise 400; in practice
Weis needs exchange volume, so spot FX still returns its no-volume result.
full uses the whole bar stream, including pre- and post-market, and is the
window on which the published Weis reference win rates were measured. It is valid only
when the active source publishes real extended-hours volume. Otherwise the endpoint
returns 422 NO_EXTENDED_VOLUME; obey its retryable field, or
use rth.
|
{
"symbol": "ES=F",
"asof": "2026-07-24",
"session_window": "rth",
"session_is_live": true,
"source": "leandata",
"waves_total": 63,
"current_wave": {
"dir": "up", "bars": 7, "move": 12.25,
"volume": 41822.0, "vol_vs_median20": 0.63
},
"recent_waves": [
{ "dir": "down", "move": -21.75, "bars": 14,
"volume": 128640.0, "end_time": "2026-07-24 10:35:00-04:00" },
{ "dir": "up", "move": 18.5, "bars": 9,
"volume": 74210.0, "end_time": "2026-07-24 11:20:00-04:00" }
],
"events": [
{ "type": "no_supply_long", "dir": "long",
"time": "2026-07-24 12:15:00-04:00", "price": 6404.75,
"reference": {
"es_win": 0.514, "es_n": 23077, "es_p": "3e-5",
"verdict": "CONFIRMED(ES)",
"warning": "reverses on BTC (48.3%) — do not port across assets"
} }
],
"disclaimer": "Wave/event analytics derived from Weis Wave methodology. Reference stats are ES-futures-specific (2010-2026, pre-registered tests); edges do NOT transfer across assets (verified: BTC null/reversed). Educational analytics, not investment advice."
} | Field | Type | Meaning |
|---|---|---|
session_window | string | The window used: rth, asia, or full. |
note_window | string (optional) | On rth and full, states whether the returned wave construction matches the full-stream window used for the published reference win rates. |
note_session | string (optional) | On asia, states that the window is experimental and the reference win rates do not transfer directly. |
waves_total | integer | Completed waves detected in the fetched window. Event detection needs at least 46. |
current_wave.dir | string | up, down, or forming when no wave has been established yet. |
current_wave.bars | integer | Bars elapsed since the last confirmed pivot. |
current_wave.move | float | Price change since that pivot, in the instrument’s own units. |
current_wave.volume | float | Volume accumulated in the wave so far. |
current_wave.vol_vs_median20 | float | null | That volume divided by the median volume of the last twenty completed waves. Below 1.0 means this move is being made on less than normal effort. null when there is not enough history. |
recent_waves[] | array | The eight most recent completed waves, oldest first. Each has dir, move, bars, volume and end_time. |
events[].type | string | One of cib_long, cib_short, no_supply_long, no_demand_short, sot_short. |
events[].dir | string | long or short — the direction the tradition assigns to the event, not a recommendation. |
events[].time | string | Timestamp of the bar at which the event became knowable. Never earlier than the data that confirms it. |
events[].price | float | Close at that bar. |
events[].reference | object | Pre-registered statistics for that event type on ES 5-minute data, 2010–2026: es_win (win rate as a fraction), es_n (sample size), es_p (p-value where significant), verdict, and note or warning where the result reverses or fails to transfer. |
note | string (optional) | Present when there is wave structure but fewer than 46 waves, so no event can fire yet. |
error | string (optional) | Present instead of an analysis when the source has no volume or fewer than 120 bars. |
disclaimer | string | Always present, and it carries the cross-asset warning. Surface it. |
A maximum of ten events are returned, most recent last. Reference win rates hover between 45% and 52% and two of them run the opposite way from the tradition — that is the measured truth, not a bug. See the Weis Wave page for the full table and the pre-registration.
GET /v1/maxpain/{symbol}
The max pain strike for every expiration inside 45 days, computed from the latest open-interest settlement available from the upstream source. Part of the paid plan; use a key for ongoing calls. What is max pain? works the calculation through by hand.
This endpoint is arithmetic, not a model. It takes open interest, tries every listed strike, and returns the one that minimises the total payout to option buyers if the underlying settled there. There is no volatility input, no pricing assumption and no fitted parameter, which means anyone holding the same option chain computes the same number. That is worth stating because the next endpoint on this page is not like that.
| Parameter | In | Type | Default | Notes |
|---|---|---|---|---|
symbol | path | string | required | US listed stock or ETF with options. Not normalised the way the other endpoints are — see the coverage note below. |
distribution | query | boolean | false | Adds oi_by_strike to each expiration: call and put open interest per strike, within 15% of that expiration’s max pain. Roughly an order of magnitude more data, so it is off by default. |
{
"symbol": "AAPL",
"as_of": "2026-07-23",
"spot": 333.02,
"spot_date": "2026-07-24",
"contracts": 1536,
"max_pain": [
{ "expiration": "2026-07-24", "dte": 1,
"max_pain": 322.5, "spot_vs_max_pain_pct": 3.26,
"call_oi": 161348, "put_oi": 113197, "put_call_ratio": 0.702,
"highest_call_oi_strike": 330.0, "highest_put_oi_strike": 320.0 },
{ "expiration": "2026-07-27", "dte": 4,
"max_pain": 325.0, "spot_vs_max_pain_pct": 2.47,
"call_oi": 17290, "put_oi": 15748, "put_call_ratio": 0.911,
"highest_call_oi_strike": 340.0, "highest_put_oi_strike": 320.0 }
],
"method": "For each expiration, the strike that minimises the total payout to option
buyers if the underlying settled there. Computed from open interest alone:
no pricing model, no volatility assumption, no fitted parameter.",
"note": "Open interest from the latest settlement available upstream. Check as_of rather
than assuming it is yesterday; dte is counted from that measured settlement date.
highest_call_oi_strike and highest_put_oi_strike are simply the strikes carrying
the most open interest; we have not measured whether price respects them, and
they are not support or resistance levels. Descriptive output only: no forecast
and no directional claim is made.",
"disclaimer": "Educational analytics, not investment advice."
}
Two of the ten expirations returned by that call are shown. Every expiration inside 45 days
comes back, and you should always quote one alongside its dte — max pain is
computed per expiration, and the near-dated and far-dated figures routinely disagree.
| Field | Type | Meaning |
|---|---|---|
as_of | string | The latest upstream settlement date used for the open-interest calculation. This is the date that matters; check it rather than assuming the source has already published the immediately previous trading day. |
spot | float | Last daily close used for the distance calculations, with spot_date giving its date. It can be one day newer than as_of. |
contracts | integer | Distinct contracts in the chain after de-duplication. |
max_pain[].dte | integer | Calendar days from as_of to that expiration. Never negative — expired contracts inside the fetch window are dropped. |
max_pain[].max_pain | float | The strike itself. |
max_pain[].spot_vs_max_pain_pct | float | How far spot sits above (positive) or below (negative) that strike, as a percentage. |
max_pain[].call_oi / put_oi | integer | Total open interest on each side for that expiration, with put_call_ratio as puts divided by calls. |
max_pain[].highest_call_oi_strike | float | The strike carrying the most call open interest. A description of where contracts sit, nothing more — see the warning below. |
max_pain[].highest_put_oi_strike | float | Same for puts. |
max_pain[].oi_by_strike | array (optional) | Present only with ?distribution=true. |
method | string | How the figure was computed, in one sentence. Stable across calls; safe to show a user. |
stale | boolean (optional) | Present and true only when the upstream option-data source could not be reached and this is the last snapshot we hold. Check as_of before using it. We would rather serve a dated cached chain than a 503. |
disclaimer | string | Always present. Surface it; do not strip it. |
These are not support and resistance levels, and we have not measured whether price
respects them. highest_call_oi_strike and
highest_put_oi_strike tell you where contracts are concentrated, which is a fact
about positioning, not a fact about price. A put wall sits below spot on almost every ticker
on almost every day, because puts are bought below spot as protection — so its existence
carries close to no information on its own. Report where the strikes are; leave what to make
of them to the person asking.
Coverage, and how this differs from the free pages. The Max Pain pages cover 25 stocks and 18 ETFs, as HTML, rebuilt nightly, free and with no key — and they stay that way. This endpoint exists for the broader set of liquid US stocks and ETFs, for JSON rather than a web page, and for an on-demand read from the latest settlement available upstream rather than the site’s last nightly build. On demand does not mean intraday: open interest still updates only after settlement. If the ticker you want is already on a free page and you only need to read it, use the page.
GET /v1/gamma/{symbol}
Estimated dealer gamma exposure: net and gross GEX, the zero gamma (flip) level, and the strikes carrying the most gamma. Part of the paid plan; use a key for ongoing calls. What is gamma exposure? explains the hedging mechanism from scratch, including what zero gamma means and where the estimate is known to be wrong.
Unlike /v1/maxpain, this is an estimate. It solves implied
volatility from each contract’s end-of-day quote using Black-Scholes with a zero rate
and no dividends, and it assumes dealers are long every call and short every put. That
assumption is a public convention, not a measurement — open interest never reveals who holds
which side. Two providers can publish different flip levels for the same ticker on the same
day and both be applying their method correctly. Quote the sign and the rough magnitude;
do not quote the decimals as if they were observed.
| Parameter | In | Type | Default | Notes |
|---|---|---|---|---|
symbol | path | string | required | Same coverage rule as /v1/maxpain: US listed stocks and ETFs. |
by_strike | query | boolean | false | Adds gamma.by_strike: net GEX at every strike within 15% of spot, sorted by strike. This is what a gamma profile chart is drawn from. Off by default because it is large. |
{
"symbol": "SPY",
"as_of": "2026-07-24",
"spot": 738.93,
"spot_date": "2026-07-24",
"gamma": {
"net": -10365541161,
"gross": 25880523413,
"ratio": -0.401,
"regime": "negative",
"flip": 752.32,
"flip_vs_spot_pct": 1.81,
"flip_status": "resolved",
"contracts_used": 3072,
"heaviest_strikes": [
{ "strike": 740.0, "gex": -1504707244, "share": 0.058 },
{ "strike": 730.0, "gex": -1063055315, "share": 0.041 },
{ "strike": 720.0, "gex": -1054508436, "share": 0.041 }
],
"by_expiration": [
{ "expiration": "2026-07-27", "net": -250129641, "abs": 2597715124 },
{ "expiration": "2026-07-28", "net": -779956392, "abs": 1623353292 },
{ "expiration": "2026-07-29", "net": -199724630, "abs": 1326588006 }
]
},
"method": "Black-Scholes with zero rate and zero dividend, implied volatility solved from
the bid/ask mid of each contract's end-of-day quote, then gamma x open interest
x 100 x spot^2 x 1%, calls positive and puts negative. The flip level is the
zero crossing nearest spot on a 0.9x-1.1x grid. This is an estimate that assumes
dealers are long every call and short every put — a convention, not a
measurement.",
"note": "An estimate, not observed data — see method. It uses the latest settlement available
upstream and cannot update intraday whatever the timestamp suggests. Positive net gamma is
associated with hedging that leans against price, negative with hedging that leans
with it; that is a description of a mechanism, not a forecast and not a directional
recommendation. We have not published a measured hit rate for these levels.",
"disclaimer": "Educational analytics, not investment advice."
}
Three of the fifteen expirations in by_expiration are shown, and the heaviest
strikes list is truncated to three of five.
| Field | Type | Meaning |
|---|---|---|
gamma.net | integer | Dollars of stock the estimate says dealers must trade per 1% move, calls positive and puts negative. The sign is the headline: positive means hedging leans against price, negative means it leans with it. |
gamma.gross | integer | The same sum ignoring sign — how much hedging is going on in total. A small net inside a large gross means the two sides nearly cancel and a modest shift could flip the sign. |
gamma.ratio | float | net / gross, so a scale-free measure of how one-sided the book is. Near zero means finely balanced; near ±1 means lopsided. |
gamma.regime | string | positive or negative, derived from the sign of net. Provided so you do not have to re-derive it. |
gamma.flip | float | null | The zero gamma level: the price at which net gamma would cross zero, taken as the crossing nearest spot on a grid from 0.9× to 1.1× spot. null is a valid answer — read flip_status. |
gamma.flip_vs_spot_pct | float | null | Distance from spot to the flip as a percentage. This is usually more useful than the level itself: a wide gap means the current regime has some staying power, a narrow one means an ordinary session could invert it. |
gamma.flip_status | string | resolved, or no_sign_change_within_10pct. The second is a state, not a failure — gamma keeps one sign right across the traded strikes, so the whole range reads as a single regime and there is no crossing to quote. If the chain were simply too thin, you would have received a 404 instead. |
gamma.contracts_used | integer | Contracts that had a usable quote and a solvable implied volatility. Deep in-the-money and badly quoted contracts are skipped, so this is lower than the chain size. |
gamma.heaviest_strikes | array | The five strikes with the largest absolute GEX: strike, signed gex, and share of gross. Concentrations of estimated hedging, not levels with a measured hit rate. |
gamma.by_expiration | array | Net and absolute GEX per expiration, oldest first. Gamma is not spread evenly across expirations and the split differs by symbol, so read this array rather than assuming the front expiration dominates — a large expiry rolling off can reset the picture. |
gamma.by_strike | array (optional) | Present only with ?by_strike=true. |
method | string | The full calculation and its assumptions, in one string. Show it to anyone who asks where the number came from. |
stale | boolean (optional) | Same meaning as on /v1/maxpain: upstream was unreachable and this is the last snapshot held. |
disclaimer | string | Always present. |
Both options endpoints use the latest settlement available from the upstream source. Check
as_of: it may not be the immediately previous trading day, and neither endpoint can
update intraday. That limitation bites hardest close to expiry, which is exactly when the
same-day flow you cannot see is largest. Say so when you quote either endpoint.
Symbol formats
Symbols are normalised server-side, so several common spellings resolve to the same instrument. The rules:
| Market | Format | Examples |
|---|---|---|
| US stocks and ETFs | Plain ticker | NVDA, TSLA, SPY, QQQ |
| US futures | Root plus =F | ES=F, NQ=F, GC=F, CL=F |
| Hong Kong | Digits plus .HK; leading zeros and an HK: prefix are both accepted, and codes shorter than four digits are padded to four | 3690.HK, 03690.HK, HK:3690 all resolve to 3690.HK; 700.HK and HK:700 resolve to 0700.HK |
| China A-shares | 6-digit code; the exchange suffix is inferred (6/9/5 leading digit → Shanghai, 0/1/2/3 → Shenzhen) | 600519, 600519.SH, SH600519, 600519.XSHG all resolve to 600519.SS |
| Crypto spot | Base and quote joined by a hyphen; USD, USDT, USDC, EUR and GBP quotes are accepted | BTC-USD, ETH-USD, SOL-USDT |
| Crypto perpetuals | Base plus -PERP; Kraken’s own PF_ code also works | BTC-PERP, SOL-PERP, PF_XBTUSD |
| Spot FX | Currency pair without a slash; 15 major currencies | EURUSD, eurusd, EUR-USD, EURUSD=X all resolve to EURUSD=X. EUR/USD does not work — the slash is a path separator, so it 404s before the symbol is parsed. |
Crypto bars come from Kraken, which publishes real per-bar volume; spot reads cover the last
60 hours and perpetuals about seven days. On thinly traded pairs many five-minute bars record
no trades at all, and every Weis condition is a volume ratio, so /v1/weis returns
422 TOO_ILLIQUID rather than waves built on gaps in trading. The response reports
the share of bars that traded. /v1/brooks is unaffected — its event definitions
use price only. Liquid pairs such as BTC-USD, ETH-USD and
SOL-USD traded in every bar when we checked. Note that no win rate has been
measured on any crypto pair: the pre-registered transfer tests on BTC confirmed none of eight
signals, and one reversed. Treat crypto reads as descriptive.
Spot FX works on /v1/brooks only, with a standing caveat in the response: the
reference rates and the conditional model were measured on ES, NQ and QQQ, so FX reads carry
a coverage_note and are descriptive. /v1/weis cannot run on spot FX at all
(no consolidated volume exists over the counter) — use the CME currency futures instead:
6E=F, 6B=F, 6J=F, 6A=F. The options
endpoints do not cover FX and there is no substitute to offer (see the prompt below for why
currency ETFs do not work either).
Never pass a plain English instrument name as a ticker. GOLD resolves — to a US-listed equity, Gold.com, Inc., not the metal. The API
flags that specific trap with a symbol_note, but the general rule matters more:
map the name to an instrument first. Gold is GC=F for futures,
GLD for the ETF or GC=F for futures; the S&P 500 is
SPY or ES=F; the Nasdaq is QQQ or NQ=F;
crude is CL=F or USO. A wrong ticker returns a 404 that
suggests the right one, and a 404 is never charged — so guessing is safe.
The two options endpoints are narrower than the table above. /v1/maxpain and /v1/gamma take US listed stocks and ETFs only; Hong
Kong, A-share, futures and crypto symbols have no option chain here and return
400. Cash-settled index options — SPX, SPXW,
NDX, RUT, VIX — also return 400, for a
different reason worth being straight about: the chain is available to us, but our price source
carries no index level, and every figure on both endpoints needs the underlying price. Rather
than manufacture one from a tracking ETF we decline the request. Use the ETF itself:
SPY for the S&P 500, QQQ for the Nasdaq 100, IWM for
the Russell 2000.
Errors
Every error is RFC 9457 application/problem+json — the standard shape, with
type, title, status and detail. Every body
additionally guarantees error_code and retryable. Access and billing
errors add richer handoff fields such as available_actions and
message_for_your_human; ordinary request and validation errors do not promise them.
The retry contract, which is the one thing worth memorising: retryable is always false on 401,
402 and 403, and none of the three should ever be retried. A
402 in particular will never succeed on retry — it is a billing state, and the
second call returns the same thing as the first. Branch on that one boolean and an agent
cannot get stuck hammering a paywall. Transient upstream failures, quota responses with a
reset time, and a temporary extended-volume source fallback can carry
retryable: true. Obey the field in the body instead of inferring from status
alone. Access errors also tell an agent when to request a free key and when to hand checkout
to a person.
| Status | error_code | retryable | Meaning and what to do |
|---|---|---|---|
400 | REQUEST_FAILED | false | Two cases. Either you asked for ?session=asia on something that is not a 24-hour instrument — drop the parameter, or use a futures (=F) or spot FX (=X) symbol. Or you called an options endpoint with a symbol it does not cover: an index root, or a Hong Kong, A-share, futures, FX or crypto symbol. The message says which, and names the tracking ETF where one exists. |
422 | VALIDATION_ERROR | false | The request body or one of its fields failed validation — most often an invalid email or malformed free-key request. Fix the request; repeating it unchanged cannot work. |
422 | NO_EXTENDED_VOLUME | Read the body | session=full needs genuine pre/post-market volume, but the active source supplied mostly zero-volume extended bars. If retryable: true, the API temporarily fell back from a source that normally carries that volume: retry once. If false, use session=rth or a symbol/source with real extended-hours volume. |
401 | NO_API_KEYINVALID_API_KEY | false | The key is not in our records — check the header name and that the key starts with qd_; keys are case-sensitive and carry no whitespace. With no key, the normal path is the anonymous first look or, for recognised AI-platform fetchers, the shared platform pool; NO_API_KEY appears when a source counter cannot be assigned. The body includes request_free_api_key. |
429 | FREE_QUOTA_EXHAUSTEDFREE_QUOTA_EXHAUSTED_SERVICE_WIDE | true | Read access_kind: anonymous means the one first-look response is spent, and ai_platform means the shared no-key pool for recognised AI-platform fetchers (100 successful calls per platform per UTC day) is spent — in both cases the next action requests a free personal key. evaluation_key means the free key has spent its 10 shared calls and the next action is checkout. The body carries Retry-After and quota_resets_at. The service-wide variant protects paid capacity; paid keys are exempt. A separate FREE_POOL_BURST 429 is a per-minute limit inside the platform pool — its Retry-After is under a minute and a short wait genuinely clears it. |
402 | SUBSCRIPTION_EXPIREDUSAGE_CAP_REACHED | false | The subscription behind the key has lapsed, or the anti-abuse cap for this billing period has been hit. Do not retry. The body says which case it is, carries the checkout link, and carries a sentence to relay to the person who owns the key. |
429 | FREE_KEY_REQUEST_LIMIT | true | The email or source has requested too many free-key delivery messages for the UTC day. The response does not reveal whether an address already exists. Wait for the reset or use the checkout action; do not loop on the delivery endpoint. |
403 | PRODUCT_NOT_ENTITLED | false | The key is valid but is a legacy per-product key that does not cover this endpoint — for example an old Weis-only key calling /v1/brooks. Email quantdata@quantdata.uk; existing subscribers are moved across by hand rather than paying twice. Current keys cover every endpoint and never see this. |
404 | REQUEST_FAILED | false | No data for that symbol, or not enough sessions to compute a read. On the options endpoints it also covers a chain too thin to produce a stable figure, and — on /v1/gamma only — too few contracts with a usable quote to estimate gamma at all. Failed lookups do not consume usage. Read the message: it suggests a likely correct ticker where there is one. A thin chain on /v1/gamma is worth one retry against /v1/maxpain, which needs no quotes and often still works. |
503 | REQUEST_FAILED | true | Options endpoints only. The upstream option-data source could not be reached and we hold no cached snapshot for that symbol. Back off and try again. If a snapshot does exist you will get a 200 carrying stale: true instead — we serve a dated cached chain rather than fail. |
The extra fields
| Field | Type | Meaning |
|---|---|---|
error_code | string | Guaranteed on every error. Stable machine-readable identifier: NO_API_KEY, INVALID_API_KEY, PRODUCT_NOT_ENTITLED, SUBSCRIPTION_EXPIRED, USAGE_CAP_REACHED, FREE_QUOTA_EXHAUSTED, FREE_QUOTA_EXHAUSTED_SERVICE_WIDE, FREE_POOL_BURST, FREE_KEY_REQUEST_LIMIT, VALIDATION_ERROR, NO_EXTENDED_VOLUME, or REQUEST_FAILED for other request errors. Branch on this, never on the prose. |
retryable | boolean | Guaranteed on every error. false on 401, 402 and 403. It is true on transient 5xx responses and daily-limit 429 responses that carry a reset time; NO_EXTENDED_VOLUME may be either, depending on whether the source fallback is temporary. Obey the value literally. |
available_actions | array (access/billing errors) | Not guaranteed on ordinary request errors. When present, gives machine-readable next steps such as request_free_api_key, evaluate_for_free, subscribe, or ask_a_human. |
message_for_your_human | string (access/billing errors) | Not guaranteed on ordinary request errors. When present, it is a pre-written sentence for the model to relay verbatim to its user. |
free_without_a_key | array | The one anonymous API first look plus the free Max Pain pages, GEX pages and methodology page. For continued API evaluation, use the separate request_free_api_key action. |
what_you_get | string | A plain description of what the API data is and is not — descriptive statistics about how similar past sessions resolved, not a forecast and not a recommendation. |
doc_url | string | A link back to this page. |
A full body — this is a 401 with no key sent at all:
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
{
"type": "https://quantdata.uk/problems/no-api-key",
"title": "No API key supplied",
"status": 401,
"error_code": "NO_API_KEY",
"retryable": false,
"detail": "Request a free API key by email at /v1/access/free-key, then send it in the
X-API-Key header. Keys start with qd_.",
"doc_url": "https://quantdata.uk/docs",
"what_you_get": "Descriptive statistics and model output: how sessions that resembled the
current one resolved historically. It is not a forecast of today, not a
recommendation to buy or sell anything, and not investment advice.",
"free_without_a_key": [
{ "url": "https://api.quantdata.uk/v1/{endpoint}/{symbol}", "cost_usd": 0,
"returns": "One anonymous first-look response per source per UTC day. After that,
request a free qd_ key by email for 10 successful calls
per UTC day shared across all four APIs." },
{ "url": "https://quantdata.uk/max-pain/{ticker}", "cost_usd": 0,
"returns": "Max pain, put/call ratio, call and put walls and open interest by
expiration — one page per covered stock or ETF (25 stocks and 18 ETFs), rebuilt nightly from
end-of-day option open interest. Lowercase tickers. Static pages, no key,
no account." },
{ "url": "https://quantdata.uk/gex/{ticker}", "cost_usd": 0,
"returns": "Estimated dealer gamma exposure for the covered stocks and ETFs: net and gross GEX,
the zero gamma (flip) level and the heaviest strikes, rebuilt nightly.
Same static-page rules: no key, no account." },
{ "url": "https://quantdata.uk/methodology", "cost_usd": 0,
"returns": "How every number on this service was measured: held-out years,
pre-registration, calibration, and the fact-check of the trading
literature's famous probability claims against 3,946 S&P 500 futures
trading days. Read this before deciding whether the paid endpoints are
worth anything to you." }
],
"available_actions": [
{ "action": "request_free_api_key", "method": "POST",
"url": "https://api.quantdata.uk/v1/access/free-key", "content_type": "application/json",
"body_schema": {
"email": "required email address",
"marketing_opt_in": "optional boolean; true only with explicit consent; the recipient must
reply SUBSCRIBE to the delivery email before updates begin",
"source": "optional short attribution string"
},
"delivery": "The qd_ key is sent only to that email address.",
"cost_usd": 0, "card_required": false,
"calls_per_utc_day": 10,
"covers": ["brooks", "weis", "maxpain", "gamma"] },
{ "action": "evaluate_for_free", "method": "GET", "url": "https://quantdata.uk/methodology",
"cost_usd": 0,
"why": "Every number this service returns, with how it was measured and where the
models fail. Read it before deciding whether the paid endpoints are worth
anything to you." },
{ "action": "subscribe", "checkout_url": "https://buy.stripe.com/...",
"price_usd_per_month": 149, "includes": "every endpoint",
"free_trial_days": 3,
"note": "A card is collected up front and the first 3 days are free. A person
has to complete the checkout — card authentication (3-D Secure) requires the
cardholder and cannot be done unattended by an agent. The key is shown on the
page immediately after checkout. If checkout uses the same email as a free
key, that key is upgraded in place." },
{ "action": "ask_a_human", "mailto": "quantdata@quantdata.uk" }
],
"message_for_your_human": "This endpoint needs a Quant Data API key. I can request a free one
using your email address; no account, card or GUI is required, and
all four endpoints share 10 successful calls per
UTC day. The raw key is sent only to that inbox. If you later need
uninterrupted access, the full service is $149/month with
the first 3 days free after adding a card. You would
need to complete that checkout yourself. The output is historical
statistics, not a forecast or trading recommendation.",
"disclaimer": "Educational analytics, not investment advice."
} Long strings are wrapped here for reading; the real payload sends them on one line. Every status at a glance:
HTTP 429 FREE_QUOTA_EXHAUSTED retryable TRUE anonymous, platform-pool or free-key daily allowance used up; inspect access_kind
HTTP 429 FREE_QUOTA_EXHAUSTED_SERVICE_WIDE
retryable TRUE service-wide evaluation-traffic cap; paid keys are exempt
HTTP 429 FREE_POOL_BURST retryable TRUE per-minute limit inside the shared platform pool; Retry-After is under a minute
HTTP 429 FREE_KEY_REQUEST_LIMIT retryable TRUE free-key delivery limit reached for email/source/day
HTTP 401 NO_API_KEY retryable false no key, and the source address could not be read
HTTP 401 INVALID_API_KEY retryable false the key is not in our records
HTTP 403 PRODUCT_NOT_ENTITLED retryable false a legacy key that does not cover this endpoint
HTTP 402 SUBSCRIPTION_EXPIRED retryable false the subscription behind this key has lapsed
HTTP 402 USAGE_CAP_REACHED retryable false anti-abuse cap hit for this billing period
HTTP 422 VALIDATION_ERROR retryable false malformed free-key body or invalid field value
HTTP 422 NO_EXTENDED_VOLUME retryable varies session=full source lacks real pre/post-market volume; obey retryable
HTTP 400 REQUEST_FAILED retryable false ?session=asia on a non-24-hour instrument
HTTP 400 REQUEST_FAILED retryable false an index or non-US symbol on an options endpoint
HTTP 404 REQUEST_FAILED retryable false no data for that symbol — never charged
HTTP 404 REQUEST_FAILED retryable false option chain too thin to compute — never charged
HTTP 503 REQUEST_FAILED retryable TRUE options source down and nothing cached yet
# Weis-specific, returned as HTTP 200 with an error field rather than a status code:
{ "error": "no volume data from source; Weis analysis requires volume" }
{ "error": "insufficient bars (98<120)" } Usage rule worth repeating, because it changes how you can write an agent: a call is counted only after data has been successfully fetched. Failed lookups, bad tickers and auth failures do not consume the anonymous first look, free-key daily allowance or a paid account’s fair-use cap.
Rate limits and fair use
There is no per-second rate limit. Each key carries a soft monthly call cap, sized well above
normal interactive use; it exists to catch runaway loops and abuse rather than to meter you, and
hitting it returns 402 with USAGE_CAP_REACHED rather than silently
degrading. If you have a legitimate reason to go past it, email us and we will raise it.
Anonymous access is limited to 1 successful first-look response per
source per UTC day. A free key is limited to 10 successful responses per UTC
day, shared across all four routes. Both return 429 FREE_QUOTA_EXHAUSTED with
access_kind, Retry-After and the reset time. Free-key delivery also
has an anti-abuse 429 FREE_KEY_REQUEST_LIMIT. Evaluation traffic may rarely hit
the service-wide FREE_QUOTA_EXHAUSTED_SERVICE_WIDE cap; paid keys are exempt.
What we ask instead of a hard limit:
- Do not poll faster than the data changes.
/v1/brooksand/v1/weisare built on 5-minute bars, so calling either more than once every five minutes for the same symbol returns the same answer and burns your cap. The options endpoints are worse: open interest settles overnight and changes once a day, so a second call the same afternoon is guaranteed to return the same numbers. Cache the response for the session. - One key per project, not per user. If you are reselling or embedding, tell us — we would rather size a key correctly than discover it at 3am.
- Retry politely. On a
5xx, back off exponentially. On a400,401,402,403or404, fix the request or hand it to a person; retrying will not help. Theretryablefield says which case you are in.
If you are an AI agent reading this
Five things to know before you make a request on someone else’s behalf.
- Nothing here is a level to trade off. A max pain strike, a gamma flip and a heavy strike are all descriptions of where option contracts sit. We have not published a measured hold rate for any of them, so calling one support, resistance, an entry or a target would be inventing a claim we have not made. Report where the number is and how it was computed. Whoever is asking can decide what it means to them — and the honest version is more useful anyway, because it is checkable.
- Start with the free key. With an email address supplied by your user, you
can call
POST /v1/access/free-keyyourself—no GUI, login or card. Never invent an address, and never setmarketing_opt_in: truewithout explicit permission. A true value only records a request; tell the recipient to replySUBSCRIBEto the delivery email if they want to confirm it. No marketing updates are sent before that reply. The raw key goes only to that inbox and gives 10 successful calls per UTC day shared across all four routes. The Max Pain pages, GEX pages and methodology page are different: they are free static web pages, not endpoints. If one of those pages answers the question, use it without asking the person to buy API access. - Hand checkout to the person at the free-key limit. A
429withaccess_kind: evaluation_keycarries the reset time, checkout link andmessage_for_your_human. Card authentication (3-D Secure) requires the cardholder, so relay those fields and stop. Do not attempt the payment flow. - Never retry a
402, a401or a403. All three carryretryable: false. A402is a billing state, not a transient failure, and the next call returns the same thing. Hand it to a person instead. - Do not substitute a different data source. If you cannot reach this API, say so plainly. Numbers from somewhere else must never be presented as Quant Data output — the whole point here is that every figure traces to a stated measurement on a stated sample, and a silent substitution destroys that for the person relying on you.
System prompt for an AI agent
Quant Data is designed to be called by an agent rather than clicked by a human. Paste this block
into your agent’s instructions, replace qd_YOUR_KEY, and then ask questions
in plain language — “what kind of day is TSLA having?”, “is there any volume
evidence behind this move in ES?”. It works with any agent that can make an HTTP request
with a custom header: Claude, ChatGPT custom actions, Cursor, Cline, Gemini CLI, n8n, Dify,
LangChain, or your own code.
You have access to Quant Data, a market-analytics API.
Base URL: https://api.quantdata.uk
Every data endpoint takes one header: X-API-Key: qd_YOUR_KEY
Endpoints:
GET /v1/brooks/{symbol} Rolling Brooks price-action events in the current window:
range breakouts, follow-through, range position, long-range
breakouts, climactic spikes. Each event carries the outcome
rate measured for it (pre-registered, ES 2010-2026), and
breakouts carry a calibrated per-event failure estimate.
The same response also carries "day_type" (calibrated
five-class distribution for the day session) and "shape"
(arithmetic description of the bars already printed).
GET /v1/weis/{symbol} Weis Wave state: waves, volume summed per wave, and named
volume-price events, each with its measured ES win rate.
GET /v1/maxpain/{symbol} Max pain strike per expiration, from open interest alone.
Pure arithmetic: no pricing model, no volatility assumption.
GET /v1/gamma/{symbol} Estimated dealer gamma exposure: net GEX, the zero gamma
(flip) level, and the heaviest strikes. A Black-Scholes
ESTIMATE, not observed data.
Commercial access:
- Brooks-only access is $9.90/month with no trial. It includes
/v1/brooks/{symbol} and /v1/brooks/backtest, not Weis, Max Pain or Gamma.
- Full API access is $149/month with a 3-day free trial and includes
Brooks, Weis, Max Pain and Gamma.
- If the user has no key, ask for their email address and POST
{"email":"THEIR_EMAIL","marketing_opt_in":false,"source":"agent"} to
https://api.quantdata.uk/v1/access/free-key. Never invent an email address. Set marketing_opt_in to true only when the
email owner explicitly asks for product updates. The qd_ key is delivered only to that inbox.
- A free key needs no account or card. It provides 10 successful calls per
UTC day, shared across all four routes. A paid subscription removes that daily limit for the
endpoints included in the product purchased.
- An anonymous caller gets 1 first-look response. Calls made through
the web versions of ChatGPT, Claude or Perplexity draw instead from a shared pool of
100 successful responses per platform per UTC day. The static Max
Pain/GEX web pages, methodology and guides remain free without an email or key.
Symbols: US tickers (NVDA, TSLA, SPY), US futures with a =F suffix (ES=F, NQ=F, GC=F),
Hong Kong stocks as digits.HK (Meituan = 3690.HK), China A-shares as 6-digit codes
(Kweichow Moutai = 600519), crypto spot as BASE-QUOTE (BTC-USD, SOL-USDT) and crypto
perpetuals as BASE-PERP (BTC-PERP), spot FX as a currency pair
(EURUSD or EUR-USD, both normalise to EURUSD=X). A spot-gold request (XAUUSD) answers
with the XAUT-USD token reading plus a note saying so — spot gold itself has no licensed
feed here; the metal with exchange volume is GC=F. Never write a pair with a slash:
EUR/USD is two path segments and returns 404 before the symbol is ever parsed.
Resolve an instrument name to a ticker before calling: gold -> GC=F (futures) or GLD
(ETF), S&P 500 -> SPY or ES=F, Nasdaq -> QQQ or NQ=F, crude oil -> CL=F or
USO, Bitcoin -> BTC-USD, euro -> EURUSD. Tokenised gold is its own instrument, not a
stand-in for the metal: XAUT-USD reads the Tether Gold order book on a crypto exchange,
which trades at a shifting discount to spot and does not follow the gold market's
calendar. Send it only when the user asked for the token.
Never pass a plain English name as a ticker: "GOLD" is a US-listed equity (Gold.com,
Inc.), not the metal. If you are unsure, try the ETF; a 404 comes back with a suggestion
and is never charged.
Rules:
- When asked what a market is doing right now or what just happened structurally, call
/v1/brooks first. Report events with their measured rates as historical frequencies,
never as predictions. An empty events array is a finding ("nothing has fired yet"), not
an error.
- Besides "events", every /v1/brooks response carries "day_type" and "shape". They are
different kinds of statement. Never merge them and never let one lend its numbers to the
other.
- "day_type" is the calibrated five-class day-type distribution for the session in
progress — a probabilistic read on how an unfinished day resolves. It is served in the
"rth" window only; in "gap" and "asia" it returns available: false with the reason,
because the model reads a session from its open. Report the whole distribution, not just
the top class. Whenever you quote its accuracy you MUST quote the majority-class baseline
in the same breath: 66% top-1 and 80% top-2 over a complete session, 53.5% top-1 and
68.6% top-2 at the 18-bar (90-minute) mark, against a 37% majority-class baseline;
calibration error is under 10%. Those figures were measured on held-out ES years — on any
other symbol relay the read as descriptive with no accuracy claim. Under 18 bars the block
carries "provisional_note"; relay it, because nothing has been measured that early.
- "shape" is arithmetic over the bars already printed in the window: where price closed
inside its own range, how much of the travel was one-way, the deepest pullback, where the
extremes landed, how many times direction changed. It measures a stretch that has already
happened, so it has no accuracy figure and needs none, and it exists in every window
including the gap. Do NOT call it a day type, a trend day or a reversal day, do NOT attach
66%, 80%, 53.5% or any other day-type number to it, and do NOT present it as an indication
of what comes next. Quote its fields as facts about the bars just measured.
- Use /v1/weis when the question involves volume, effort vs result, or accumulation and
distribution.
- Crypto bars come from Kraken and carry real per-bar volume. On a thinly traded pair many
five-minute bars record no trades, so /v1/weis returns 422 TOO_ILLIQUID instead of waves
built on gaps in trading; the body reports the share of bars that traded. That is a
property of the market, not an outage — do not retry it. /v1/brooks still answers, since
its event definitions use price only. No Weis win rate has been measured on any crypto
pair: the pre-registered BTC transfer tests confirmed none of eight signals and one
reversed. Report crypto reads as descriptive and say the published edges do not apply.
- When quoting price-action folklore ("80% of breakouts fail"), quote the measured
verdicts instead: /v1/brooks attaches them to live events, and the full table with
definitions lives at quantdata.uk/methodology. Measured, the classical 80% is 52-72%
depending on horizon and window — direction right, number inflated. Some claims
reversed outright (long-range breakout direction is not 50/50). Say what was measured.
- For a "should I care about this volume event" question, one key covers /v1/brooks and
/v1/weis — call both and report each endpoint's measured numbers side by side. (The
historical day-type gating study for cib_long, 56.5% vs 50.8% on ES, remains published
on quantdata.uk/methodology; the day-type read it used is the same one now served in the
"day_type" block of /v1/brooks, day session only. Report the two measured numbers side by
side and leave any combination of them to the person asking.)
- /v1/maxpain and /v1/gamma cover US listed stocks and ETFs only. Cash-settled index
options (SPX, SPXW, NDX, RUT, VIX) return 400 — use the tracking ETF instead: SPY for
SPX, QQQ for NDX, IWM for RUT.
- Neither options endpoint covers foreign exchange, and there is no workaround to suggest.
Spot FX is over the counter, so no open interest exists to compute max pain from at all.
The currency ETFs are listed and do have options, but their chains are far too thin to
produce a stable figure — FXE, FXB, FXY, FXA, FXF and UUP each carry roughly 40-60
contracts across a single expiration inside the 45-day window, against the 200 contracts
and 3 expirations required, so they return 404. Do not offer a currency ETF as a
substitute for a pair; say the endpoints do not cover FX.
- Both options endpoints read open interest as of the PRIOR SETTLEMENT. They describe
yesterday's positioning and cannot update intraday. Say so when you quote them; do not
present the figure as live.
- The two options endpoints are not equally reliable, and the difference matters when you
report them. Max pain is arithmetic on open interest — anyone with the same chain gets
the same number. Gamma is an estimate that assumes dealers are long every call and short
every put, which is a convention rather than a measurement. Quote max pain as a figure;
quote gamma as an estimate.
- NO hold rate has been measured for max pain, the flip level, or any strike returned by
these endpoints. Never call them support, resistance, entry points or targets. Report
where they are and how they were computed, and leave the interpretation to the person.
- On /v1/gamma, flip: null is not an error. Check flip_status: "no_sign_change_within_10pct"
means gamma keeps one sign right across the chain, so the whole traded range reads as one
regime. That is a state worth reporting, not a failure.
- On /v1/maxpain, always cite the expiration and its dte alongside the strike. Max pain is
computed per expiration and the near-dated and far-dated figures routinely disagree; a
max pain quoted without its expiry is meaningless.
- Weis reference win rates are ES-specific. For BTC, ETH and gold, state that the edge
did not transfer (BTC: 0 of 8 pre-registered signals confirmed, no_supply reversed).
- Two Weis events are reversed versus the tradition: no_demand_short measured 45.6% and
sot_short 47.6%. Report the measured number, not the traditional reading.
- /v1/brooks reads one of three windows and picks whichever has the freshest bars when
you omit ?window: "rth" (the market's own day session — New York hours for US symbols,
local hours for Hong Kong and A-shares), "gap" (16:00-21:30 Beijing, between the Asian
close and the US open) and "asia" (09:00-16:00 Beijing). Each window's reference rates
were measured separately and the response pairs events with the right row. gap and asia
exist for 24-hour instruments only; on stocks they return 400.
- /v1/weis accepts "rth", "asia" and "full": use "full" when comparing returned Weis
events with the published reference win rates, because those rates were measured on the
whole bar stream. "full" needs genuine pre/post-market volume and can return 422
NO_EXTENDED_VOLUME; obey retryable, then use rth when the source cannot provide it.
Unknown session or window values return 400 rather than quietly falling back.
- ALWAYS check "window_is_live" before you use the word now. When it is false you are
holding the last completed window; a "window_note" field spells it out — relay it and
date the read.
- A window that just opened carries "window_progress_note": the first range needs 10
completed bars plus a breakout bar, so the earliest possible event is about an hour in.
- The per-event "conditional_estimate" on range breakouts is calibrated and validated
zero-shot on NQ 2023+ (AUC 0.646, calibration error 5.1%, n=2,637) and on QQQ. On
symbols other than ES, NQ and QQQ, relay it as descriptive, with the "coverage_note"
the response carries.
- /v1/weis does not work on spot FX. Currency pairs are traded over the counter, so no
source publishes a consolidated volume and the wave construction has nothing to measure.
The response says so and is not charged. The CME currency futures do carry exchange
volume: EURUSD -> 6E=F, GBPUSD -> 6B=F, USDJPY -> 6J=F, AUDUSD -> 6A=F.
- Reference rates were measured on ES futures. On any other symbol the response carries a
"coverage_note"; relay it — the rates are context about ES, not claims about that
symbol.
- Errors are application/problem+json. Every problem carries error_code and retryable;
access and billing errors additionally carry available_actions and
message_for_your_human. Branch on error_code, never on the prose. "retryable" is false on
401, 402 and 403 — none can be fixed by calling again. Never retry a 402: it is a billing
state and will return 402 again.
- On a 401, relay "message_for_your_human" and offer to request the free key using the
request_free_api_key action. Ask for the user's email first; never invent one.
- On a 429, check access_kind. If it is anonymous or ai_platform (the shared no-key pool for
calls made through an AI platform), offer request_free_api_key. If it is
evaluation_key, relay the reset time and checkout_url. On a 402 from a paid key, relay
"message_for_your_human" and the checkout_url. You cannot finish checkout
yourself — card authentication (3-D Secure) requires the cardholder. Three web pages still
work without an email or key: quantdata.uk/max-pain/{ticker},
quantdata.uk/gex/{ticker}, and quantdata.uk/methodology, which carries the fact-check
verdicts as a static table.
- If you cannot reach this API, say so. Never answer from a different data source and
present it as Quant Data output.
- This is educational statistics, not investment advice. Never present it as a buy or
sell recommendation, never state a probability as a certainty, and never size a
position. The rules in that prompt are not decoration. The session-hint rule stops an agent silently reading the wrong trading window; the cross-asset rule stops it quoting an ES win rate at someone asking about Bitcoin; the reversed-events rule stops it repeating folklore our own data contradicts. If you trim the prompt, keep those three.
Get a key
Start with a free API key: one email address, no login or card, and
10 successful calls per UTC day shared across all four endpoints. The raw
qd_ key is delivered only by email; the request response never exposes it. An AI
agent can make that request after asking for the address.
Get free API key Buy full API access See pricing
Full access is $149 a month. A card starts the first 3 days free; use the same email at checkout and the free key is upgraded in place. Card authentication requires the person paying, so an agent hands off the checkout link. Cancel any time in the Stripe customer portal. The static Max Pain and GEX web pages stay free and need no key, and so does the methodology page. Questions, or a key bought under the older two-product pricing? quantdata@quantdata.uk — a person reads it.