> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xarchive.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Book walls

> The biggest walls resting in the live book, and how each one ended, filled or pulled, from book() and the trades at its price.

<Note>
  This script reads only live data, so it has no history to draw: open it on a chart and watch it fill in.
</Note>

<Card title="Open on a chart" icon="chart-candlestick" href="https://terminal.0xarchive.io/?symbol=BTC&interval=1m&scripts=example%3Abook-walls" horizontal>
  Load it on BTC 1m in the terminal, then change its settings or open it in the script editor.
</Card>

A wall is a level with a lot resting on it: \$1M or more by default, for at least a minute. This script draws the biggest walls resting now, three a side, as lines with their size, and marks each wall that loses more than half its size: **F** when it was filled, traded away by takers, and **P** when it was pulled, gone with no trades at its price and not moved nearby. Filled walls show where size was absorbed; pulled walls show liquidity that left before price reached it.

## How it works

[`book()`](/terminal/guides/data#the-live-book) reads the live order book level by level, and `onUpdate` runs for each book the page receives, with the levels that changed and the size each one had before. `trades()` runs first at the same moment, so the trades of a block come before the book that block left: the script sums the size traded at each price since the book last changed, and an update that shrinks a wall checks that sum. At least half the size gone traded away is a fill; less is a pull. Market makers move size around many times a minute, so a level counts as a wall only once it has rested for **Rested at least**, from when it first reached the size; the script keeps that time per price in live state.

Most walls that leave their price have moved, not gone: on BTC on 2026-10-16, 33 of 34 walls that left with no trades turned up again on the same side within 0.1% within 3 seconds. So a wall that leaves its price and turns up on its side within 0.1% within 3 seconds, at least half as big, is the same wall moving: it keeps the time it began resting, its line follows it, and it gets no mark. That covers a wall pulled and put back at the same price too. A wall that leaves with no trades is marked **P** only once 3 seconds pass with no wall turning up near it, so its mark can land on the bar after. Change `MOVE_PCT` and `MOVE_MS` at the top to follow walls further or longer.

The marks are tallies, so each bar shows the walls that ended in it.

`changes` leaves out levels that only came into the 400 levels held or left them as the book moved, so a wall that drifts out of reach is not taken for a pull.

The book is live only: the marks start when the chart opens, and older bars have none. The lines are drawn only on the bar still forming, so they are always the walls resting now.

## Try changing

* Change **Wall (USD)** for the market: \$1M is a wall on BTC, and more than most other markets' whole book.
* Lower **Rested at least** to see how often size near the touch is pulled and placed again.
* Count the filled and pulled size per bar as two histograms in a pane of their own, to see which way walls end over a session.

## The script

```ts book-walls.ts theme={"theme":"github-dark"}
// Walls in the live book, and how each one ended: filled (traded away) or pulled (gone with no
// trades at its price). A wall that moves a few ticks is followed, not marked. The book is live
// only, so the marks start when the chart opens.
indicator('Book walls', { overlay: true });

const wallUsd = input.float('Wall (USD)', 1_000_000, { min: 10_000, step: 50_000 });
// Market makers move size around all the time; a wall counts once it has rested this long.
const minAge = input.int('Rested at least (s)', 60, { min: 0, max: 3_600 });
const shown = input.int('Walls a side', 3, { min: 1, max: 10 });

// A wall that leaves its price and turns up on its side this close, this soon, is the same wall moving.
const MOVE_PCT = 0.1;
const MOVE_MS = 3_000;

const chart = bars();
// Trades first: a block's trades then come before the book that block left.
const tape = trades();
const b = book({ levels: 400 });

// Size traded at each price since the book last changed.
const traded = state.map<number>(1_024, 'live');
// When each wall began resting, by side and price; a wall that moves keeps its time.
const since = state.map<number>(2_048, 'live');
// Walls that arrived at their price in the last MOVE_MS: one that left nearby may have moved there.
const arrived = state.map<number>(256, 'live');
// Walls gone with no trades in the last MOVE_MS, not yet known to be pulled: when, how much, resting since.
const goneAt = state.map<number>(256, 'live');
const goneUsd = state.map<number>(256, 'live');
const goneSince = state.map<number>(256, 'live');
// The walls that ended this bar, by how, side and price, in USD.
const ended = tallyBy(256);
const marks = plot.markers('Wall ended');

const keyOf = (side: string, price: number) => `${side}:${price}`;
const sideOf = (key: string) => (key.startsWith('bid') ? 'bid' : 'ask');
const priceOf = (key: string) => Number(key.slice(4));

// The key in `walls` on `side` nearest `price`, within MOVE_PCT, that `fits`; null if none.
const nearest = (walls: typeof arrived, side: string, price: number, fits: (key: string) => boolean) => {
  let best: string | null = null;
  let bestGap = (price * MOVE_PCT) / 100;
  for (const [key] of walls.entries()) {
    if (sideOf(key) !== side) continue;
    const gap = Math.abs(priceOf(key) - price);
    if (gap <= bestGap && fits(key)) {
      best = key;
      bestGap = gap;
    }
  }
  return best;
};

const forget = (key: string) => {
  goneAt.delete(key);
  goneUsd.delete(key);
  goneSince.delete(key);
};

tape.onTrade((t) => {
  // Older bars have trades but no book: nothing to match them to.
  if (!b.available) return;
  const key = String(t.price);
  traded.set(key, (traded.get(key) ?? 0) + t.size);
});

b.onUpdate((u) => {
  if (u.snapshot) {
    // A new book: its walls rest from now, as far as anyone can tell.
    since.clear();
    arrived.clear();
    goneAt.clear();
    goneUsd.clear();
    goneSince.clear();
    for (const l of u.bids) if (l.notional >= wallUsd) since.set(keyOf('bid', l.price), u.time);
    for (const l of u.asks) if (l.notional >= wallUsd) since.set(keyOf('ask', l.price), u.time);
    traded.clear();
    return;
  }
  // A wall gone with no wall turning up near it was pulled.
  for (const [key, at] of [...goneAt.entries()]) {
    if (u.time - at <= MOVE_MS) continue;
    const start = goneSince.get(key) ?? at;
    if (at - start >= minAge * 1_000) ended.add(`pulled:${key}`, goneUsd.get(key) ?? 0);
    forget(key);
  }
  for (const [key, at] of [...arrived.entries()]) if (u.time - at > MOVE_MS) arrived.delete(key);
  // Walls that left the levels held are still listed: once the list is long, drop what is no wall now.
  if (since.size >= 2_000) {
    for (const [key] of [...since.entries()]) {
      if (!(u.size(sideOf(key), priceOf(key)) * priceOf(key) >= wallUsd)) since.delete(key);
    }
  }
  for (const c of u.changes) {
    const key = keyOf(c.side, c.price);
    const was = c.previous * c.price >= wallUsd;
    const is = c.size * c.price >= wallUsd;
    if (!was) {
      if (!is) continue;
      // A wall arriving where one just left nearby is that wall, moved: it keeps its time.
      const from = nearest(goneAt, c.side, c.price, (k) => c.size * c.price >= (goneUsd.get(k) ?? 0) / 2);
      if (from !== null) {
        since.set(key, goneSince.get(from) ?? u.time);
        forget(from);
      } else if (since.size < 2_000) since.set(key, u.time);
      if (arrived.size < 250) arrived.set(key, u.time);
      continue;
    }
    // Still most of it there: the wall stands, unless it is now too small to be one.
    if (c.size > c.previous / 2) {
      if (!is) since.delete(key);
      continue;
    }
    const start = since.get(key);
    since.delete(key);
    arrived.delete(key);
    const gone = c.previous - c.size;
    if ((traded.get(String(c.price)) ?? 0) >= gone / 2) {
      if (start !== undefined && u.time - start >= minAge * 1_000) ended.add(`filled:${key}`, gone * c.price);
      continue;
    }
    if (start === undefined) continue;
    // Gone with no trades: moved, if a wall as big just arrived near it; else pulled, unless one arrives soon.
    const to = nearest(arrived, c.side, c.price, (k) => u.size(sideOf(k), priceOf(k)) * priceOf(k) >= (gone * c.price) / 2);
    if (to !== null) {
      since.set(to, Math.min(start, since.get(to) ?? start));
      arrived.delete(to);
    } else if (goneAt.size < 250) {
      goneAt.set(key, u.time);
      goneUsd.set(key, gone * c.price);
      goneSince.set(key, start);
    }
  }
  traded.clear();
});

chart.onBar(() => {
  for (const [key, usd] of ended.entries()) {
    const [how, side, price] = key.split(':');
    marks.add({
      position: 'at',
      price: Number(price),
      shape: how === 'filled' ? 'xcross' : 'circle',
      color: side === 'bid' ? color.up : color.down,
      text: `${how === 'filled' ? 'F' : 'P'} ${fmt.usd(usd)}`,
    });
  }
  // The walls resting now, on the bar still forming: drawn again with each update.
  if (chart.confirmed || !b.available) return;
  const walls = (levels: readonly BookLevel[], tint: Color) => {
    const big = levels.filter((l) => l.notional >= wallUsd);
    big.sort((x, y) => y.notional - x.notional);
    for (const l of big.slice(0, shown)) {
      draw.line({ from: { bar: chart.index - 20, price: l.price }, to: { bar: chart.index, price: l.price }, color: tint, width: 2, extend: 'right' });
      draw.label({ at: { bar: chart.index + 2, price: l.price }, text: fmt.usd(l.notional), style: 'left', color: color.panel, textColor: tint, size: 'small' });
    }
  };
  walls(b.bids, color.up);
  walls(b.asks, color.down);
});
```

## Next step

See the next example, [Footprint cells](/terminal/examples/footprint-cells).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.