Appearance

Personalize your experience

Mode
Accent Color
Direction
Book vs Tape Profile

Book vs Tape Profile

Order Book View

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

What it answers

Is the resting liquidity actually trading, and where has volume concentrated? Open it from the order book toolbar with View → Book vs Tape Profile, 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 Book vs Tape Profile 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. The traded side builds as prints arrive.

Anatomy

Left bars
Resting size at each price now (√ scale).
Right bars
Volume traded at each price since the view opened, split buy (bid colour) and sell (ask colour).
POC
— the most-traded price in view.
VWAP
Volume-weighted average traded price.
Mid
The current mid, dotted.

How to read it

  1. resting size beside large , with an unchanged price: absorption — the level is defended.
  2. resting size with no : untested liquidity, which may be when tested.
  3. Price above VWAP with buy-dominated bars: buyers have been in control.
  4. POC is the price that attracted the most activity; price often returns to it.

How it is computed

  • Each print is bucketed to the current and counted once (deduplicated by trade id).
  • Resting bars come from the book at the same buckets.
  • The absorption ratio in the is ÷ resting size at that price.
Core calculation
for (const p of prints) {
  if (seen.has(p.id)) continue; seen.add(p.id);
  const bucket = Math.round(p.price / step) * step;
  const bin = profile.get(bucket) ?? { buy: 0, sell: 0 };
  bin[p.side] += p.quantity; profile.set(bucket, bin);
}
const vwap = Σ(price × volume) / Σ(volume);
const poc  = argmax(price → buy + sell);
const absorption = (bin.buy + bin.sell) / resting.quantity;

Trading signals

Absorption

High into a wall that does not means a passive participant is absorbing aggression. Fade the aggressor near that level.

Exhaustion

Volume drying up at a level that previously traded heavily: interest has moved elsewhere.

!
Caution:The tape profile covers only the seen since the view opened (the feed's recent ), not the .

Settings that affect this view

Price grouping
Bucket size for both .
Liquidity walls
Highlights resting walls.

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.

Book vs tape profile — standalone HTML, live Binance feed, no dependencies
orderbook-profile.html
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Book vs tape profile — 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 ───────────────────────────────────────────────────────────────────// Left: what is resting at each price now. Right: what has actually traded there// (buy / sell split). POC = the most-traded price; VWAP = volume-weighted price.// Big resting size that keeps absorbing prints without moving is a real wall.const traded = new Map(); let seen = 0;function draw(X, W, H, b) {  for (; seen < b.prints.length; seen++) {    const p = b.prints[seen], k = Math.round(p.price / TICK) * TICK, v = traded.get(k) || { buy: 0, sell: 0 };    v[p.side] += p.qty; traded.set(k, v);  }  if (b.prints.length >= 400) seen = b.prints.length - 1;  const hi = b.asks.at(-1).price, lo = b.bids.at(-1).price, rows = Math.round((hi - lo) / TICK) + 1, rowH = (H - 20) / rows, cx = W / 2;  const rest = new Map([...b.bids, ...b.asks].map(l => [l.price, l]));  const maxR = Math.max(...[...rest.values()].map(l => l.qty));  let maxT = 1e-9, poc = null, vn = 0, vd = 0;  for (const [p, v] of traded) { const t = v.buy + v.sell; vn += p * t; vd += t; if (p >= lo && p <= hi && t > maxT) { maxT = t; poc = p; } }  for (let i = 0; i < rows; i++) {    const p = hi - i * TICK, y = 18 + i * rowH, l = rest.get(p), v = traded.get(p);    if (l) { const len = Math.sqrt(l.qty / maxR) * (cx - 50); X.fillStyle = alpha(p > b.mid ? ASK : BID, 0.6); X.fillRect(cx - 40 - len, y, len, rowH - 1); }    if (v) { const t = v.buy + v.sell, len = (t / maxT) * (cx - 50), bl = (v.buy / t) * len;      X.fillStyle = alpha(BID, 0.8); X.fillRect(cx + 40, y, bl, rowH - 1); X.fillStyle = alpha(ASK, 0.8); X.fillRect(cx + 40 + bl, y, len - bl, rowH - 1); }    if (i % Math.ceil(13 / rowH) === 0) txt(fmt(p), cx, y + rowH / 2, 'center', p > b.mid ? ASK : BID);  }  const guide = (p, col, label) => { const y = 18 + ((hi - p) / TICK) * rowH + rowH / 2; X.strokeStyle = col; X.beginPath(); X.moveTo(0, y); X.lineTo(W, y); X.stroke(); txt(label, W - 4, y - 7, 'right', col); };  if (poc !== null) guide(poc, ACCENT, 'POC ' + fmt(poc));  if (vd > 0) guide(vn / vd, WARN, 'VWAP ' + fmt(vn / vd));  txt('◀ resting', cx - 44, 8, 'right', MUTED); txt('traded ▶', cx + 44, 8, 'left', MUTED);}// ─── 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

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

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

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

  • Concentration

    Trading / Order Book / Views

    Lorenz curve and Gini coefficient of each side — how few levels hold the liquidity.

  • Depth Bands

    Trading / Order Book / Views

    Liquidity within 5 / 10 / 25 / 50 / 100 bp of mid, over time — how much depth is actually close.

  • Depth City

    Trading / Order Book / Views

    An isometric 3-D skyline of resting size along the price axis — towers are walls.

Browse all knowledge base articles →