Methodology
This page exists so you can argue with us. Every figure quoted anywhere on this site is produced the way it is described here, and where a test failed, the failure is written down in the same detail as the successes.
Three rules everything follows
- Held-out years. A model is never scored on data it was fitted to. We split by calendar year, not by random rows — shuffling intraday bars leaks tomorrow into today and inflates everything.
- Pre-registration. For the claim and event tests, the rule, the entry, the barriers and the success definition are all written down before the outcome is computed. This is what stops you from quietly trying eleven variants and reporting the one that worked.
- Publish the failures. A win rate is only informative if you know how many things were tested. The failures are on this page in the same tables as the confirmations.
The same three rules get applied to things that are not price data. In the finance influencer audit we grade market commentary sources — newsletters, Substacks, X accounts — against their full posting history, benchmark-adjusted, failures published.
The day-type model
Status note: this model's read is served in the day_type block of
GET /v1/brooks/{symbol}, alongside the rolling event read described in
the section below. Because a day type is a property of a whole
session, the block exists in the day session only; in the Asian and gap windows it returns
available: false with the reason. The implementation was rewritten to run without
a deep-learning runtime on the server, and the rewrite was checked against the original
across the full history before it went live — same model, same weights, same numbers. The
held-out results and the gating study below are unchanged and still power the
QQQ strategy case study.
A day type is a coarse description of how a session behaved: did it trend one way all day, chop sideways, grind upward inside a channel, or reverse. Experienced chart readers name these on sight. The model does the same thing, in probabilities.
- Data. S&P 500 E-mini futures (ES), minute bars from 2010 to 2026, resampled to 5-minute bars, sliced into US regular-hours sessions. 3,946 sessions in total.
- Labels. Five classes — TrendUp, TrendDown, Range, TrendingRange, Reversal — assigned from the shape of the completed session. The majority class covers 37% of days, which is the baseline any honest accuracy number has to beat.
- Features. 18 per-bar values describing where price sat within the session so far, how far it had travelled, and how the current bar related to the previous session's close. No indicators, no news, no order-flow data.
- Model. A small sequence classifier — 3 layers, width 96, dropout 0.15 — that reads the session's bars in order and pools them. It is deliberately small: of the 3,946 sessions, only 761 carry a day-type label (491 train, 111 validation, 159 test; a pool of roughly 3,000 sessions is used unlabelled for pretraining), and with 761 labelled examples a bigger model would memorise rather than generalise.
- Calibration. Raw classifier scores are overconfident. We fit a single temperature parameter on validation data so the stated probability matches observed frequency. Measured calibration error is under 10 percentage points, meaning when the API says 70% the outcome happens roughly 70% of the time.
Results, on years the model never saw
The 90-minute number is the one that matters for live use, and it is the smaller one. Two hours into a session the market has not decided yet, and a model that claimed otherwise would be lying.
Single stocks are out of distribution. The model was fitted to index futures.
Reads on single stocks, Hong Kong listings, A-shares and non-index futures were never
validated and carry no accuracy claim — descriptive only. (The same boundary discipline
carries over to the rolling event read that now lives at /v1/brooks: its
response marks every symbol outside the measured set with a coverage_note.)
The rolling event read, and how it was measured
Since 2026-08-06, GET /v1/brooks/{symbol} detects classical price-action
events — the day's first range breakout, breakout follow-through, closes in the top or
bottom third of an established range, breakouts from long-lived ranges, climactic spikes —
and attaches to each the outcome rate measured for that exact definition in that exact
window. Three windows were measured separately on ES 5-minute bars, 2010–2026, under a
pre-registered protocol with the definitions frozen before the confirmatory run: the US day
session, the Asian session (09:00–16:00 Beijing), and the gap window
(16:00–21:30 Beijing — between the Asian close and the US open).
| Breakout failure rate | within 5 bars | within 10 bars | within 20 bars |
|---|---|---|---|
| US day session (n≈3,900) | 52.1% | 62.6% | 70.6% |
| Gap window (n≈3,900) | 50.4% | 62.8% | 72.4% |
| Asian session (n≈3,900) | 46.7% | 58.7% | 69.4% |
One event per day — the first breakout — so the live detection and these rates describe the same population. Two classical claims failed outright in this program and the endpoint says so where relevant: the 50/50 long-range-breakout direction claim reversed (57.8% upward in the US session, skewed in every window), and the sell-zone half of the range-position claim did not confirm in the US session or the gap window.
The per-event conditional estimate
Every breakout looks different, so beyond the base rates the endpoint fits a small
gradient-boosted model over twelve rolling features of the event itself — range tightness,
breakout-bar strength, how often the range had been tested, recent travel — and returns a
calibrated probability that this particular breakout closes back through its level within
10 bars. The confirmatory protocol was pre-registered with one judged cell:
NQ futures, 2023 and later — an instrument and a period the model never trained on.
Result: AUC 0.646; the lowest predicted tercile of breakouts failed 54.2% of the time
against 79.1% in the highest; calibration error 5.1% with the calibrator frozen before the
run; n=2,637. A plain logistic regression passes the same gates, and the transfer repeats
on QQQ (AUC 0.660, n=2,017). On symbols other than ES, NQ and QQQ the estimate is
descriptive and the response carries a coverage_note saying so.
Full pre-registration documents, frozen definitions, the amendment record (including the
switch of the volatility base to a 5-day average when the 20-day version proved
unobtainable in production — verified to move every published figure by less than half a
point), and the complete result tables including the failed cells are in the repository's
research records. POST /v1/brooks/backtest runs the identical detectors over
bars you supply, so the calibration table can be reproduced on your own data.
The price-action claims fact-check
Trading courses are full of confident probabilities. We took the ones that are specific enough to be testable, wrote each as an explicit rule before measuring anything, and ran it over 3,946 ES trading days of 5-minute data, 2010–2026.
| Claim | Traditionally quoted | Measured |
|---|---|---|
| One of the day's two extremes is already set, 5 minutes in (1 bar) | 20% | 26.3% |
| One of the day's two extremes is already set, 35 minutes in (7 bars) | 50% | 58.3% |
| One of the day's two extremes is already set, 90 minutes in (18 bars) | 90% | 81.2% |
| On a bull day, the low of the day forms early in the session | 90% | 83.3% |
| On a bear day, the high of the day forms early in the session | 90% | 80.4% |
| After an opening-range (18-bar) breakout, the opposite extreme holds | 90% | 83.5% |
| Trading-range breakouts fail (the famous "80% rule") | 80% | 52.0% / 62.4% / 69.7% |
The last row is measured within 5, 10 and 20 bars respectively — the claim's truth depends entirely on how long you are willing to wait, which the claim never says. Every row covers the same 3,946 days except two: the opening-range breakout can only be scored on the 3,578 days that produced a breakout at all, and the “80% rule” row counts range-breakout events rather than days — roughly 5,000 to 9,700 events depending on the bar window.
Meta-verdict: the direction is right every time. Where the teaching quotes a big round number — 90%, 80% — the measured rate comes in below it by roughly 7 to 10 percentage points, and by considerably more on the "80% rule". The two earliest checkpoints run the other way: an extreme is pinned slightly sooner than the courses say. A workable calibration for the rest of the genre: trust the direction, discount the round number by about a tenth. It is also, quietly, a compliment — these were people describing real market behaviour from memory, and they got the sign right in every case.
The seven rows above are the complete measured set, and this page is where they live: free to read, no key and no account. They are not the whole library — 15 more high-measurability claims are structured but not yet measured, and they will be added here when they are. The long-form write-up, including what happened when the framework was trained into a model and the three places it failed outright, is at Al Brooks price action, tested.
How to cite this
The table above is the measured rate for each claim, with the sample it was measured on. Free to read, no key and no account — link straight to it.
Plain text
Quant Data. "The price-action claims fact-check: seven classic claims measured against 3,946 ES trading days." quantdata.uk. https://quantdata.uk/methodology#claims BibTeX
@misc{quantdata-claims-factcheck,
author = {Quant Data},
title = {The price-action claims fact-check: seven classic claims measured against 3,946 ES trading days},
year = {2026},
howpublished = {\url{https://quantdata.uk/methodology#claims}},
} Permanent link
https://quantdata.uk/methodology#claims Quoting a figure from this page is fine without asking. If you want the underlying per-observation table for something we have not published, or you spot a number you think is wrong, mail quantdata@quantdata.uk — a corrected number is worth more to us than a cited one.
Weis Wave events
A Weis Wave chart throws away the time axis and groups price into up-waves and down-waves, labelling each with the total volume that traded inside it. The idea, from the Wyckoff tradition, is that a rally on shrinking volume and a sell-off on heavy volume mean different things.
Wave construction
- A close-price zigzag: a new wave begins when price closes more than a threshold away from the running extreme in the opposite direction.
- Threshold = 0.3937 × ATR(60). Using a multiple of average true range rather than a fixed number of points makes the wave definition scale-free, so the same rule works on a $4 stock and on Nasdaq futures without retuning. The coefficient itself was calibrated once on ES and then frozen — it is not refitted per symbol, which is the whole point of the cross-market tests below.
- Each completed wave records direction, cumulative volume and price displacement.
The five events
- cib_long / cib_short — a wave with unusually heavy volume and displacement, immediately answered by a much lighter wave in the other direction. The "counter-attack" shape: a big push that fails to follow through.
- no_supply_long — a down-wave on notably light volume: nobody is pressing the sell side.
- no_demand_short — the mirror image: an up-wave on light volume, traditionally read as a warning that the rally is unsupported.
- sot_short — the shortening-of-thrust family: successive pushes covering less ground.
How they were tested
Pre-registered before measurement: entry on the event bar, symmetric barriers at ±3 × ATR, and a win defined as reaching the barrier in the event's claimed direction first. Run over ES 5-minute data, 2010–2026, with multiple-comparison correction across the family of events.
| Event | Win rate | n | p | Verdict |
|---|---|---|---|---|
| cib_long | 51.8% | 13,867 | 1.6e-5 | Confirmed on ES |
| no_supply_long | 51.4% | 23,077 | 3e-5 | Confirmed on ES |
| cib_short | 48.6% | 12,642 | — | Not confirmed |
| no_demand_short | 45.6% | 22,262 | — | Reversed |
| sot_short | 47.6% | 9,427 | — | Reversed |
Two of the traditional short signals did not merely fail to work — they came out reliably below 50%, which on a symmetric test means the opposite side was winning. Read carefully: the inverse reading was not pre-registered, so we treat "no-demand is actually bullish information on ES" as a hypothesis produced by this data, not as a confirmed edge. It is in the API as a measured rate with that caveat attached, and nothing more.
A pattern worth noticing: every bearish event underperformed on an index that spent 2010–2026 mostly rising. Some of this is a genuine asymmetry in how index futures behave; some of it is a bull market. We cannot fully separate the two, and we are not going to pretend otherwise.
The one result that replicated: day type as a filter
The strongest finding on this site is not either read alone. It is the day-type read used as a gate on a Weis event — only taking the bullish counter-attack when the model reads the session as trending up.
| Market | Under TrendUp | Opposite context | p | Status |
|---|---|---|---|---|
| ES · S&P 500 futures | 56.5% | 50.8% | 0.002 | Trained asset |
| NQ · Nasdaq futures | 56.1% n=1,536 | 45.3% n=689 | 2e-6 | Zero-shot |
| QQQ · Nasdaq ETF | 54.3% n=1,200 | 44.3% n=592 | 7e-5 | Zero-shot |
Read the comparison column carefully: it is context versus context, not gated versus ungated. On ES, the 50.8% is the same event in the opposite day-type context — unconditionally, cib_long runs 51.8% on ES. On NQ and QQQ the comparison figure is the event in sessions the model read as TrendDown. The two contexts sit on opposite sides of the unconditional rate: the gate is sorting the same event into a stronger and a weaker population, not merely skimming the top off an average.
Zero-shot means no retraining, no refitting, no per-market threshold tuning: the ES-calibrated model and the ES-calibrated wave threshold pointed at a market they had never seen. That is the test that most published trading research quietly skips, and it is the reason we put more weight on a 54.3% here than on an 80% from a backtest fitted in place.
Where it fails
Gold, Bitcoin and Ethereum share an anti-signature. Run under the same frozen test protocol, the same events do not merely weaken on these markets, they invert — on BTC, zero of eight pre-registered signals confirmed, and no_supply runs the wrong way (measured 48.3%, n=8,899). Whatever the ES/NQ/QQQ signature is measuring, it is a property of index-type equity markets, not a law of markets.
Classical chart patterns did not survive. We ran Bulkowski-style detectors over 16 years of intraday index-futures data, producing more than 121,000 detections, then trained a model to predict each detection's outcome. AUC 0.54 — barely distinguishable from a coin flip. Pattern shape alone, in this data, carries almost no outcome information.
The Asian session is a pre-registered NO_GO. We tested whether the day-type gate carries into the Asian overnight session on 24-hour futures. It was registered in advance, it did not meet the threshold, and the verdict stands as NO_GO. The API defaults to the US day session for exactly this reason.
Max Pain and the options metrics
The permanently free Max Pain and GEX pages are
computed, not scraped from anyone's dashboard — and the paid endpoints
/v1/maxpain and /v1/gamma serve the same two figures. These options
endpoints sit in the $149 full API product alongside Brooks and Weis; they are not standalone
add-ons, and the $9.90 Brooks-only product does not unlock them. There is exactly one implementation of this
arithmetic, a single module that the free pages, the API responses and our own research
scripts all import. That is a mechanical guarantee, not a promise: with no second copy of the
formula to drift, the number you can check for free on this site is the number a key holder's
agent receives, and anything we publish about how these figures behave was measured on the
code that produced yours. Here is precisely what each number is.
- Source data. End-of-day option open interest per contract, for expirations out to about 45 days, plus the end-of-day underlying price. Open interest is the number of contracts currently outstanding at each strike — a slow-moving positioning measure, not a trade feed.
- Cadence. Recomputed nightly after the close. The pages are static and read a stored file, so if a data pull fails the page keeps the previous day's numbers and shows their date rather than going blank or silently drifting.
- Open interest is always one session old, everywhere. The OCC tallies open interest after the close and publishes it the following morning, so every open-interest figure in existence describes the previous session while the price beside it is current. This is a property of the clearing cycle, not of any one vendor: every max pain and gamma number on this site, on any competing site, and inside any broker platform pairs yesterday's positioning with today's price. It matters most in the last days before expiration, when open interest churns fastest. We publish both stamps rather than one blended date so the offset is visible, and our own pin study excludes any observation where the two stamps would let a price be graded against positioning that already saw the outcome.
- Scope. Derived metrics only. We do not redistribute raw quotes or full option chains.
The two figures are not equally reliable, which is why each gets its own endpoint rather than sharing one response. Max pain is pure arithmetic on open interest: no pricing model, no volatility assumption, so anyone holding the same chain gets the same number. Gamma exposure is a Black-Scholes estimate — implied volatility is solved with r = 0 and q = 0 — and the solve can fail on thin or inconsistent chains. On the 2026-07-23 snapshot, when this site covered 25 tickers, all 25 produced a max pain figure while a zero gamma flip resolved on 22 of them. The coverage has grown since; in the 2026-09-03 files behind the free pages, max pain resolves on 43 of 43 tickers and a zero gamma flip on 37. The gap between those two counts is the point, and it moves night to night as chains change. Splitting the endpoints keeps a gamma degradation from ever dragging down a max pain answer, and stops the two numbers from reading as equally hard.
Why r = 0 and q = 0, stated properly. Setting both to zero is a choice, not an oversight, and the reason is that we would rather be visibly approximate than invisibly wrong. On options inside about 45 days, the discounting term moves the solved implied volatility by a small fraction of the width of the bid-ask spread the price came from, and gamma moves less than that. Putting a specific rate in its place would require picking one — which curve, which tenor, borrow costs, and a dividend forecast per ticker — and every one of those is a guess that would arrive dressed as precision. A zero is legible: anyone can reproduce our number exactly, and anyone who needs the discounted version knows precisely what to add. The same principle governs everything on this page. Where an input is genuinely unknown we would rather say so in the formula than smuggle an assumption into a decimal place.
Max pain
Max pain is the settlement price at which the largest amount of open option value expires worthless — equivalently, the price that minimises the total payout option sellers owe option buyers.
If the notation below is unfamiliar, start with the plain-English guide, What is max pain? — the formula is that guide said compactly, nothing more.
For every candidate settlement price K:
payout(K) = 100 * [ SUM over call strikes X of max(K - X, 0) * call_OI(X)
+ SUM over put strikes X of max(X - K, 0) * put_OI(X) ]
max_pain = the K that minimises payout(K)
100 is the contract multiplier. Computed per expiration, over the strikes
that actually have open interest. What it is not: a forecast. It is an arithmetic property of today's open interest, it moves as open interest moves, and the well-known tendency of price to sit near it into expiration is a weak statistical regularity with a contested explanation — not something to trade off on its own.
Put/call ratio and the walls
The put/call ratio here is open-interest based: total put open interest divided by total call open interest for that expiration. The call wall is the strike carrying the most call open interest, the put wall the strike carrying the most put open interest. They are where positioning is concentrated, which is worth seeing, and nothing more than that.
Gamma exposure (GEX)
Gamma measures how fast an option's directional exposure changes as the underlying moves. Gamma exposure aggregates that across every open contract to estimate how much hedging flow a 1% move would mechanically generate.
If the Greek letters are new to you, read What is gamma exposure? and What is zero gamma? first — the formula reads much more easily on the way back.
Per contract i, at end of day, with spot S:
1. solve Black-Scholes for the implied volatility that reproduces the
contract's end-of-day price
2. take gamma at that implied volatility
3. GEX_i = gamma_i * OI_i * 100 * S^2 * 0.01 (positive for calls,
negative for puts)
Net GEX = sum of GEX_i. Contracts whose open interest and quote disagree,
or where the IV solve does not converge, are dropped rather than guessed.
Gamma flip = the price level where net GEX(x) crosses zero, searched on a
grid from 0.9*S to 1.1*S, taking the crossing nearest spot. Sign convention: calls positive, puts negative, which is the common dealer-long-calls convention. Net GEX above zero is usually described as a dampening regime and below zero as an amplifying one. Treat that as a widely used heuristic, not as something we have independently verified — we publish the computation, not a claim about what it predicts.
Cross-check. The pipeline was validated against optioncharts.io on SPY and matched on 15 of 15 expirations before any of this went live. We compute gamma ourselves rather than reading an upstream field, so this check is what stands between the formula and a silent error.
What none of this tells you
It does not tell you what to buy, when to enter, how much to size, or where to stop. It has no view on your account, your horizon, or your risk tolerance, and it never will — that is the line between analytics and advice, and it is not a formality.
It also does not tell you that a 56% base rate is profitable. Costs, slippage, the distribution of winners against losers, and your own behaviour all sit between a base rate and a result. A real edge of a few percentage points is a starting point for research, not a conclusion.
If you find an error in anything on this page, email quantdata@quantdata.uk. Corrections get published.
Convinced by the method?
If the numbers above are the size you expected from an honest test, Brooks-only is $9.90 a month with no trial; every endpoint is $149 a month, with the first 3 days free.
Get free API key Buy full API access
Paid access starts with 3 days free after a card is added. The Max Pain and GEX pages stay free and need no key at all.
Questions first? quantdata@quantdata.uk — a person reads it.