Scripting basics
This page is the reference for how an indicator runs: what gets called, what's available on ctx, and how drawing reaches the chart. Read it once and refer back. For workflow (the editor, library, versions, publishing), see Working with the editor.
The Indicator class
Every indicator is a Python class that subclasses Indicator and implements init and on_bar:
from indicator_api import Indicator
class MyIndicator(Indicator):
id = "my_indicator" # optional; auto-generated if omitted
name = "My Indicator" # optional; defaults to the class name
description = "What it does" # optional
params = { ... } # see Parameters page
def init(self, ctx):
... # create helpers, set up state
def on_bar(self, ctx):
... # read values, draw via ctx.plot.*
Class-level metadata (id, name, description) is optional. The runtime finds your indicator by scanning for the single Indicator subclass in your source.
⚠️ There is no
compute(). An earlier version of this API used acompute(self, bars)method that returned plot objects. That model is gone. Defining acompute()method on your subclass raises aTypeErrorwhen the class is loaded, shown in the editor on save. Theinit/on_barmodel below is the only way to write an indicator.
Optional advanced metadata
lookback(default500) — how many bars backctx.close[N]can reach. Raise it only if your logic needs deep history.chunk_lookback(default0) — an advanced tuning hint for unusual indicators. Leave it at the default.
init and on_bar
init(self, ctx)runs once, before the first bar. Create your calculation helpers here (ctx.rolling_mean(...),ctx.ema(...), …), store them onself, and initialise any state you'll carry forward.on_bar(self, ctx)runs once per bar, in chronological order. Read helper values and the current bar offctx, run your per-bar logic, and draw by callingctx.plot.*.on_complete(self, ctx)(optional) runs once after the last bar. Use it to finish anything still open — a session box or regime band you started drawing but haven't closed yet. Without it, an unfinished drawing is simply never shown.
State persists on self between on_bar calls — remembering the previous bar's value is just self._prev = float(ctx.close) at the end of on_bar. An indicator returns nothing; drawing is a side effect of calling ctx.plot.*.
ctx
ctx is the indicator's whole world. The same object is passed to init and on_bar; in init only ctx.params and the helper factories are usable (there is no "current bar" yet).
Price access
ctx.open, ctx.high, ctx.low, ctx.close, ctx.volume, plus the derived prices ctx.hl2, ctx.hlc3 (also available as ctx.tp, typical price), ctx.hlcc4, ctx.ohlc4. Each behaves like the current bar's value:
def on_bar(self, ctx):
rng = ctx.high - ctx.low # arithmetic works directly
if ctx.close > ctx.close[1]: # comparison works directly
...
c = float(ctx.close) # explicit scalar when you need one
- History:
ctx.close[1]is the previous bar's close,ctx.close[2]two bars back, etc. Use non-negative integers. You getNonepast the indicator'slookback, before the first bar, or when the value is missing — guard withif prev is not None:. A negative index is not a way to reach the future; it just returnsNone. float(...)forces a plain number.int(ctx.close)truncates the price (rarely intended).
Bar metadata
ctx.bar_index— 0-based position of the current bar.ctx.bar_time— the current bar's open time in epoch milliseconds. An hour check on this value selects bars by the hour they opened in.ctx.bar_time_ago(n)— the open time of the barnbars back (Noneif out of range). Use it to anchor a drawing to an earlier bar — for example a pivot that was only confirmed a few bars later.ctx.is_first_bar—Trueonly onbar_index == 0.
Parameters
ctx.params.<name> returns the user's current value for a declared parameter, e.g. ctx.params.period. See Parameters.
Calculation helpers
ctx exposes a library of calculation helpers — ctx.rolling_mean, ctx.ema, ctx.rsi, ctx.macd, ctx.bollinger_bands, ctx.atr, ctx.pivot_high, ctx.rolling_linreg, and more. Create them once in init, read them each bar via .value (or named outputs like .upper / .macd). They are the intended way to calculate — see Built-in Calculations for the full catalogue and exact signatures.
Drawing
ctx.plot.series, ctx.plot.fill, ctx.plot.shape, ctx.plot.rectangle, ctx.plot.polyline, ctx.plot.text, ctx.plot.bar_color, ctx.plot.background_band, ctx.plot.volume_profile. Call these from on_bar as you go. See Plots and drawings.
What you can import
The built-in helpers cover most needs and are the recommended path — they're fast, updated each bar, and can't read the future. Beyond them, the full numpy, pandas and scipy APIs are available, and so is the standard library except for the parts that do I/O, threading or dynamic code loading (math, statistics, collections, dataclasses, functools, itertools, typing, re, datetime, decimal, fractions, enum are all fine). Reach for raw numpy/pandas when a helper doesn't fit — but keep the calculation bar-by-bar, or the look-ahead scanner will stop you (see Errors).
Sandbox limits
| Limit | Value |
|---|---|
| Run time — live chart | 20 seconds |
| Run time — Validate (preview) | 30 seconds |
| Run time — backtest | 300 seconds |
| Memory per run | 1.5 GB |
| Source size | 64 KB |
| Network access | None |
| Disk access | None |
Forbidden modules (os, sys, subprocess, socket, urllib, importlib, shutil, threading, pickle, …) and builtins (exec, eval, compile, open, __import__, input) are blocked at save time. There is no override — the sandbox protects shared infrastructure. If you hit a timeout, prefer the built-in helpers over hand-rolled per-bar loops and reduce window sizes; see Errors.