Parameters
params is the dict on your indicator class that declares the user-facing inputs. Every key becomes a control in the side panel; every value defines its type, default, and bounds. You read the resolved values inside the indicator via ctx.params.<name>.
Two declaration forms
Dict shorthand (recommended)
Covers the common cases and is what the editor's default template uses:
class MyInd(Indicator):
params = {
"period": {"type": "int", "default": 14, "min": 2, "max": 200},
"smooth": {"type": "bool", "default": True},
"mode": {"type": "str", "default": "fast", "choices": ["fast", "slow"]},
}
Supported keys: type ("int", "float", "bool", "str"), default, min, max, choices, and description.
Param form
Use the Param dataclass when you need a source parameter (the kind modifier), or simply prefer the explicit form:
from indicator_api import Indicator, Param
class MyInd(Indicator):
params = {
"period": Param(type=int, default=14, min=2, max=200,
description="Window length"),
"src": Param(type=dict,
default={"type": "source", "value": "close"},
kind="source",
description="Bar series to run on"),
}
The two forms are otherwise equivalent — mix them freely in one class.
⚠️ Source params must use
Param(...). The dict shorthand does not carry thekindfield — it's dropped during normalization. A source parameter declared with the dict form would be treated as a plaindictparam and never resolve to a price series. UseParam(..., kind="source").
Reading parameters inside the indicator
Declared parameters arrive on ctx.params. Read them by attribute in init or on_bar:
class MyInd(Indicator):
params = {
"period": {"type": "int", "default": 14},
"smooth": {"type": "bool", "default": True},
}
def init(self, ctx):
self.ma = ctx.rolling_mean(source=ctx.close, period=ctx.params.period)
def on_bar(self, ctx):
value = self.ma.value
if ctx.params.smooth and value is not None:
...
ctx.plot.series("ma", value=value)
ctx.params is fully populated before init runs, so it's safe to size a helper from a parameter (period=ctx.params.period).
Types and the controls they get
type | Notes |
|---|---|
int | Whole-number input. min / max bound it. |
float | Decimal input. min / max bound it. |
bool | Checkbox. |
str | Text input. With choices, a dropdown. |
str + kind="color" | A colour swatch with opacity (see below). |
dict + kind="source" | A bar-series picker (see below). |
Colour parameters
Let the user pick your line colours instead of hard-coding them. Declare with Param(type=str, kind="color") and a hex default, then pass the value straight to color=:
from indicator_api import Indicator, Param
class MyMA(Indicator):
params = {
"period": Param(type=int, default=20, min=2, max=500),
"ma_color": Param(type=str, default="#fde68a", kind="color",
description="Line colour"),
}
def init(self, ctx):
self.ma = ctx.rolling_mean(source=ctx.close, period=ctx.params.period)
def on_bar(self, ctx):
ctx.plot.series("ma", value=self.ma.value, color=ctx.params.ma_color)
Like source params, colour params need the Param(...) form — the dict shorthand has no kind.
Source parameters
A source parameter lets the user choose which price series your indicator runs on, so the same code works on close, hl2, volume, etc. without edits.
Declare it with Param(type=dict, kind="source", ...) and a default of {"type": "source", "value": "<name>"}, where <name> is one of:
open, high, low, close, volume, hl2, hlc3, hlcc4, ohlc4.
The runtime resolves the picked series before your code runs, so ctx.params.<name> is something you can feed straight into a helper's source=:
from indicator_api import Indicator, Param
class FlexibleMA(Indicator):
name = "Flexible MA"
params = {
"period": Param(type=int, default=20, min=2, max=500),
"src": Param(type=dict,
default={"type": "source", "value": "close"},
kind="source",
description="Price series the average runs on"),
}
def init(self, ctx):
self.ma = ctx.rolling_mean(source=ctx.params.src,
period=ctx.params.period)
def on_bar(self, ctx):
ctx.plot.series("ma", value=self.ma.value, label="MA")
📌 Chained sources are not available yet. A second source shape —
{"type": "source", "indicator_id": "...", "output": "..."}, feeding one indicator's output into another — is recognised but rejected with a clear "not yet supported" message. Use a built-in series (value) for now; chaining is planned for a follow-up.
Validation
After defaults are applied and values are type-coerced:
minandmaxare inclusive (min=2means 2 is legal).choices, when present, is enforced.- A value that can't be coerced to the declared
typeis rejected.
Any failure raises a ParamValidationError and the user sees an inline validation error in the panel rather than a silently-wrong chart.
Schema is re-derived from your code on save
Every save re-parses your source and rebuilds the parameter schema from scratch. Your code is the source of truth, not whatever the panel last showed:
- Remove a param and it's gone.
- Tighten
minfrom 1 to 2 and the new bound applies; a chart sitting at value 1 will surface a validation error on its next run. - Rename a param and the old name is gone; existing charts fall back to the new param's default.
This is intentional — it keeps the saved indicator and its code in lockstep.