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 want | Change this |
|---|---|
| Zoom in / bigger candles | raise chart.columnWidth |
| Zoom out / fit more candles | lower chart.columnWidth |
| Less space between candles | check 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 column | draw.text size with scale: "x" ties them to the column instead of the row height |
| Thinner or wider candle body | the body width in your chart.candle renderer - size it off candle.widthPx to work at any zoom |
| Merge price rows together | raise chart.condenseLevel |
| Show the order book behind the candles | use('Depth') (time intervals only) |
| Stroke the bid/offer edge over it | chart.bbo = true |
| Chart opens too far zoomed out | chart.initialWindow = "5m" (time intervals only) |
| Chart sits still then jumps when zoomed in | chart.scroll = "continuous" |
| Show delta x volume instead of sell x buy | chart.numberDisplay = "deltaVol" |
| Run an indicator on this chart | use("Name") |
| Change colors, numbers, bars - anything drawn | the 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.
use(name)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.initialWindoware 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.
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.
chart.columnWidth = 118 // px per candle column
// ...so these fractions are 60px and 43px wide
const PROFILE_W = 0.55
const DELTA_W = 0.39chart.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.columnWidthshould 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.columnWidthby 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.
chart.columnWidth = 118
chart.columnGap = 8 // pinned, not the scaling defaultchart.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 fromchart.columnWidthif your script declares one. Setting both, this wins.
// 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.
chart.yZoom = 2 // rows twice as tall as the auto fitchart.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.
truefor full strength, or0..1to 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:
bidandask(hex orrgba()),max, andcurve, withopacitystill fading the layer. A level's brightness is its resting size againstmax, painted in the colour - whose own alpha is what a level ATmaxreaches. So a brighter big book is a higher alpha on the colour, and the size that counts as big ismax(left out, each instrument keeps its own: ES 150, NQ 50, everything else 50). curvebends the ramp between them.1is 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.condenseLevelmerges depth the same way it merges volume. - Turning it on also unlocks the zoom: the normal limit of one candle per
chart.columnWidthhas 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.
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.
truefor full strength, or0..1for 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.
use('Depth')
chart.bbo = 0.9 // what the book-style presets setchart.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.
use('Depth')
chart.scroll = "continuous" // what the Heatmap preset setschart.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.rectangleplaces rows by price and fractions of the candle column;draw.textanchors at the same column fraction.- A chart whose script registers no
chart.candlerenderer draws plain candlesticks. Registering one takes the candles over, sochart.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. Passfrom/widthto confine it to part of the column (widthon its own stays centered, sowidth: 1spans it),style: 'hollow'to outline the body so rows show through it, andup/down/flat/wickPxfor the rest. It is a no-op on a candle with no OHLC, so it needs noshowCandleguard.candle.widthPxis that column in pixels, so a fraction of6 / candle.widthPxis 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/showSummaryare the chart's readability gates - honor them or numbers turn to mush when zoomed out.sizeindraw.textis a base size at reference zoom;scalepicks the axis that stretches it ("y"default,"x","both", or"fixed"px).- Omitting
sizeuses 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 withscale: "x"to tie it to the column instead.
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.textdraw in real prices and ms timestamps.anchor: "candle"ondraw.rectangle,draw.textordraw.linemaps the times into the column holding them, skipping the gap between candles. A box around one footprint row isprice1: 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 achart.candlerow.draw.rectanglewithbelow: truepaints behind everything already drawn. A full-height rectangle (view.minPricetoview.maxPrice) withbelow: trueis background shading - session hours, for example - rather than a wash over the candles. The built-in RTH Session indicator is built on it.
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.rangecovers whatever the chart has loaded. Usetape.coverage()if you need the exact spans. draw.circletakesanchor:"time"(default) draws at the moment itself,"candle"at the center of the column holding it.view.scrollis 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.widthPxis 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 byview.msPerPx: those times are market ms andview.msPerPxis the chart's own axis unit, which is not the same clock on a tick or range chart.
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)',
})
}
})