LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/Donchian Breakout
Trend & Momentumid · donchian

Donchian Breakout

The Turtle trade — buy n-day highs, sell n-day lows.

In plain English

Drew lines through the highest high and lowest low of the last 20 days. When price punches above the top line, the herd just turned bullish — buy. When it falls below the bottom, bears took over — sell. The Turtle Traders made fortunes on this rule in the 1980s.

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

Drew lines through the highest high and lowest low of the last N days. Price punching above the top line = the herd just turned bullish. Below the bottom = bearish breakdown. The Turtle Traders made fortunes on this exact rule in the 1980s. Still works wherever trends persist.

The math
formula
buy if close > max(high, n); sell if close < min(low, n)
parameters
periodPeriodrange 10 → 100default · 20
Live demo

Real Donchian Breakout 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 232295
TypeScript · MIT-licensed
const donchianBot: BotDef = {
  id: "donchian",
  name: "Donchian Breakout",
  category: "trend",
  glyph: "⊕",
  tagline: "The Turtle trade — buy n-day highs, sell n-day lows.",
  formula: "buy if close > max(high, n); sell if close < min(low, n)",
  params: [
    { key: "period", label: "Period", kind: "number", default: 20, min: 10, max: 100, step: 1 },
  ],
  run: (ctx, p): BotResult => {
    const period = Math.round(num(p, "period", 20));
    const px = closes(ctx.candles);
    const ch = donchian(ctx.candles, period);
    const signals: Signal[] = [];
    const sigArr: (1 | -1 | 0)[] = px.map(() => 0);
    for (let i = period; i < px.length; i++) {
      // Use prior bar's channel to avoid look-ahead
      const u = ch.upper[i - 1];
      const l = ch.lower[i - 1];
      if (u == null || l == null) continue;
      if (px[i] > u) {
        signals.push({ i, kind: "buy", price: px[i], label: `${period}d high` });
        sigArr[i] = 1;
      } else if (px[i] < l) {
        signals.push({ i, kind: "sell", price: px[i], label: `${period}d low` });
        sigArr[i] = -1;
      }
    }
    const bt = backtestLongOnly(px, sigArr);
    const last = px[px.length - 1];
    const u = ch.upper[ch.upper.length - 1] ?? last;
    const l = ch.lower[ch.lower.length - 1] ?? last;
    const range = u - l;
    const pos = range === 0 ? 0.5 : (last - l) / range;
    return {
      signals,
      metrics: [
        { key: "upper", label: "Upper", value: fmtNum(u), tone: "bull" },
        { key: "lower", label: "Lower", value: fmtNum(l), tone: "bear" },
        { key: "pos", label: "Position", value: `${(pos * 100).toFixed(0)}%`, tone: pos > 0.7 ? "bull" : pos < 0.3 ? "bear" : "neutral" },
        { key: "ret", label: "Return", value: fmtPct(bt.equity[bt.equity.length - 1] - 1), tone: "info" },
      ],
      overlay: [
        { values: ch.upper, color: "var(--bull)", label: "Upper", dashed: true },
        { values: ch.lower, color: "var(--bear)", label: "Lower", dashed: true },
      ],
      summary: `${period}-bar channel ${l.toFixed(2)}–${u.toFixed(2)}. ${signals.length} breakouts.`,
      beginner:
        "Drew lines through the highest high and lowest low of the last N days. Price punching above the top line = the herd just turned bullish. The Turtle Traders made fortunes on this exact rule.",
      verdict: {
        side: pos > 0.85 ? "buy" : pos < 0.15 ? "sell" : "hold",
        text:
          pos > 0.85
            ? "Price near the top of the channel — breakout territory."
            : pos < 0.15
            ? "Price near the bottom — breakdown territory."
            : "Price mid-channel — wait for a break.",
        confidence: Math.abs(pos - 0.5) * 2,
      },
      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
  • ·Commodity futures. The original Turtle setup — trend-following on uncorrelated futures — still produces real risk-adjusted returns.
  • ·Crypto. BTC's history is a series of 20-day breakouts that turn into 6-month moves.
  • ·Position-trading timeframes (weeks to months). The 20-day default is the textbook Turtle window.
✗ Fails when
  • ·Range-bound markets. Channel breaks are false signals; the price punches above and immediately rolls back.
  • ·Single-name stocks during earnings season. Gaps create artificial breakouts.
  • ·Short windows. A 5-day Donchian on minute data fires constantly and means nothing.
How to read its verdict

BUY when close > upper channel of the prior bar. SELL when close < lower channel. The 'position' metric (where the price sits in the channel) tells you how late you are to the breakout — 100% means at the top, 0% at the bottom. Confidence is |position - 0.5| × 2.

FAQ
What lookback should I use?+
20 is the System 1 Turtle setting. 55 is System 2 — slower, fewer false breaks, larger profits per trade. For crypto try 14-21; for futures 20-55; for stocks 20 is fine but consider pairing with a 20-day exit on the opposite channel for a clean two-line system.
Doesn't this cause look-ahead bias?+
The bot uses the prior bar's channel to avoid that. So the breakout is computed against yesterday's channel, fired today. That's the textbook implementation.