Algorithmic 5-Min EMA & MACD Confluence
Strategy Overview: The Algorithmic 5-Min EMA & MACD Confluence strategy is a sophisticated dual-indicator trend-following model designed to aggressively capture intraday momentum while filtering out false signals. Operating on a 5-minute timeframe, the algorithm utilizes the convergence of a fast 9-period and a slow 21-period Exponential Moving Average (EMA) to establish the primary directional bias. To confirm the validity of this trend, the system mandates simultaneous alignment from the Moving Average Convergence Divergence (MACD) histogram, executing long positions only when the MACD is positive and short positions when it is negative. By demanding this dual-layer verification, the strategy drastically reduces whipsaw losses common in choppy, range-bound markets. The architecture acts as a continuous reversal engine, dynamically flipping its market exposure the moment both indicators signal a regime change, ensuring traders are always positioned in the direction of prevailing order flow. To guarantee strict risk mitigation and prevent severe portfolio drawdowns, it incorporates a mechanical intraday time exit, forcefully squaring off all open trades at exactly 15:15 every trading session. This effectively neutralizes any overnight gap risk and eliminates theta decay exposure. Powered by lightning-fast pandas exponential weighted functions, this quantitative framework offers institutional-grade precision for derivative traders.
ewm (Exponential Weighted Functions) to calculate the MACD and EMA dynamically. It automatically aggregates your base 1-minute data into a 5-minute timeframe for smoother signal generation.
1. Imports and Configuration
Summary: This section imports required libraries and defines the core backtest configuration. It sets the 5-minute timeframe, designates the parameters for the dual indicators (9/21 EMAs and 12/26/9 MACD), and enforces a mandatory 15:15 end-of-day exit.
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"
FAST_EMA = 9
SLOW_EMA = 21
MACD_FAST = 12
MACD_SLOW = 26
MACD_SIGNAL = 9
# ==================================================
2. Data Loading and Preparation
Summary: Reads the historical price data from a CSV, verifies essential OHLC columns, converts strings to datetime objects, and resamples the minute-level data into clean 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()
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. EMA & MACD Indicator Calculations
Summary: Computes the fast and slow Exponential Moving Averages (EMA) to establish trend direction. It also calculates the MACD line, signal line, and histogram using pandas’ exponentially weighted functions (ewm) to identify momentum shifts.
print("Calculating EMA and MACD indicators...")
df["ema_fast"] = df["close"].ewm(span=FAST_EMA, adjust=False).mean()
df["ema_slow"] = df["close"].ewm(span=SLOW_EMA, adjust=False).mean()
macd_fast_line = df["close"].ewm(span=MACD_FAST, adjust=False).mean()
macd_slow_line = df["close"].ewm(span=MACD_SLOW, adjust=False).mean()
df["macd_line"] = macd_fast_line - macd_slow_line
df["macd_signal"] = df["macd_line"].ewm(span=MACD_SIGNAL, adjust=False).mean()
df["macd_hist"] = df["macd_line"] - df["macd_signal"]
4. Utility Functions
Summary: Contains helper functions for backtest evaluation. The stats function compiles win rates and PnL metrics, while close_out correctly 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 constantly monitors for an automated 15:15 market exit to prevent overnight exposure, initiates positions when the EMA crossover strictly aligns with MACD histogram momentum, and dynamically manages continuous position reversals as market regimes flip.
# -------------- STRATEGY: EMA + MACD --------------
def backtest_ema_macd(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":"EMA_MACD", "dir":direction, "entry":entry,
"exit":r["close"], "pnl":pnl, "outcome":"TIME_EXIT"
})
in_trade = False
continue
if pd.isna(r["ema_fast"]) or pd.isna(r["ema_slow"]) or pd.isna(r["macd_hist"]):
continue
# Signal Logic
uptrend = (r["ema_fast"] > r["ema_slow"]) and (r["macd_hist"] > 0)
downtrend = (r["ema_fast"] < r["ema_slow"]) and (r["macd_hist"] < 0)
# 2. ENTRY LOGIC
if not in_trade:
if uptrend:
direction = "long"
entry = r["close"]
entry_time = r["dt"]
in_trade = True
elif downtrend:
direction = "short"
entry = r["close"]
entry_time = r["dt"]
in_trade = True
# 3. REVERSAL LOGIC
else:
if direction=="long" and downtrend:
exit_price = r["close"]
pnl = (exit_price - entry) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"EMA_MACD", "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 uptrend:
exit_price = r["close"]
pnl = (entry - exit_price) - SLIPPAGE
trades.append({
"date": r["d"], "entry_time": entry_time, "exit_time": r["dt"],
"strategy":"EMA_MACD", "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: Executes the backtest, prints high-level terminal output, and formats the transaction log into a multi-sheet Excel workbook (ema_macd_results.xlsx) detailing weekly performance, monthly metrics, and drawdowns.
# -------------- EXECUTION & REPORTING --------------
macd_trades = backtest_ema_macd(df)
summary = {"EMA_MACD": stats(macd_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']}")
output_file = "ema_macd_results.xlsx"
print(f"\nSaving results to {output_file}...")
with pd.ExcelWriter(output_file, engine="xlsxwriter") as writer:
if macd_trades:
trades_df = pd.DataFrame(macd_trades)
trades_df.to_excel(writer, sheet_name="EMA_MACD", 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)
