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 a compute(self, bars) method that returned plot objects. That model is gone. Defining a compute() method on your subclass raises a TypeError when the class is loaded, shown in the editor on save. The init / on_bar model below is the only way to write an indicator.

Optional advanced metadata

  • lookback (default 500) — how many bars back ctx.close[N] can reach. Raise it only if your logic needs deep history.
  • chunk_lookback (default 0) — 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 on self, 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 off ctx, run your per-bar logic, and draw by calling ctx.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 get None past the indicator's lookback, before the first bar, or when the value is missing — guard with if prev is not None:. A negative index is not a way to reach the future; it just returns None.
  • 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 bar n bars back (None if 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 — True only on bar_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.

Documentation