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?, visible_on?, warmup_days?, continuous?)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.
When you declare warmup_days the DataFrame is self-describing: a string session column (the ET trading-day key) and a tz-aware ET DatetimeIndex. Cross-day reads need NO Quant Charts API, just pandas: previous-session close df[df.session == df.session.unique()[-2]].close.iloc[-1], a 3-session VP POC, or a 3000-bar EMA over the warm buffer. Whether per-day incremental panning is safe (causal) vs a full-span recompute (span-aggregate like Volume Profile) is auto-detected; there is no incremental argument.
Python indicators run on OHLC bars. For TBBO tick-level indicators, use a Rust .rs file with the #[indicator] macro instead.
Parameters
name(str, defaultrequired): Display name shown in the indicator paneloverlay(bool, defaultFalse): IfTrue, draws on price chart. IfFalse, separate panedescription(str, defaultNone): Optional tooltip descriptiondata_mode(str, default'ohlc'): Always'ohlc'for Python indicators. Use Rust for tick.timeframe(Timeframe, defaultNone): The COMPUTE timeframe the indicator runs at."tick"(default) follows the chart;"1m"/"5m"/etc. computes on its own bars at that timeframe, independent of the chart.visible_on(str | list, default'auto'): Which chart timeframes the indicator DRAWS on (visibility, not compute)."auto"(default) = directional: visible on its compute timeframe and every FINER chart timeframe, hidden on coarser ones (a 1m indicator shows on 1m/30s/tick, hidden on 5m)."all"= every timeframe.["1m","5m"]= only those. For a held level (VWAP-as-level, POC, prior-day levels) preferplot_type=PlotType.STEPLINEso a coarse series on a finer chart reads as steps, not interpolated ramps.warmup_days(int, default0): Prior trading days of read-only history prepended before each day so the indicator is warm at the first bar (e.g. a 20-day volume profile, or a 200-bar SMA that needs a few prior sessions to fill). Truncated at a contract rollover. 0 = legacy per-day.continuous(bool, defaultFalse): IfTrue, the indicator computes over the WHOLE loaded window in one pass and its line bridges session gaps, instead of resetting each session. Use for cumulative-over-the-window indicators (session/anchored VWAP, range-anchored profiles). Note its values extend/shift as you pan in earlier history. DefaultFalse= per-session reset, stable as more days load.
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?, visible_on?, description?, data_mode?, uses_sltp?, emit_sltp?, required_columns?, warmup_days?, continuous?)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, defaultrequired): Display name shown in the strategy paneloverlay(bool, defaultTrue): IfTrue, draws on the price charttimeframe(Timeframe, defaultNone): Default execution timeframevisible_on(str | list, default'auto'): Which chart timeframes the strategy's plotted lines DRAW on.'auto'(default) = directional (the execution timeframe and every finer chart timeframe, hidden on coarser);'all'= every timeframe;['1m','5m']= only those. Trades and markers always reposition regardless.description(str, defaultNone): Optional tooltip descriptiondata_mode(str, default'ohlc'): Always'ohlc'for Python strategiesuses_sltp(bool, defaultFalse): IfTrue, the strategy returnssl_long/tp_long/sl_short/tp_shortarraysemit_sltp(str, default'entry_only'):'entry_only'(set once at entry) or'per_tick'(re-evaluated each bar)required_columns(list[str], defaultauto): Columns the strategy reads. SHM only ships these to workers.warmup_days(int, default0): Prior trading days of read-only history prepended before each day so indicators are warm at the first bar (e.g. a 20-day volume profile, or a 200-bar SMA that needs a few prior sessions to fill). Counted in trading days, truncated at a contract rollover. 0 = legacy per-day, identical results.continuous(bool, defaultFalse): IfTrue, positions carry across calendar days instead of force-closing at EOD (still force-flat at a detected contract rollover / maintenance gap). Needed for daily, hourly, and swing strategies.
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_startmethod 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, defaultrequired): 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_startis visible incalculate(). - Calls inside
@day_startshould 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, defaultrequired): Display name for the scriptdescription(str, defaultNone): Optional descriptionversion(str, defaultNone): 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 ofcalculate(self, df). - Import from
quant_charts.script_helpersfor data access and export.