One measurement passed its null test with p = 0.0000 and was rejected anyway. The other passed two gates and died on sample size. Neither failed because of a bad p-value — and that is the point.
The question took three weeks: does the market's capacity to absorb order flow change over time, and does that change tell you anything?
absorption = |net taker flow| / |log return|
High means the market ate the flow — money moved, price barely did. Low means fragile.
We had already tested this on a direction axis: are up bars different from down bars? After controlling properly, 11/17 symbols, residual 31%, below threshold. No asymmetry.
Every version had the same structural flaw: it is a ratio whose two terms come from the same bar. Group by anything correlated with either term and you are comparing groups that are not matched on the other. That flaw cost three measurements before it was written down as a rule.
Does absorption vary over time, and is the variation structural? 17 symbols, 1000 bars of 4h, 90-bar rolling window.
Gate 1 — does it vary at all? Yes, and this was a surprise. The pre-registration predicted this gate would kill the hypothesis, expecting absorption to be volatility wearing a hat.
| raw | controlled | |
|---|---|---|
| coefficient of variation | 0.297 | 0.370 |
Controlling by |return| quintile made it more variable, not less — 124% of the raw variation survived. Wrong prediction, and worth knowing.
Gate 2 — is the variation structural?
| observed CV | 0.3696 |
| null median CV | 0.3313 [5%–95%: 0.3115 – 0.3511] |
| p | 0.0000 |
| effect ratio | 1.12× |
p = 0.0000. The observed value sits outside the null's 95th percentile. By the standard most people use, that is a finding. It is not one, and the pre-registered threshold said so: effect ratio must be ≥ 1.25.
The series comes from a 90-bar rolling window. Consecutive windows share 89 bars. Lag-1 autocorrelation is 0.9875. So we ran a ladder:
| rung | CV | % of observed |
|---|---|---|
| pure noise (full permutation) | 0.204 | 55% |
| block permutation null | 0.331 | 90% |
| observed | 0.370 | 100% |
These are nested. Rung 1: destroy the series completely — adjacent bars unrelated — push it through the same 90-bar window, and you still get CV = 0.204. Consecutive windows share 89 of 90 bars, so the window median can only inch along, and inching along is a smooth wandering series. Over half the variation exists whether or not absorption does anything. The wave is the seam of the window, not the water.
Rung 2: block permutation keeps the window artifact and local autocorrelation, destroying only long-range order. CV = 0.331.
Rung 3: the real series, 0.370. Everything above rung 2 is all the hypothesis gets: 0.370 / 0.331 = 1.12×.
Had we used a free permutation null, the effect ratio would have been 1.81× and this would have passed. Same data, same observed value — finding or no finding, decided entirely by a choice made before looking at anything.
A different approach, and the one we believed in. Absorption as a ratio was cursed by construction, so go to trade level instead and split impact into permanent and temporary:
temporary = how much the price came back
permanent = how much stayed
absorption = high temporary / low permanent
The structural advantage: numerator and denominator come from different points in time. The same-bar trap cannot be built.
BTC trade-level data, 200 windows of 20 minutes spread over 30 days, top 1% trades by size, horizons in seconds. The null matters most: for each large trade, 20 random trades from the same window, same direction mix, no size condition. Question: is the move caused by the trade's size, or by taker direction and short-term price direction being correlated anyway?
| horizon (s) | LARGE | RANDOM | NET | n |
|---|---|---|---|---|
| 1 | 0.9059 | 0.6646 | 0.2413 | 12822 |
| 5 | 1.0647 | 0.8964 | 0.1683 | 12820 |
| 30 | 1.0725 | 0.7733 | 0.2992 | 12751 |
| 120 | 0.9570 | 0.5711 | 0.3860 | 12646 |
| 600 | 0.6166 | 0.3699 | 0.2468 | 11474 |
Gate 1 — does impact decay? Yes. Peaks at 120s, falls to 64% of peak by 600s. A hump, which is the shape you want.
This corrected an earlier reading. In a pilot with n=43 we found impact growing with horizon and concluded the framework could not be built at this scale. With 200 windows it decays normally. The pilot was noise.
Gate 2 — is size doing the work? Yes. Net 0.3860 bp, bootstrap 95% CI [0.058, 0.731], p = 0.024. Large trades move price 1.68× what random trades do.
Gate 3 — SNR:
| SE (window-blocked bootstrap) | 0.1690 |
| SNR = 0.3860 / 0.1690 | 2.28 (threshold 3) |
Two gates passed. The shape is right, the null is beaten, the direction makes sense. And it dies because 200 windows are not enough to separate 0.386 bp from zero with the confidence committed to in writing.
We could run 600 windows. We are not going to, because the pre-registration says the sample is locked and will not be grown if the result disappoints. That rule exists precisely for moments like this one, when there is a reason that feels principled.
Somewhere in the middle of this, the average turned out to be hiding two populations. Of large trades that actually pushed price:
| pushed | 429 / 596 (72%) |
| … came back (absorbed) | 235 / 429 (55%) |
| … kept going (pierced) | 193 / 429 (45%) |
| average 120s effect | +2.797 bp |
That average is a mix of two opposite things. And the ratio is not constant — across windows it ranges from 30% to 89%, CV = 0.256.
So it shipped as a state measure: "right now the market is absorbing 54% of large orders." Not a signal. The API response and the UI card both carry the limit in text, because we checked whether it forecasts anything and the honest answer is that we do not know and the early numbers say no:
There is also a tautology risk we cannot yet rule out: "absorbed" is defined as price did not continue in the order's direction, and window return measures where price went. With order flow directionally imbalanced (measured: −0.3), those may be two views of one thing. Only a leading correlation would mean anything, and the leading correlation is nothing.
So it is a description of the last 15 minutes, published as exactly that.
Three weeks, six branches on this hypothesis, six rejections. What changed is that the rejections now come from different gates:
| branch | died at |
|---|---|
| price impact asymmetry | mechanical — up bars carry 10.6% more volume |
| funding asymmetry | out-of-sample — 8/8 in discovery, 6/17 in validation |
| absorption, direction axis | effect below threshold after two controls |
| absorption, time axis | effect ratio 1.12× despite p = 0.0000 |
| impact decomposition | SNR 2.28 after passing decay and null gates |
Early on everything died of mechanical artifacts. Now they die of insufficient evidence for real-looking effects, which is a more expensive and more interesting place to fail.
Gate 1 of measurement 1 passed when we had predicted it would fail. Because gates 2 and 3 were already written, there was no argument to have with ourselves about whether 1.12× was "close enough."
Measurement 1: 17 symbols, 1000 bars of 4h (~167 days), one asset class, one market period. Net flow is a taker-imbalance proxy from kline data, not true exchange netflow. 90-bar window fixed in advance, not swept.
Measurement 2: BTC only, 200 windows × 20 min over 30 days, 12,822 large trades against 256,426 random ones. Single symbol means no generalization — BTC is the most liquid venue, so absorption capacity should be highest here. Five horizons fixed in advance; the null selected the max across the same five.
The shipped measure: ten major pairs, 1-hour window, recomputed every 10 minutes. The window started at 15 minutes and had to be widened — below the top few pairs, a 15-minute window produced literally zero qualifying trades, because the tick size is coarse enough that price often does not move at all within 5 seconds of a large order. At 60 minutes those symbols give 10–46 usable observations. One pair (DOT) is still too thin and its row simply does not render.
It describes; it does not predict. If someone finds the leading relationship we could not, we would like to hear about it.
Why did they all fail at the same point? — nineteen candidates, one shared gate.
Does volume profile create support? — a candidate that passed the null test and collapsed under a volatility control.