LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/MACD Histogram
Trend & Momentumid · macd

MACD Histogram

Momentum from EMA difference & its signal line.

In plain English

MACD asks: is momentum waking up or running out of steam? It looks at the difference between two trend-followers. When that difference flips from negative to positive, momentum just turned on — buy. When it flips negative, it's running out — sell.

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

MACD looks at the difference between two EMA-based trend-followers. When that difference flips from negative to positive, momentum just woke up. When it flips negative, it's running out of steam. The histogram is the difference between the MACD line and its own signal line — that's where the actual cross signals come from.

The math
formula
MACD = EMA(fast) − EMA(slow); signal = EMA(MACD)
parameters
fastFast EMArange 5 → 26default · 12
slowSlow EMArange 17 → 52default · 26
signalSignal EMArange 3 → 18default · 9
Live demo

Real MACD Histogram 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 167229
TypeScript · MIT-licensed
const macdBot: BotDef = {
  id: "macd",
  name: "MACD Histogram",
  category: "trend",
  glyph: "▥",
  tagline: "Momentum from EMA difference & its signal line.",
  formula: "MACD = EMA(fast) − EMA(slow); signal = EMA(MACD)",
  params: [
    { key: "fast", label: "Fast EMA", kind: "number", default: 12, min: 5, max: 26, step: 1 },
    { key: "slow", label: "Slow EMA", kind: "number", default: 26, min: 17, max: 52, step: 1 },
    { key: "signal", label: "Signal EMA", kind: "number", default: 9, min: 3, max: 18, step: 1 },
  ],
  run: (ctx, p): BotResult => {
    const fast = Math.round(num(p, "fast", 12));
    const slow = Math.round(num(p, "slow", 26));
    const sig = Math.round(num(p, "signal", 9));
    const px = closes(ctx.candles);
    const m = macd(px, fast, slow, sig);
    const signals: Signal[] = [];
    const sigArr: (1 | -1 | 0)[] = px.map(() => 0);
    for (let i = 1; i < m.histogram.length; i++) {
      const a = m.histogram[i - 1], b = m.histogram[i];
      if (a == null || b == null) continue;
      if (a <= 0 && b > 0) {
        signals.push({ i, kind: "buy", price: px[i], label: "hist > 0" });
        sigArr[i] = 1;
      } else if (a >= 0 && b < 0) {
        signals.push({ i, kind: "sell", price: px[i], label: "hist < 0" });
        sigArr[i] = -1;
      }
    }
    const bt = backtestLongOnly(px, sigArr);
    const lastH = m.histogram[m.histogram.length - 1] ?? 0;
    return {
      signals,
      metrics: [
        { key: "hist", label: "Hist", value: fmtNum(lastH, 3), tone: lastH > 0 ? "bull" : "bear" },
        { key: "line", label: "MACD", value: fmtNum(m.line[m.line.length - 1] ?? 0, 3), tone: "neutral" },
        { key: "trades", label: "Trades", value: String(bt.trades), tone: "neutral" },
        { key: "win", label: "Win rate", value: `${(bt.winRate * 100).toFixed(0)}%`, tone: bt.winRate > 0.5 ? "bull" : "warn" },
      ],
      pane: {
        kind: "histogram",
        series: [
          { values: m.histogram, color: "var(--plasma)", label: "Hist" },
          { values: m.line, color: "var(--bull)", label: "MACD" },
          { values: m.signal, color: "var(--cyan)", label: "Signal" },
        ],
        refLines: [{ value: 0, color: "var(--fg-faint)" }],
        height: 90,
      },
      summary: `MACD histogram ${lastH.toFixed(3)}. ${signals.length} crosses, ${bt.trades} trades, win ${(bt.winRate * 100).toFixed(0)}%.`,
      beginner:
        "MACD looks at the difference between two trend-followers. When that difference flips from negative to positive, momentum just woke up. When it flips negative, it's running out of steam.",
      verdict: {
        side: lastH > 0 ? "buy" : "sell",
        text: lastH > 0 ? "Histogram above zero — momentum tilts long." : "Histogram below zero — momentum tilts short.",
        confidence: Math.min(1, Math.abs(lastH) * 5),
      },
      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
  • ·Pulse-checking momentum. Histogram crossing zero = momentum just turned, regardless of trend direction.
  • ·Catching divergences. Price making higher highs while MACD makes lower highs is one of the strongest reversal signals in technical analysis.
  • ·Daily and 4h timeframes on liquid names. The default 12/26/9 was tuned for daily.
✗ Fails when
  • ·Choppy ranges. Histogram crosses zero every other bar; whipsaws kill returns.
  • ·Slow-drifting markets. MACD can sit near zero for weeks waiting for momentum.
  • ·Short-window instruments (intraday small caps). Default periods are too slow.
How to read its verdict

BUY when histogram crosses above zero. SELL when below. Confidence scales with the magnitude of the histogram. Above-zero histogram with rising MACD line is the strongest BUY shape; mirror for SELL.

FAQ
Why not just use the line/signal cross?+
Histogram crossing zero is mathematically identical to line crossing signal — they're the same event. Using the histogram view makes momentum strength visible at a glance.
12/26/9 — should I tune these?+
Yes for non-daily timeframes. Hourly: 12/26/9 is too slow, try 6/13/4. Weekly: try 5/13/5. Default 12/26/9 was Gerald Appel's choice for daily stocks; it's still close to optimal there.