LIVE
connecting to the live quote stream…
--:--:--UTC
Learn/Bots/Bollinger Bands
Trend & Momentum
⌇

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.

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 297–357
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: "long", price: px[i], label: "lower band reclaim" });
        sigArr[i] = 1;
      } else if (px[i - 1] > upPrev && px[i] <= u) {
        signals.push({ i, kind: "short", 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.