Appearance
Personalize your experience
Price Ladder & Stepped Depth
Order Book ViewThe 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.
Arrangements
| Arrangement | Layout | Best 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 | |
| Mirrored | One price grid with the price in the centre; bid sizes fan left, ask sizes . | Order-entry work on a single price grid |
Row anatomy
Bar styles and colour modes
| Setting | Options | Effect |
|---|---|---|
| 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 colour | Flat · Gradient · Intensity · Delta | Intensity and Delta add a faint row tint; the staircase stays the main . |
| Row stripes | On / Off | Alternate row shading. |
How the staircase is computed
// 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.
1<!doctype html>2<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width,initial-scale=1">6<title>Stepped price ladder — order book view</title>7<style>8 html, body { margin: 0; height: 100%; background: #0b0e14; overflow: hidden; }9 canvas { display: block; width: 100%; height: 100%; }10 #status { position: fixed; left: 8px; bottom: 6px; font: 600 10px ui-monospace, Menlo, monospace;11 color: #cbd5e1; background: rgba(15,23,42,.85); border: 1px solid rgba(148,163,184,.3);12 border-radius: 4px; padding: 2px 6px; pointer-events: none; }13 #status i { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 5px; background: #f59e0b; }14</style>15</head>16<body>17<canvas id="c"></canvas>18<div id="status"><i></i>connecting…</div>19<script>20// ─── Canvas + palette ───────────────────────────────────────────────────────21const C = document.getElementById('c'), X = C.getContext('2d');22let W = 0, H = 0;23function fit() {24 const r = C.getBoundingClientRect(), d = Math.min(devicePixelRatio || 1, 2);25 W = r.width; H = r.height; C.width = W * d; C.height = H * d; X.setTransform(d, 0, 0, d, 0, 0);26}27addEventListener('resize', fit); fit();28const BID = '#0ecb81', ASK = '#f6465d', GRID = 'rgba(148,163,184,.16)';29const TEXT = '#e2e8f0', MUTED = '#64748b', ACCENT = '#8b5cf6', WARN = '#f59e0b';3031// ─── Feed settings ──────────────────────────────────────────────────────────32// Real-time Binance spot book. Change SYMBOL / GROUP for another market33// (e.g. 'ETHUSDT' with GROUP 0.1). If the exchange cannot be reached the page34// falls back to a synthetic book and says so in the status badge.35const SYMBOL = 'BTCUSDT';36const GROUP = 1; // aggregation step (price bucket)37const LEVELS = 30; // levels per side after grouping38const TICK = GROUP; // views size cells / buckets by the grouped step3940// ─── Real-time Binance book: REST snapshot + 100ms diff stream ──────────────41// Binance's documented procedure: buffer the diff stream, fetch a snapshot,42// drop events with u <= lastUpdateId, then apply in order; a gap43// (U != previous u + 1) means resnapshot. qty 0 deletes a level.44const live = { bids: new Map(), asks: new Map(), lastU: 0, lastE: 0, synced: false, buffer: [], ok: false, ups: [] };45const prints = [];46let source = 'connecting';47const statusEl = document.getElementById('status');48function setStatus(text, color) { statusEl.innerHTML = '<i style="background:' + color + '"></i>' + text; }4950function applyDiff(e) {51 for (const [p, q] of e.b) { const v = +q; v === 0 ? live.bids.delete(+p) : live.bids.set(+p, v); }52 for (const [p, q] of e.a) { const v = +q; v === 0 ? live.asks.delete(+p) : live.asks.set(+p, v); }53 live.lastU = e.u; live.lastE = e.E;54 const now = Date.now(); live.ups.push(now); while (now - live.ups[0] > 1000) live.ups.shift();55}56async function resnapshot() {57 live.synced = false;58 const r = await fetch('https://api.binance.com/api/v3/depth?symbol=' + SYMBOL + '&limit=1000');59 if (!r.ok) throw new Error('HTTP ' + r.status);60 const s = await r.json();61 live.bids = new Map(s.bids.map(([p, q]) => [+p, +q]));62 live.asks = new Map(s.asks.map(([p, q]) => [+p, +q]));63 live.lastU = s.lastUpdateId;64 const pending = live.buffer.filter(e => e.u > s.lastUpdateId); live.buffer = [];65 let first = true;66 for (const e of pending) {67 if (first && !(e.U <= live.lastU + 1 && e.u >= live.lastU + 1)) continue;68 first = false; applyDiff(e);69 }70 live.synced = true; live.ok = true; source = 'live';71}72// Snapshot failures fall back to the synthetic book and keep retrying; the page73// switches back to live as soon as a snapshot lands.74function trySnapshot() {75 resnapshot().catch(() => { fallback(); setTimeout(trySnapshot, 5000); });76}77function connect() {78 const s = SYMBOL.toLowerCase();79 let ws;80 try { ws = new WebSocket('wss://stream.binance.com:9443/stream?streams=' + s + '@depth@100ms/' + s + '@aggTrade'); }81 catch (e) { fallback(); return; }82 ws.onopen = () => trySnapshot();83 ws.onmessage = m => {84 const { stream, data } = JSON.parse(m.data);85 if (stream.endsWith('@aggTrade')) {86 // m = buyer is maker → the aggressor sold87 prints.push({ t: data.T, side: data.m ? 'sell' : 'buy', qty: +data.q, price: +data.p });88 if (prints.length > 400) prints.shift();89 return;90 }91 if (!live.synced) { live.buffer.push(data); if (live.buffer.length > 600) live.buffer.shift(); return; }92 if (data.u <= live.lastU) return;93 if (data.U > live.lastU + 1) { live.buffer = [data]; trySnapshot(); return; }94 applyDiff(data);95 };96 ws.onerror = () => { if (!live.ok) fallback(); };97 ws.onclose = () => { live.synced = false; setTimeout(connect, live.ok ? 2000 : 8000); };98}99setTimeout(() => { if (!live.ok) fallback(); }, 15000);100connect();101102// ─── Synthetic fallback (same shape as the live book) ───────────────────────103let center = 76500;104const base = { bid: [], ask: [] };105for (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); }106function fallback() {107 if (source === 'synthetic' || live.ok) return;108 source = 'synthetic';109 setStatus('SYNTHETIC · live feed unreachable, retrying', '#94a3b8');110}111function tickSynthetic() {112 if (Math.random() < 0.35) center += (Math.random() < 0.5 ? -1 : 1) * GROUP;113 for (const s of ['bid', 'ask']) for (let i = 0; i < LEVELS; i++) {114 let q = base[s][i] * (0.92 + Math.random() * 0.16);115 if (Math.random() < 0.003) q *= 5;116 if (q > 40) q *= 0.7;117 base[s][i] = q;118 }119 if (Math.random() < 0.6) {120 const side = Math.random() < 0.5 ? 'buy' : 'sell';121 prints.push({ t: Date.now(), side, qty: +(Math.random() ** 3 * 8 + 0.01).toFixed(3), price: side === 'buy' ? center + GROUP : center });122 if (prints.length > 400) prints.shift();123 }124}125function tick() { if (source === 'synthetic') tickSynthetic(); }126127// ─── Snapshot: group raw levels into LEVELS buckets per side ────────────────128let prev = new Map();129const r8 = v => Math.round(v * 1e8) / 1e8;130function group(map, side) {131 const out = new Map();132 for (const [p, q] of map) {133 const k = r8((side === 'bid' ? Math.floor(p / GROUP + 1e-9) : Math.ceil(p / GROUP - 1e-9)) * GROUP);134 out.set(k, (out.get(k) || 0) + q);135 }136 return [...out.entries()].sort((a, b) => (side === 'bid' ? b[0] - a[0] : a[0] - b[0])).slice(0, LEVELS);137}138function rawSynthetic(side) {139 const out = [];140 for (let i = 0; i < LEVELS; i++) {141 if ((i === 7 || i === 13) && Math.random() < 0.45) continue; // two flickering levels142 out.push([side === 'bid' ? center - i * GROUP : center + GROUP + i * GROUP, base[side][i]]);143 }144 return out;145}146function snapshot() {147 let rb, ra;148 if (source === 'live') {149 if (!live.synced) return null;150 rb = group(live.bids, 'bid'); ra = group(live.asks, 'ask');151 setStatus('LIVE · Binance ' + SYMBOL + ' · ' + live.ups.length + ' upd/s · ' + Math.max(0, Date.now() - live.lastE) + 'ms', '#22c55e');152 } else if (source === 'synthetic') {153 rb = rawSynthetic('bid'); ra = rawSynthetic('ask');154 } else return null;155 if (!rb.length || !ra.length) return null;156 const build = (levels, s) => {157 let tot = 0, notl = 0;158 return levels.map(([price, q]) => {159 const key = s + price;160 tot += q; notl += q * price;161 return { price, qty: q, total: tot, notional: q * price, cumNotional: notl,162 delta: prev.has(key) ? q - prev.get(key) : q, isNew: !prev.has(key) };163 });164 };165 const bids = build(rb, 'bid'), asks = build(ra, 'ask');166 const next = new Map();167 bids.forEach(l => next.set('bid' + l.price, l.qty));168 asks.forEach(l => next.set('ask' + l.price, l.qty));169 prev = next;170 const all = [...bids, ...asks], avg = all.reduce((s, l) => s + l.qty, 0) / all.length;171 all.forEach(l => { l.isWall = l.qty > avg * 3; });172 return { bids, asks, bestBid: bids[0].price, bestAsk: asks[0].price,173 mid: (bids[0].price + asks[0].price) / 2, t: Date.now(), prints };174}175176// ─── Helpers ────────────────────────────────────────────────────────────────177function txt(s, x, y, a = 'left', c = TEXT) { X.fillStyle = c; X.textAlign = a; X.textBaseline = 'middle'; X.fillText(s, x, y); }178function fmt(v, d = 2) { return Math.abs(v) >= 1e4 ? v.toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) : v.toFixed(d); }179function alpha(hex, a) { return hex + Math.round(Math.max(0, Math.min(1, a)) * 255).toString(16).padStart(2, '0'); }180function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }181182// ─── View ───────────────────────────────────────────────────────────────────183// Each row's bar is the cumulative depth from the touch out to that level, on184// one scale for both sides. A riser joins each row to the next, so the rows draw185// one continuous staircase — the same shape as the depth chart.186function draw(X, W, H, b) {187 const rowH = 16, cy = H / 2;188 const n = Math.min(b.bids.length, b.asks.length, Math.floor((H / 2 - 14) / rowH));189 const max = Math.max(b.bids[n - 1].total, b.asks[n - 1].total) * 1.08;190 const maxQ = Math.max(...b.bids.slice(0, n).map(l => l.qty), ...b.asks.slice(0, n).map(l => l.qty));191 const barW = W * 0.62;192 X.fillStyle = '#111827'; X.fillRect(0, cy - 12, W, 24);193 txt(fmt(b.mid), 10, cy, 'left'); txt('spread ' + (b.bestAsk - b.bestBid).toFixed(1), W - 10, cy, 'right', MUTED);194 for (const [lv, col, up] of [[b.asks, ASK, true], [b.bids, BID, false]]) {195 let prevW = 0;196 for (let i = 0; i < n; i++) {197 const l = lv[i], y = up ? cy - 12 - (i + 1) * rowH : cy + 12 + i * rowH;198 const bw = (l.total / max) * barW, x = W - bw;199 X.fillStyle = alpha(col, 0.3); X.fillRect(x, y, bw, rowH); // tread200 X.fillStyle = col; X.fillRect(x, y, 1.5, rowH); // step edge201 X.fillRect(x, up ? y + rowH - 1.5 : y, bw - prevW, 1.5); // riser, on the edge facing the touch202 const sw = (l.qty / maxQ) * barW * 0.5;203 X.fillStyle = alpha(col, l.isWall ? 1 : 0.7); X.fillRect(W - sw, y + rowH - 2, sw, 2); // per-level size strip204 txt(fmt(l.price), 10, y + rowH / 2, 'left', col);205 txt(l.qty.toFixed(3), W * 0.3, y + rowH / 2, 'right');206 txt(l.total.toFixed(2), W * 0.42, y + rowH / 2, 'right', MUTED);207 prevW = bw;208 }209 }210}211212// ─── Loop: redraw 4× a second (the live book updates every 100ms) ───────────213let last = 0;214(function loop(now) {215 requestAnimationFrame(loop);216 if (now - last < 250) return;217 last = now; tick();218 const b = snapshot();219 if (!b) return;220 X.clearRect(0, 0, W, H);221 X.font = '11px ui-monospace, Menlo, monospace';222 draw(X, W, H, b);223})(0);224</script>225</body>226</html>See also
Related articles
Related to Price Ladder & Stepped Depth
- 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.