Common patterns
Short, copy-pasteable recipes for the things you'll do most. Each one is a complete, working indicator — paste it into the editor, hit Validate, and tweak from there.
If you've read Getting Started and Scripting basics, you have the prerequisites. Every recipe follows the same shape: create helpers and state in init, read and draw in on_bar.
Reference the previous bar
ctx.close[1] is the previous bar's close — one bar back, never the future. It returns None when there is no prior bar, so guard it.
from indicator_api import Indicator
class PrevClose(Indicator):
def on_bar(self, ctx):
ctx.plot.series("prev", value=ctx.close[1],
color="#9e9e9e", label="Previous close")
Plots the previous bar's close (a gap on bar 0, where there is none).
Highest high / lowest low over N bars
ctx.rolling_max / ctx.rolling_min give you a Donchian-style channel in two helpers.
from indicator_api import Indicator
class Donchian(Indicator):
params = {"period": {"type": "int", "default": 20, "min": 2, "max": 200}}
def init(self, ctx):
self.hi = ctx.rolling_max(source=ctx.high, period=ctx.params.period)
self.lo = ctx.rolling_min(source=ctx.low, period=ctx.params.period)
def on_bar(self, ctx):
ctx.plot.series("upper", value=self.hi.value, color="#22c55e",
label=f"High({ctx.params.period})")
ctx.plot.series("lower", value=self.lo.value, color="#ef4444",
label=f"Low({ctx.params.period})")
Plots the rolling highest high and lowest low — a basic Donchian channel.
💡 Tip: Windowed helpers return
Noneuntil their window is full — pass that straight toctx.plot.seriesand it renders as a gap at the left edge. That's expected warm-up, not a bug. Most recipes below have it.
Crossover detection
Crossover is stateful here: remember the previous bar's values on self, compare against them next bar.
from indicator_api import Indicator
class EmaCross(Indicator):
def init(self, ctx):
self.fast = ctx.ema(source=ctx.close, period=20)
self.slow = ctx.ema(source=ctx.close, period=50)
self._pf = None
self._ps = None
def on_bar(self, ctx):
f, s = self.fast.value, self.slow.value
if f is None or s is None:
ctx.plot.series("fast", value=None)
ctx.plot.series("slow", value=None)
return
ctx.plot.series("fast", value=f, color="#2196f3", label="Fast")
ctx.plot.series("slow", value=s, color="#9e9e9e", label="Slow")
if self._pf is not None:
if self._pf <= self._ps and f > s:
ctx.plot.shape("x", price=float(ctx.low),
shape="arrow_up", color="#22c55e",
position="below_bar")
elif self._pf >= self._ps and f < s:
ctx.plot.shape("x", price=float(ctx.high),
shape="arrow_down", color="#ef4444",
position="above_bar")
self._pf, self._ps = f, s
Plots two EMAs and drops a green arrow on cross-up, red on cross-down.
Color a histogram by sign
Pass color= on every series call; when it changes from bar to bar, the line is drawn multi-coloured.
from indicator_api import Indicator
class MacdHistogram(Indicator):
def init(self, ctx):
self.macd = ctx.macd(source=ctx.close, fast=12, slow=26, signal=9)
def on_bar(self, ctx):
h = self.macd.histogram
if h is None:
ctx.plot.series("hist", value=None, pane="sub")
return
ctx.plot.series("hist", value=h, pane="sub", style="histogram",
color="#22c55e" if h > 0 else "#ef4444",
label="MACD hist")
Plots the MACD histogram in a sub-pane, green above zero, red below.
Horizontal line at yesterday's close
Track the close at the end of every bar; when a new daily session starts, the last bar's close is yesterday's close. Plot it as a flat series.
from indicator_api import Indicator
class YdayClose(Indicator):
def init(self, ctx):
self.sess = ctx.session_window(anchor="daily")
self._yday = None
self._last_close = None
def on_bar(self, ctx):
if self.sess.just_started and self._last_close is not None:
self._yday = self._last_close
ctx.plot.series("yday", value=self._yday, color="#e58b39",
line_width=1.0, label="Yesterday's close")
self._last_close = float(ctx.close)
Draws a horizontal line at yesterday's daily close, stepping each new day.
Mark every new 20-bar high
Compare the current high to the rolling max ending one bar back.
from indicator_api import Indicator
class NewHighs(Indicator):
params = {"period": {"type": "int", "default": 20, "min": 5, "max": 200}}
def init(self, ctx):
self.prior_max = ctx.rolling_max(source=ctx.high,
period=ctx.params.period)
self._prev_max = None
def on_bar(self, ctx):
if self._prev_max is not None and ctx.high > self._prev_max:
ctx.plot.shape("new_high", price=float(ctx.high),
shape="arrow_up", color="#22c55e",
position="above_bar")
self._prev_max = self.prior_max.value
Drops a green arrow above every bar that prints a new N-bar high. _prev_max holds the rolling max as of the previous bar, so the current bar is compared against history only.
Use the built-in RSI
The helper library covers the classics — reach for ctx.rsi / ctx.macd / ctx.atr before any hand-rolled equivalent.
from indicator_api import Indicator
class Rsi(Indicator):
params = {"period": {"type": "int", "default": 14, "min": 2, "max": 100}}
def init(self, ctx):
self.rsi = ctx.rsi(source=ctx.close, period=ctx.params.period)
def on_bar(self, ctx):
ctx.plot.series("rsi", value=self.rsi.value, pane="sub",
color="#9c27b0", label=f"RSI({ctx.params.period})")
ctx.plot.rectangle("band", price_top=70, price_bottom=30,
fill_color="#9c27b0", fill_opacity=0.06, pane="sub")
Plots RSI in a sub-pane with a shaded 30–70 band.
Show a value as text on the chart
ctx.plot.text pins a string to a (time, price). Called every bar with the same name it's last-write-wins, so it ends up on the latest bar with the latest value.
from indicator_api import Indicator
class AtrLabel(Indicator):
params = {"period": {"type": "int", "default": 14, "min": 2, "max": 100}}
def init(self, ctx):
self.atr = ctx.atr(period=ctx.params.period)
def on_bar(self, ctx):
a = self.atr.value
if a is None:
return
ctx.plot.text("atr", time=ctx.bar_time, price=float(ctx.high),
text=f"ATR({ctx.params.period}) = {a:.5f}",
anchor="bottom-left", color="#ffffff",
background="#0d1117", size=12)
Pins the current ATR value as a small label above the most recent bar.
Where to go next
- Reference: Built-in Calculations — every helper, with output names.
- Reference: Plots and drawings — every
ctx.plot.*method. - Reference: Errors — error categories and why custom indicators are backtest-safe by design.
- Tutorial: Your first indicator — a longer walk-through, end to end.