Global Macro 30-Min Inside Bar Breakout
Strategy Overview: The Global Macro 30-Min Inside Bar Breakout is a potent, pure price-action framework engineered to exploit volatility compression and subsequent expansion. Completely devoid of lagging indicators, this algorithm systematically scans the 30-minute timeframe to identify “Inside Bars”βperiods where price action is entirely engulfed by the preceding “Mother Bar,” signaling a state of market equilibrium and coiled energy. When the Nifty aggressively breaks above the Mother Bar’s high, the script fires a long entry to capture the bullish expansion. Conversely, a decisive breakdown below the Mother Bar’s low triggers a short position to ride the bearish cascade. The strategy utilizes robust asymmetric risk management, targeting 100-point profit zones while rigidly capping drawdowns with a 50-point stop loss. To ensure seamless execution, it employs a dynamic tracking system that forward-fills the Mother Bar parameters until a breakout materializes. A built-in reversal mechanism guarantees that if a false breakout occurs, the algorithm instantly flips its directional bias to catch the true momentum wave. Finally, an automated 15:15 time-based exit protocol aggressively squares off all open positions, neutralizing overnight gap risks and locking in intraday alpha.
.shift(1) and .ffill() to correctly map and persist the Mother Bar highs and lows until a definitive price action breakout occurs.
1. Imports and Configuration
Summary: This section establishes the base libraries and configurable variables. It configures the algorithm to run on a 30-minute timeframe, enforces the 15:15 automated exit, and defines the structural risk parameters (50-point stop loss and 100-point profit).
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 = "30min"
# Risk Management
STOP_LOSS_POINTS = 50.0
TARGET_POINTS = 100.0
# ==================================================
2. Data Loading and Preparation
Summary: This segment reliably processes historical CSV data by parsing date strings into standardized datetime objects, converting price fields into numeric values, eliminating missing entries, and aggregating minute-level data into well-structured 30-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. Inside Bar Pattern Recognition
Summary: The core logic that shifts the data to compare current bars against previous ones. It flags a candle as an “inside bar” if its high is lower than the previous high and its low is higher than the previous low. Crucially, it then uses ffill() to persist those Mother Bar limits until a breakout trigger fires.
# ----------------- INDICATORS --------------------
print("Finding Inside Bars and Mother Bar levels...")
# Shift data to compare with the previous candle
df["prev_high"] = df["high"].shift(1)
df["prev_low"] = df["low"].shift(1)
df["prev_close"] = df["close"].shift(1)
# An inside bar has a lower high and a higher low than the preceding candle
is_inside = (df["high"] < df["prev_high"]) & (df["low"] > df["prev_low"])
# Store the Mother Bar High and Low when an inside bar is detected
df["mother_high"] = np.where(is_inside, df["prev_high"], np.nan)
df["mother_low"] = np.where(is_inside, df["prev_low"], np.nan)
# Forward fill the levels so they remain active until a new inside bar forms
df["mother_high"] = df["mother_high"].ffill()
df["mother_low"] = df["mother_low"].ffill()
4. Utility Functions
Summary: Helper functions for evaluating backtest health. The stats block computes win rate and general PnL, while close_out systematically records long versus short point differentials 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: Iterates row by row through the market data. It calculates SL/TP conditions dynamically, manages the 15:15 structural time exit, and initiates positions when a confirmed crossover of the Mother Bar limits takes place. If a breakout collapses back through the opposite bound, the system executes an automatic reversal trade.
# -------------- STRATEGY: INSIDE BAR BREAKOUT --------------
def backtest_inside_bar(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":"INSIDE_BAR", "dir":direction, "entry":entry,
"exit":r["close"], "pnl":pnl, "outcome":"TIME_EXIT"
})
in_trade = False
continue
if pd.isna(r["mother_high"]) or pd.isna(r["mother_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":"INSIDE_BAR", "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 - Ensure it's a fresh crossover, not just sitting above/below the line
breakout_up = (r["close"] > r["mother_high"]) and (r["prev_close"] <= r["mother_high"])
breakout_down = (r["close"] < r["mother_low"]) and (r["prev_close"] >= r["mother_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":"INSIDE_BAR", "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":"INSIDE_BAR", "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 execution trigger. Once the backtest loops complete, it outputs high-level data to the console and subsequently processes all individual trade logs into a segmented Excel file (inside_bar_results.xlsx) for deep dive drawdown and weekly/monthly performance reviews.
# -------------- EXECUTION & REPORTING --------------
ib_trades = backtest_inside_bar(df)
summary = {"INSIDE_BAR": stats(ib_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 = "inside_bar_results.xlsx"
print(f"\nSaving results to {output_file}...")
with pd.ExcelWriter(output_file, engine="xlsxwriter") as writer:
if ib_trades:
trades_df = pd.DataFrame(ib_trades)
trades_df.to_excel(writer, sheet_name="INSIDE_BAR", 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)
