Donchian Breakout
The Turtle trade — buy n-day highs, sell n-day lows.
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.
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.
buy if close > max(high, n); sell if close < min(low, n)
Real Donchian Breakout bot, running on real Yahoo data when the symbol is available. Drag the params — the bot re-runs instantly.
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.
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,
};
},
};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.
- Copy the whole block above.
- On /quant, click + Import your bot in the bot library.
- Paste, hit save. It hot-loads into your workspace.
- Edit any param defaults or logic to your taste — it's now yours.
- ·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.
- ·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.
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.
What lookback should I use?+
Doesn't this cause look-ahead bias?+
Buy when fast moving average crosses above slow.
Volatility envelopes around a moving average.
Linear regression line + std-deviation envelopes.
Momentum from EMA difference & its signal line.
Catch oversold bounces, fade overbought spikes.