Appearance

Personalize your experience

Mode
Accent Color
Direction
Level Imbalance

Level Imbalance

Order Book View

Per-level bid vs ask pressure, diverging from zero, with the cumulative drift through the book.

What it answers

Level by level, which side is heavier — and where does the weight accumulate? Open it from the order book toolbar with View → Level Imbalance, or from Settings → . The , bar and order entry stay in place; only the book pane changes.

Connecting to Binance BTC/USDT…
Connecting to Binance BTC/USDT…
Real time: the production Level Imbalance renderer on the live Binance spot book (REST snapshot + 100ms diff stream + trades). Switch symbol or source above; hover for a readout, click to pick a price.

Anatomy

Bar
(bidQty − askQty) ÷ (bidQty + askQty) for level i. Bars to the left are bid heavy, to the ask heavy.
Bright bar
A significant imbalance: |imbalance| > 60%.
Dashed line
Running Σ(bid − ask) from the outward, scaled to its own maximum.
Labels
Bid price on the left, ask price on the , inside the bar.

How to read it

  1. Read the top first: near- imbalance is the most predictive.
  2. A run of bars all on one side is a consistent lean, not noise.
  3. Where the dashed drift line changes direction, the deeper book starts to offset the near book.

How it is computed

  • and asks are paired by rank (level i on each side).
  • Imbalance per pair is normalised to −1…1.
  • Drift is the running sum of raw size differences, which shows magnitude as well as direction.
Core calculation
const rows = computeLevelImbalance(snapshot);   // utils: pairs level i of each side
let running = 0;
for (const r of rows) {
  r.imbalance;              // (bidQty - askQty) / (bidQty + askQty)
  r.isSignificant;          // |imbalance| > 0.6
  running += r.bidQty - r.askQty;
}

Trading signals

Top-of-book imbalance

A strong L1–L3 imbalance is a well-documented short-horizon predictor of the next mid move, toward the light side.

Drift reversal

Bid heavy near the but ask heavy deep (the drift line ): moves in favour of the may stall once they reach that depth.

!
Caution:Imbalance moves very fast at the . Read it alongside the Order- imbalance view, which shows whether the book actually changed, not just how it looks.

Settings that affect this view

Levels
Number of paired .
Glow
Highlights significant .

Standalone HTML implementation

A complete, dependency-free page that implements this view on a canvas against the real-time book. It runs below exactly as written. The harness keeps a full local book from the REST snapshot and the 100ms diff stream (resyncing on any sequence gap) and reads from aggTrade. Change SYMBOL / GROUP for another market, or replace snapshot() with your own feed, and the draw() function works unchanged.

Level imbalance — standalone HTML, live Binance feed, no dependencies
orderbook-imbalance.html
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Level imbalance — order book view</title><style>  html, body { margin: 0; height: 100%; background: #0b0e14; overflow: hidden; }  canvas { display: block; width: 100%; height: 100%; }  #status { position: fixed; left: 8px; bottom: 6px; font: 600 10px ui-monospace, Menlo, monospace;            color: #cbd5e1; background: rgba(15,23,42,.85); border: 1px solid rgba(148,163,184,.3);            border-radius: 4px; padding: 2px 6px; pointer-events: none; }  #status i { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 5px; background: #f59e0b; }</style></head><body><canvas id="c"></canvas><div id="status"><i></i>connecting…</div><script>// ─── Canvas + palette ───────────────────────────────────────────────────────const C = document.getElementById('c'), X = C.getContext('2d');let W = 0, H = 0;function fit() {  const r = C.getBoundingClientRect(), d = Math.min(devicePixelRatio || 1, 2);  W = r.width; H = r.height; C.width = W * d; C.height = H * d; X.setTransform(d, 0, 0, d, 0, 0);}addEventListener('resize', fit); fit();const BID = '#0ecb81', ASK = '#f6465d', GRID = 'rgba(148,163,184,.16)';const TEXT = '#e2e8f0', MUTED = '#64748b', ACCENT = '#8b5cf6', WARN = '#f59e0b';// ─── Feed settings ──────────────────────────────────────────────────────────// Real-time Binance spot book. Change SYMBOL / GROUP for another market// (e.g. 'ETHUSDT' with GROUP 0.1). If the exchange cannot be reached the page// falls back to a synthetic book and says so in the status badge.const SYMBOL = 'BTCUSDT';const GROUP = 1;            // aggregation step (price bucket)const LEVELS = 30;          // levels per side after groupingconst TICK = GROUP;         // views size cells / buckets by the grouped step// ─── Real-time Binance book: REST snapshot + 100ms diff stream ──────────────// Binance's documented procedure: buffer the diff stream, fetch a snapshot,// drop events with u <= lastUpdateId, then apply in order; a gap// (U != previous u + 1) means resnapshot. qty 0 deletes a level.const live = { bids: new Map(), asks: new Map(), lastU: 0, lastE: 0, synced: false, buffer: [], ok: false, ups: [] };const prints = [];let source = 'connecting';const statusEl = document.getElementById('status');function setStatus(text, color) { statusEl.innerHTML = '<i style="background:' + color + '"></i>' + text; }function applyDiff(e) {  for (const [p, q] of e.b) { const v = +q; v === 0 ? live.bids.delete(+p) : live.bids.set(+p, v); }  for (const [p, q] of e.a) { const v = +q; v === 0 ? live.asks.delete(+p) : live.asks.set(+p, v); }  live.lastU = e.u; live.lastE = e.E;  const now = Date.now(); live.ups.push(now); while (now - live.ups[0] > 1000) live.ups.shift();}async function resnapshot() {  live.synced = false;  const r = await fetch('https://api.binance.com/api/v3/depth?symbol=' + SYMBOL + '&limit=1000');  if (!r.ok) throw new Error('HTTP ' + r.status);  const s = await r.json();  live.bids = new Map(s.bids.map(([p, q]) => [+p, +q]));  live.asks = new Map(s.asks.map(([p, q]) => [+p, +q]));  live.lastU = s.lastUpdateId;  const pending = live.buffer.filter(e => e.u > s.lastUpdateId); live.buffer = [];  let first = true;  for (const e of pending) {    if (first && !(e.U <= live.lastU + 1 && e.u >= live.lastU + 1)) continue;    first = false; applyDiff(e);  }  live.synced = true; live.ok = true; source = 'live';}// Snapshot failures fall back to the synthetic book and keep retrying; the page// switches back to live as soon as a snapshot lands.function trySnapshot() {  resnapshot().catch(() => { fallback(); setTimeout(trySnapshot, 5000); });}function connect() {  const s = SYMBOL.toLowerCase();  let ws;  try { ws = new WebSocket('wss://stream.binance.com:9443/stream?streams=' + s + '@depth@100ms/' + s + '@aggTrade'); }  catch (e) { fallback(); return; }  ws.onopen = () => trySnapshot();  ws.onmessage = m => {    const { stream, data } = JSON.parse(m.data);    if (stream.endsWith('@aggTrade')) {      // m = buyer is maker → the aggressor sold      prints.push({ t: data.T, side: data.m ? 'sell' : 'buy', qty: +data.q, price: +data.p });      if (prints.length > 400) prints.shift();      return;    }    if (!live.synced) { live.buffer.push(data); if (live.buffer.length > 600) live.buffer.shift(); return; }    if (data.u <= live.lastU) return;    if (data.U > live.lastU + 1) { live.buffer = [data]; trySnapshot(); return; }    applyDiff(data);  };  ws.onerror = () => { if (!live.ok) fallback(); };  ws.onclose = () => { live.synced = false; setTimeout(connect, live.ok ? 2000 : 8000); };}setTimeout(() => { if (!live.ok) fallback(); }, 15000);connect();// ─── Synthetic fallback (same shape as the live book) ───────────────────────let center = 76500;const base = { bid: [], ask: [] };for (let i = 0; i < LEVELS; i++) { base.bid.push(0.4 + i * 0.5 + Math.random() * 3); base.ask.push(0.4 + i * 0.5 + Math.random() * 3); }function fallback() {  if (source === 'synthetic' || live.ok) return;  source = 'synthetic';  setStatus('SYNTHETIC · live feed unreachable, retrying', '#94a3b8');}function tickSynthetic() {  if (Math.random() < 0.35) center += (Math.random() < 0.5 ? -1 : 1) * GROUP;  for (const s of ['bid', 'ask']) for (let i = 0; i < LEVELS; i++) {    let q = base[s][i] * (0.92 + Math.random() * 0.16);    if (Math.random() < 0.003) q *= 5;    if (q > 40) q *= 0.7;    base[s][i] = q;  }  if (Math.random() < 0.6) {    const side = Math.random() < 0.5 ? 'buy' : 'sell';    prints.push({ t: Date.now(), side, qty: +(Math.random() ** 3 * 8 + 0.01).toFixed(3), price: side === 'buy' ? center + GROUP : center });    if (prints.length > 400) prints.shift();  }}function tick() { if (source === 'synthetic') tickSynthetic(); }// ─── Snapshot: group raw levels into LEVELS buckets per side ────────────────let prev = new Map();const r8 = v => Math.round(v * 1e8) / 1e8;function group(map, side) {  const out = new Map();  for (const [p, q] of map) {    const k = r8((side === 'bid' ? Math.floor(p / GROUP + 1e-9) : Math.ceil(p / GROUP - 1e-9)) * GROUP);    out.set(k, (out.get(k) || 0) + q);  }  return [...out.entries()].sort((a, b) => (side === 'bid' ? b[0] - a[0] : a[0] - b[0])).slice(0, LEVELS);}function rawSynthetic(side) {  const out = [];  for (let i = 0; i < LEVELS; i++) {    if ((i === 7 || i === 13) && Math.random() < 0.45) continue;     // two flickering levels    out.push([side === 'bid' ? center - i * GROUP : center + GROUP + i * GROUP, base[side][i]]);  }  return out;}function snapshot() {  let rb, ra;  if (source === 'live') {    if (!live.synced) return null;    rb = group(live.bids, 'bid'); ra = group(live.asks, 'ask');    setStatus('LIVE · Binance ' + SYMBOL + ' · ' + live.ups.length + ' upd/s · ' + Math.max(0, Date.now() - live.lastE) + 'ms', '#22c55e');  } else if (source === 'synthetic') {    rb = rawSynthetic('bid'); ra = rawSynthetic('ask');  } else return null;  if (!rb.length || !ra.length) return null;  const build = (levels, s) => {    let tot = 0, notl = 0;    return levels.map(([price, q]) => {      const key = s + price;      tot += q; notl += q * price;      return { price, qty: q, total: tot, notional: q * price, cumNotional: notl,               delta: prev.has(key) ? q - prev.get(key) : q, isNew: !prev.has(key) };    });  };  const bids = build(rb, 'bid'), asks = build(ra, 'ask');  const next = new Map();  bids.forEach(l => next.set('bid' + l.price, l.qty));  asks.forEach(l => next.set('ask' + l.price, l.qty));  prev = next;  const all = [...bids, ...asks], avg = all.reduce((s, l) => s + l.qty, 0) / all.length;  all.forEach(l => { l.isWall = l.qty > avg * 3; });  return { bids, asks, bestBid: bids[0].price, bestAsk: asks[0].price,           mid: (bids[0].price + asks[0].price) / 2, t: Date.now(), prints };}// ─── Helpers ────────────────────────────────────────────────────────────────function txt(s, x, y, a = 'left', c = TEXT) { X.fillStyle = c; X.textAlign = a; X.textBaseline = 'middle'; X.fillText(s, x, y); }function fmt(v, d = 2) { return Math.abs(v) >= 1e4 ? v.toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) : v.toFixed(d); }function alpha(hex, a) { return hex + Math.round(Math.max(0, Math.min(1, a)) * 255).toString(16).padStart(2, '0'); }function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }// ─── View ───────────────────────────────────────────────────────────────────// For level i: (bidQty − askQty) / (bidQty + askQty), drawn left when bids are// heavier and right when asks are. The dashed line is the running drift// Σ(bid − ask) — where the book's weight accumulates as you go deeper.function draw(X, W, H, b) {  const n = Math.min(b.bids.length, b.asks.length), cx = W / 2, half = cx - 80, top = 18, rowH = (H - top) / n;  let run = 0; const drift = [];  for (let i = 0; i < n; i++) {    const bq = b.bids[i].qty, aq = b.asks[i].qty, imb = (bq - aq) / (bq + aq), y = top + i * rowH;    const col = imb >= 0 ? BID : ASK, len = Math.abs(imb) * half;    X.fillStyle = alpha(col, Math.abs(imb) > 0.6 ? 0.9 : 0.45);    X.fillRect(imb >= 0 ? cx - len : cx, y + 1, len, rowH - 2);    run += bq - aq; drift.push([run, y + rowH / 2]);    if (rowH > 11) { txt(fmt(b.bids[i].price), 72, y + rowH / 2, 'right', BID); txt(fmt(b.asks[i].price), W - 72, y + rowH / 2, 'left', ASK); }  }  const m = Math.max(...drift.map(d => Math.abs(d[0])), 1e-9);  X.beginPath(); drift.forEach(([v, y], i) => (i ? X.lineTo : X.moveTo).call(X, cx - (v / m) * half, y));  X.setLineDash([4, 3]); X.strokeStyle = TEXT; X.lineWidth = 1.5; X.stroke(); X.setLineDash([]);  txt('◀ bid heavy', cx - 6, 9, 'right', BID); txt('ask heavy ▶', cx + 6, 9, 'left', ASK);}// ─── Loop: redraw 4× a second (the live book updates every 100ms) ───────────let last = 0;(function loop(now) {  requestAnimationFrame(loop);  if (now - last < 250) return;  last = now; tick();  const b = snapshot();  if (!b) return;  X.clearRect(0, 0, W, H);  X.font = '11px ui-monospace, Menlo, monospace';  draw(X, W, H, b);})(0);</script></body></html>

See also

Related articles

  • Depth Radar

    Trading / Order Book / Views

    Polar depth: the angle is distance from mid and the radius is cumulative depth — the book drawn as a shape.

  • Order-Flow Imbalance (OFI)

    Trading / Order Book / Views

    Top-of-book order-flow imbalance per update, its cumulative drift and a live pressure gauge.

  • 3D Book Towers

    Trading / Order Book / Views

    The book as a city of towers: one tower per price level for the latest fourteen slices of the book, older slices fading back.

  • 3D Depth Canyon

    Trading / Order Book / Views

    The depth chart extruded through time. Cumulative bid and ask depth form two valley walls, and the mid runs along the valley floor.

  • 3D Liquidity Terrain

    Trading / Order Book / Views

    The recorded book as a lit landscape: time across, price in depth, resting size as height. Walls are mountains, thin books are plains.

  • 3D Ridgeline

    Trading / Order Book / Views

    Every price level as its own ridge of resting size through time, as in the book terrain chart: far ridges behind, near ridges in front, newest on the right.

  • 3D Trade Cloud

    Trading / Order Book / Views

    Every print as a pillar at its time and price, its height the trade size, coloured by the aggressor, over the mid path.

  • Book vs Tape Profile

    Trading / Order Book / Views

    What is resting at each price beside what has actually traded there — intent against action.

  • Butterfly

    Trading / Order Book / Views

    Bid level N against ask level N, fanned out from a central spine — a level-by-level comparison of the two sides.

Browse all knowledge base articles →