MACD Histogram Strategy for MetaTrader 5:
Working MQL5 Code + Backtest
This strategy enters long when the MACD histogram crosses above zero with volume above its 20-period average. It enters short when the histogram crosses below zero with the same volume confirmation. The volume filter ensures that only crosses backed by meaningful market participation trigger trades. This is particularly effective on EURUSD 4H where MACD signals are frequent but many lack follow-through. The strategy uses a fixed stop-loss based on ATR and targets a 2:1 reward-to-risk ratio for exits.
What is MACD Histogram Zero-Line Crossover with Volume?
The MACD (Moving Average Convergence Divergence) histogram represents the difference between the MACD line and its signal line. When the histogram crosses above zero, the MACD line has crossed above the signal line — a bullish signal. When it crosses below zero, the MACD line has fallen below the signal line — a bearish signal. Adding volume confirmation to these zero-line crosses filters out weak signals that lack market participation. High volume on a zero-line cross indicates institutional commitment to the move, increasing the probability of follow-through.
| Parameter | Value |
|---|---|
| zero Line | 0 |
| macd Fast | 12 |
| macd Slow | 26 |
| macd Signal | 9 |
MACD Histogram Zero-Line Cross with Volume Confirmation: The Setup
Entry Rules
- ▶MACD histogram (12,26,9) crosses above zero (long) or below zero (short)
- ▶Current bar volume must be above the 20-period volume SMA
- ▶Enter on the next bar open after conditions are met
- ▶Position size: 0.1 lots per trade
Exit Rules
- ■Stop-loss at 1.5x ATR(14) from entry price
- ■Take-profit at 3.0x ATR(14) from entry (2:1 reward-to-risk)
- ■Exit if opposite MACD histogram zero-line cross occurs before TP/SL
Working MQL5 Code
The MQL5 Expert Advisor creates indicator handles for MACD and ATR in `OnInit()`. On each new 4H bar, it reads the MACD histogram values for the two most recent completed bars from buffer index 2 (the histogram buffer in MT5's iMACD). A bullish zero-line cross is detected when the previous histogram was <= 0 and the current is > 0. Volume confirmation compares the current bar's tick volume to a custom SMA calculation. The strategy supports both long and short trades: longs on bullish crosses, shorts on bearish crosses. Stop-loss is set at 1.5x ATR and take-profit at 3.0x ATR (2:1 R:R). Opposite signals close existing positions before potentially opening new ones.
#property copyright "Spenddock Strategy"
#property link "https://spenddock.com"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
// === INPUTS ===
input int MACDFast = 12; // MACD fast period
input int MACDSlow = 26; // MACD slow period
input int MACDSignal = 9; // MACD signal period
input int VolumePeriod = 20; // Volume SMA period
input int ATRPeriod = 14; // ATR period
input double StopATRMult = 1.5; // Stop loss ATR multiplier
input double TPATRMult = 3.0; // Take profit ATR multiplier
input double LotSize = 0.1; // Trade lot size
input int MagicNumber = 234567; // Expert Advisor magic number
// === GLOBALS ===
CTrade trade;
int macdHandle;
int atrHandle;
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
macdHandle = iMACD(_Symbol, PERIOD_H4, MACDFast, MACDSlow, MACDSignal, PRICE_CLOSE);
atrHandle = iATR(_Symbol, PERIOD_H4, ATRPeriod);
if(macdHandle == INVALID_HANDLE || atrHandle == INVALID_HANDLE)
{
Print("Failed to create indicator handles");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
if(macdHandle != INVALID_HANDLE) IndicatorRelease(macdHandle);
if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
}
// Calculate simple moving average of tick volume
double GetVolumeSMA(int period, int shift)
{
double sum = 0;
for(int i = shift; i < shift + period; i++)
{
sum += (double)iVolume(_Symbol, PERIOD_H4, i);
}
return sum / period;
}
void OnTick()
{
// Only run on new bar
static datetime lastBar = 0;
datetime currentBar = iTime(_Symbol, PERIOD_H4, 0);
if(currentBar == lastBar) return;
lastBar = currentBar;
// Get MACD histogram values (buffer index 2 = histogram)
double histBuffer[];
ArraySetAsSeries(histBuffer, true);
if(CopyBuffer(macdHandle, 2, 1, 2, histBuffer) < 2) return;
double histCurrent = histBuffer[0]; // Most recent completed bar
double histPrevious = histBuffer[1]; // Bar before that
// Get ATR value
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
if(CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) <= 0) return;
double atrValue = atrBuffer[0];
// Get volume data
double currentVolume = (double)iVolume(_Symbol, PERIOD_H4, 1);
double avgVolume = GetVolumeSMA(VolumePeriod, 1);
// Detect zero-line crosses
bool bullishCross = histPrevious <= 0 && histCurrent > 0;
bool bearishCross = histPrevious >= 0 && histCurrent < 0;
bool volumeConfirm = currentVolume > avgVolume;
bool hasPosition = PositionSelect(_Symbol);
// === CLOSE OPPOSITE POSITIONS ===
if(hasPosition)
{
long posType = PositionGetInteger(POSITION_TYPE);
// Close long on bearish cross
if(posType == POSITION_TYPE_BUY && bearishCross)
trade.PositionClose(_Symbol);
// Close short on bullish cross
if(posType == POSITION_TYPE_SELL && bullishCross)
trade.PositionClose(_Symbol);
}
// === ENTRY LOGIC ===
hasPosition = PositionSelect(_Symbol);
if(!hasPosition && volumeConfirm)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(bullishCross)
{
double sl = NormalizeDouble(ask - atrValue * StopATRMult, digits);
double tp = NormalizeDouble(ask + atrValue * TPATRMult, digits);
trade.Buy(LotSize, _Symbol, ask, sl, tp, "MACD Bull Cross");
}
else if(bearishCross)
{
double sl = NormalizeDouble(bid + atrValue * StopATRMult, digits);
double tp = NormalizeDouble(bid - atrValue * TPATRMult, digits);
trade.Sell(LotSize, _Symbol, bid, sl, tp, "MACD Bear Cross");
}
}
}Backtest Results: EURUSD (4H, 2020-2025)
Total Return
+31.4%
Annualized
+5.6%
Win Rate
46%
Max Drawdown
-19.1%
Sharpe Ratio
0.62
Total Trades
74
Profit Factor
1.22
Past performance does not guarantee future results. Backtest results may not reflect actual trading conditions including slippage, commissions, and liquidity constraints.
Optimization Tips
Adjust the reward-to-risk ratio based on the currency pair's trending behavior. EURUSD often mean-reverts, so a 1.5:1 R:R with a tighter stop (1.0x ATR) may produce a higher win rate and similar overall returns.
Add a trend filter using EMA(200). Only take long MACD crosses when price is above EMA(200), and short crosses when below. This aligns the MACD signals with the higher-timeframe trend and can significantly improve win rate.
Filter by time of day. EURUSD 4H bars that span the London and New York overlap sessions (12:00-16:00 UTC) tend to produce stronger moves. Weight signals during these sessions more heavily or only trade them.
Consider using the MACD line cross (not histogram zero-line) as an earlier entry signal, with the histogram zero-line cross as confirmation. This can improve entry timing at the cost of slightly more false signals.
Test histogram magnitude thresholds. Instead of entering on any zero-line cross, require the histogram to cross above 0.0001 (or below -0.0001 for shorts). This filters out weak, ambiguous crosses near the zero line.
Common Mistakes with MACD Histogram on MetaTrader 5
Using real volume data on Forex. Forex is decentralized, so MT5 only provides tick volume (number of price changes), not actual traded volume. While tick volume correlates with real volume, the correlation is not perfect. Consider the volume filter as a proxy, not ground truth.
Not accounting for swap (overnight financing) costs. With 74 trades on 4H EURUSD, many positions are held overnight. Depending on the swap direction (long/short) and your broker, these costs can accumulate and reduce the 31% return by 2-5%.
Over-leveraging on Forex. The 0.1 lot default is conservative, but many traders scale up aggressively. With a -19% max drawdown, using 1.0 lots could produce a -190% drawdown (i.e., account wipeout). Always respect the per-trade risk percentage (recommended: 1-2% of account).
Backtesting with MT5's default spread settings. During high-impact news events (NFP, ECB decisions), EURUSD spreads can widen from 0.8 pips to 5-10 pips. If a MACD signal triggers during these events, the entry price will be much worse than backtested. Consider adding a spread filter.
Build This Strategy in 60 Seconds with SpendDock
Instead of copying and debugging this code, describe your strategy in plain English. SpendDock generates the MQL5 code, backtests it against real market data, and lets you iterate until it is right.
Try SpendDock Free