Index 5-Min Bollinger Bands Mean Reversion

🦊 Premium Script

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.

πŸ“ˆ EXPECTED WIN RATE
44.85%
πŸ“‰ MAX DRAWDOWN
995 Pts
πŸ’° TOTAL PNL
+8,551.3 Pts
πŸ”„ TOTAL TRADES
2736
πŸ† TOTAL WINS
1227
!
Important This algorithm specifically utilizes the rolling(window=20) function in Pandas to calculate the Simple Moving Average and standard deviations dynamically, resampled to 5-minute OHLC structures.
ℹ️
Disclaimer These results are generated based on automated backtesting performed using Python code and algorithms. Actual results may vary, and manual backtesting outcomes could differ due to varying assumptions, data interpretation, and market conditions. This information is provided for educational and informational purposes only and should not be considered as financial advice. Before making any trading or investment decisions, please consult with a qualified financial advisor or professional.

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).

python script Copy Code
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.

python script Copy Code
# ----------------- 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.

python script Copy Code
# ----------------- 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.

python script Copy Code
# -------------- 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.

python script Copy Code
# -------------- 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.

python script Copy Code
# -------------- 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.")