Donchian Channels are a rolling max and min — two lines of pandas. The only thing to get right is the prior-bar shift for breakout signals, as the explainer warned. Here’s the code.
What you’ll need
- Python 3.9+,
pandas high,lowseries.
The channel
import pandas as pd
def donchian(high, low, period: int = 20):
upper = high.rolling(period).max()
lower = low.rolling(period).min()
middle = (upper + lower) / 2
return pd.DataFrame({"upper": upper, "middle": middle, "lower": lower})
rolling(period).max() and .min() are the whole indicator — the stepped bands
from the output chart.
The breakout signal (mind the shift)
For a breakout you must compare price to the channel excluding the current bar — otherwise today’s high is inside its own channel and can never break out:
dc = donchian(df["high"], df["low"], 20)
# Prior channel: shift the bands back one bar.
prior_upper = dc["upper"].shift(1)
prior_lower = dc["lower"].shift(1)
df["breakout_long"] = df["close"] > prior_upper # new 20-bar high
df["breakout_short"] = df["close"] < prior_lower
breakout_long fires on a genuine new 20-bar high — the classic Donchian entry to
backtest in AlgoGen.
A Turtle-style system: asymmetric channels
The Turtles entered on a long channel and exited on a shorter one:
entry = donchian(df["high"], df["low"], 20)
exit_ = donchian(df["high"], df["low"], 10)
go_long = df["close"] > entry["upper"].shift(1)
exit_long = df["close"] < exit_["lower"].shift(1) # 10-bar low ends the long
Gotchas
- Shift for signals, not for display. The plotted channel uses the current bar; the signal uses the prior-bar channel. Mixing these up is the #1 Donchian bug — either your breakouts never fire, or they look like they trigger every bar.
- Highs and lows, not closes. The channel is built from
highandlow; some breakout variants trigger on a close beyond the channel (fewer false breaks) vs an intrabar high/low touch (earlier, noisier). Decide which. - Range whipsaw. Expect many false breakouts in sideways markets — that’s the nature of the tool, not a bug.
The lazy one-liner
import pandas_ta as ta
df.ta.donchian(lower_length=20, upper_length=20, append=True) # DCL/DCM/DCU
Same indicator elsewhere: MQL5, Pine Script, EasyLanguage, NinjaScript.
This post is educational, not financial advice. Indicators describe the past; they don’t predict the future. Backtest anything before you risk real money on it.
Sources and further reading
- Windowing operations (pandas documentation)
