Appearance

Personalize your experience

Mode
Accent Color
Direction
Price Ladder & Stepped Depth

Price Ladder & Stepped Depth

Order Book View

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.

Stepped depth bars

Each row has two bars. The tread the row out to the depth from the to that level, on one scale shared by both . A riser runs along the row edge that faces the spread, from the previous level's depth to this one, so consecutive join into a single staircase outline. A thin size strip along the bottom of the row shows that level's own size.

Connecting to Binance BTC/USDT…
Connecting to Binance BTC/USDT…
Real time (Binance): stacked ladder. The staircase widens away from the spread; tall steps are large levels.
ℹ
Note:The staircase always uses a linear scale. On a √ or log axis every bar would land at 50–100% of the row and the steps would flatten into a solid block. When the sits beside the ladder it also switches to linear, so the and the steps match row for row.

Arrangements

ArrangementLayoutBest for
Stacked above the spread row, bids below. Asks step up, bids step down.Tall, narrow panes and depth beside the ladder
left, asks , both reading in toward a centre divider. Bars grow from the divider.Wide, short panes under or over a
MirroredOne price grid with the price in the centre; bid sizes fan left, ask sizes .Order-entry work on a single price grid
Connecting to Binance BTC/USDT…
Connecting to Binance BTC/USDT…
Real time (Binance): column arrangement — both staircases grow outward from the centre divider.
Connecting to Binance BTC/USDT…
Connecting to Binance BTC/USDT…
Real time (Binance): mirrored (centre-price) arrangement.

Row anatomy

Tread
depth to this level ÷ the deeper side's full depth × 1.08 headroom.
Riser
The step from the previous level's to this one, on the edge facing the spread.
Size strip
This level's size ÷ the largest level on its side. Fully opaque when the level is a wall.
Edge pulse
A 3px edge flash when the size changes by at least 5%. It never the whole row.
Cursor highlight
Outlined when the shared is on the row; tinted when the row lies between the and the cursor.
Selected
Outlined in the accent when the row is the pinned price.
Own orders
An accent on the outer edge plus your size in the Mine column. to .
Print marker
A thin info-coloured edge when a trade landed on the level in the last 1.5 seconds.
Wall marker
A solid edge on the inner side when the level is a .

Bar styles and colour modes

SettingOptionsEffect
Bar style · Size · Both · None = staircase; Size = full-height per-level bars; Both = staircase plus size strip (default).
Bar Size · · Heat Heat shade the whole by size, with the staircase outline kept on top.
Row colourFlat · Gradient · Intensity · DeltaIntensity and Delta add a faint row tint; the staircase stays the main .
Row stripesOn / OffAlternate row shading.

How the staircase is computed

OBLadder / OBLevelRow
// One linear depth axis for both sides, shared with an aligned depth chart.
const depthMax = Math.max(bids.at(-1).total, asks.at(-1).total) * 1.08;
const frac = (total: number) => Math.min(1, total / depthMax);

// Row i (0 = touch)
const tread = frac(level.total) * 100;                      // % of the row
const prev  = i > 0 ? frac(levels[i - 1].total) * 100 : 0;  // previous step
const riser = { edge: side === 'ask' && stacked ? 'bottom' : 'top',
                from: prev, width: tread - prev };

Trading from the ladder

  • a row to show B / S / MKT. B and S stage a at that price; MKT sweeps up to it.
  • With armed (⚡ in the order entry bar) orders send at once. Market orders still when "Confirm market orders" is on.
  • Click a row to pin its price. Limit orders in the order entry bar then rest there.
  • Your working orders show in the Mine column. your size to every order at that level.

Standalone HTML implementation

A dependency-free canvas version of the ladder.

Stepped price ladder — standalone HTML, live Binance feed, no dependencies
orderbook-ladder.html
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Stepped price ladder — 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 ───────────────────────────────────────────────────────────────────// Each row's bar is the cumulative depth from the touch out to that level, on// one scale for both sides. A riser joins each row to the next, so the rows draw// one continuous staircase — the same shape as the depth chart.function draw(X, W, H, b) {  const rowH = 16, cy = H / 2;  const n = Math.min(b.bids.length, b.asks.length, Math.floor((H / 2 - 14) / rowH));  const max = Math.max(b.bids[n - 1].total, b.asks[n - 1].total) * 1.08;  const maxQ = Math.max(...b.bids.slice(0, n).map(l => l.qty), ...b.asks.slice(0, n).map(l => l.qty));  const barW = W * 0.62;  X.fillStyle = '#111827'; X.fillRect(0, cy - 12, W, 24);  txt(fmt(b.mid), 10, cy, 'left'); txt('spread ' + (b.bestAsk - b.bestBid).toFixed(1), W - 10, cy, 'right', MUTED);  for (const [lv, col, up] of [[b.asks, ASK, true], [b.bids, BID, false]]) {    let prevW = 0;    for (let i = 0; i < n; i++) {      const l = lv[i], y = up ? cy - 12 - (i + 1) * rowH : cy + 12 + i * rowH;      const bw = (l.total / max) * barW, x = W - bw;      X.fillStyle = alpha(col, 0.3); X.fillRect(x, y, bw, rowH);              // tread      X.fillStyle = col; X.fillRect(x, y, 1.5, rowH);                          // step edge      X.fillRect(x, up ? y + rowH - 1.5 : y, bw - prevW, 1.5);                 // riser, on the edge facing the touch      const sw = (l.qty / maxQ) * barW * 0.5;      X.fillStyle = alpha(col, l.isWall ? 1 : 0.7); X.fillRect(W - sw, y + rowH - 2, sw, 2); // per-level size strip      txt(fmt(l.price), 10, y + rowH / 2, 'left', col);      txt(l.qty.toFixed(3), W * 0.3, y + rowH / 2, 'right');      txt(l.total.toFixed(2), W * 0.42, y + rowH / 2, 'right', MUTED);      prevW = bw;    }  }}// ─── 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.

  • Depth Chart

    Trading / 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.

  • DOM Ladder

    Charts / Chart Types / Volume Charts

    Depth of market on the chart's price scale: resting bids and asks, your orders, traded volume and delta at every price, with recent candles beside it.

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

Browse all knowledge base articles →