Skip to content
ChartDeck ChartScript docs Account Open the terminal

Heatmaps

The heatmap drawing, columns of colored cells over time and price, painted as one image, with palettes, scales, the automatic reference and budgets.

A heatmap shows a value at every price and time, as color: the brighter the cell, the bigger the value. ChartDeck's Liquidation Heatmap and Order Book Heatmap are heatmaps, and a script can draw its own with the heatmap drawing.

A heatmap is one drawing that holds many columns. Each column covers a span of time (one bar by default) and a stack of equal cells from a bottom price up, one value per cell. The chart paints the whole heatmap as a single image per pane, so a heatmap of a hundred thousand cells costs about as much to draw as one picture of the chart: it is not made of boxes.

#A first heatmap

Create the heatmap once, with state, and add a column on every bar. This one spreads each bar's volume over four rows between its low and its high, more towards the close:

ChartScriptOpen in editor
//@chartscript 1
indicator("Volume heat", { overlay: true, behind_chart: true })
state hm = heatmap.new({ palette: heatmap.palette_viridis })

const step = (high - low) / 4
if (step > 0) {
    const nearClose = math.floor((close - low) / step)
    const cells = array.new_float(4, volume * 0.1)
    cells.set(math.min(3, nearClose), volume)
    hm.column(time, low, step, cells)
}

hm.column(x, bottom, step, values) adds one column at x (the bar's time): cell k covers the prices from bottom + k × step to bottom + (k + 1) × step and gets values[k]. A cell whose value is na, 0 or negative stays empty and is not stored. behind_chart: true puts the heatmap (and everything else the script draws) behind the candles, where a heatmap belongs.

Do not create a heatmap without state: a const hm = heatmap.new() makes a new heatmap on every bar, and a script keeps only its 8 newest.

#Where a column goes

xloc decides what x and width mean:

  • xloc.bar_time (the default): x is a time, width a duration in milliseconds. width = na makes the column one bar wide. A column that spans an hour on a 15-minute chart covers four bars.
  • xloc.bar_index: x is a bar index and width a number of bars (na = 1).

hm.cell(x, low, high, value) adds a single cell from low to high, for scattered values that do not form columns.

#Colors and scale

The value of a cell becomes a color in three steps, the same ones the built-in heatmaps use:

  1. The range. min and max are the values at the bottom and the top of the palette. With max = na (the default) the top is set automatically: the reference percentile (92 by default) of the cells in view, recomputed as you scroll and zoom, so a few giant cells cannot wash the rest out.
  2. The scale. heatmap.scale_linear (default), scale_sqrt (weak cells stand out more) or scale_log (values over several orders of magnitude). intensity multiplies the values first: 2 is twice as bright.
  3. The palette curve. Cells under floor (a share of the range, 0.05 by default) stay clear; above it the palette index rises as the remaining share to the power gamma (0.9 by default).
PaletteColors
heatmap.palette_heatthe Liquidation Heatmap's ramp; follows the theme
heatmap.palette_bid, heatmap.palette_askthe Order Book Heatmap's bid and ask ramps
heatmap.palette_viridis, heatmap.palette_magmaperceptual ramps that read well in both themes
heatmap.palette_monofrom clear to the heatmap's color
ChartScriptOpen in editor
//@chartscript 1
indicator("Volume heat, square root", { overlay: true, behind_chart: true })
state hm = heatmap.new({ palette: heatmap.palette_mono, color: color.accent, scale: heatmap.scale_sqrt, floor: 0.1, reference: 98, opacity: 80 })
hm.cell(time, low, high, volume)

#ChartDeck's own heat data

Two built-in data sources come as ready-made columns, heatmap.column values with the fields time, span, bottom, step, values and max. Add one with hm.add(col): it goes at its own time and span.

ChartScriptOpen in editor
//@chartscript 1
indicator("My liquidation heat", { overlay: true, behind_chart: true })
state hm = heatmap.new({ palette: heatmap.palette_heat, floor: 0.07, gamma: 1.1, reference: 99.5 })
state lastT = 0
const col = liq.heat()
// on a chart below 1 hour, every bar of the hour gets the same column: add it once
if (!na(col) && col.time != lastT) {
    hm.add(col)
    lastT = col.time
}

The official Liquidation heatmap and Order book heatmap scripts (View source in those studies' settings) are complete examples, including their legend keys.

#Budgets

LimitValueWhen you go over
Heatmaps per script8the oldest is removed, as with other drawings
Non-empty cells, all heatmaps of a script together500,000the run stops with CS5120
Rows in one column2,000the run stops with CS5121

Each stored cell also counts towards the script's memory (CS5403). To stay well inside the budget, add a column only when it is new, choose a step that gives tens or hundreds of rows rather than thousands, and clear a heatmap you rebuild (hm.clear()).

#How it is drawn

Only the columns in view are painted, into one image the size of the pane, which is kept until the heatmap, the view or the theme changes. On a live bar only the columns that changed travel from the script to the chart. A heatmap is drawn under the script's other drawings (lines, boxes, labels) and, with behind_chart: true, under the candles.

The ChartScript documentation is in English for now; translations will follow.