Supertrend
The Supertrend indicator from the public library (supertrend), written against the current DSL.
What it plots
One line on price that trails below the bars in an uptrend and above them in a downtrend, flipping when the close breaks through.
Key decisions
Piece | Why it matters |
|---|---|
ATR from true range | The band width follows volatility. |
Bands that only tighten | Each band moves toward price unless the previous close broke through it. |
| The trend flag depends on the whole path and never converges, so no number of bars bounds it. |
Full script
meta({
shortName: "Supertrend",
kind: "overlay",
dataSource: "candles",
category: "custom",
});
warmup((w) => w.forever());
dialog({
title: "Supertrend Configuration",
description: "Configure Supertrend indicator settings",
width: 400,
height: 400,
resizable: false,
showEnabledCheckbox: true,
resetToDefaults: {
enabled: true,
confirmationMessage: "Are you sure you want to reset all Supertrend settings to their default values?",
},
});
const atrPeriod = input.number({
id: "atrPeriod",
label: "ATR Period",
default: 10,
min: 1,
step: 1,
});
const multiplier = input.number({
id: "multiplier",
label: "Multiplier",
default: 3.0,
min: 0.1,
step: 0.1,
});
const lineColor = input.color({
id: "lineColor",
label: "Line Color",
default: "#2962FF",
allowAlpha: true,
});
const lineStyle = input.lineStyle({
id: "lineStyle",
label: "Line Style",
default: LineStyle.Solid,
});
const labelsEnabled = input.boolean({
id: "labelsEnabled",
label: "Show Labels",
default: true,
});
layout(
tab({ id: "general", title: "General" },
section({ id: "parameters", title: "Parameters" }, atrPeriod, multiplier),
section({ id: "display", title: "Display Options" }, labelsEnabled),
),
tab({ id: "appearance", title: "Appearance" },
section({ id: "line", title: "Line", columns: 2 }, lineColor, lineStyle),
),
);
output.line({
id: "supertrend",
lineWidth: 2,
colorFrom: lineColor,
lineStyleFrom: lineStyle,
labelFrom: labelsEnabled,
titleWhenLabeled: "Supertrend",
});
// onBar runs again for the forming bar on every tick. The chart rolls ctx.accum back between
// ticks; foldBar does not depend on that, so the same code is right on every runtime.
// foldBar keeps two states: `done` (as of the last closed bar) and `tip` (this bar). `step`
// builds the tip from `done` without mutating it, so a repeat of the same bar recomputes the tip
// instead of stacking on top of it. A new bar time commits the tip.
function foldBar(ctx, id, init, step) {
const s = ctx.accum(id, () => ({ t: null, done: init, tip: init }), (s) => {
if (s.t !== ctx.time) {
s.done = s.tip;
s.t = ctx.time;
}
s.tip = step(s.done);
return s;
});
return s.tip;
}
function mean(values) {
let sum = 0;
for (const v of values) sum += v;
return sum / values.length;
}
/** @param {ScriptedCtx} ctx */
export function onBar(ctx) {
const period = ctx.params.atrPeriod;
const mult = ctx.params.multiplier;
const st = foldBar(ctx, "st", { n: 0, close: null, trs: [], atr: null, trend: null }, (prev) => {
const n = prev.n + 1;
if (prev.close == null) return { n, close: ctx.close, trs: [], atr: null, trend: null };
// ATR: the mean true range until `period` bars exist, Wilder smoothing after.
const tr = Math.max(
ctx.high - ctx.low,
Math.abs(ctx.high - prev.close),
Math.abs(ctx.low - prev.close),
);
if (n <= period) {
const trs = prev.trs.concat(tr);
return { n, close: ctx.close, trs, atr: mean(trs), trend: null };
}
const atr = (prev.atr * (period - 1) + tr) / period;
const hl2 = (ctx.high + ctx.low) / 2;
const basicUpper = hl2 + mult * atr;
const basicLower = hl2 - mult * atr;
let trend;
if (prev.trend == null) {
trend = { upper: basicUpper, lower: basicLower, isUp: prev.close > hl2 };
} else {
// The bands only tighten, unless the previous close broke through them.
const upper = basicUpper < prev.trend.upper || prev.close > prev.trend.upper ? basicUpper : prev.trend.upper;
const lower = basicLower > prev.trend.lower || prev.close < prev.trend.lower ? basicLower : prev.trend.lower;
let isUp = prev.trend.isUp;
if (!isUp && ctx.close > upper) isUp = true;
else if (isUp && ctx.close < lower) isUp = false;
trend = { upper, lower, isUp };
}
return { n, close: ctx.close, trs: [], atr, trend };
});
if (st.trend == null) return;
ctx.plot("supertrend", st.trend.isUp ? st.trend.lower : st.trend.upper);
}Related
Bars and clocks for why a trend flip waits for a closed bar, and onBar.
