Institutional 5-Min Opening Range Breakout (ORB)
Strategy Overview: The Institutional 5-Min Opening Range Breakout (ORB) is a premier volatility expansion strategy designed to capture explosive morning momentums. Operating on the premise that institutional order flow dictates the daily trend within the first 15 minutes, this algorithm meticulously maps the highest high and lowest low from 9:15 to 9:30 AMs. Once the initial balance is established, the system remains coiled and waits for a decisive breakouts. A price surge above the opening range high triggers an aggressive long position, while a collapse below the range low initiates a short bias. By eliminating lagging indicators, the strategy focuses entirely on pure price action and immediate market supply/demand imbalances. To protect against sudden mean reversion, the framework integrates an asymmetric 1:2 risk-reward model, strictly enforcing a 50-point stop loss while targeting a 100-point profit zone. Furthermore, it embeds an agile reversal mechanism to instantly flip directions if the initial breakout proves to be a false traps. This highly disciplined approach culminates with a mandatory 15:15 square-off protocol, ensuring zero overnight exposure to unpredictable market gaps and theta decay, making it an indispensable tool for intraday quantitative traders.
1. Imports and Configuration
Summary: This section imports required libraries and defines fundamental backtesting parameters. It establishes the 5-minute timeframe, designates the critical 9:15 to 9:30 AM opening range window, enforces a 15:15 intraday exit time, and sets a rigid 50-point stop loss alongside a 100-point profit 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"
ORB_START = dt.time(9, 15) # Start of the opening range
ORB_END = dt.time(9, 30) # End of the opening range (15-min ORB)
# Risk Management
STOP_LOSS_POINTS = 50.0
TARGET_POINTS = 100.0
# ==================================================
2. Data Loading and Preparation
Summary: This module efficiently processes historical CSV data by parsing date strings into standardized datetime formats, converting price fields to numeric values, removing missing data, and resampling the dataset into precise 5-minute 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. Opening Range Calculation
Summary: This code zeroes in on the first 15 minutes of the trading session. It groups the data by day to find the absolute highest and lowest price points during this critical window, establishing the orb_high and orb_low boundaries, and then maps these levels onto the rest of the intraday data.
# ----------------- INDICATORS --------------------
print("Calculating Opening Range Breakout (ORB) levels...")
# Filter the data to only include the opening range window
orb_df = df[(df["t"] >= ORB_START) & (df["t"] < ORB_END)]
# Group by date to find the highest high and lowest low of that specific morning window
orb_levels = orb_df.groupby("d").agg(
orb_high=("high", "max"),
orb_low=("low", "min")
).reset_index()
# Merge the ORB levels back into our main dataframe
df = df.merge(orb_levels, on="d", how="left")
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.
# -------------- 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 skips the initial 15-minute range formation window, waiting to execute trades sequentially based on 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 breaks down in the opposite direction.
# -------------- STRATEGY: OPENING RANGE BREAKOUT --------------
def backtest_orb(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":"ORB", "dir":direction, "entry":entry,
"exit":r["close"], "pnl":pnl, "outcome":"TIME_EXIT"
})
in_trade = False
continue
# Skip trading if we are still inside the opening range formation time
if r["t"] < ORB_END:
continue
if pd.isna(r["orb_high"]) or pd.isna(r["orb_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":"ORB", "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 beyond the opening range levels
breakout_up = r["close"] > r["orb_high"]
breakout_down = r["close"] < r["orb_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":"ORB", "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":"ORB", "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 . 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 --------------
orb_trades = backtest_orb(df)
summary = {"ORB": stats(orb_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 = "orb_results.xlsx"
print(f"\nSaving results to {output_file}...")
with pd.ExcelWriter(output_file, engine="xlsxwriter") as writer:
if orb_trades:
trades_df = pd.DataFrame(orb_trades)
trades_df.to_excel(writer, sheet_name="ORB", 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)
