Volatility 5-Min Donchian Channel Breakout
Strategy Overview: The Volatility 5-Min Donchian Channel Breakout is a pure price-action volatility system designed to capture explosive intraday momentum. It completely bypasses lagging moving averages by utilizing a 20-period lookback window to map the absolute highest highs and lowest lows, dynamically plotting precise breakout thresholds. When the Nifty decisively closes above the upper Donchian band, the algorithm fires a long signal to ride the breakout surge. Conversely, a breakdown below the lower channel boundary triggers a short position, capitalizing on downward cascades and support failures . Unlike pure continuous-reversal systems, this algorithm integrates an asymmetric 1:2 risk-reward architectureâenforcing strict 50-point stop losses and 100-point take-profit targets. To ensure statistical integrity, the code utilizes a shift mechanism (shift(1)) to calculate the exact previous highs/lows, eliminating forward-looking data leakage. If price action aggressively fades a breakout, a built-in reversal protocol will seamlessly flip the directional bias. By stripping away oscillating indicators and focusing strictly on raw market structure, it provides an unfiltered lens into true supply and demand dynamics. As with all our intraday models, a hard 15:15 square-off sequence neutralizes all active positions to immunize your capital from unpredictable overnight gap risk.
rolling().max().shift(1) functions to dynamically map support and resistance channels, preventing forward-looking data leakage during backtesting.
1. Imports and Configuration
Summary: This section imports required libraries and defines the core backtest parameters. It sets the 5-minute timeframe, establishes a 20-period lookback for the Donchian Channels, enforces a 15:15 intraday exit time, and sets a strict 50-point stop loss alongside a 100-point target.
import pandas as pd
import numpy as np
import datetime as dt
# ===================== CONFIG =====================
FILE_PATH = "Index_1_minute.csv"
DAYFIRST = True
EXIT_TIME = dt.time(15, 15)
SLIPPAGE = 0.5
# Strategy params
TIMEFRAME = "5min"
DONCHIAN_PERIOD = 20 # Lookback period for Highest High / Lowest Low
# Risk Management
STOP_LOSS_POINTS = 50.0
TARGET_POINTS = 100.0
# ==================================================
2. Data Loading and Preparation
Summary: This segment safely handles the ingestion of historical CSV data. It converts raw date strings into standard datetime objects, coerces price information to numeric formats, removes nulls, and accurately resamples the minute data into structured 5-minute OHLC candles.
# ----------------- LOAD & PREP --------------------
print("Loading and preparing data...")
df = pd.read_csv(FILE_PATH)
df.columns = [c.strip().lower() for c in df.columns]
required = {"date","open","high","low","close"}
missing = required - set(df.columns)
if missing:
raise ValueError(f"CSV missing columns: {missing}. Need exactly {sorted(required)}")
df["dt"] = pd.to_datetime(df["date"], dayfirst=DAYFIRST)
df = df.sort_values("dt").reset_index(drop=True)
for c in ["open","high","low","close"]:
df[c] = pd.to_numeric(df[c], errors="coerce")
df = df.dropna(subset=["open","high","low","close","dt"]).copy()
# Resampling
df = df.set_index("dt").resample(TIMEFRAME).agg({
"open": "first",
"high": "max",
"low": "min",
"close": "last"
}).dropna().reset_index()
df["d"] = df["dt"].dt.date
df["t"] = df["dt"].dt.time
3. Donchian Channel Indicator Logic
Summary: This is where the core indicator logic resides. By utilizing Pandas’ rolling functions, it dynamically maps the absolute highest high and lowest low of the previous 20 periods. The .shift(1) method is critical here to prevent forward-looking data leakage during backtesting.
# ----------------- INDICATORS --------------------
print("Calculating Donchian Channels (Price Action)...")
# Calculate rolling highest high and lowest low of the PREVIOUS N periods
df["donchian_high"] = df["high"].rolling(window=DONCHIAN_PERIOD).max().shift(1)
df["donchian_low"] = df["low"].rolling(window=DONCHIAN_PERIOD).min().shift(1)
4. Utility Functions
Summary: Provides clean helper logic for backtesting. The stats function aggregates all executed trades into high-level metrics like Win Rate and Average PnL, whilst close_out universally structures point calculation for both long and short positions upon trade exit.
# -------------- UTILITIES ----------------
def stats(trades):
if not trades:
return {"trades":0,"wins":0,"win_rate":0.0,"avg_pnl":0.0,"total_pnl":0.0}
pnl = np.array([x["pnl"] for x in trades])
wins = (pnl > 0).sum()
return {
"trades": len(trades),
"wins": int(wins),
"win_rate": round(100*wins/len(trades), 2),
"avg_pnl": round(pnl.mean(), 2),
"total_pnl": round(pnl.sum(), 2),
}
def close_out(direction, entry, row_close):
return (row_close - entry) if direction=="long" else (entry - row_close)
5. The Core Backtest Engine
Summary: The central logic loop. It executes trades sequentially based on Donchian band breakouts. In addition to monitoring hard Stop-Loss/Take-Profit triggers, it facilitates an automatic 15:15 market-close liquidation. Dynamic positional flips occur instantly if price reverses through opposite channel extremes.
# -------------- STRATEGY: DONCHIAN BREAKOUT --------------
def backtest_donchian_breakout(df):
print("Running backtest loop...")
trades = []
in_trade = False
direction = None
entry = None
entry_time = None
for _, r in df.iterrows():
# 1. INTRADAY TIME EXIT
if r["t"] >= EXIT_TIME:
if in_trade:
pnl = close_out(direction, entry, r["close"]) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"DONCHIAN", "dir":direction, "entry":entry,
"exit":r["close"], "pnl":pnl, "outcome":"TIME_EXIT"
})
in_trade = False
continue
if pd.isna(r["donchian_high"]) or pd.isna(r["donchian_low"]):
continue
# 2. SL / TP LOGIC
if in_trade:
sl_hit = False
tp_hit = False
exit_price = None
outcome = None
# Check conservative High/Low hits within the current candle
if direction == "long":
if r["low"] <= entry - STOP_LOSS_POINTS:
sl_hit = True
exit_price = entry - STOP_LOSS_POINTS
outcome = "STOP_LOSS"
elif r["high"] >= entry + TARGET_POINTS:
tp_hit = True
exit_price = entry + TARGET_POINTS
outcome = "TARGET"
elif direction == "short":
if r["high"] >= entry + STOP_LOSS_POINTS:
sl_hit = True
exit_price = entry + STOP_LOSS_POINTS
outcome = "STOP_LOSS"
elif r["low"] <= entry - TARGET_POINTS:
tp_hit = True
exit_price = entry - TARGET_POINTS
outcome = "TARGET"
# If stopped out or target reached, close the trade
if sl_hit or tp_hit:
pnl = close_out(direction, entry, exit_price) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"DONCHIAN", "dir":direction, "entry":entry,
"exit":exit_price, "pnl":pnl, "outcome":outcome
})
in_trade = False
direction = None
entry = None
entry_time = None
# Skip entry logic on the same candle we hit SL/TP
continue
# Signal Logic
breakout_up = r["close"] > r["donchian_high"]
breakout_down = r["close"] < r["donchian_low"]
# 3. ENTRY LOGIC
if not in_trade:
if breakout_up:
direction = "long"
entry = r["close"]
entry_time = r["dt"]
in_trade = True
elif breakout_down:
direction = "short"
entry = r["close"]
entry_time = r["dt"]
in_trade = True
# 4. REVERSAL LOGIC
else:
if direction=="long" and breakout_down:
exit_price = r["close"]
pnl = (exit_price - entry) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"DONCHIAN", "dir":"long", "entry":entry,
"exit":exit_price, "pnl":pnl, "outcome":"REVERSE"
})
# Reverse to short
direction = "short"
entry = r["close"]
entry_time = r["dt"]
elif direction=="short" and breakout_up:
exit_price = r["close"]
pnl = (entry - exit_price) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"DONCHIAN", "dir":"short", "entry":entry,
"exit":exit_price, "pnl":pnl, "outcome":"REVERSE"
})
# Reverse to long
direction = "long"
entry = r["close"]
entry_time = r["dt"]
return trades
6. Execution and Reporting
Summary: Acts as the system’s runtime orchestrator. It triggers the backtest engine, displays terminal summaries, then structures the detailed timeline of trades into an Excel workbook complete with dedicated Weekly, Monthly, and Drawdown tracking tabs.
# -------------- EXECUTION & REPORTING --------------
donchian_trades = backtest_donchian_breakout(df)
summary = {"DONCHIAN": stats(donchian_trades)}
print(f"â
Backtest complete ({TIMEFRAME} candles)")
for name, s in summary.items():
print(f"\n{name} -> Trades: {s['trades']}, Wins: {s['wins']}, WinRate: {s['win_rate']}%, "
f"AvgPnL: {s['avg_pnl']}, TotalPnL: {s['total_pnl']}")
# Write results to Excel
output_file = "donchian_breakout_results.xlsx"
print(f"\nSaving results to {output_file}...")
with pd.ExcelWriter(output_file, engine="xlsxwriter") as writer:
if donchian_trades:
trades_df = pd.DataFrame(donchian_trades)
trades_df.to_excel(writer, sheet_name="DONCHIAN", index=False)
trades_df["date_dt"] = pd.to_datetime(trades_df["date"])
trades_df["week"] = trades_df["date_dt"].dt.to_period("W-FRI")
weekly = trades_df.groupby("week", as_index=False)["pnl"].sum()
weekly.rename(columns={"pnl": "total_points"}, inplace=True)
weekly["week"] = weekly["week"].astype(str)
weekly.to_excel(writer, sheet_name="Weekly_Report", index=False)
trades_df["month"] = trades_df["date_dt"].dt.to_period("M")
monthly = trades_df.groupby("month", as_index=False)["pnl"].sum()
monthly.rename(columns={"pnl": "total_points"}, inplace=True)
monthly["month"] = monthly["month"].astype(str)
monthly.to_excel(writer, sheet_name="Monthly_Report", index=False)
dd_rows = []
for m, mdf in trades_df.groupby("month"):
mdf = mdf.sort_values("exit_time")
cum = mdf["pnl"].cumsum()
max_dd = cum.min()
dd_rows.append({"month": str(m), "max_drawdown": max_dd})
dd_df = pd.DataFrame(dd_rows)
dd_df.to_excel(writer, sheet_name="Drawdown_Report", index=False)
pd.DataFrame([{"strategy": k, **v} for k, v in summary.items()]).to_excel(
writer, sheet_name="Summary", index=False
)
print("Process finished successfully.")
Browse the Full Quantitative Repository:
- â Intraday 10/30 Moving Average Crossover
- â Index 5-Min Bollinger Bands Mean Reversion
- â Algorithmic 5-Min EMA & MACD Confluence
- â Equity 5-Min RSI Momentum & EMA Trend
- â Volatility 5-Min Donchian Channel Breakout
- â Global Macro 30-Min Inside Bar Breakout
- â Institutional 15-Min Opening Range Breakout
- â Quantitative 5-Min Daily Floor Pivots Breakout
- â Price Action 30-Min Candlestick Engulfing Pattern
- â Systematic 5-Min Pin Bar Reversal (Hammer & Shooting Star)
