A 2010 paper claims one variable explains two-thirds of short-term price movement. I reproduced that result on four years of data covering every update to the best buy and sell prices in the market. The signal still cannot be traded at a profit.

Cont, Kukanov & Stoikov’s “The Price Impact of Order Book Events” describes the mechanics of how orders move prices and defines a measure called Order-Flow Imbalance (OFI): the running difference between buying pressure and selling pressure. The paper shows this one number accounts for about 65% of the price movement happening at the same moment. That is what an R² of 65% means, on a scale where 100% would be a perfect match and 0% would be no relationship at all. I implemented it exactly, pointed it at four years of SOXL tick quotes, and reproduced the result: R² ≈ 0.67, with the sign of the effect positive on 100% of 1,000 trading days. The paper does not test whether the measure can be traded, so I did. Every version I tried ran into the same constraint: you buy slightly above the market price and sell slightly below it. That gap is called the spread, and it costs more than the signal is worth.

The paper

CKS define OFI from best-bid/ask quote events: every time the top of the book updates, you compute a signed contribution — buying pressure adds, selling pressure subtracts — and sum them over an interval. Their central result is that mid-price changes over an interval are a linear function of OFI over that same interval, with high explanatory power and an impact coefficient that scales inversely with market depth.

Two properties of that claim constrain what can be built on it:

  1. The relationship is contemporaneous. OFI explains the price move that happens alongside it. The model says order flow moves price while the flow is arriving. It does not claim OFI tells you where price goes next. Reproducing the paper and finding a tradeable signal are separate problems.
  2. The claim is about mechanism. That order flow pushes price is a fact about how markets work. It says nothing about whether you can capture the move net of the bid-ask spread.

The implementation

The core of the code is what each quote update contributes to OFI. Take two snapshots of the top of the order book in a row. Each one has a best bid (Pᵦ, qᵦ) — the highest price anyone will pay, and how many shares they want at it. Each also has a best ask (Pₐ, qₐ) — the lowest price anyone will sell at, and how many shares are on offer:

bid_term = (Pb↑ or same) * qb_now  −  (Pb↓ or same) * qb_prev
ask_term = (Pa↓ or same) * qa_now  −  (Pa↑ or same) * qa_prev
e_n      = bid_term − ask_term

Bid price up → you add the new bid size; bid price down → you subtract the old one; size grows at the same price → you add the delta. The ask side is symmetric and subtracted, so an ask pulling away is bullish and an ask stacking up is bearish. OFI over any window is just the sum of the per-event e_n inside it. I hand-traced a six-event sequence covering all five cases and locked it behind a unit test, since every later result depends on this primitive being correct.

The pipeline:

flowchart LR
    Q["Alpaca SIP<br/>NBBO quote events"] -->|per event| E["e_n<br/>signed flow"]
    E -->|sum within bar| B["1-second bars<br/>OFI + mid + spread"]
    B --> F["features<br/>trailing OFI, rvol,<br/>book imbalance, targets"]
    F --> T{"tests"}

OFI is computed on the event stream and then aggregated into bars, not bar-to-bar; computing it bar-to-bar discards the sequence information the variable is built from. Every trading day is processed independently so no event ever spans the overnight gap, and I keep only regular hours to avoid auction and thin-book artifacts. The dataset is 23.15 million one-second bars — 1,000 trading days from June 2022 to June 2026 — on SOXL, the 3× leveraged semiconductor ETF, with NVDA and the semis pulled for cross-asset work later.

Reproducing the paper

The test is the paper’s claim: regress the price change over each interval on the OFI over that same interval, per day, and average over all 1,000 days.

Bar interval mean R² median R² impact β > 0
1-second 0.574 0.590 100% of days
10-second 0.668 0.697 100% of days
30-second 0.670 0.703 100% of days

This reproduces the paper. At 10–30 second intervals the R² is 0.67–0.70, against CKS’s reported ~65%, and the impact coefficient is positive on all 1,000 days. The positive coefficient means net buying coincides with rising price, and one variable accounts for two-thirds of the variance. The implementation is correct and the effect is present in the data.

A 67% R² measures how order flow pushes price as it happens. It does not measure any ability to predict where price goes next.

Can it predict the next move?

The paper’s result is contemporaneous. A trade requires prediction: does OFI now forecast the move next? I tested this three ways.

  • Horizon scan of forward correlation. Forward OFI→return correlation is positive out-of-sample on 6 of 6 held-out days, peaking at 0.09–0.11 for trailing-60-second OFI predicting the next 60 seconds, and it strengthens with volatility. That is an order of magnitude weaker than the contemporaneous R². Microstructure theory predicts this: most of the impact is already in the price by the time the flow can be acted on.
  • Hidden-Markov model on OFI. A three-state Gaussian HMM on one-bar OFI, using a causal forward filter so no future information enters a prediction, gives a 50.0% directional hit-rate at the 10-second horizon. With one input feature the HMM separates volatility regimes and carries no directional information, so it adds nothing over the linear measure.
  • Conditioning on volatility and signal strength. The sign of trailing OFI has a hit-rate that climbs monotonically with the volatility quartile (50.4% → 55.0%), and the top decile of signal strength reaches 58.2%. The predictive content is concentrated in extreme observations and absent on the average bar.

A weak forward signal exists, confirmed three ways. The remaining question is whether it exceeds trading costs.

Net results after costs

Each predictive signal was measured against the cost of trading it. None is profitable net of cost.

Test Gross Cost Net Verdict
60s→300s flow rule, year-scale walk-forward +1.70 bps 3.20 bps taker −1.50 (t=−1.9) not tradeable
— centered (strips out bull-market long-tilt) ~1.1 bps 3.20 negative not tradeable
LightGBM / logistic vs. the simple rule 1.12 / 0.07 bps 3.20 negative no model beats the rule
Maker, touch fill (= what paper-trading shows) — — +0.73 fill model unrealistic
Maker, realistic queue / through fills — — −0.08 / −0.09 breakeven ceiling
Vol-gated maker, frozen 2022–25 out-of-sample — — −1.87 / −2.14 (t≈−3) negative out-of-sample
Cross-asset NVDA/SOXX → SOXL lead-lag +0.006 IC — ~0 by 2s sub-second only

Maker fills. Posting limit orders instead of crossing the spread returns +0.73 bps under the zero-queue fill assumption that Alpaca’s paper-trading uses. Modelling the queue realistically moves the result to breakeven: the spread recovered by posting is given back to adverse selection, because the orders that go unfilled are the ones that would have been profitable. The entire difference between the two numbers comes from the fill model.

Pre-registered out-of-sample test. The vol-gated maker returned +1.55 bps per episode in the discovery year. I froze the exact pipeline and ran it on 2022–2025 with no retuning. It came back negative with a t-stat near −3. The in-sample number reflected conditions specific to that year. The kill criterion was written down before the out-of-sample run, which is what made the result interpretable.

The leveraged-ETF close. SOXL is mechanically forced to rebalance near the close, so I tested whether that flow can be front-run. On green days the 3:50→4:00 move drifts up (+8.6 bps) and on red days it drifts down (−5.3), an IC of 0.095. The directional hit-rate is 51.3%, against 50% for a random entry. The effect appears only on green days and is likely contaminated by SOXL’s four-year price appreciation. Gross is +7 bps against a spread of ~4.9 bps, with no clean exit.

Cross-asset latency. NVDA leads SOXL: same-second correlation 0.577, a +1-second lead of 0.047, and no measurable lead by +2 seconds. That is a stale-quote signature. I measured a single trade on 4,678 of NVDA’s biggest one-second jumps: NVDA jumps ~5.6 bps, SOXL follows +8.08 bps in the same second and only +0.51 bps the next second. The catchable move is 0.24 cents a share against a 1.62-cent spread, so the move that survives to a reaction time I can reach is smaller than the cost of taking it. Colocation does not change this: the large move is sub-second and is captured by firms with microwave links and FPGAs.

Retail-latency, public-tape order-flow signals on SOXL do not beat execution costs in any configuration I tested, across four years. The configurations were taker, maker, vol-gated, cross-asset, close-rebalance, and latency-arb. Gross alpha is consistently about half of cost. Since the paper’s contemporaneous result reproduces on the same code path, the negative results come from market structure and not from an implementation error.

Methods that transfer

The testing harness applies to any instrument and any signal:

  • Reproduction before extension. I reproduced the paper’s contemporaneous R² before testing anything beyond it. That establishes that the later negative results come from the market and not from a coding error.
  • Signal strength and trading cost measured on the same bars. A real IC that does not clear its trading cost is the common outcome in a liquid market.
  • Pre-registration, then walk-forward validation. The rule is frozen and the kill criterion written down, then tested on untouched data. This reversed the conclusion on two strategies that were positive in-sample.
  • Per-day breakdown of every aggregate. A positive overall number driven by three outlier days is noise.
  • Cost measured at the moment the signal fires. Signals fire when spreads are widest, so an average spread understates the cost. This selection effect turned more than one positive-looking rule negative.

The verdict

The paper’s result holds. Order flow explains two-thirds of short-term price movement, on every one of the 1,000 days tested, and the result reproduces. By the time the order flow is observable, the move it explains is already in the price. The portion that carries into the next interval is worth less than the spread required to trade it.

Faster or more complex intraday signals are not a promising direction; I tested six variants and none cleared costs. Longer horizons are a more promising direction, because the return per trade is larger relative to the spread: month-end rebalancing flow, the volatility risk premium, and instruments too small for latency-sensitive firms to trade. None of these depend on execution speed.

Reproducing the paper took an afternoon. Establishing that the signal is not tradeable took four years of data, six strategies, and kill criteria written down in advance.