Back to Algorithms
microstructure April 12, 2026 4 min read

Queue imbalance — the cleanest microstructure signal you can build

A careful walk through queue imbalance — what it is, why it works, how to implement it robustly, and where it breaks.

#queue-imbalance #order-book #signal

Queue imbalance is one of the cleanest short-horizon signals in modern microstructure. It’s shallow enough to fit on an index card, deep enough to still work in 2026, and it cuts through most of the noise that makes retail “order flow” analysis useless.

This page is the version I’d hand a junior researcher on day one.

The definition

At the top of the book, with best bid size Qb and best ask size Qa:

QI = (Qb - Qa) / (Qb + Qa)

That’s it. QI lives in [-1, 1]. Positive means more size on the bid than the ask; negative is the opposite.

For the next few seconds to minutes, mid-price drift correlates with QI. When QI is strongly positive, price tends to drift up; strongly negative, down. It’s one of the most reproducible microstructure observations in the literature.

Why it (mostly) works

Three overlapping causes, all pointing the same direction:

  1. Inventory pressure. Aggressive buyers consume the ask faster than the bid; the remaining book tilts toward the bid. Reverse for sellers.
  2. Informed flow. If informed traders are accumulating on the bid, they discourage passive sellers and attract other passive buyers, widening the imbalance further.
  3. Spoofing / quote layering (less charitably) used to exaggerate imbalances to nudge price. Most modern venues have controls for this, but the statistical residue is still there in some tail regimes.

The combined effect: QI tends to lead mid-price by a window that depends on the venue — anywhere from 50ms in high-turnover futures to several seconds in thinner names.

The honest caveats

Before you build anything, a tour of ways this signal lies.

It’s not causal in the direction you want

QI correlates with forward returns. That doesn’t mean “imbalance causes price to move.” Often the same underlying force (information arrival) causes both. That matters because:

  • If you try to trade QI with passive orders on the same side, you may get negatively selected: the orders that fill are the ones that were about to be run over.
  • Aggressive execution on a QI signal pays the spread. The signal has to beat that cost.

Top-of-book only is fragile

QI as defined uses only the best bid and ask sizes. In a liquid asset, these can flicker drastically second-to-second because of a single large order. A more stable formulation averages over the top N levels, or weights them:

QI_depth = sum_i w_i * (Qb_i - Qa_i) / sum_i w_i * (Qb_i + Qa_i)

With exponentially decaying weights w_i = exp(-i/τ). Pick τ to taste.

Queue turnover matters

A book with 100 contracts at the top, replaced every second, is very different from 100 contracts that’s been there for a minute. The former is hot, liquidity is arriving and being consumed; the latter is stale, likely decoration. Condition on queue turnover (order arrival rate at top-of-book) and you’ll find QI works much better when turnover is high.

Regime shifts are brutal

In calm regimes, QI drifts slowly and tends to mean-revert. In shock regimes, QI spikes and trends. A strategy tuned on calm data will get destroyed the first time a news event hits. Partition your analysis by realized volatility before drawing conclusions.

Reference implementation

A clean, single-level QI in Python:

from collections import deque

class QueueImbalance:
    def __init__(self, ewma_span: float = 20.0):
        self.alpha = 2.0 / (ewma_span + 1.0)
        self.qi_ewma: float | None = None

    def update(self, best_bid_size: float, best_ask_size: float) -> float:
        total = best_bid_size + best_ask_size
        qi = 0.0 if total <= 0 else (best_bid_size - best_ask_size) / total

        if self.qi_ewma is None:
            self.qi_ewma = qi
        else:
            self.qi_ewma += self.alpha * (qi - self.qi_ewma)

        return self.qi_ewma

EWMA-smoothed so single-tick noise doesn’t dominate. The span parameter is regime-dependent; start with 20 updates, scan later.

A multi-level version

import math

def depth_weighted_qi(book, depth: int = 5, tau: float = 2.0) -> float:
    num, den = 0.0, 0.0
    for i, (bid, ask) in enumerate(zip(book.bids[:depth], book.asks[:depth])):
        w = math.exp(-i / tau)
        num += w * (bid.size - ask.size)
        den += w * (bid.size + ask.size)
    return 0.0 if den <= 0 else num / den

Notice what’s missing: any assumption about price. QI is a pure size signal. That’s a feature — it’s orthogonal to most price-based indicators, which means it composes well with them in an ensemble.

Using it well

A few things that consistently help:

  • Normalize by regime. Compute QI’s recent rolling standard deviation and threshold on a z-score rather than a raw value.
  • Gate by spread. In very wide spreads, QI is unreliable because mid-price isn’t a real quote.
  • Gate by queue turnover. Low-turnover QI is more often decoration than intent.
  • Combine with trade imbalance. QI is static liquidity; trade imbalance is realized aggression. They disagree usefully.

Where it ends

QI works best on short horizons — seconds to minutes. As you push the prediction horizon out, information-based drivers overwhelm pure imbalance. That’s the point at which you reach for VPIN, for informed-volume proxies, and eventually for macro-flavored features. QI doesn’t compete with those; it feeds them.

The Order Flow Imbalance and MBO Research Framework pages cover the next stops on that ladder.

Takeaway

QI is a good signal precisely because it’s honest about what it measures: the current, visible, passive balance between buyers and sellers at the top of book. It’s not a prediction of where price is going — it’s a measurement of which direction the passive side of the book is leaning right now, and that measurement happens to have short-horizon predictive power in most liquid markets.

Build it first. Trust it conditionally. Don’t expect it to be the whole strategy.