Bollinger Bands
The Bollinger Bands indicator from the public library (bollinger), written against the current DSL.
What it plots
A middle line on price (the SMA of the close) with upper and lower bands standardDeviations standard deviations away.
Key decisions
Piece | Why it matters |
|---|---|
SMA and standard deviation of one window | The bands widen and tighten with recent volatility. |
Three | Middle, upper and lower are styled separately and readable separately as dependency outputs. |
| Both numbers come from the same |
Full script
meta({
shortName: "BB",
kind: "overlay",
dataSource: "candles",
category: "custom",
});
dialog({
title: "Bollinger Bands Configuration",
description: "Configure Bollinger Bands settings",
width: 400,
height: 400,
resizable: false,
showEnabledCheckbox: true,
resetToDefaults: {
enabled: true,
confirmationMessage: "Are you sure you want to reset all Bollinger Bands settings to their default values?",
},
});
const period = input.number({
id: "period",
label: "Period",
default: 20,
min: 1,
step: 1,
});
const standardDeviations = input.number({
id: "standardDeviations",
label: "Standard Deviations",
default: 2,
min: 0.1,
step: 0.1,
});
const middleColor = input.color({
id: "middleColor",
label: "Middle Color",
default: "#2962FF",
allowAlpha: true,
});
const bandColor = input.color({
id: "bandColor",
label: "Band Color",
default: "#FF6D00",
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.window(period));
layout(
tab({ id: "general", title: "General" },
section({ id: "parameters", title: "Parameters" }, period, standardDeviations),
section({ id: "display", title: "Display Options" }, labelsEnabled),
),
tab({ id: "appearance", title: "Appearance" },
section({ id: "lines", title: "Lines", columns: 2 }, middleColor, bandColor, lineStyle),
),
);
output.line({
id: "middle",
lineWidth: 2,
colorFrom: middleColor,
lineStyleFrom: lineStyle,
labelFrom: labelsEnabled,
titleWhenLabeled: "BB Mid",
});
output.line({
id: "upper",
lineWidth: 1,
colorFrom: bandColor,
lineStyleFrom: lineStyle,
labelFrom: labelsEnabled,
titleWhenLabeled: "BB Upper",
});
output.line({
id: "lower",
lineWidth: 1,
colorFrom: bandColor,
lineStyleFrom: lineStyle,
labelFrom: labelsEnabled,
titleWhenLabeled: "BB Lower",
});
// 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 mult = ctx.params.standardDeviations;
const closes = foldBar(ctx, "closes", [], (prev) => prev.concat(ctx.close).slice(-p));
if (closes.length < p) return;
const sma = mean(closes);
let sq = 0;
for (const c of closes) sq += (c - sma) * (c - sma);
const sd = Math.sqrt(sq / p); // population standard deviation of the same window
ctx.plot("middle", sma);
ctx.plot("upper", sma + sd * mult);
ctx.plot("lower", sma - sd * mult);
}Related
Series types for bands, and onBar for the forming-bar rule behind foldBar.
