A Quant Charts indicator is a Python class decorated with @indicator that implements calculate(self, df) and calls plot(). This walkthrough builds a working dual-moving-average indicator.
How to Build a Quant Charts Indicator (Python)
01. Your first Python indicator
A guided 6-step walkthrough that ends with a working dual_sma.py indicator: two moving averages plus a fill that turns green when fast is above slow and red when it crosses below.
You will use @indicator, declare two input.int parameters and two input.color parameters, compute averages with ta.sma, and call plot() and fill(). By the end you will know how to: declare a Python indicator, expose user-tunable parameters, read the OHLC dataframe, plot named series, and fill between them. Read each step in order; the final step has the complete file you can paste into indicators/.
Step 1. Imports
Pull only what you need from quant_charts. Each imported name is documented in the Python tab.
Example
import numpy as np
from quant_charts import indicator, input, ta, plot, fill
Notes
tais the vectorized technical-analysis namespace (ta.sma,ta.ema,ta.rsi,ta.atr, ...).- Importing
plotandfillhere lets you call them as plain functions insidecalculate().
Step 2. The decorator
Mark the class as an indicator. The decorator registers it with the engine and exposes its parameters in the chart settings panel.
Example
@indicator(
name="Dual SMA",
description="Fast/slow SMA with cross-direction fill.",
overlay=True,
data_mode="ohlc",
required_columns=["close"],
)
class DualSMA:
pass
Notes
overlay=Truedraws on the price pane. Set toFalsefor a separate subpane (e.g. RSI, MACD).data_mode="ohlc"runs the indicator on aggregated bars. Use"both"if it should also work in tick-by-tick TBBO view.required_columnsmakes the loader ship only the columns you read across the worker boundary. Keeps sweep memory tight.
Step 3. Parameters
Declare class-level input.* fields. They become editable inputs in the indicator panel and the analyzer parameter form.
Example
class DualSMA:
fast_period = input.int(20, "Fast Period", min=2, max=500)
slow_period = input.int(50, "Slow Period", min=2, max=500)
fast_color = input.color("#7aa2f7", "Fast Color")
slow_color = input.color("#e0af68", "Slow Color")
bull_fill = input.color("#22c55e40", "Bull Fill")
bear_fill = input.color("#ef444440", "Bear Fill")
Notes
input.int(default, label, min=, max=, step=)exposes an integer field.input.floatis the same shape for floats.input.color(default_hex, label)accepts 6-char#rrggbbor 8-char#rrggbbaa. The trailing40here is hex alpha (40/255 ~= 25%).- Read parameters as
self.fast_period,self.fast_color, etc., insidecalculate().
Step 4. The calculate body
Compute both averages and decide the fill color per bar.
Example
def calculate(self, df):
close = np.asarray(df["close"], dtype=np.float64)
fast = ta.sma(close, self.fast_period)
slow = ta.sma(close, self.slow_period)
# element-wise: pick bull color where fast > slow, bear color otherwise
return fast, slow
Notes
- Convert pandas columns to numpy with
np.asarray(..., dtype=np.float64). The TA namespace expects numpy arrays. ta.sma(close, period)returns an array the same length asclose, with NaN for the warmup bars beforeperiodsamples are available.- The return value of
calculate()is ignored when you useplot()/fill()directly. Returning is optional for indicators (signal dicts are for strategies, not indicators).
Step 5. Plot the lines and fill between
Two plot() calls register named series on the chart. fill() references those names and shades the area between them.
Example
def calculate(self, df):
close = np.asarray(df["close"], dtype=np.float64)
fast = ta.sma(close, self.fast_period)
slow = ta.sma(close, self.slow_period)
plot(fast, "Fast SMA", color=self.fast_color, linewidth=2)
plot(slow, "Slow SMA", color=self.slow_color, linewidth=2)
# fill takes the two plot names; color is hex with optional alpha
fill("Fast SMA", "Slow SMA", color=self.bull_fill)
Notes
- The
nameyou pass toplot()is whatfill()references. Names must be unique within an indicator. - Single-color fill is the simplest path. To get a regime-switching fill (green when bull, red when bear), call
fill()twice with two pairs of bookkeeping plots, or usebgcolor()on a per-bar basis. The single-color path is enough for this tutorial.
Step 6. The full file
Save this as dual_sma.py under your workspace/indicators/ and Quant Charts auto-loads it.
Example
import numpy as np
from quant_charts import indicator, input, ta, plot, fill
@indicator(
name="Dual SMA",
description="Fast/slow SMA with cross-direction fill.",
overlay=True,
data_mode="ohlc",
required_columns=["close"],
)
class DualSMA:
fast_period = input.int(20, "Fast Period", min=2, max=500)
slow_period = input.int(50, "Slow Period", min=2, max=500)
fast_color = input.color("#7aa2f7", "Fast Color")
slow_color = input.color("#e0af68", "Slow Color")
fill_color = input.color("#7aa2f733", "Fill")
def calculate(self, df):
close = np.asarray(df["close"], dtype=np.float64)
fast = ta.sma(close, self.fast_period)
slow = ta.sma(close, self.slow_period)
plot(fast, "Fast SMA", color=self.fast_color, linewidth=2)
plot(slow, "Slow SMA", color=self.slow_color, linewidth=2)
fill("Fast SMA", "Slow SMA", color=self.fill_color)
Step 7. Run it
Open the indicator in Quant Charts and attach it to a chart.
- Open the Indicator panel from the chart top bar, click "Add", select "Dual SMA".
- Tune
Fast Period/Slow Periodfrom the panel; the chart re-runs on every change.
Notes
- Validation runs on save. If it fails, errors print to the in-app terminal panel.
- Adjusting an
input.colorfrom the panel respects the 8-char hex with alpha format.
Complete example: moving averages
# qc-api: 1.0.7
# DISCLAIMER: This software is for educational and informational purposes only and does not constitute
# financial advice, investment advice, or trading advice. Past performance is not indicative of future
# results. Trading futures and other financial instruments involves substantial risk of loss. You are
# solely responsible for your own trading decisions. Quant Charts LLC assumes no liability for any
# losses incurred. All rights reserved. (c) Quant Charts LLC
"""Moving Averages: SMA, EMA, and WMA on one overlay (Python / OHLC).
Required columns: close.
Data mode: OHLC.
The canonical moving-average example. Plots three moving averages of close on
the price pane and emits trend tags from their stack order:
ma_bull : close > EMA > SMA (fast above slow, price leading)
ma_bear : close < EMA < SMA
Use `ta.sma`, `ta.ema`, `ta.wma` rather than a hand-rolled rolling mean: they
are vectorized and JIT-compiled for large series.
"""
import numpy as np
from quant_charts import indicator, input, plot, define_tag, ta
@indicator(
name="Moving Averages",
description="SMA + EMA + WMA overlay with bull/bear stack-order tags",
overlay=True,
data_mode="ohlc",
required_columns=["close"],
)
class MovingAverages:
sma_period = input.int(50, "SMA Period", min=2, max=1000,
tooltip="Simple moving average lookback in bars.")
ema_period = input.int(20, "EMA Period", min=2, max=1000,
tooltip="Exponential moving average lookback in bars.")
wma_period = input.int(20, "WMA Period", min=2, max=1000,
tooltip="Weighted moving average lookback in bars.")
sma_color = input.color("#A1A1AA", "SMA Color")
ema_color = input.color("#7AA2F7", "EMA Color")
wma_color = input.color("#73DACA", "WMA Color")
def calculate(self, df):
close = np.asarray(df["close"], dtype=np.float64)
sma = ta.sma(close, self.sma_period)
ema = ta.ema(close, self.ema_period)
wma = ta.wma(close, self.wma_period)
plot(sma, f"SMA({self.sma_period})", color=self.sma_color, linewidth=2)
plot(ema, f"EMA({self.ema_period})", color=self.ema_color, linewidth=2)
plot(wma, f"WMA({self.wma_period})", color=self.wma_color, linewidth=2)
finite = np.isfinite(sma) & np.isfinite(ema)
ma_bull = finite & (close > ema) & (ema > sma)
ma_bear = finite & (close < ema) & (ema < sma)
define_tag("ma_bull", "close > EMA > SMA", color="#73DACA")
define_tag("ma_bear", "close < EMA < SMA", color="#F7768E")
return {
"ma_bull": ma_bull,
"ma_bear": ma_bear,
}