ChartnautDocs

Series types

Every series an indicator draws is declared with an output.* call at the top of the script and fed from onBar with the matching ctx.* verb. This page lists every kind, its verb and its options.

Output kinds

Declare

Emit in onBar

Draws

output.line

ctx.plot(id, value, color?)

A line; color overrides it from this bar

output.histogram

ctx.bar(id, value, color?)

Bars from base (default 0); color overrides one bar

output.area

ctx.area(id, value, color?)

A line filled down to zero; color overrides line and fill from this bar

output.baseline

ctx.baseline(id, value)

A line coloured topColor above base and bottomColor below

output.fill

ctx.fill(id, top, bottom, color?)

A ribbon between two values; color overrides the fill from this bar

output.markers

ctx.mark(id, marker), ctx.label(id, label), ctx.note(id, opts)

Arrows, circles, squares and text at bars or prices

output.priceLine

ctx.hline(id, price)

A full-width horizontal line; last write per bar wins

output.hline

ctx.hline(id, price, { time1 })

A horizontal segment from time1, with a lifecycle

output.range

ctx.range(id, { time1, time2, price1, price2 })

A rectangle, with a lifecycle

output.trendline

ctx.trendline(id, { time1, price1, time2, price2 })

A diagonal segment, with a lifecycle

output.bandTemplate

ctx.plot with ids built from its patterns

One upper line, lower line and fill per row of an input.array

output.layer

the returned handle: upsert, patch, seal, remove

Rows the script paints itself. See Layers.

The verb must match the kind. ctx.plot("hist", v) on an output.histogram fails lint with outputs/kind-mismatch, and a histogram fed with ctx.plot draws nothing. An id you emit but never declared fails with outputs/unknown-id; one you declare but never emit is a warning (outputs/declared-unused).

Output ids are unique across all kinds: a line and a histogram cannot share an id. The ids are also the keys other scripts read when they declare this indicator as a dependency, so renaming one breaks its readers.

Options every series takes

Option

Effect

pane

Draw in a subpane from pane({ id }). See Panes & scales.

scale, priceFormat

Bind to a price scale from scale({ id }), or format the labels ("price", "volume", "percent")

color / colorFrom

A fixed colour, or one bound to an input.color

visibleFrom

Show or hide the series from an input.boolean

output.line also takes lineWidth / lineWidthFrom and lineStyle / lineStyleFrom. Line, histogram, area and baseline take labelFrom (an input.boolean for the price-scale label) and titleWhenLabeled. output.area takes lineWidth / lineWidthFrom. output.priceLine takes a static price, title, lineWidth and lineStyle. output.bandTemplate takes pane but not scale.

Bind styling to inputs with the *From options rather than reading colours in onBar. Read a colour in onBar only when it changes per bar: see Per-bar colour.

Lines, histogram and fill

meta({ shortName: "MACD", kind: "pane" });
dialog({ title: "MACD" });
const fast = input.number({ id: "fast", label: "Fast", default: 12, min: 1 });
const slow = input.number({ id: "slow", label: "Slow", default: 26, min: 1 });
const sig = input.number({ id: "signal", label: "Signal", default: 9, min: 1 });
warmup((w) => w.sum(w.ema(slow), w.ema(sig)));
const macdLine = output.line({ id: "macd", color: "#2196f3" });
output.line({ id: "signal", color: "#ff9800" });
output.histogram({ id: "hist" });
output.fill({ id: "spread", color: "rgba(33,150,243,0.12)" });
output.markers({ id: "crosses", on: macdLine });
layout(tab({ id: "main", title: "Main" }, section({ id: "p", title: "Parameters" }, fast, slow, sig)));

export function onBar(ctx) {
  const st = ctx.accum("st", () => ({ t: 0, done: null, tip: null }), (s) => {
    if (s.t !== ctx.time) { s.done = s.tip; s.t = ctx.time; }
    const p = s.done;
    const ema = (prev, n, x) => (prev == null ? x : prev + (2 / (n + 1)) * (x - prev));
    const f = ema(p && p.f, ctx.params.fast, ctx.close);
    const sl = ema(p && p.sl, ctx.params.slow, ctx.close);
    const macd = f - sl;
    const signal = ema(p && p.signal, ctx.params.signal, macd);
    s.tip = { f, sl, macd, signal, wasAbove: p ? p.macd > p.signal : null };
    return s;
  });
  const { macd, signal, wasAbove } = st.tip;
  const hist = macd - signal;

  ctx.plot("macd", macd);
  ctx.plot("signal", signal);
  ctx.bar("hist", hist, hist >= 0 ? "#26a69a" : "#ef5350");
  ctx.fill("spread", Math.max(macd, signal), Math.min(macd, signal));
  if (ctx.barConfirmed !== false && wasAbove === false && macd > signal) {
    ctx.mark("crosses", { shape: "arrowUp", position: "belowBar", text: "Cross" });
  }
}

ctx.fill takes both bounds in one call. When either bound is missing on a bar, pass NaN for it and the ribbon breaks there.

Breaking a line

A line, area, baseline or fill breaks at a bar where you emit NaN, null or undefined: the line stops at the bar before and starts again at the next value. For a fill, either bound being NaN, null or undefined breaks it. This is Pine's plot(na) with plot.style_linebr.

A bar where you do not call the verb at all is bridged: the line joins the values either side. So a line drawn only on some bars stays joined, and a gap needs an explicit NaN:

ctx.plot("orHigh", inSession ? st.high : NaN); // breaks between sessions

Per-bar colour

ctx.plot, ctx.area and ctx.fill take an optional last argument, a colour for this bar. A line takes it from that point forward; an area colours its line and fill with it; a fill colours the ribbon. A colour that is missing or not valid falls back to the series colour. ctx.baseline has no per-bar colour.

A per-bar colour wins over the colour the trader sets in the settings dialog. Expose the colours you pick from as input.color settings and read them from ctx.params, so the trader can still change them:

const up = input.color({ id: "up", label: "Up", default: "#26a69a" });
const down = input.color({ id: "down", label: "Down", default: "#ef5350" });
output.line({ id: "trend" });

export function onBar(ctx) {
  // st.isUp and st.level come from your Supertrend state
  ctx.plot("trend", st.level, st.isUp ? ctx.params.up : ctx.params.down);
}

Markers

One output.markers declaration backs ctx.mark, ctx.label and ctx.note; there is no separate label output. Without on, markers attach to the candles; on: lineHandle rides a declared output.line.

A marker's position decides whether it takes a price:

position

Anchored to

price

"aboveBar" (default), "belowBar", "inBar"

The candle

Not allowed

"atPriceTop", "atPriceMiddle", "atPriceBottom"

An exact price

Required

ctx.mark("signals", { shape: "arrowDown", position: "atPriceTop", price: ctx.high, text: "SH" });

shape is "circle" (default), "square", "arrowUp", "arrowDown" or "none". time defaults to ctx.time; pass an earlier bar's time to mark a swing after it is confirmed. Markers also take color, size (0 to 4), id, labelMode, labelColor, labelPosition and labelDedupe. ctx.label(id, { price, text, color?, background?, size? }) puts text at a price on the current bar, and ctx.note(id, { price, text }) is a text-only marker.

Several markers per output per bar are allowed. A re-run of the forming bar replaces that bar's markers instead of adding more.

priceLine or hline

Both are driven by ctx.hline; the declaration decides which one you get.

output.priceLine({ id: "prevClose", color: "#9e9e9e", title: "Prev close" });
output.hline({ id: "dayOpen", color: "#ffb300", name: "Day open" });

// in onBar:
ctx.hline("prevClose", prevClose);                    // full-width, moves as you re-emit
ctx.hline("dayOpen", ctx.open, { time1: day.start }); // segment from the day's start
  • priceLine: no time1. One chart-wide line per id; stop emitting it and it disappears.

  • hline: time1 is required on every call. Without it the level draws nothing, and lint refuses the call (outputs/hline-missing-time1).

output.hline, output.range and output.trendline share a lifecycle: mode ("replace", "all", "invalidateOnCustomFunction", "invalidateOnEvent"), invalidateWhen, expires, labels and zIndex. The modes, presets and the required expires are on Drawing on the chart.

bandTemplate

output.bandTemplate turns each row of an input.array into series. Emit each row's values with ctx.plot, using the template's patterns with {id} replaced by the row's id:

output.bandTemplate({
  from: bands,                          // an input.array handle
  upperIdPattern: "band_up_{id}",
  lowerIdPattern: "band_dn_{id}",
  fillIdPattern: "band_fill_{id}",      // fills between the two, no emit needed
  itemStyle: { colorField: "color", fillEnabledField: "fillEnabled", fillColorField: "fillColor" },
  titleTemplate: "Band {deviation}",
});

// in onBar, for each enabled row:
ctx.plot("band_up_" + row["id"], upper);
ctx.plot("band_dn_" + row["id"], lower);

itemIdField and itemEnabledField default to "id" and "enabled". VWAP is a full example.