Appearance

Personalize your experience

Mode
Accent Color
Direction
Depth Chart

Depth Chart

Order Book

Cumulative depth on both sides of the book, aligned to the ladder in every position, with big-order bubbles, liquidity flashes, prints and a slippage readout.

Overview

The depth chart plots the resting size from the outward: to the left of the mid and asks to the (or below and above in the orientation). One size is shared by both , so any asymmetry you see is a real difference in liquidity.

Connecting to Binance BTC/USDT…
Connecting to Binance BTC/USDT…
Real time (Binance): horizontal depth chart. Try the controls in the top strip — curve, scale, histogram, tick grouping and zoom.

Alignment with the ladder

Depth positionOrientationAligned to
Left / : up, bids downThe ladder's . Each level is drawn on its row, the mid on the spread row, and the size grows away from the ladder.
Top / Bottom: left, asks The ladder's centre divider. Each half of the book spans its own half of the chart.
Off—Hidden.
Connecting to Binance BTC/USDT…
Connecting to Binance BTC/USDT…
Real time (Binance): depth on the left — curve steps line up with ladder rows.
Connecting to Binance BTC/USDT…
Connecting to Binance BTC/USDT…
Real time (Binance): depth on top — mid sits over the column ladder's centre divider.

What is drawn

Curves
Step, or line. Smooth uses a monotone cubic (Fritsch–Carlson), so the curve never overshoots a level and never misstates depth.
Fill + glow
that fades away from the . The glow is off on light themes.
Size histogram
Per-level size bars along the (√ scale); walls are brighter.
Big-order bubbles
The 5 largest walls per side get a glowing bubble whose radius tracks the wall's size; the top 3 get a "size ×score" tag. The strongest wall per side pulses slowly.
Liquidity flashes
A ring on the where size was (expands) or pulled (contracts, amber) in a recent update.
Trade prints
Dots on the that fade over 4 seconds. Block get a ▲ size tag that floats up.
Markers
Hatched spread , dashed mid, micro-price , last-trade , spread in bp.
Your orders
B / S tags on the at your working prices.
Imbalance bar
A 2px bid/ask share bar under the legend.

Controls

ControlAction
Cycle step → → line
LIN / SQRT / LOGSize scale. (Beside the ladder, linear is used so the matches the bars.)
Toggle the per-level size bars
− +Finer / coarser price . "·A" marks Auto (raw ).
− zoom +Narrow or widen the price around the mid (10–100%)
Wheel / pinchZoom; double-click or the reset button returns to 100%
Keyboard+ / − zoom, 0 resets, Esc clears the
Click / tapPick a price; it is pinned in both panes

Hover readout

  • Side and level (BID L12), price.
  • Size at the level, depth and that depth's share of the side.
  • .
  • average: the average fill for a market order that takes everything from the to this level, and its slippage in bp.
  • Distance from mid, wall score, and your working size if you have orders there.

Stability

The axes ease toward their targets instead of snapping: they grow quickly (τ = 70 ms) so nothing clips, and slowly (τ = 260 ms). Each level's depth also eases (τ = 120 ms) and is clamped to a running maximum, so the stays monotone while it moves. On a symbol or change the eased state resets, so a new book never tweens from the old one.

Easing
const tau = target > current ? 70 : 260;              // grow fast, shrink calmly
next = current + (target - current) * (1 - Math.exp(-dt / tau));

// per level, kept monotone outward from the touch
let running = 0;
for (const entry of levels) running = Math.max(running, eased(entry)), entry.total = running;
★
Pro tip:Rendering uses two canvases: the base (book, axes, markers) redraws on book, settings or changes and while easing; the overlay (, selection, ) redraws at pointer rate without touching the base.

Standalone HTML implementation

A dependency-free canvas version of the depth with wall bubbles.

Depth chart — standalone HTML, live Binance feed, no dependencies
orderbook-depth.html
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Depth chart — 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 ───────────────────────────────────────────────────────────────────// Cumulative depth staircase: bids step down-left from the touch, asks up-right.// One size axis for both sides, so any asymmetry is a real liquidity imbalance.function draw(X, W, H, b) {  const pad = 30, lo = b.bids.at(-1).price, hi = b.asks.at(-1).price;  const max = Math.max(b.bids.at(-1).total, b.asks.at(-1).total) * 1.1;  const xp = p => pad + ((p - lo) / (hi - lo)) * (W - pad * 2);  const yv = v => H - pad - (v / max) * (H - pad * 2);  X.strokeStyle = GRID;  for (let i = 0; i <= 4; i++) {    const y = yv((max / 4) * i); X.beginPath(); X.moveTo(pad, y); X.lineTo(W - pad, y); X.stroke();    txt(((max / 4) * i).toFixed(0), pad - 4, y, 'right', MUTED);  }  for (const [lv, col] of [[b.bids, BID], [b.asks, ASK]]) {    const pts = [[xp(lv[0].price), yv(0)]];    let py = yv(0);    for (const l of lv) { pts.push([xp(l.price), py]); py = yv(l.total); pts.push([xp(l.price), py]); }    const g = X.createLinearGradient(0, H - pad, 0, pad);    g.addColorStop(0, alpha(col, 0.45)); g.addColorStop(1, alpha(col, 0.04));    X.beginPath(); pts.forEach(([x, y], i) => (i ? X.lineTo(x, y) : X.moveTo(x, y)));    X.lineTo(pts.at(-1)[0], yv(0)); X.closePath(); X.fillStyle = g; X.fill();    X.beginPath(); pts.forEach(([x, y], i) => (i ? X.lineTo(x, y) : X.moveTo(x, y)));    X.shadowColor = col; X.shadowBlur = 8; X.strokeStyle = col; X.lineWidth = 1.5; X.stroke(); X.shadowBlur = 0;    for (const l of lv) if (l.isWall) {                                        // big-order bubbles      X.beginPath(); X.arc(xp(l.price), yv(l.total), 4 + Math.sqrt(l.qty), 0, 7);      X.strokeStyle = col; X.lineWidth = 1.2; X.stroke();    }  }  X.setLineDash([3, 3]); X.strokeStyle = MUTED;  X.beginPath(); X.moveTo(xp(b.mid), pad); X.lineTo(xp(b.mid), H - pad); X.stroke(); X.setLineDash([]);  txt(fmt(b.mid), xp(b.mid), H - pad / 2, 'center');}// ─── 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

  • Advanced Order Book

    Trading / Order Book

    A live market-depth workspace: a stepped price ladder, a depth chart kept in sync with it, sixteen alternative views of the book, a trades bar and one-row…

  • Order Book Settings

    Trading / Order Book

    Every option in the order book settings modal, by tab. Changes apply live and are saved in this browser.

  • Price Ladder & Stepped Depth

    Trading / Order Book

    The classic DOM, with each row's bar showing the cumulative depth out to that level. Together the rows draw the same staircase as the depth chart.

  • 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.

  • Market Impact

    Trading / Order Book / Views

    The slippage curve: what a market order of any size would cost right now.

  • 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 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.

Browse all knowledge base articles →