Do order blocks predict anything?

Asked as: do order blocks actually predict anything

Controlled for recent momentum, the order-block measure added nothing out of sample. It was redundant, not underpowered — and the distinction is the method.

Not in the form that can be measured from price. I built the most natural price-derived version of the idea — a recency-weighted measure of how much rejection and dwell had accumulated at a level — and under momentum control it carried nothing. On held-out data it made the model marginally worse.

My own note at the time is the shortest accurate summary: the fill-state proxy was momentum in a costume. I call that shape a Costume Variable — a feature that appears to measure a new mechanism and turns out to be a re-encoding of one already in the model.

That is the finding. Most of what follows is the part that took longer to learn: why a null is nearly useless unless you publish what would have had to be true for it to have gone the other way.

What was actually tested

The retail vocabulary here is order blocks, unmitigated levels, stacked liquidity. The measurable core of all of them is the same claim: a level accumulates a state, and that state predicts what happens on the next visit.

Resting orders are not visible on retail data, so any version of the claim stated in terms of actual orders is untestable rather than false — and a claim that cannot be contradicted by observation is not a finding, it is a framing. The version that can be tested is the price-derived one: build the best proxy for accumulated pressure that price history allows, and ask whether it predicts.

That is what I built, and I built it to spec rather than to fail: a logistic model of outcome on momentum plus pressure, fitted on the first 70% of the history by time, evaluated on the held-out remainder.

The result

The partial effect of pressure, read after momentum, was a likelihood-ratio χ²(1) of 0.32 at p = 0.57.

Split into tertiles, the outcome rate ran 47% / 44% / 47% — flat, and not even monotonic.

On the held-out portion, adding pressure to a momentum-only model moved log-loss from 0.6664 to 0.6667. Slightly worse. That is the signature of a variable contributing noise rather than information.

The proxy was not too weak to detect. It was carrying the same information momentum already carried, at lower fidelity.

What this test could have caught, stated before you ask

A null is only interpretable beside the smallest effect the test could have found. Without that bound, we found nothing and we could not have found anything are the same sentence, and the second one is worthless. So here is the bound.

The model was fitted on 1,027 level arrivals and checked on a held-out 441, with a base outcome rate of 46.6%. At that size, an effect of odds ratio 1.20 per standard deviation of the pressure measure would have been detected with 80% power at the conventional five-per-cent threshold. The effect actually observed was odds ratio 1.04 per standard deviation.

In the units the tertile table above uses, the same bound reads: this test could have caught a spread of about ten percentage points between the lowest- and highest-pressure thirds — roughly 42% against 52%. What it saw was three points, running the wrong way in the middle.

Why that is enough to publish as a finding rather than a shrug. Order blocks are not taught as marginally predictive. The claim in the wild is that a level with orders resting at it is materially more likely to hold — a difference you could trade, not one you could only detect with a corpus. An effect of that size would have been caught here several times over. It was not there.

And what it does not license. Effects smaller than that bound are not excluded by this test, and I am not going to pretend otherwise. If resting pressure moves the odds by two or three per cent per standard deviation, this sample could not have seen it, and neither could you trade it. The honest claim is bounded: the large effect the folklore asserts is refuted; a small one remains unmeasured.

The bound is derived from the observed standard error of the pressure coefficient — the conventional route when a test has already been run — at alpha 0.05 two-sided and 80% power, on the Wald approximation to the likelihood-ratio statistic reported above.

Why testing it alone would have shown the opposite

The correction generalises well beyond levels, and it is the cheapest correction in this article.

Tested on its own, a pressure measure looks informative. It genuinely separates outcomes. That is not an illusion — levels that were recently active really do behave differently from levels that were not.

But the measure is built from price history, so it correlates with recent price history by construction, whether or not the mechanism it names exists at all. Any feature assembled from past prices inherits the predictive content of past prices and then presents it under a new name.

The test that distinguishes the two is conditional and cheap: put the simple variable in first, then ask what the elaborate one adds. Most feature testing asks whether the new thing predicts. The question that matters is whether it predicts anything the old thing did not.


The five ways this test could have lied to me

Everything above is the easy half. The harder half is that a conditional test run carelessly produces a confident null that is worth nothing. Below are five failure modes, each one measured on my own research rather than argued from first principles, and each one changing what a null is allowed to mean.

1 · Your sample size is not your sample size

The single most common defect in retail and semi-professional testing is counting overlapping observations as independent ones.

If an outcome is evaluated over a forward window, and a new observation starts on every bar, then consecutive observations share most of their future. They are not independent draws. They are heavily overlapping views of the same price path, and a test that treats them as independent will report a standard error that is far too small and a p-value that is far too confident.

I measured this on my own EURUSD study — 98,864 bars, a 24-bar horizon and a 20-pip barrier:

quantitymeasured
raw walks96,991
mean average uniqueness0.2608
uniqueness-weighted walks25,297
mean walks live at any one time6.36
mean walk duration6.41 bars

The honest sample is 25,297, not 96,991 — an overstatement of 3.83×. Every standard error computed on the raw count is too small by roughly the square root of that, and every p-value derived from it is correspondingly too eager.

The implication for a null is the opposite of the obvious one, and worth sitting with: inflated n makes it easier to reject, so a null computed on an inflated n is a stronger null than it looks. My order-block result was already a failure to reject; correcting the effective sample size makes that failure more robust, not less. But run the same arithmetic on a positive result and it will frequently dissolve.

And there is a second source of overlap that is easier to miss, because it is in the feature rather than the outcome. The arithmetic above is about the forward window: two observations overlap because their futures overlap. But two observations also fail to be independent when they carry the same label — and a feature computed at a coarser grain than the bars it is attached to guarantees that. A daily label spread across hourly bars makes every occasion twenty-four bars long before the market is consulted at all. In my own set, three of six features are exactly that (see failure mode five). If you are conditioning hourly data on a daily statistic, your occasion count is your day count, and the gap between those two numbers is a factor of twenty-four.

One warning, because it is the trap inside the fix. There is a widely-copied form of the design-effect calculation that divides the long-run variance by p(1−p)/n. That form returns approximately n for any series you give it — it cannot fail, it validates everything, and it is worse than not correcting at all because it produces a number that looks like diligence. The ratio has to be between two variances of the same estimator.

2 · A beautiful cell that does not clear

The second failure mode is the one that catches careful people, because it requires no error at all — only a search.

I ran an interaction search across my own instrument stack on EURUSD: 15 modules, 1,323 candidate cells, each one a pair of conditions crossed against a common outcome ledger. The best cell that came back was this:

entropy_perm = Q2 and regime = COMPRESSION — expectancy −4.93 pips over 426 episodes, n = 2,032, HAC t-statistic −4.55.

A t of −4.55 on 426 episodes with a coherent mechanical story. In most published research that is a finding, and it would have been written up as one.

It is not one. Against a rotation null drawn over the same search — 819 rotations, which is every whole-week rotation the 829-week span permits, so the p-value carries no sampling error and the test is exact rather than Monte-Carlo — the family p-value is 0.0646.

The detail that makes this decisive rather than marginal: the smallest p this test could possibly have returned is 0.0012. The bar was reachable. It was not met. This was not a test that lacked the resolution to find something — it had resolution to spare, looked at 1,323 cells, found the best one, and that best one is not distinguishable from what the same search finds in rotated labels.

The verdict recorded on the artifact is INSIDE_NULL, and the sentence that matters is: crossing the modules adds nothing the singles did not have.

The lesson for anyone testing order blocks, or anything else, with more than one variant: the number of things you tried is part of your result. A t of −4.55 means one thing when it is the only test you ran and something entirely different when it is the best of 1,323. If you have tried several definitions of a level, several lookbacks and several outcome windows, you have run a search, and your best result needs to be priced against the search that produced it.

3 · Your baseline may have eaten the signal

The baseline problem is subtler, and I have not seen it discussed in retail material at all.

If your reference model estimates its own baseline from data that already contains the effect you are looking for, the baseline absorbs the effect and the test becomes blind to exactly the thing it was built to find.

I hit this on a change-point monitor watching twelve market channels over 201 weekly observations. It flagged one candidate break with a complete attribution story. Then I tested it against a control: rotate each channel independently, which preserves every channel’s own distribution and autocorrelation while destroying the co-movement between channels, and recount.

  • observed joint change-points: 1
  • mean across 200 rotated surrogates: 1.365
  • every one of the 200 surrogates produced at least as many as the real data

The real series is not merely indistinguishable from independent channels. It is quieter than them. I do not yet know why — the obvious candidate is that a covariance estimated from data containing the co-movement absorbs it — and I publish that as a hypothesis rather than a mechanism, because the artifact contains no test of it.

The transferable point stands regardless: a control that can only make your result look better is not a control. If your null model is fitted on the same data as your signal model, find out what it absorbed before you trust either.

4 · The instrument, not the market

The instrument explanation is the cheapest to check and the most humbling.

Before concluding that the market has nothing to say, confirm your measurement device can say anything. I found one of my own features declaring four possible states and, across the entire corpus, having ever emitted one. It passed every structural check — the code was correct, the labels were valid, the tests were green — and it could not possibly have conditioned anything, because it never varied.

A declared range is not a realised range. Before reading a null as a fact about the market, measure the realised spread of your own feature: if the thing you are conditioning on is pinned at one value, or nearly so, the null is a fact about your instrument.

Applied here: my tertile split (47% / 44% / 47%) is doing double duty. It shows the outcome does not vary with pressure — and it also shows the pressure measure did have enough spread to form three genuinely different groups. Had those tertiles been near-identical in pressure, the flat outcome rate would have proved nothing at all.


5 · Your label may be the wrong size for the question

The last one I only measured this week, and it is the one I would now check first, because it is the cheapest and it invalidates everything downstream of it.

A feature can vary plenty and still be structurally incapable of answering the question you are asking it — if its resolution does not match the question’s.

I was testing six of my own conditioning features for how much of each label is explained by the time of day, on 98,864 hourly bars. Four came back at exactly 0.0000. Exact zeros across four independent features are not what noise looks like, so before writing that down I tested the obvious mechanism, and it was the answer:

featurelabel changes per daycan it express an hour?
liquidity10.407yes
session (control)3.298yes — by construction
atr1.936yes
regime0.476barely
entropy0.018no — that is once every ~55 days
return-memory0.002no
har-vol0.000no — constant on 100% of days
jumps0.000no — constant on 100% of days

Three of the six emit a label that never changes within a day. Their mutual information with hour-of-day is zero by construction, whatever the market is doing. And this is not a “weak feature” story — har-vol has three states, a modal share of 0.552 and more label entropy than any other feature in the set. It varies enormously. It just never varies within a day, so it cannot answer an hourly question and its zero says nothing at all about whether the clock is in the underlying quantity.

Two things make this checkable rather than a caution:

Run a positive control. I included a feature whose label is the hour bucket. It returned exactly 1.0000. Without that, a screen of zeros is indistinguishable from a broken measurement — an empty diagnostic has to be provably empty.

Know your noise floor. Mutual information estimated from a finite sample is biased upward, by roughly (k−1)(m−1) / (2N ln2). At this sample size that is about 0.0005 bits. Four features read below their own noise floor, which is what makes those zeros structural rather than merely small.

The falsifier that could not fire

There is a sharper version of this worth stating on its own, because it changed how I read my own pre-registered predictions.

Before running that measurement I had written down a prediction with an explicit falsifier: if this particular feature moves little, the mechanism I proposed is wrong. The feature moved by exactly zero. On its face the falsifier had fired and the mechanism was dead.

It had not, and it was not. The feature’s label is daily — it could not have moved on that metric under any market condition. My falsifier had assumed the feature was capable of the movement it was testing for, and it was not. So the mechanism came out untested, not refuted, and recording that distinction was the only honest option.

A pre-registered falsifier is only as good as the assumption that its condition, when met, is met for the reason you think. Writing the prediction down first is what made this visible — had I reasoned about the result afterwards, “the mechanism is wrong” is exactly the conclusion I would have reached, and it would have been the wrong one.

What does survive

Something real is in there, and it would be dishonest to let a null on one construction imply a null on the whole category.

What a level did most recently predicts what it does next. The probability of absorption given that the level last absorbed is 61%, against 33% when it last swept — an odds ratio of 3.49, and it holds out of sample with log-loss genuinely better than base rate.

That is a real effect. It is also a general property of price series: recent behaviour persists for a while. It is not evidence about resting liquidity, institutional participation, or levels as objects with state, and presenting it as such would be exactly the move this article is about.

And I have made that exact move myself, in public, on this same effect. An earlier finding of mine read a level’s history as durable identity — the two halves of a split history correlated at r = +0.31, which is what memory would produce and is equally what short-range trend produces, because the halves share a boundary. Over sixteen years and 183,689 revisits, controlling for the most recent outcome and asking whether a genuinely distant one still adds anything returns an odds ratio of 1.01 at p = 0.53. The retraction is published as the level that remembered nothing, and the shape it names — the Recency Mirage — is the same shape as the Costume Variable seen from the other side. One is a feature re-encoding a simpler variable; the other is a finding that turned out to be the same. I did not spot either from the inside.

Neither shape is new, and the names are the only part I am claiming. A variable that carries no information once a correlated simpler one is in the model is collinearity, and statisticians have had that for a century. A relationship that exists only because both series carry their own recent history is spurious regression in the Granger–Newbold sense, and the econometrics literature has had that since 1974. There is also an established recency illusion in linguistics, which is a different phenomenon with a neighbouring name. What I think the names add is nothing about the mechanism and everything about the moment of recognition: both describe what the error looks like from inside the analysis that is making it, which is the vantage point from which the textbook terms are hardest to apply to yourself. If you find these useful, the underlying statistics are older and better documented than anything here.

The version of this I did build, and what it says

The null above is about a predictive proxy. It is not a claim that zones cannot be tracked — I track them, as objects with a lifecycle, and the base rates that falls out of are worth more than the feature test that started this article.

Every order block and fair-value gap is instantiated as a zone with a birth bar, a direction, a size class, and a state that advances as price arrives. The definitions are mechanical, and they are the ones that matter for the retail claim:

  • TOUCHED — price entered the zone at all (fill fraction above zero)
  • MITIGATED — price filled at least half of it (fill fraction ≥ 0.5)
  • INVALIDATED — a bar closed beyond the far edge. For a support zone, price closed below its floor. This is the level failing, definitionally.

On EURUSD, over a trailing 520-bar H1 window — about a month — with the input file pinned by content hash:

count
zones born in the window112
invalidated — price closed straight through87
still live at the end of the window25

Of the 25 still live: 16 have never been touched, 4 have been touched, and 5 are mitigated at half or more. Seven are order blocks, eighteen are fair-value gaps, and in this window every live zone is a support zone.

The retail claim is that a level with accumulated orders is more likely to hold. In this window, 87 of the 112 that were born did not hold — and “did not hold” here is not a judgement call, it is a bar closing beyond the far edge.

One number in that census is not a finding and I am not going to present it as one. The artifact also reports zero zones expired, and expiry fires at an age cap of 500 bars inside a 520-bar window — so only a zone born in the first twenty bars could ever reach it. Zero expiries is an artifact of the window length, not evidence that zones never age out. It is in the file; it means nothing yet.

And the honest boundary, which the artifact draws on itself. Its signal_v1 block — the fields that would carry prediction, profit, loss and capital protection — reads NOT YET COMPUTED in all four. This is measured state, published as state. The paired validation that would turn a zone census into a predictive claim has not been run, and until it has, none of the above is a signal. That is the same discipline the rest of this article is about, applied to my own instrument rather than to somebody else’s idea.

Five reasons a null happens, and only one of them means stop

The reason all of the above matters is that we found nothing is not one result — it is five, and they imply opposite next actions. Reporting them all with the same word is the single biggest waste in quantitative research, my own included.

the null is limited bywhat it meanswhat to do next
effectthe effect is genuinely near zero and your test had room to see itstop — this is finished, and the bound is a product
powerthe effect could be real but sits under your detection floormore data, or a cleaner outcome — it is reachable
occasionsthe condition fires too rarely, or too clustered, to resolve anythingunreachable on this sample; stop on this corpus
statisticyour measure is pinned near its own ceiling or floorchange the instrument, not the hypothesis
geometrythe effect exists but not in the shape you tested forre-specify — and price the re-specification as a search

My order-block result is effect-limited, and that is why it is publishable as a finding rather than parked as inconclusive: the measure did not merely fail to reach significance, it made held-out prediction worse, which is a direction a merely-underpowered variable does not have.

The interaction result above is also effect-limited, and its bound is unusually sharp because the achievable floor was measured: p could have gone to 0.0012 and stopped at 0.0646.

A number is only true as of the moment it was computed

One more, added the same day I learned it, because it is the failure mode that outlives every other one on this page.

Everything above is about whether a number was right when it was measured. This is about what happens afterwards. A published figure has a shelf life, and nothing about being correct on the day protects it.

Two of my own, found this week:

  • A document in my estate stated that an append-only register held 159 entries. It holds 194. The sentence was true when written and had quietly stopped being true, and nothing in the writing of it could have prevented that.
  • Worse, because it reads as precision rather than staleness: a document quoted “114 codes defined”. The registry defines 119. 114 is the count of codes in a particular state — a different quantity that happens to be adjacent, and adjacent numbers are the ones that survive review.

The cure is not diligence, because diligence is what already failed. It is to attach to each published number the command that regenerates it and the commit it was last checked at, then have something re-run the command and compare. I run exactly that.

And it had been failing, unread. Four claims were refusing when I looked — one of them stale because of a commit I had made myself that morning. I had made a discovery, not propagated it, and the instrument built to catch precisely that was already saying so to nobody.

A rail that is red and unread is a rail that does not exist. That is the transferable part, and it is cheaper to accept from someone else’s estate than to learn in your own.

The honest application to this article: the figures in the section above are enrolled that way; the χ², the tertiles and the log-loss in my own headline result are not. They were computed, they are reported faithfully, and they currently have no mechanism regenerating them on demand.

And that enrolment order is backwards, which is worth saying plainly rather than leaving as an observation. The headline figures are the ones that get quoted, cited and attacked; the supporting ones are not. Putting the load-bearing numbers last into the rail means the most-scrutinised values on the page are the least-verified, and publishing that fact does not fix it. They now ship with their artifact, which closes the recomputation gap for a reader today; enrolling them in the regenerate-and-compare rail — so a drift is caught here rather than by someone else — is the outstanding work, and it goes first, not last.

Limits

One instrument — AUDJPY — price-derived measures only, bar data. A single-instrument null establishes nothing about any other, and naming which instrument is part of the claim rather than a detail: the downloadable artifact states the pair in its first field, and a reader should be able to see that without opening it. The supporting measurements in this article are drawn from my EURUSD research rather than from the order-block study itself — they are there to show the method, not to bound that specific result.

The scope matters more than usual here, so it is worth stating twice: this result kills one price-derived proxy as a predictor under momentum control. It does not kill the fill-state concept, and it does not kill zone tracking — the zone lifecycle this article publishes, with its TOUCHED, MITIGATED and INVALIDATED base rates, tracks zones and reports those rates, and that machinery stands.

What each of the remaining routes actually needs, stated as status rather than as a wish:

  • Book-based measures are no longer blocked on data. I hold exchange top-of-book quotes and have censused them — 21,158,845 rows across seventeen roots in a single trading day. The constraint is that this is exchange data for exchange-traded instruments, so it speaks to futures directly and to a retail spot feed only by analogy. What is missing is the study, not the tape.
  • Order-flow measures need signed flow, which top-of-book quotes do not carry. That one is still a data question.
  • Zone-state measures are built and publishing. What they are missing is the paired validation, and their own artifact says so in four fields.

The permanent lesson is narrower and more useful than “order blocks don’t work”: accumulated rejection measured from price is dominated by memory. If you want to measure state at a level, either use a source that is not the price series itself, or track the level as an object and report its base rates — but do not build a scalar out of past prices, put it beside momentum, and expect it to survive.

And whatever you measure it with, publish the floor beside the verdict. A null without its bound is not a result, it is a shrug with a p-value attached.

The artifact

SHA256: f4d2d7450f13405fea85d8afac2e2ee8d1aa9f1c4cbc010b954052b30e73d1d0

Download dataset

Claims examined

Claim 01§ claim-f48d20b2

That level has orders stacked at it, so it's more likely to hold.

My reading: Unproven

I built the honest version: a recency-weighted measure of accumulated rejection and dwell at a level, fitted as a logistic model of outcome on momentum plus pressure, trained on the first 70% of the history by time and checked on the held-out remainder. The partial effect of pressure, read after momentum, came in at a likelihood-ratio chi-squared of 0.32 on one degree of freedom, p = 0.57. Split into tertiles the outcome rate was flat: 47%, 44%, 47%. And on held-out data, adding pressure made the model very slightly worse. The measure was not weak. It was redundant — and the distinction between those two words is the whole of the method.

Claim 02§ claim-9215dcc1

It worked in my testing, so the concept is sound.

My reading: Misleading

Tested alone, a pressure measure looks informative, because levels that were recently active do behave differently from levels that were not. The test that matters is conditional: put the simple variable in the model first, then ask what the new one adds. This is not a high bar and it is not expensive. It is simply a different question from the one most feature testing asks, and it is the question that separates a discovery from a renaming.

Claim 03§ claim-8821787f

So price-based analysis of levels is worthless.

My reading: False

I am not claiming nothing survives, and it would be dishonest to let a null on one construction imply a null on the category. The sequential effect is real and holds out of sample: an odds ratio of 3.49, with held-out log-loss genuinely better than base rate. The honest framing is that the predictive spine is what the level did last, not how loaded it is — and that this is a general property of price series rather than evidence for any story about resting liquidity.

Claim 04§ claim-81bdcfb9

A null result just means you didn't have enough data.

My reading: Misleading

The claim is sometimes true and is not knowable without measuring it. A null with no bound beside it cannot be distinguished from a test that never had the resolution to reject. So the bound has to be computed and published in the same place as the verdict: what is the smallest effect this test would have caught? If the observed effect is far below that floor, the honest reading is that the question is unresolved on this sample. If the observed effect is a large fraction of the floor and still fails, the line of enquiry is finished and the null is a product. Those two nulls look identical on the page and imply opposite next actions.

Each claim above has a permanent address — the § link — whose canonical home is the refutation index, where it carries its variant phrasings and the true proposition stated on its own feet; this article is the evidence behind it. If a claim's text ever changes, it becomes a new claim at a new address, and the old one stops resolving rather than silently meaning something else.

Cite This Article

Hadal Research. (2026). Do order blocks predict anything?. Hadal Research. https://hadalinstruments.com/research/do-order-blocks-predict-anything/ (SHA-256: f4d2d7450f13405fea85d8afac2e2ee8d1aa9f1c4cbc010b954052b30e73d1d0) Version fa5df51, 2026-08-29.

Version fa5df51 identifies the commit that last changed this page in Hadal's content repository. That repository is not public, so the identifier does not resolve externally — it is published so a citation pins one specific state rather than a moving page. To obtain the exact version cited, use the press and research route.

---