Decorators

Top-level annotations that turn a Python file into an indicator, strategy, script, or day-start hoist.

@indicator

@indicator(name, overlay?, description?, data_mode?, timeframe?)

Mark a Python class as a chart indicator.

The @indicator decorator transforms a Python class into a chart indicator that receives price data and draws visual output. The class must implement a calculate(self, df) method that receives the chart DataFrame.

Call plot(), hline(), or fill() inside calculate() to draw on the chart. Access price data via the global series: close, open, high, low, volume.

Python indicators run on OHLC bars. For TBBO tick-level indicators, use a Rust .rs file with the #[indicator] macro instead.

Parameters

  • name (str, default required): Display name shown in the indicator panel
  • overlay (bool, default False): If True, draws on price chart. If False, separate pane
  • description (str, default None): Optional tooltip description
  • data_mode (str, default 'ohlc'): Always 'ohlc' for Python indicators. Use Rust for tick.
  • timeframe (Timeframe, default None): Target timeframe for the indicator

Returns

None. Use plot() inside calculate() to draw output.

Example

from quant_charts import indicator, input, plot, close

@indicator("SMA", overlay=True)
class SMA:
    period = input.int(20, "Period", min=1, max=500)
    color = input.color("#2962FF", "Color")

    def calculate(self, df):
        result = close.rolling(self.period).mean()
        plot(result, f"SMA({self.period})", color=self.color)

Notes

  • calculate() is called every time the chart data updates.
  • Use input.* class variables to create user-configurable parameters.
  • Multiple plot() calls create multiple series on the same indicator.

@strategy

@strategy(name, overlay?, timeframe?, description?, data_mode?, uses_sltp?, emit_sltp?, required_columns?)

Mark a Python class as a backtestable trading strategy.

The @strategy decorator creates a trading strategy that returns entry/exit signals for backtesting. The class must implement calculate(self, df) which returns a dict of boolean signal Series.

The backtester uses these signals to simulate trades and compute equity curves, win rates, drawdowns, and other performance metrics.

Python strategies run on OHLC bars only. For tick-level TBBO strategies use a Rust .rs file.

Parameters

  • name (str, default required): Display name shown in the strategy panel
  • overlay (bool, default True): If True, draws on the price chart
  • timeframe (Timeframe, default None): Default execution timeframe
  • description (str, default None): Optional tooltip description
  • data_mode (str, default 'ohlc'): Always 'ohlc' for Python strategies
  • uses_sltp (bool, default False): If True, the strategy returns sl_long/tp_long/sl_short/tp_short arrays
  • emit_sltp (str, default 'entry_only'): 'entry_only' (set once at entry) or 'per_tick' (re-evaluated each bar)
  • required_columns (list[str], default auto): Columns the strategy reads. SHM only ships these to workers.

Returns

A dict with keys: "entry_long", "exit_long", "entry_short", "exit_short". With uses_sltp=True, also "sl_long", "tp_long", "sl_short", "tp_short".

Example

from quant_charts import strategy, input, indicators, cross_above, cross_below, Timeframe

@strategy("MA Cross", overlay=True, timeframe=Timeframe.M1)
class MACross:
    fast = input.int(10, "Fast", min=1, max=100)
    slow = input.int(20, "Slow", min=2, max=200)

    def calculate(self, df):
        fast_ma = indicators.sma(period=self.fast, color="#26A69A")
        slow_ma = indicators.sma(period=self.slow, color="#EF5350")

        return {
            'entry_long': cross_above(fast_ma, slow_ma),
            'exit_long': cross_below(fast_ma, slow_ma),
            'entry_short': cross_below(fast_ma, slow_ma),
            'exit_short': cross_above(fast_ma, slow_ma),
        }

Notes

  • Use indicators.sma() / indicators.ema() to add visual overlays alongside your strategy.
  • Signal Series must be boolean. Use cross_above() / cross_below() for crossovers.
  • Conflicting signals on the same bar: exit is resolved before entry.
  • Hoist param-independent work into a @day_start method for fast optimization sweeps.

@day_start

@day_start(method)

Mark a method to run once per day per worker, before the parameter sweep.

Hoists parameter-independent computation out of the per-combo calculate() body. The decorated method runs once per trading day per worker process; results stored on self are visible inside calculate() for every parameter combination.

Use this for any heavy work that does not depend on swept params: full-series ta calls with literal periods, day-level summary stats, regime detection, cached indicator outputs.

Parameters

  • method (callable, default required): Method on the strategy/indicator class with signature (self, df) -> None

Returns

None. Store hoisted arrays as self.<name> for use inside calculate().

Example

from quant_charts import strategy, day_start, input, ta, close, cross_above, cross_below

@strategy("Hoisted EMA Cross")
class HoistedCross:
    fast = input.int(10, "Fast", min=2, max=50)
    slow = input.int(30, "Slow", min=5, max=200)

    @day_start
    def prep(self, df):
        # hoisted: param-independent, runs once per day not once per combo
        self.atr = ta.atr(df['high'], df['low'], df['close'], 14)

    def calculate(self, df):
        fast_ma = close.rolling(self.fast).mean()
        slow_ma = close.rolling(self.slow).mean()
        return {
            'entry_long': cross_above(fast_ma, slow_ma),
            'exit_long':  cross_below(fast_ma, slow_ma),
            'entry_short': cross_below(fast_ma, slow_ma),
            'exit_short':  cross_above(fast_ma, slow_ma),
        }

Notes

  • Critical for fast optimization sweeps: a 100-combo sweep with hoisted ATR runs the ATR once instead of 100 times.
  • Anything assigned to self.* inside @day_start is visible in calculate().
  • Calls inside @day_start should use literal periods, not swept parameters.

@script

@script(name, description?, version?)

Mark a Python class as a utility script for data analysis. Used in .py files only (not notebooks).

The @script decorator creates a standalone script that runs once when executed. Unlike indicators and strategies, scripts do not draw on the chart. Instead they process data, export files, or produce analysis output.

Note: Scripts are standalone .py files executed from the file explorer. For notebook-based analysis, use regular Python cells instead.

Scripts have access to script_helpers for exporting data, reading chart state, and accessing indicator values.

Parameters

  • name (str, default required): Display name for the script
  • description (str, default None): Optional description
  • version (str, default None): Optional version string

Returns

Optional dict, displayed in the output panel.

Example

from quant_charts import script, input
from quant_charts.script_helpers import get_chart_data, export_csv

@script("Data Export")
class DataExporter:
    filename = input.string("export.csv", "Filename")

    def execute(self, df):
        df.to_csv(self.filename)
        return {"status": "success", "rows": len(df)}

Output

>>> Output
{'status': 'success', 'rows': 14523}

Notes

  • Scripts implement execute(self, df) instead of calculate(self, df).
  • Import from quant_charts.script_helpers for data access and export.