Chart Settings Script

Each chart has a settings script (gear panel) with the same globals as a code indicator, but a different execution model: it runs once as a static description of the chart, not on every tick. It re-runs only when the script is saved, the symbol or interval changes, or historical data first loads.

For a worked build of a renderer from scratch, see Create a Custom Footprint Chart.

The values on this page - use, chart.condenseLevel, chart.numberDisplay, chart.columnWidth, and the chart.candle / chart.frame renderers - are honored only here; indicators may reference them but they are ignored there.

What do you want to change?

Each row is a thing people ask for and the one value that does it. Several of these look similar and are not: the space a candle occupies, the space between candles, and the candle drawing itself are three different numbers.

You wantChange this
Zoom in / bigger candlesraise chart.columnWidth
Zoom out / fit more candleslower chart.columnWidth
Less space between candlescheck the drawing FILLS the column first - unsized draw.text is capped at ~0.7x row height and the empty column it leaves dwarfs any gap. Give text a size + scale: "x". Only then lower chart.columnGap
Numbers too small / too much empty columndraw.text size with scale: "x" ties them to the column instead of the row height
Thinner or wider candle bodythe body width in your chart.candle renderer - size it off candle.widthPx to work at any zoom
Merge price rows togetherraise chart.condenseLevel
Show the order book behind the candlesuse('Depth') (time intervals only)
Stroke the bid/offer edge over itchart.bbo = true
Chart opens too far zoomed outchart.initialWindow = "5m" (time intervals only)
Chart sits still then jumps when zoomed inchart.scroll = "continuous"
Show delta x volume instead of sell x buychart.numberDisplay = "deltaVol"
Run an indicator on this chartuse("Name")
Change colors, numbers, bars - anything drawnthe chart.candle renderer; nothing else draws the footprint

use()

Imports an indicator from your library so it runs on this chart. Resolves by indicator id first, then exact name (library order breaks name ties). A name that matches nothing shows an error toast.

  • Imports can be conditional - the import set is re-evaluated live, so an indicator can be enabled only when a condition holds.
  • Enabled indicators run with their own per-tick execution model, exactly as if toggled on in Chart Settings -> Indicators.
API
use(name)
Example
use("VWAP")
use("Big Trades")

chart.condenseLevel / chart.numberDisplay

Chart display config. These shape the DATA the renderer receives - row merging, and which pair of numbers is meaningful - not how it looks. The look is entirely the chart.candle renderer.

  • condenseLevel - how many price rows merge into one displayed row (the Condense Prices setting).
  • numberDisplay - "sellBuy" or "deltaVol", the value pair shown at each price level.
  • These two and chart.columnWidth / chart.columnGap / chart.depth / chart.initialWindow are read back after every run of your script, and a line you leave out falls back to its default. Deleting one reverts that setting rather than keeping whatever was set before.
Example
chart.condenseLevel = 2
chart.numberDisplay = "deltaVol"

chart.columnWidth

Width of one candle column in pixels - equivalently, the zoom: wider candles mean fewer on screen. This is also the space draw.rectangle's from / width and draw.text's at fractions divide up, so a layout that splits the column into regions - a profile beside a delta histogram, say - sets the width it was designed against.

  • 0 (the default) leaves the width to the zoom, as before.
  • Applied when the settings script runs - on save, preset pick, or a symbol or interval change. Zooming afterwards behaves normally. A width wider than the usual 160px zoom-in limit raises that limit; a narrower one is only a starting point and never restricts zooming in.
  • Clamped to 400px.
Example
chart.columnWidth = 118   // px per candle column

// ...so these fractions are 60px and 43px wide
const PROFILE_W = 0.55
const DELTA_W = 0.39

chart.columnGap

Pixels of empty space between neighbouring candle columns. The gap is taken off the drawable span and split evenly either side, so less padding means wider rows for the same chart.columnWidth.

  • 0 (the default) keeps the automatic gap. That gap grows with the column AND with the chart's own size scale, so the same column keeps a different share of itself on different screens - on a 100px column it is about 17px in a small panel and about 30px on a large one.
  • Declaring a value pins the gap in pixels. A layout that fixes chart.columnWidth should fix this too, or its drawable span changes from screen to screen.
  • The gap comes out of the column, not from between them - so raising it alone shrinks your rows. To push candles apart while keeping the layout identical, raise chart.columnWidth by the same number of pixels.
  • Capped at 60px, and never allowed to consume the column: on narrow columns a large gap thins the drawable span rather than erasing it.
Example
chart.columnWidth = 118
chart.columnGap = 8   // pinned, not the scaling default

chart.initialWindow

How much time the chart OPENS showing: milliseconds, or a duration written the way an interval is - "30s", "5m", "2h". The zoom follows from it and the chart's width, so it means the same span on a phone as on a wide monitor.

  • A starting point, not a lock. Scrolling and zooming afterwards work normally; the declared zoom re-applies when the script, the symbol or the replay changes.
  • It also makes that zoom reachable: a chart normally stops zooming in once a candle hits its maximum width, which on a 1m interval is 375ms per pixel. A declared window lowers that limit, so a book-style chart can open - and stay - where its cells make sense.
  • Time intervals only. A tick or range chart's x axis counts trades rather than milliseconds, so there is nothing for this to mean and it is ignored.
  • Leave it out (or 0) and the zoom comes from the interval, or from chart.columnWidth if your script declares one. Setting both, this wins.
Example
// Five minutes of tape across the chart, whatever its width.
chart.initialWindow = '5m'

chart.yZoom

Vertical stretch of the auto-fitted price scale. The chart normally fits the visible price range to the pane; 2 fits half that range, so every row is twice as tall. 1 (the default) keeps the automatic fit; capped at 8.

  • It scales the automatic fit rather than pinning a range, so it keeps working as price moves and as the pane resizes.
  • Dragging the price axis still overrides it - a manual scale always wins until the chart re-centers.
Example
chart.yZoom = 2   // rows twice as tall as the auto fit

chart.depth

Paints the order book under the candles: resting size at each price as a shaded strip, bids below the bid and offers above the offer. It is drawn on the chart's own price rows, so it merges the way the rows do.

This is the one chart setting an indicator can set too. The built-in Depth indicator is nothing but this assignment, so the usual way to turn the book on is to tick Depth in the Indicators menu, and the way to restyle it is to duplicate that indicator and edit the gradient. An indicator that declares it wins over the settings script, and if several do, the last one enabled wins.

  • true for full strength, or 0..1 to fade it back behind the numbers. 0 (the default) is off.
  • Time intervals only. On a tick or range chart the x axis is not wall-clock time, so a book snapshot has no column to land in and the setting is ignored.
  • Depth is collected as a replay plays, so it starts where you started playing and ends where playback has reached. There is none for a range you have scrolled to but not played, and none behind the buffer's rolling window.
  • Assign an object instead of a number to change the gradient: bid and ask (hex or rgba()), max, and curve, with opacity still fading the layer. A level's brightness is its resting size against max, painted in the colour - whose own alpha is what a level AT max reaches. So a brighter big book is a higher alpha on the colour, and the size that counts as big is max (left out, each instrument keeps its own: ES 150, NQ 50, everything else 50).
  • curve bends the ramp between them. 1 is linear, below 1 lifts ordinary levels toward full brightness, above 1 holds them down so only the biggest books light up.
  • The bid and offer step lines this layer strokes are chart.bbo, below - a separate setting, because the layer itself is usually declared by the shared Depth indicator while stroking the edge is a per-chart look.
  • Condensed rows sum the ticks they cover, so raising chart.condenseLevel merges depth the same way it merges volume.
  • Turning it on also unlocks the zoom: the normal limit of one candle per chart.columnWidth has nothing to protect once the book is the display, so the chart zooms in to 4ms per pixel, and repaints on the replay clock rather than only when a trade lands.
Example
chart.depth = 0.6   // behind the numbers, not competing with them

// Or as a gradient. Here a big book is 400 lots rather than 50, and one that
// size burns at 0.9 alpha instead of 0.48 - so walls stand out and ordinary
// levels stay quiet.
chart.depth = {
  bid: 'rgba(59, 130, 246, 0.9)',
  ask: 'rgba(239, 68, 68, 0.9)',
  max: 400,
  curve: 1.4,
}

chart.bbo

The bid and offer step lines the depth layer strokes, marking where the top of the book was at each moment. They are also the boundary the gradient is clipped to, which is why bids never paint above the bid.

  • Off by default. true for full strength, or 0..1 for the opacity, over the depth layer's own.
  • Needs depth to be on - the same layer draws it, so with no depth there is nothing to stroke.
  • It is the CHART's setting rather than part of chart.depth, because the layer is usually declared by the built-in Depth indicator, and that one entry is shared by every chart importing it. Whether the edge is stroked is a per-chart look: a chart drawing candles already shows where price is, while the book-style charts draw none and read the edge as the price track. That is why Heatmap and Bookmap Circles set it and nothing else does.
  • Derived from the MBP-10 book, not a separate quote feed: it steps when the top of the ladder changes, so it is the book edge at each observed state rather than every quote.
Example
use('Depth')
chart.bbo = 0.9   // what the book-style presets set

chart.scroll

How the right edge advances while the chart is following live data.

  • "bars" (the default) steps the edge one candle at a time, which is what a candle chart wants: the live candle grows in place and the chart shifts when it closes.
  • "continuous" advances it with the replay clock. Required once you zoom in far enough that a whole candle is wider than the viewport, where stepping a candle at a time cannot keep up and the chart would sit still and then jump past its own viewport.
  • Following survives zooming. Only panning away from the live edge stops it, and panning back resumes it.
  • Time intervals only: a tick or range axis has no wall-clock edge to track, so the setting is ignored there.
Example
use('Depth')
chart.scroll = "continuous"   // what the Heatmap preset sets

chart.candle()

Registers the per-candle renderer. The chart invokes it for each visible candle on each frame with the aggregated candle view - no history loops, no per-tick cost. This is the ONLY footprint renderer: whatever the callback draws is what the chart shows, candle included.

  • draw.rectangle places rows by price and fractions of the candle column; draw.text anchors at the same column fraction.
  • A chart whose script registers no chart.candle renderer draws plain candlesticks. Registering one takes the candles over, so chart.candle(() => {}) is how a script says "no candles at all".
  • draw.candlestick() puts that default candle back inside your own layout - a filled body over the middle 70% of the column with a wick that thickens from 1px to 3px as the column widens. Pass from / width to confine it to part of the column (width on its own stays centered, so width: 1 spans it), style: 'hollow' to outline the body so rows show through it, and up / down / flat / wickPx for the rest. It is a no-op on a candle with no OHLC, so it needs no showCandle guard.
  • candle.widthPx is that column in pixels, so a fraction of 6 / candle.widthPx is a 6px mark at any zoom. Use it for anything that should keep a fixed thickness - a wick, a body - instead of growing with the column.
  • showRowText / showSummary are the chart's readability gates - honor them or numbers turn to mush when zoomed out.
  • size in draw.text is a base size at reference zoom; scale picks the axis that stretches it ("y" default, "x", "both", or "fixed" px).
  • Omitting size uses the chart's automatic sizing, which is capped at about 0.7x the row height - on short rows the text shrinks and leaves most of the column empty, which reads as a large gap between candles. Give text a size with scale: "x" to tie it to the column instead.
Example
chart.candle((candle, draw) => {
  for (const price of Object.keys(candle.rows)) {
    const { buys, sells } = candle.rows[price]
    draw.rectangle({
      price: Number(price),
      width: (buys + sells) / candle.maxLevelVolume,
      color: buys > sells ? "#3b82f6" : "#ef4444",
    })
    if (candle.showRowText) {
      draw.text({ price: Number(price), text: `${sells} x ${buys}` })
    }
  }
})

chart.frame()

Registers a whole-frame renderer, invoked on each frame with the full visible view - all visible candles, the price range, and the time range. Use it for overlays that span candles (session levels, value areas) computed against the visible window.

  • draw.line / draw.hline / draw.rectangle / draw.text draw in real prices and ms timestamps.
  • anchor: "candle" on draw.rectangle, draw.text or draw.line maps the times into the column holding them, skipping the gap between candles. A box around one footprint row is price1: p - c.increment / 2, price2: p + c.increment / 2, time1: c.time, time2: c.time + c.interval, anchor: "candle" and hugs the column exactly like a chart.candle row.
  • draw.rectangle with below: true paints behind everything already drawn. A full-height rectangle (view.minPrice to view.maxPrice) with below: true is background shading - session hours, for example - rather than a wash over the candles. The built-in RTH Session indicator is built on it.
Example
chart.frame((view, draw) => {
  let poc = 0
  let pocVolume = 0
  for (const candle of view.candles) {
    if (candle.maxLevelVolume > pocVolume) {
      pocVolume = candle.maxLevelVolume
      poc = candle.poc
    }
  }
  draw.hline({ price: poc, color: "#eab308", style: "dashed" })
})

chart.frame() and the tape

view.trades is every trade in the visible range, oldest first and UNFILTERED - filtering it is the trade-size control a renderer implements. view.minTradeSize is the instrument's own floor (ES 20, most instruments 1) for a renderer that has to work on any instrument.

  • Times are market time, the clock the replay runs on, and so are view.startTime / view.endTime. The chart places them: exactly where the axis is time, on the containing candle where it is not (a tick chart counts trades, not ms), so one renderer works on every interval.
  • The tape and the candles are one dataset: candles are folded from the tape, so tape.range covers whatever the chart has loaded. Use tape.coverage() if you need the exact spans.
  • draw.circle takes anchor: "time" (default) draws at the moment itself, "candle" at the center of the column holding it.
  • view.scroll is the chart's scroll mode, which is how a renderer picks between them: "bars" steps the live edge one candle at a time, so a trade belongs on its column; "continuous" advances with the clock, so it belongs at its own time. The shipped Big Trades indicator anchors on it, which is what lets one script sit on a footprint chart and a heatmap.
  • Scale radii by view.msPerPx (min(1, baseline / view.msPerPx)) so dots stay separate when zoomed out.
  • view.widthPx is the plot's width in pixels, for budgeting how many drawings fit on screen. Take it from here rather than dividing the market-time span by view.msPerPx: those times are market ms and view.msPerPx is the chart's own axis unit, which is not the same clock on a tick or range chart.
The trade dots
chart.frame((view, draw) => {
  const zoom = Math.min(1, 35 / view.msPerPx)

  for (const trade of view.trades) {
    if (trade.size < view.minTradeSize) continue

    const ratio = (Math.min(trade.size, 50) - 1) / 49
    const radius = (3 + 17 * ratio) * zoom
    if (radius < 1) continue

    draw.circle({
      time: trade.time,
      price: trade.price,
      radius,
      color: trade.side === 'buy' ? 'rgba(59, 130, 246, 0.88)' : 'rgba(239, 68, 68, 0.88)',
    })
  }
})