LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/SMA Crossover
Trend & Momentumid · sma-cross

SMA Crossover

Buy when fast moving average crosses above slow.

In plain English

Two moving averages chase each other across the chart. A fast one (recent prices) and a slow one (older prices). When the fast one cuts up through the slow one, the trend just turned bullish — buy. When it falls below, the trend rolled over — sell. Old, simple, surprisingly hard to beat in trending markets.

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

Two simple moving averages — a fast one and a slow one — chasing each other across the chart. When the fast one cuts up through the slow one, the trend just shifted bullish. When it falls below, the trend's rolling over. Old, unromantic, and surprisingly hard to beat in trending markets.

The math
formula
buy if SMA(fast) > SMA(slow) and was below yesterday
parameters
fastFast periodrange 3 → 50default · 12
slowSlow periodrange 10 → 200default · 26
Live demo

Real SMA Crossover 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 3393
TypeScript · MIT-licensed
const smaCrossover: BotDef = {
  id: "sma-cross",
  name: "SMA Crossover",
  category: "trend",
  glyph: "↗",
  tagline: "Buy when fast moving average crosses above slow.",
  formula: "buy if SMA(fast) > SMA(slow) and was below yesterday",
  params: [
    { key: "fast", label: "Fast period", kind: "number", default: 12, min: 3, max: 50, step: 1 },
    { key: "slow", label: "Slow period", kind: "number", default: 26, min: 10, max: 200, step: 1 },
  ],
  run: (ctx, p): BotResult => {
    const fast = Math.round(num(p, "fast", 12));
    const slow = Math.round(num(p, "slow", 26));
    const px = closes(ctx.candles);
    const f = sma(px, fast);
    const s = sma(px, slow);
    const signals: Signal[] = [];
    const sigArr: (1 | -1 | 0)[] = px.map(() => 0);
    for (let i = 1; i < px.length; i++) {
      const fp = f[i - 1], fc = f[i], sp = s[i - 1], sc = s[i];
      if (fp == null || fc == null || sp == null || sc == null) continue;
      if (fp <= sp && fc > sc) {
        signals.push({ i, kind: "buy", price: px[i], label: "golden cross" });
        sigArr[i] = 1;
      } else if (fp >= sp && fc < sc) {
        signals.push({ i, kind: "sell", price: px[i], label: "death cross" });
        sigArr[i] = -1;
      }
    }
    const bt = backtestLongOnly(px, sigArr);
    const lastF = f[f.length - 1];
    const lastS = s[s.length - 1];
    const trending = lastF != null && lastS != null && lastF > lastS;
    const eqRet = bt.equity[bt.equity.length - 1] - 1;
    return {
      signals,
      metrics: [
        { key: "trend", label: "Trend", value: trending ? "BULL" : "BEAR", tone: trending ? "bull" : "bear" },
        { 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" },
        { key: "ret", label: "Return", value: fmtPct(eqRet), tone: eqRet > 0 ? "bull" : "bear" },
      ],
      overlay: [
        { values: f, color: "var(--bull)", label: `SMA${fast}` },
        { values: s, color: "var(--cyan)", label: `SMA${slow}` },
      ],
      summary: `${signals.length} crosses · ${bt.trades} trades · ${(bt.winRate * 100).toFixed(0)}% win rate · backtest return ${fmtPct(eqRet)}.`,
      beginner:
        "Two moving averages chase each other. When the faster one cuts up through the slow one, money flows in. When it falls below, it cuts losses. Old, simple, surprisingly hard to beat.",
      verdict: {
        side: trending ? "buy" : "sell",
        text: trending
          ? "Fast SMA is above slow SMA. Trend is up — bias long."
          : "Fast SMA is below slow SMA. Trend is down — bias short or stand aside.",
        confidence: Math.min(1, Math.abs((lastF ?? 0) - (lastS ?? 0)) / (px[px.length - 1] || 1) * 20),
      },
      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
  • ·Persistent trends — SPY 2017, NVDA 2023, BTC 2020-21. The crossover catches the early lean and rides it.
  • ·Daily and weekly timeframes. SMAs need enough bars to smooth out noise; intraday is too jumpy.
  • ·Pair-trading and rotation strategies, where you only need a directional bias, not a price target.
✗ Fails when
  • ·Choppy ranges. Two SMAs whipsaw each other — every fake breakout becomes a fresh signal. Win rate craters below 40%.
  • ·Earnings gaps. SMAs lag by definition; the cross usually fires after the move is done.
  • ·Mean-reverting assets like utilities or crude oil futures during contango. Catch a few false trends, lose a lot.
How to read its verdict

BUY when fast SMA > slow SMA (and was below previously). SELL when the reverse. Confidence is proportional to the gap between the two — wider gap means stronger trend. HOLD never fires for SMA Crossover; it's a binary regime signal.

FAQ
What's the best fast/slow combo?+
There isn't one. 12/26 is the MACD default. 50/200 is the textbook 'golden cross' for daily charts. 9/21 is intraday. Lazybull's defaults (12/26) work fine; tune to fit your asset.
Should I trust the backtest win rate?+
Half-trust it. The backtest assumes you exit on the opposite cross, which means you ride drawdowns to zero before the system flips. In practice you'd add a stop. Use the win rate as a shape indicator — high (60%+) means the bot likes the asset; low (under 45%) means try a reversion bot.