Drawing on the chart
Everything a script draws, whether a line, a marker, a level or a zone, follows one pattern: declare the output at the top of the file, then draw it from onBar with the matching ctx.* call and the same id.
Declare
Script | How you declare |
|---|---|
Indicator |
|
Definition |
|
Study | No declaration. Annotation helpers take their own ids: see the table at the end |
The older definition shortcuts output.line / output.mark / output.hline still work. Use outputs.declare in new definitions.
Declare kind to ctx call
Kind | Draw with |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A bar where you skip the call is bridged: the line joins the values either side. Emit NaN, null or undefined to break it, as Pine's plot.style_linebr does. A per-bar colour wins over the colour set in the settings dialog, so pick it from input.color settings. See Series types.
A call whose id is not declared fails lint with outputs/unknown-id, and a call that does not match the declared kind with outputs/kind-mismatch. A declared output that is never drawn warns with outputs/declared-unused.
An hline output draws only emissions that carry time1. Without it the level is invisible, and a definition fails lint with outputs/hline-missing-time1. Use time1: ctx.time for "starts at this bar", or the time of the swing, open or breakout the level belongs to. For a chart-wide line on an indicator, declare output.priceLine instead.
Example (definition)
outputs.declare({ id: "close_line", kind: "line", name: "Close", color: "#64b5f6" });
outputs.declare({ id: "signal", kind: "mark", name: "Signal", labelMode: "onHover" });
outputs.declare({ id: "zone", kind: "range", name: "Zone" });
export function onBar(ctx) {
ctx.plot("close_line", ctx.close);
if (ctx.close > ctx.open) {
ctx.mark("signal", { price: ctx.high, text: "Up", position: "atPriceTop" });
}
ctx.range("zone", {
time1: ctx.time,
time2: ctx.time,
price1: ctx.low,
price2: ctx.high,
});
}Markers
ctx.mark always takes two arguments, the output id and an object. ctx.mark fills in time (this bar), shape ("circle") and position ("aboveBar") when you leave them out. Shapes are "circle", "square", "arrowUp", "arrowDown" and "none". Positions "aboveBar", "belowBar" and "inBar" anchor to the candle and ignore price. Positions "atPriceTop", "atPriceMiddle" and "atPriceBottom" anchor to an exact price and need price. Pass time to place a marker on an earlier bar, such as a swing you confirmed a few bars later.
Lifecycle: how levels, zones and trendlines end
hline, range and trendline outputs take a mode on the declaration:
Mode | Behaviour |
|---|---|
| One live drawing per id. Each emit moves or grows it |
| Each distinct |
| Like |
| Like |
Under "all", give every emit a fresh time1. A literal time1 collapses every emit onto one drawing.
The two invalidating modes also need expires: how long a drawing stays live if nothing takes it out. It is a duration, "30m", "12h", "5d", "4w", or "max" for no expiry. Time rather than bars, so it means the same on every chart timeframe and on the server. Missing expires fails lint with output/expires-required; expires on a mode that never closes warns with output/expires-without-mode.
output.hline({
id: "pdh",
mode: "invalidateOnCustomFunction",
invalidateWhen: "wickAbove",
expires: "5d",
labelMode: "always",
});On each bar the engine checks events first, then the predicate, then expiry. The bar a drawing is born on never closes it. A drawing that expires closes with closeReason: "expired".
Under "invalidateOnCustomFunction", a level whose time1 is earlier than the bar that emits it, such as a swing high confirmed three bars later, is checked against the bars in between as well. If price already took it out, it is born closed.
expires is available on indicator output.hline, output.range and output.trendline. Definition outputs.declare does not take it yet.
Presets
Output | Presets for |
|---|---|
|
|
|
|
| None. A function only |
There is no default predicate. "invalidateOnCustomFunction" without invalidateWhen fails lint with outputs/invalidate-needs-policy, and does nothing at runtime.
Custom predicates
A function on the declaration receives { bar, entry, history }. bar is the bar being checked (time, open, high, low, close). entry is the drawing: top / bottom for a range, price for an hline, the endpoints for a trendline, plus id, time1, emittedAt, ageBars and your meta. history is the bars before bar, oldest first; on the chart it holds at most the last 200. Return true to close. Keep it pure: no side effects, no async. Destructure the argument as ({ bar, entry, history }): a predicate that reads ctx.low is reading the wrong shape and fails lint with output/invalidate-predicate-shape, and naming the argument ctx warns with output/invalidate-predicate-ctx-name.
output.range({
id: "fvg",
mode: "invalidateOnCustomFunction",
invalidateWhen: ({ bar, entry }) =>
entry.meta.dir === "bull" ? bar.low <= entry.bottom : bar.high >= entry.top,
expires: "10d",
});
// in onBar, once you have found a bullish gap { time, low, high }
ctx.range("fvg", {
time1: gap.time,
time2: ctx.time,
price1: gap.low,
price2: gap.high,
meta: { dir: "bull" },
});Put the rule on the declaration and pass per-drawing numbers through meta. A per-emit invalidateWhen accepts a preset name. A function on an emit fails lint with output/invalidate-per-emit-fn: the engine cannot re-check it after the stretch of history that created it, on the chart or on the server. meta is plain JSON.
Scripts that read these drawings from another script use ctx.indicators.<as>.lifecycle(): see Declaring indicator dependencies.
Labels
Option | Values |
|---|---|
|
|
|
|
|
|
Call | Lint |
|---|---|
| warns |
| warns |
| fails |
| fails |
Study annotations
Studies draw with their own helpers, which take times explicitly:
Call | Signature |
|---|---|
plot |
|
mark |
|
hline |
|
range |
|
note |
|
Empty values
Skip the call when a value is not ready. Never draw NaN or undefined.
if (ema == null) return;
ctx.plot("ema", ema);Series options, such as a histogram's base and fills between two lines, and the indicator-only bandTemplate are on Series types.
