First indicator guide
This walkthrough builds an overlay indicator by hand: an EMA with a length and a colour setting, and an arrow on every bar that closes up through it.
When you finish, you will know how meta, inputs, warmup, output.* and onBar fit together. To have the agent write it for you instead, follow Build your own indicator.

Steps
Open Tools → Indicators and click New.
Click Build it yourself under the suggestions and give the indicator a Name.
In the Code tab, replace the script with the one below and click Save.
Open the Chart tab. Step through a few days with the arrows beside the date.
On a chart, the indicator's settings dialog shows Length and Color in a Parameters section, the layout the script declares.
A save that does not compile is still kept. The chart goes on showing the last version that did, and Errors shows what stopped it. Build a custom indicator covers the builder itself.
Full script
meta({ shortName: "EMA Cross", kind: "overlay" });
dialog({ title: "EMA Cross" });
const length = input.number({
id: "length",
label: "Length",
default: 20,
min: 1,
});
const color = input.color({
id: "color",
label: "Color",
default: "#2196f3",
});
warmup((w) => w.ema(length));
const emaLine = output.line({ id: "ema", color: "#2196f3", colorFrom: color });
output.markers({ id: "cross_up", on: emaLine, color: "#26a69a" });
layout(
tab({ id: "main", title: "Main" },
section({ id: "params", title: "Parameters" }, length, color))
);
function onBar(ctx) {
const k = 2 / (ctx.params.length + 1);
const st = ctx.accum(
"st",
() => ({ time: null, ema: null, close: null, prevEma: null, prevClose: null }),
(s) => {
if (s.time !== ctx.time) {
// A new bar: the values held so far belong to the previous bar.
s.prevEma = s.ema;
s.prevClose = s.close;
s.time = ctx.time;
}
s.ema = s.prevEma == null ? ctx.close : s.prevEma + k * (ctx.close - s.prevEma);
s.close = ctx.close;
return s;
}
);
ctx.plot("ema", st.ema);
if (st.prevEma != null && st.prevClose <= st.prevEma && ctx.close > st.ema) {
ctx.mark("cross_up", { shape: "arrowUp", position: "belowBar", text: "Cross" });
}
}What each part does
Piece | Job |
|---|---|
| Draws on the price pane. |
| Settings the trader can change. Read them as |
| How many bars before the visible range the EMA needs to settle: |
| The series the chart may draw. |
| The settings dialog's tabs and sections |
| Memory across bars: the EMA and the previous bar's close and EMA |
| Values for the declared outputs, bar by bar |
onBar runs again on every tick of the bar that is still forming, and ctx.accum keeps what the last run wrote. That is why the script stores the previous bar's values and recomputes the current bar from them, instead of stepping the EMA forward on every call.
Common mistakes
Plotting an id you never declared with
output.*.Keeping the EMA in a module-level
letinstead ofctx.accum.Leaving out
warmup. A script that keeps state fails lint withwindow/warmup-required.Passing
priceto a marker withposition: "aboveBar","belowBar"or"inBar". Only"atPriceTop","atPriceMiddle"and"atPriceBottom"take a price, and they require one.Setting
kind: "pane"when you meant an overlay on price.
Settings inputs and dialog and Drawing on the chart cover the rest of what an indicator can declare and draw.
