---
title: "Liquidation Heat"
description: "Where the leverage opened on recent bars would be liquidated, painted behind the candles as a heatmap. Every bar seeds liquidation prices below its close for…"
order: 87
section: "cookbook"
---

<!-- source: docs/indicators/cookbook/liquidation-heat.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Liquidation Heat

![Liquidation levels painted behind the candles as a heatmap that follows price](/wrun/images/liquidation-heat.png)

Where the leverage opened on recent bars would be liquidated, painted behind the candles as a heatmap. Every bar seeds liquidation prices below its close for longs and above it for shorts at four leverage tiers (10x, 25x, 50x and 100x), each weighted by a share of the bar's volume; a level the price trades through is consumed and leaves the map, the rest fade with age until `lookback` bars have passed. The map is 64 rows centred on the close, so it follows price up and down: the brighter a row, the more leverage sits there waiting to be flushed, and a dense band just above or below the price is where a sweep would run.

The parts are an `out.grid` of 64 rows written cell by cell from `onBar()`, two data-only outputs that place the grid on the price axis, and a `plot.heatmap` declaration over the grid ([Price canvases](../presentation/price-canvases.md)). Every number is computed in the indicator from the chart's own candles ([Data sources](../core-concepts/data-sources.md)). This is also the `liquidation-heat` template: the **Liquidation Heat** card under **Order flow** in the editor's starter list, and it compiles as written.

## The wrun indicator

```typescript sample=liquidation-heat
// Liquidation Heat: where the leverage opened on recent bars would be liquidated, painted behind the candles as a
// heatmap. Every bar seeds liquidation prices below its close for longs and above it for shorts at four leverage
// tiers (10x, 25x, 50x, 100x), weighted by the bar's volume; a level the price trades through is consumed, the rest
// fade with age. The map is 64 rows centred on the close, so it follows price: the brighter a row, the more leverage
// sits there waiting to be flushed.

param.int("lookback", 240, { min: 20, max: 2000, label: "Lookback in bars", description: "Bars a seeded level stays on the map before it has faded out" });
param.number("row_pct", 0.1, { min: 0.01, max: 2, step: 0.01, label: "Row height in percent", description: "Row height as a percent of the close" });
input("close", ohlcv.close); // the primary input: the chart's own candles define the grid and seed the levels
output("grid_low", none, overlay, { description: "The bottom edge of the map's lowest row on this bar" }); // data-only: the heatmap reads it
output("row_step", none, overlay, { description: "The map's row height in price on this bar" });
out.grid("heat", { rows: 64 }); // 64 data-only outputs heat_0..heat_63, one per row of the map, written with out_heat(row, value)
plot.heatmap({ name: "liq_heat", grid: "heat", price_low: "grid_low", price_step: "row_step", palette: ["theme.bg", "#8b5cf640", "#8b5cf6", "#c026d3"], scale: "linear", min: 0, auto_quantile: 0.98, opacity: 0.8, behind_candles: true, label: "Liquidation heat", tooltip: "{{value:si}} seeded at {{price}}" }); // violet to magenta, hues no candle theme uses, faint for most cells: the candles stay readable on top

const ROWS = 64; // rows in the map, the grid's declared count
const TIERS = 4; // leverage tiers seeded per bar
const MAX_LEVELS = 4096; // seeded levels ride a ring: the oldest is recycled
const leverage = new StaticArray<f64>(TIERS);
const levelPrice = new StaticArray<f64>(MAX_LEVELS); // each seeded level's price, weight and the bar it was seeded on
const levelSize = new StaticArray<f64>(MAX_LEVELS);
const levelBar = new StaticArray<i32>(MAX_LEVELS);
const rowHeat = new StaticArray<f64>(ROWS); // this bar's map, folded from the live levels
let head = 0; // the ring position
let used = 0; // levels in use
let barIndex = 0; // bars seen
let lookback = 240; // settings, read in onStart()
let rowPct = 0.001;

function onStart(): void {
  lookback = i32(p_lookback());
  rowPct = p_row_pct() / 100.0;
  leverage[0] = 10.0;
  leverage[1] = 25.0;
  leverage[2] = 50.0;
  leverage[3] = 100.0;
}

function seed(price: f64, size: f64): void { // one liquidation level into the ring
  levelPrice[head] = price;
  levelSize[head] = size;
  levelBar[head] = barIndex;
  head = (head + 1) % MAX_LEVELS;
  if (used < MAX_LEVELS) used += 1;
}

function clearRows(): void { // no map on this bar: every cell empty
  for (let r = 0; r < ROWS; r += 1) out_heat(r, NaN);
}

// onBar() runs once per bar: consume the levels the bar traded through, seed this bar's, then fold the live levels
// onto the 64-row grid around the close and write the grid, its bottom edge and its step.
function onBar(): void {
  const close = bar.close();
  const high = bar.high();
  const low = bar.low();
  barIndex += 1;
  if (isNaN(close) || close <= 0.0) {
    out_grid_low(NaN);
    out_row_step(NaN);
    clearRows();
    return;
  }
  for (let k = 0; k < used; k += 1) { // a level inside the bar's range was flushed: it leaves the map
    if (levelSize[k] > 0.0 && levelPrice[k] >= low && levelPrice[k] <= high) levelSize[k] = 0.0;
  }
  const volume = bar.volume();
  const weight = isNaN(volume) || volume <= 0.0 ? 1.0 : volume; // a market with no volume still seeds, one unit a bar
  const share = weight / f64(TIERS); // each tier takes an equal share of the bar's volume
  for (let i = 0; i < TIERS; i += 1) { // longs opened here are liquidated below the close, shorts above it
    seed(close * (1.0 - 1.0 / leverage[i]), share);
    seed(close * (1.0 + 1.0 / leverage[i]), share);
  }
  const step = close * rowPct;
  const gridLow = close - step * f64(ROWS / 2); // the middle row holds the close
  for (let r = 0; r < ROWS; r += 1) rowHeat[r] = 0.0;
  for (let k = 0; k < used; k += 1) {
    if (levelSize[k] <= 0.0) continue;
    const age = barIndex - levelBar[k];
    if (age > lookback) { // faded out
      levelSize[k] = 0.0;
      continue;
    }
    const r = i32(Math.floor((levelPrice[k] - gridLow) / step));
    if (r < 0 || r >= ROWS) continue; // outside the map this bar; it comes back when price moves toward it
    rowHeat[r] += levelSize[k] * (1.0 - f64(age) / f64(lookback));
  }
  out_grid_low(gridLow);
  out_row_step(step);
  for (let r = 0; r < ROWS; r += 1) out_heat(r, rowHeat[r] > 0.0 ? rowHeat[r] : NaN); // NaN = an empty cell
}
```

## How it works

**A grid the module writes.** `out.grid("heat", { rows: 64 })` declares 64 data-only outputs `heat_0` to `heat_63`, one per row of the map, and the generated writer `out_heat(row, value)` fills them in `onBar()`; a row outside 0 to 63 aborts the run by name, and NaN leaves a cell empty. The two outputs beside it place the grid: `grid_low` is the bottom edge of row 0 on each bar and `row_step` every row's height in price, so the map moves with the close and keeps its shape when the chart's scale changes. `plot.heatmap({ grid: "heat", price_low: "grid_low", price_step: "row_step" })` ties the three together; a heatmap takes `cells` or `grid`, never both.

**The levels.** Each bar seeds eight levels into a ring of 4,096: for every tier, a long opened at the close is liquidated at `close x (1 - 1 / leverage)` and a short at `close x (1 + 1 / leverage)`, each level weighted by a quarter of the bar's volume (one unit on a market without volume). Before seeding, the bar's range is swept: a level between the low and the high was flushed and is zeroed. Then every live level is folded onto the 64 rows around the close with a linear fade, `1 - age / lookback`; a level older than `lookback` is dropped, and one outside the map this bar is kept, since it comes back when price moves toward it.

**The scale.** The palette runs from the chart's background through a faint violet (`#8b5cf640`, violet at a quarter strength, so it reads on a light chart too) and full violet to magenta: hues no candle colour uses, so the amber and orange (or green and red) bars stay readable where they cross the map. `scale: "linear"` with `min: 0` keeps most cells faint and lets only the dense rows glow, and `auto_quantile: 0.98` sets the top of the scale at the 98th percentile of every cell in the run. `behind_candles: true` keeps the bars on top. `row_pct` (0.1% of the close) sets the row height: finer rows separate the tiers, coarser rows merge them into bands.

## Where it runs

Any chart: the map is built from the chart's own candles, so it draws on every market and interval. It reads best on perpetual and futures charts, where the leverage it models is traded; on spot or a prediction market the rows still draw, but there is no leverage to be flushed at them.

## When data is missing

A bar whose close is NaN writes an empty map and places nothing. A market without volume seeds one unit a bar, so the map still draws, with every bar weighing the same. The map holds only what the chart has seen: the first `lookback` bars of a run build it up from nothing.

## Customize it

- **A longer memory.** Raise `lookback` to keep levels longer before they fade; lower it for a map of the last session only.
- **Finer or coarser rows.** `row_pct` sets the row height as a percent of the close; `rows: 64` in `out.grid` sets how many rows the map spans (2 to 128).
- **Other tiers.** Change the four leverages in `onStart()`; the ring holds eight levels per bar regardless.
- **A fixed scale.** Set `min` and `max` on the heatmap to compare days on one scale, or `floor` to hide the faintest cells.

## Run it

1. In the editor's Explorer, press the **Templates** icon ("Browse starter templates") and pick **Liquidation Heat** under **Order flow**.
2. Press **Run** on a perpetual chart: the map paints behind the candles, bright where recent leverage would be liquidated, and clears where price has already swept.
3. Hover a cell for the weight seeded at that price; at the editor's Console prompt, type `row_step` to read the row height in price.

## Concepts used

- [Price canvases](../presentation/price-canvases.md) for `out.grid`, `plot.heatmap` over a grid, `price_low` and `price_step`
- [Data sources](../core-concepts/data-sources.md) for the chart's own candles as the primary input
- [Plotting](../presentation/plotting.md) for data-only outputs
