Indicators
Built-in indicators
| Id | Name | Where | Parameters (defaults) |
|---|---|---|---|
sma |
Simple moving average | Over the price | period (20) |
ema |
Exponential moving average | Over the price | period (50) |
bb |
Bollinger Bands | Over the price | period (20), mult (2) |
rsi |
Relative strength index | Own pane, 0–100 | period (14) |
macd |
MACD | Own pane | fast (12), slow (26), signal (9) |
volume |
Volume | Own pane | none |
The math follows the usual conventions: EMA starts from the SMA of its first period, RSI uses Wilder's smoothing, MACD's histogram is MACD minus signal.
Adding them
With the indicators attribute, parameters after colons in the order above:
<trade-chart indicators="sma:20, ema:50, bb:20:2, rsi:14, macd:12:26:9, volume"></trade-chart>
Changing the attribute replaces the indicators it added; ones you add in code stay.
From code, with parameters by name or in order, and an optional style:
const key = chart.addIndicator("rsi", { period: 21 });
chart.addIndicator("macd", [8, 21, 5]);
chart.addIndicator("ema", [200], { colors: ["#f5a524"], width: 2 });
chart.updateIndicator(key, { period: 14 }); // new parameters
chart.updateIndicator(key, undefined, { levels: [20, 80] }); // new style only
chart.removeIndicator(key);
chart.clearIndicators();
chart.indicatorList; // [{ key, id, label, params, style, placement }]
Parameters outside their allowed range are clamped (a period is at least 1 or 2, depending on the indicator).
Styling one indicator
| Style | |
|---|---|
colors |
Colours of its lines, in order. MACD: [macd, signal]; Bollinger Bands: [upper, middle, lower] |
width |
Line width in px |
upColor, downColor |
Histogram bars (MACD, volume) |
bandColor |
Fill between the bands (Bollinger Bands) |
levels |
Reference lines instead of the defaults, e.g. [20, 80] for RSI |
height |
Starting height of its pane, as a share of the chart, e.g. 0.25 |
Panes
Indicators with their own pane appear below the price, each with its own scale, a header with its values at the hovered candle, and a × to close it. Users can drag the line between panes to resize them. Panes take at most 75% of the height together (layout.paneMaxShare), and each is at least 48 px (layout.paneMinHeight).
When the user closes an indicator, tl-indicatorschange fires with the ones left, so you can update your own controls:
chart.addEventListener("tl-indicatorschange", (e) => syncMyToggles(e.detail.map((i) => i.id)));
Hide the × with interaction: { closeIndicators: false }.
Writing your own
Register an indicator once, then use it like a built-in one, in the attribute too.
import { registerIndicator, sma, ema } from "tickline";
registerIndicator({
id: "mid",
name: "Midpoint average",
placement: "main", // "main": over the price; "pane": its own pane
params: [{ name: "period", default: 10, min: 1, max: 500 }],
compute: (candles, p) => ({
lines: [{ name: "Mid", values: sma(candles.map((c) => (c.high + c.low) / 2), p.period), color: "#f5a524" }],
}),
label: (p) => `Mid ${p.period}`,
});
chart.addIndicator("mid", { period: 14 }); // or indicators="mid:14"
compute gets the candles (oldest first) and the parameters, and returns one value per candle it was given. Use NaN where there's no value yet (the start of a moving average). It runs again only when the data changes, not every frame.
On long histories (more than history.indicatorWindow candles, default 20,000) compute gets the candles around the view plus a warm-up of 20 × the largest parameter, not the whole history, so live updates stay fast. Moving averages, RSI, MACD and most other indicators give the same values this way. If yours needs every candle from the very first one (a running total, say), add window: false to its definition.
What compute can return:
interface IndicatorOutput {
lines: { name: string; values: Float64Array | number[]; color: string; width?: number }[];
histogram?: { values: Float64Array; up: Uint8Array; upColor: string; downColor: string }; // bars from zero; up[i] picks the colour
band?: { upper: number; lower: number; color: string }; // fill between two lines, by index
levels?: number[]; // dashed reference lines
range?: [number, number]; // a fixed scale for the pane (e.g. 0–100)
}
A pane indicator with a histogram and a level, e.g. a momentum oscillator:
registerIndicator({
id: "mom",
name: "Momentum",
placement: "pane",
params: [{ name: "period", default: 10, min: 1 }],
compute: (candles, p) => {
const values = Float64Array.from(candles, (c, i) => (i >= p.period ? c.close - candles[i - p.period].close : NaN));
const up = Uint8Array.from(values, (v) => (v >= 0 ? 1 : 0));
return { lines: [], histogram: { values, up, upColor: "rgba(34,195,142,.6)", downColor: "rgba(240,80,110,.6)" }, levels: [0] };
},
label: (p) => `Mom ${p.period}`,
});
Helpers you can use: sma, ema, rsi and stdev, each taking a list of numbers and a period and returning a Float64Array. listIndicators() and getIndicator(id) return the registered definitions.