ATR
The ATR indicator from the public library (atr), written against the current DSL.
What it plots
Average True Range in its own pane: volatility in price units, with a choice of smoothing.
Key decisions
Piece | Why it matters |
|---|---|
True range | The largest of high minus low and the distance from each to the previous close, so gaps count. |
| RMA (Wilder, the default), SMA, EMA or WMA once the first window is full. |
Seed, then smooth | The first ATR is the plain average of |
Warmup per smoothing type |
|
Full script
meta({
shortName: "ATR",
kind: "pane",
dataSource: "candles",
category: "custom",
});
dialog({
title: "ATR Configuration",
description: "Configure Average True Range (ATR) settings",
width: 400,
height: 350,
resizable: false,
showEnabledCheckbox: true,
resetToDefaults: {
enabled: true,
confirmationMessage: "Are you sure you want to reset all ATR settings to their default values?",
},
});
const period = input.number({
id: "period",
label: "Period",
default: 14,
min: 1,
step: 1,
});
const smoothingType = input.select({
id: "smoothingType",
label: "Smoothing",
default: "RMA",
options: [
{ value: "RMA", label: "RMA (Wilder's)" },
{ value: "SMA", label: "SMA" },
{ value: "EMA", label: "EMA" },
{ value: "WMA", label: "WMA" },
],
});
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,
});
warmup((w) => w.when(smoothingType)
.is(["SMA", "WMA"]).then(w.window(period))
.is("EMA").then(w.ema(period))
.otherwise(w.wilder(period)));
layout(
tab({ id: "general", title: "General" },
section({ id: "parameters", title: "Parameters" }, period, smoothingType),
section({ id: "display", title: "Display Options" }, labelsEnabled),
),
tab({ id: "appearance", title: "Appearance" },
section({ id: "line", title: "Line", columns: 2 }, lineColor, lineStyle),
),
);
output.line({
id: "atr",
lineWidth: 2,
colorFrom: lineColor,
lineStyleFrom: lineStyle,
labelFrom: labelsEnabled,
titleWhenLabeled: "ATR",
});
// 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 p = ctx.params.period;
const type = ctx.params.smoothingType;
const st = foldBar(ctx, "atr", { close: null, trs: [], atr: null }, (prev) => {
if (prev.close == null) return { close: ctx.close, trs: [], atr: null };
// True range counts the gap from the previous close.
const tr = Math.max(
ctx.high - ctx.low,
Math.abs(ctx.high - prev.close),
Math.abs(ctx.low - prev.close),
);
const trs = prev.trs.concat(tr).slice(-p);
if (trs.length < p) return { close: ctx.close, trs, atr: null };
let atr;
if (prev.atr == null || type === "SMA") {
atr = mean(trs); // the first full window seeds every type
} else if (type === "WMA") {
let sum = 0;
for (let i = 0; i < p; i++) sum += trs[i] * (i + 1);
atr = sum / (p * (p + 1) / 2);
} else if (type === "EMA") {
atr = prev.atr + (2 / (p + 1)) * (tr - prev.atr);
} else {
atr = (prev.atr * (p - 1) + tr) / p; // RMA (Wilder)
}
return { close: ctx.close, trs, atr };
});
if (st.atr != null) ctx.plot("atr", st.atr);
}Related
Warmup and memory for why Wilder smoothing declares 50x its period, and onBar for the forming-bar rule behind foldBar.
