LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/Bollinger Bands
Trend & Momentumid · boll

Bollinger Bands

Volatility envelopes around a moving average.

In plain English

Two rubber bands stretched a few standard deviations away from the average price. When price stretches one and snaps back, that's the trade. Wider bands mean the market is jittery; narrow bands mean a big move is coming.

No jargon. Just what this bot does.
The longer version

Two rubber bands stretched k standard deviations away from the average. When price stretches one and snaps back, that's the trade. Wider bands mean the market is jittery; narrow bands mean it's coiling. Bollinger himself wrote that the bands aren't trade signals — they're a regime indicator. Most traders ignore him and use them as signals anyway.

The math
formula
upper = SMA(n) + k·σ; lower = SMA(n) − k·σ
parameters
periodPeriodrange 10 → 50default · 20
stdMultStd multiplierrange 1 → 3default · 2
Live demo

Real Bollinger Bands bot, running on real Yahoo data when the symbol is available. Drag the params — the bot re-runs instantly.

symbolloading…
loading AMZN bars…
Source code · public

This is the actual code the bot runs — not a re-explanation, not a simplified version. Whatever ships here is what executes when you press Run All in the workbench. Read it, copy it, fork it, build a better one.

lib/quant/bots.ts·lines 298358
TypeScript · MIT-licensed
const bollBot: BotDef = {
  id: "boll",
  name: "Bollinger Bands",
  category: "trend",
  glyph: "⌇",
  tagline: "Volatility envelopes around a moving average.",
  formula: "upper = SMA(n) + k·σ; lower = SMA(n) − k·σ",
  params: [
    { key: "period", label: "Period", kind: "number", default: 20, min: 10, max: 50, step: 1 },
    { key: "stdMult", label: "Std multiplier", kind: "number", default: 2, min: 1, max: 3, step: 0.1 },
  ],
  run: (ctx, p): BotResult => {
    const period = Math.round(num(p, "period", 20));
    const k = num(p, "stdMult", 2);
    const px = closes(ctx.candles);
    const b = bollinger(px, period, k);
    const signals: Signal[] = [];
    const sigArr: (1 | -1 | 0)[] = px.map(() => 0);
    for (let i = 1; i < px.length; i++) {
      const upPrev = b.upper[i - 1], loPrev = b.lower[i - 1];
      const u = b.upper[i], l = b.lower[i];
      if (u == null || l == null || upPrev == null || loPrev == null) continue;
      if (px[i - 1] < loPrev && px[i] >= l) {
        signals.push({ i, kind: "buy", price: px[i], label: "lower band reclaim" });
        sigArr[i] = 1;
      } else if (px[i - 1] > upPrev && px[i] <= u) {
        signals.push({ i, kind: "sell", price: px[i], label: "upper band reject" });
        sigArr[i] = -1;
      }
    }
    const bt = backtestLongOnly(px, sigArr);
    const last = px[px.length - 1];
    const lu = b.upper[b.upper.length - 1] ?? last;
    const ll = b.lower[b.lower.length - 1] ?? last;
    const lm = b.mid[b.mid.length - 1] ?? last;
    const width = (lu - ll) / lm;
    return {
      signals,
      metrics: [
        { key: "width", label: "Width", value: `${(width * 100).toFixed(1)}%`, tone: width > 0.1 ? "warn" : "neutral", hint: "vol envelope" },
        { key: "upper", label: "Upper", value: fmtNum(lu) },
        { key: "lower", label: "Lower", value: fmtNum(ll) },
        { key: "trades", label: "Trades", value: String(bt.trades) },
      ],
      overlay: [
        { values: b.mid, color: "var(--fg-dim)", label: "SMA" },
        { values: b.upper, color: "var(--bear-dim)", label: "Upper", dashed: true },
        { values: b.lower, color: "var(--bull-dim)", label: "Lower", dashed: true },
      ],
      summary: `Bands width ${(width * 100).toFixed(1)}%. ${signals.length} band-touch reversals.`,
      beginner:
        "Two rubber bands stretched k standard deviations away from the average. When price stretches one and snaps back, that's the trade. Wider bands mean the market is jittery.",
      verdict: {
        side: last < ll ? "buy" : last > lu ? "sell" : "hold",
        text: last < ll ? "Below lower band — coiled spring." : last > lu ? "Above upper band — overextended." : "Inside the bands — no edge.",
        confidence: Math.min(1, Math.abs(last - lm) / (lu - lm || 1)),
      },
      equity: bt.equity,
    };
  },
};
what each piece means
  • id — unique key the workbench uses to find the bot.
  • params — the sliders + inputs you see on the cell.
  • run(ctx, p) — the function that gets called with candles + your params and returns the verdict.
  • verdict — the BUY / SELL / HOLD pill at the top of the cell.
  • metrics — the small stat boxes shown in the cell body.
use this code yourself
  1. Copy the whole block above.
  2. On /quant, click + Import your bot in the bot library.
  3. Paste, hit save. It hot-loads into your workspace.
  4. Edit any param defaults or logic to your taste — it's now yours.
Specialty · when it shines, when it fails
✓ Shines when
  • ·Range-bound markets. Touches at the bands are reversion entries.
  • ·Volatility expansion plays. Bollinger 'squeezes' (very narrow bands) often precede big moves.
  • ·Combined with RSI for confirmation. Lower-band touch + oversold RSI = stronger reversal signal.
✗ Fails when
  • ·Strong trends. Price 'walks the band' for weeks during a parabolic move; every touch is a losing fade.
  • ·Tight-band false signals. Ranges look like coils until they don't.
  • ·Default 20/2 settings. The 2-σ assumption breaks for skewed return distributions.
How to read its verdict

BUY when price reclaims the lower band from below. SELL when it rejects the upper band from above. Confidence is the distance from the mid-line, scaled by half-band width. Width of the bands (as % of mid) is a separate volatility regime indicator.

FAQ
Why 20 periods, 2 standard deviations?+
Bollinger's original choice. 20-period SMA captures roughly a month of daily bars; 2σ catches ~95% of returns under normality (which doesn't perfectly hold, but close enough). Tune with backtest on your asset.
What's a Bollinger squeeze?+
When band width narrows below historical average. Empirically, periods of low volatility tend to be followed by periods of high volatility — squeezes often precede breakouts. Bollinger called it 'the squeeze' in his 2001 book.