RoboQuant
RoboQuant engine

Roboquant Indicators (Python — frozen for new indicators, 2026-09-21)

This SDK is frozen for new indicators. New indicators are authored as .rq impl BarIndicator programs on the compiled Rust strategy SDK — see rq-engine/docs/strategy-sdk.md §1.7 (indicators are strategies without orders; same plot/draw/built-in-indicator calls, plus an on_tick/ctx.book() opt-in for order-flow). This page stays accurate for existing Python indicators, which you can still edit in place, and for the one new-work exception: an indicator needing the per-bar trade_flow schema below has no compiled-SDK equivalent yet and stays Python. Details: docs/CUSTOM_INDICATORS_CONTRACT.md §12.8.

Custom indicators add sandboxed, Python-computed overlays to RoboCharts—session boxes, custom oscillators, and liquidity tools—without changing your compiled trading strategy.

Indicators are separate entities from strategies: they do not submit orders. Use the built-in Ind runtime handles described in the strategy API reference when an indicator is part of trading logic; use this custom-indicator API when the output is only for a chart.

Minimal example

from rq_indicators import Indicator, param, ta, pl

@param("period", int, default=14, min=2, max=200, label="Period")
class MyRSI(Indicator):
    def compute(self, bars: pl.DataFrame, plot, period):
        close = bars["close"].to_numpy()
        rsi = ta.RSI(close, timeperiod=period)
        times = bars["time"].to_list()

        plot.line(times, rsi, name="RSI", color="#A78BFA", pane="oscillator")
        plot.hline(70, color="#EF4444", dashed=True, pane="oscillator")
        plot.hline(30, color="#10B981", dashed=True, pane="oscillator")

Input data shape

bars is a polars DataFrame with columns:

ColumnTypeMeaning
timei64Unix timestamp (seconds)
open, high, low, close, volumefloatOHLCV

The chart passes one vectorized window per compute call.

Extra data and range limits (@needs)

Indicators can request additional data alongside bars with the @needs(...) decorator — for example @needs("trades") for tick-level trades or @needs("trade_flow") for per-bar aggregated order flow.

Tick-level trades data is hard-capped to protect the compute service: at most a 45-day window and 5 million ticks per request. Beyond either cap the indicator returns a structured range_too_wide error telling you to zoom in to a narrower window — the chart shows the message inline and does not retry. For wide windows, use @needs("trade_flow") instead: it aggregates trades per bar server-side, so it scales to any chart range.

Parameters

Declare tunables with @param on the class:

@param("period", int, default=14, min=2, max=200, label="Period")
@param("overbought", float, default=70.0, min=50, max=95, label="Overbought")
class MyIndicator(Indicator):
    ...

The dashboard renders controls from this schema. Values are passed into compute(self, bars, plot, **params).

Plot API

Plots are declarative — you describe series; the chart renderer draws them.

plot.line(time, values, name, color, pane="price", axis_label_visible=False)
plot.histogram(time, values, name, color, baseline=0, pane="oscillator", axis_label_visible=False)
plot.markers(time, prices, name, color, shape="circle", pane="price", axis_label_visible=False)
plot.hline(value, name, color, dashed=False, pane="oscillator", axis_label_visible=False)
plot.band(time, upper, lower, name, color, opacity=0.15, pane="price", axis_label_visible=False)

axis_label_visible=True shows this plot’s name on the chart’s right-hand price scale (default hidden). Supported on line, histogram, hline, and band — not box. On markers the same kwarg has a different effect (markers never show a price-scale pill): it draws the plot name as text next to each marker; markers draw no text by default.

Supported shapes for markers: circle, triangle-up, triangle-down, cross.

Available imports

SymbolPurpose
IndicatorBase class — override compute()
paramParameter decorator
taTA-Lib on numpy arrays
np, plnumpy / polars
numba@numba.njit for hot loops

Network, filesystem, and service imports are blocked in the indicator subprocess (security).

Dashboard workflow

  1. Indicators section → create indicator → Code tab (AI or manual)
  2. Set show_on_chart=true to list it in RoboCharts My Indicators
  3. Open a chart → add your indicator from the dropdown
  4. Parameters adjust live; results are cached per symbol/timeframe/window

Compute model

  • Vectorized: the full loaded chart history in one compute() call
  • On new bars, the tail window is recomputed (not an incremental state machine in v1)
  • Errors return structured messages: syntax, runtime, timeout, forbidden import
  • Python chart workers have a 12-second compute limit by default, separate from loading market data. A compute timeout requires fewer bars or faster calculations; waiting for data or retrying the same work does not extend it.
  • Calculate rolling statistics once per window and reuse them. Recomputing percentiles for overlapping historical windows inside every bar's scan can exceed the limit even when the indicator only needs OHLCV data.

The corrected Auto Range Detector is a replacement source file for the existing Python indicator reported in #1553. It reuses rolling percentile bands without changing the parameters or range-drawing logic. Upload this file to replace the original indicator source; saved indicators are not updated automatically.

Strategy vs indicator

StrategyIndicator
TradesYes (ctx.buy / ctx.sell)No
Runs inCompiled backtest/live runtimeChart compute subprocess
OutputOrders, equity, logs, drawingsPlot specs (JSON)
Source.rq → .rqcPython compute() entry