Index 5-Min Bollinger Bands Mean Reversion
Strategy Overview: A sophisticated quantitative algorithm that operates on the principle of intraday mean reversion, capitalizing on extreme standard deviation extensions. Utilizing a 20-period Simple Moving Average coupled with a 2.0 standard deviation parameter, this system mathematically identifies overbought and oversold market extremes on the 5-minute timeframe. Instead of traditional trend-following, it executes contrarian reversalsβinitiating short positions when prices pierce the upper Bollinger Band and firing long signals when prices collapse below the lower band. By exploiting the ‘rubber band’ effect of price action snapping back to its mean, the algorithm systematically fades irrational volatility spikes. To mitigate overnight gap risks and theta decay, all active positions are forcefully squared off at precisely 15:15 every trading session. The underlying Python architecture leverages high-performance pandas vectorization for instantaneous calculations, making it an exceptional framework for quantitative traders looking to automate volatility-fade setups in highly liquid derivative markets.
rolling(window=20) function in Pandas to calculate the Simple Moving Average and standard deviations dynamically, resampled to 5-minute OHLC structures.
1. Imports and Configuration
Summary: Establishes base libraries and configures the backtest parameters. It sets the 5-minute timeframe, defines the 20-period Bollinger Bands with a 2.0 standard deviation, and strictly enforces the 15:15 automated exit time. (Note: As a pure continuous reversal system, hard stop-losses/take-profits are intentionally omitted from this config).
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"
BB_PERIOD = 20
BB_STD = 2.0
# ==================================================
2. Data Loading and Preparation
Summary: 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 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. Bollinger Bands Calculation
Summary: This section computes the core mean-reversion indicators. It calculates a 20-period Simple Moving Average (SMA) along with the rolling standard deviation. It then projects the Upper and Lower Bollinger Bands at a 2.0 standard deviation distance from the SMA.
# ----------------- INDICATORS --------------------
print("Calculating Bollinger Bands...")
# Calculate 20-period SMA and Standard Deviation
df["sma"] = df["close"].rolling(window=BB_PERIOD).mean()
df["std"] = df["close"].rolling(window=BB_PERIOD).std()
# Calculate Upper and Lower Bands
df["upper_band"] = df["sma"] + (BB_STD * df["std"])
df["lower_band"] = df["sma"] - (BB_STD * df["std"])
4. Utility Functions
Summary: Helper functions essential for backtesting mechanics. The stats function aggregates the completed trades into performance metrics like win rate and total PnL, while close_out calculates point differentials for both long and short position exits.
# -------------- 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 primary backtesting iteration loop. It executes a pure contrarian strategyβfiring short positions when price pierces the upper band (overbought) and long positions when it drops below the lower band (oversold). It constantly monitors for an automated 15:15 market exit and handles continuous position reversals as market extremes alternate.
# -------------- STRATEGY: BOLLINGER BANDS --------------
def backtest_bollinger(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":"BB_TREND", "dir":direction, "entry":entry,
"exit":r["close"], "pnl":pnl, "outcome":"TIME_EXIT"
})
in_trade = False
continue
if pd.isna(r["sma"]) or pd.isna(r["std"]):
continue
# Signal Logic
oversold = r["close"] < r["lower_band"]
overbought = r["close"] > r["upper_band"]
# 2. ENTRY LOGIC
if not in_trade:
if oversold:
direction = "short"
entry = r["close"]
entry_time = r["dt"]
in_trade = True
elif overbought:
direction = "long"
entry = r["close"]
entry_time = r["dt"]
in_trade = True
# 3. REVERSAL LOGIC
else:
#
if direction=="long" and oversold:
exit_price = r["close"]
pnl = (exit_price - entry) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"BB_TREND", "dir":"long", "entry":entry,
"exit":exit_price, "pnl":pnl, "outcome":"REVERSE"
})
#
direction = "short"
entry = r["close"]
entry_time = r["dt"]
#
elif direction=="short" and overbought:
exit_price = r["close"]
pnl = (entry - exit_price) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"BB_TREND", "dir":"short", "entry":entry,
"exit":exit_price, "pnl":pnl, "outcome":"REVERSE"
})
#
direction = "long"
entry = r["close"]
entry_time = r["dt"]
return trades
6. Execution and Reporting
Summary: Executes the backtest, prints high-level terminal output, and formats the transaction log into a multi-sheet Excel workbook (bb_trend_results.xlsx) detailing weekly performance, monthly metrics, and drawdowns.
# -------------- EXECUTION & REPORTING --------------
bb_trades = backtest_bollinger(df)
summary = {"BB_TREND": stats(bb_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 = "bb_trend_results.xlsx"
print(f"\nSaving results to {output_file}...")
with pd.ExcelWriter(output_file, engine="xlsxwriter") as writer:
if bb_trades:
trades_df = pd.DataFrame(bb_trades)
trades_df.to_excel(writer, sheet_name="BB_TREND", 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)
