Breakout — Donchian channels¶
Classic Turtle-style breakout. Buy when price closes above the highest high of the last N days; exit when it closes below the lowest low of the last M days. This is the strategy that powered Richard Dennis's Turtle Traders in the 1980s.
What it does¶
- Trades Bitcoin on 1-hour candles
- Buys when the close exceeds the highest close of the prior 20 candles
- Exits when the close drops below the lowest close of the prior 10 candles
- Risk-sized at 0.5% of equity per trade using ATR(14) for stop distance
- One position at a time — won't add to winners
Breakout systems make most of their money in 2–3 trades per year — long sustained moves. The other 90% of trades are small losers ("noise"). Win rate is low (30–40%) but the winners are huge.
The strategy file¶
STRATEGY breakout_donchian VERSION 1
LET entryWindow = 20
LET exitWindow = 10
# Risk-sized via LET arithmetic until SIZING N PCT RISK ships in Phase 24
LET stopDist = atr(btc, 14) * 2
LET riskUsd = ACCOUNT.equity * 0.005 # risk 0.5% of equity per trade
LET riskQty = riskUsd / stopDist
SYMBOLS
btc = BACKTEST:BTCUSDT EVERY 1h
RULES
WHEN btc.close > highest(btc.close, entryWindow)
AND POSITION.btc = 0
THEN BUY btc SIZING riskQty
STOP_LOSS AT btc.close - stopDist
LOG "breakout long" high=highest(btc.close, entryWindow) atr=atr(btc, 14)
WHEN btc.close < lowest(btc.close, exitWindow)
AND POSITION.btc > 0
THEN CLOSE btc
LOG "exit on lowest-low"
A few things to notice:
- Risk-sized via
LETarithmetic.riskQty = (equity × risk_pct) / stop_distancegives a position that loses exactly 0.5% of equity if the stop hits. Once Phase 24 lands you'll be able to writeSIZING 0.5 PCT RISK STOP_LOSS …and the engine does this math for you. See Planned features. - No bracket here — the take-profit logic is the exit rule (lowest-low), not a price target. We use a bare
STOP_LOSSinstead of a full bracket. highest(stream.close, N)andlowest(stream.close, N)are the Donchian channel functions; they return the rolling max/min over the last N closed candles.
How to run it¶
./scripts/fetch-dukascopy.sh BTCUSDT 2023-01-01 2024-12-31
qkt backtest strategies/breakout-donchian.qkt --from 2023-01-01 --to 2024-12-31
A 2-year backtest is the minimum for a breakout system — they need enough time to catch at least a few real moves.
What to expect¶
Trades: 48
Final realized: 3,420.00
Win rate: 0.354
Sharpe (daily): 0.78
Max drawdown: -680.50
Avg win: 218.40
Avg loss: -64.20
Largest win: 1,240.00
Note the asymmetry — average win is 3.4× average loss. Sharpe is modest (0.78) because the equity curve is lumpy: long flat periods punctuated by big winners. This is the breakout signature.
How to adapt it¶
Shorter / longer breakout windows¶
The original Turtle System 1 used (20, 10). System 2 used (55, 20):
Longer windows = fewer signals, larger captured moves, longer drawdowns between winners.
Add a long-trend filter¶
The Turtle System never entered against the long-term trend. Add a 200-period filter:
WHEN btc.close > highest(btc.close, entryWindow)
AND btc.close > sma(btc.close, 200) -- in long-term uptrend
AND POSITION.btc = 0
THEN BUY btc SIZING riskQty
STOP_LOSS AT btc.close - stopDist
Short side¶
WHEN btc.close < lowest(btc.close, entryWindow)
AND btc.close < sma(btc.close, 200)
AND POSITION.btc = 0
THEN SELL btc SIZING riskQty
STOP_LOSS AT btc.close + stopDist
WHEN btc.close > highest(btc.close, exitWindow)
AND POSITION.btc < 0
THEN CLOSE btc
Add a pyramid¶
The Turtles added to winners every 0.5 ATR of favorable move. This is what STACK pyramiding is for — see that example.
Different asset¶
Breakouts work especially well on commodities (oil, gold), futures indices, and crypto. Less well on mean-reverting instruments like equity-index ETFs. Try gold:
Common gotchas¶
- Long flat periods are normal. A 6-month drawdown without new highs is part of breakout. Don't quit during the dry spell — that's usually right before the winner.
- Slippage on entry. A breakout fires at the close above the high. The market open the next bar may be much higher. Paper-test against your real broker's slippage profile.
highest(close, N)excludes the current bar. When you ask "highest close of the last 20 bars," qkt looks at the 20 prior closes, not including the bar you're checking against. This is the correct definition — otherwise the rule could never fire (current close can't exceed itself).- Survivorship bias on long histories. Bitcoin's history is a long uptrend with two crashes. A breakout strategy looks great on 2017–2024 data; it may not work the same in 2025+ if the trend regime changes.
What this example demonstrates¶
highest()/lowest()rolling-extreme indicators- Risk-based sizing via
LETarithmetic (theSIZING N PCT RISKshortcut lands in Phase 24) - Bare
STOP_LOSS(noBRACKETbecause the take-profit is rule-driven, not price-driven) - Multiple rules cooperating (one for entry, another for exit)
POSITION.stream = 0/> 0for entry/exit gating