MACD MQL5 Indicator: Custom Histogram, Alerts & EA Code Guide
This page is a comprehensive guide to building a custom MACD indicator in MQL5, covering histogram rendering, signal-line crossover alerts, and integration into an Expert Advisor. You will learn how to use the iMACD handle pattern introduced in MQL5, configure buffer indices, and fire push/email notifications when momentum shifts occur across any symbol or timeframe.
Backtest Performance
Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.
Strategy Logic
Entry Conditions
A long trade is triggered when the MACD line crosses above the signal line and the histogram bar turns positive, confirming upward momentum. An additional filter requires the MACD line to be below zero at the moment of crossover, ensuring the signal originates from oversold territory rather than mid-trend noise. Position size is fixed at a configurable lot size with a stop-loss placed below the most recent swing low.
Exit Conditions
The trade exits when the MACD line crosses back below the signal line or when price reaches a take-profit level set at twice the stop-loss distance (2 : 1 reward-to-risk ratio). A trailing stop activates once the trade is in profit by one ATR, locking in gains as the trend extends. If the histogram flips colour on the same bar as entry, the trade is closed immediately to avoid false-signal losses.
MQL5 Expert Advisor Code
//+------------------------------------------------------------------+
//| MACD_Custom.mq5 |
//| Complete MACD indicator with histogram, alerts and EA logic |
//| Compatible with MetaTrader 5 / MQL5 5.x |
//+------------------------------------------------------------------+
#property copyright "Pineify — pineify.app"
#property link "https://pineify.app"
#property version "1.00"
#property strict
//--- Indicator / EA mode switch
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_plots 3
//--- Plot 0 : MACD histogram
#property indicator_label1 "Histogram"
#property indicator_type1 DRAW_HISTOGRAM
#property indicator_color1 clrDodgerBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
//--- Plot 1 : MACD line
#property indicator_label2 "MACD"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrRed
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1
//--- Plot 2 : Signal line
#property indicator_label3 "Signal"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrOrange
#property indicator_style3 STYLE_DOT
#property indicator_width3 1
//--- Input parameters
input int InpFastEMA = 12; // Fast EMA period
input int InpSlowEMA = 26; // Slow EMA period
input int InpSignalSMA = 9; // Signal SMA period
input bool InpAlerts = true; // Enable push / alert notifications
input bool InpEAMode = false; // Run as EA (trade on signals)
input double InpLotSize = 0.10; // EA lot size
input int InpStopPips = 30; // EA stop-loss in pips
input int InpTakePips = 60; // EA take-profit in pips
input ulong InpMagic = 202400; // EA magic number
//--- Indicator buffers
double BufferHistogram[];
double BufferMACD[];
double BufferSignal[];
double BufferDummy[]; // required internal buffer for iMACD handle
//--- Global handles and state
int g_macdHandle = INVALID_HANDLE;
bool g_lastCrossUp = false;
bool g_lastCrossDown= false;
datetime g_lastAlert = 0;
//+------------------------------------------------------------------+
//| Custom indicator / EA initialisation |
//+------------------------------------------------------------------+
int OnInit()
{
//--- Create MACD handle using MQL5 handle pattern
g_macdHandle = iMACD(_Symbol, _Period,
InpFastEMA, InpSlowEMA, InpSignalSMA,
PRICE_CLOSE);
if(g_macdHandle == INVALID_HANDLE)
{
Print("ERROR: iMACD handle creation failed. Code=", GetLastError());
return INIT_FAILED;
}
//--- Bind indicator buffers in correct index order
// iMACD returns: 0=MACD line, 1=Signal line
SetIndexBuffer(0, BufferHistogram, INDICATOR_DATA);
SetIndexBuffer(1, BufferMACD, INDICATOR_DATA);
SetIndexBuffer(2, BufferSignal, INDICATOR_DATA);
SetIndexBuffer(3, BufferDummy, INDICATOR_CALCULATIONS);
//--- Label the indicator window
IndicatorSetString(INDICATOR_SHORTNAME,
StringFormat("MACD(%d,%d,%d)",
InpFastEMA, InpSlowEMA, InpSignalSMA));
IndicatorSetInteger(INDICATOR_DIGITS, _Digits + 1);
//--- Reset cross-state tracking
g_lastCrossUp = false;
g_lastCrossDown = false;
Print("MACD_Custom: initialised on ", _Symbol, " / ", EnumToString(_Period));
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Indicator / EA calculation on every new tick or bar |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total < InpSlowEMA + InpSignalSMA)
return 0;
int limit = rates_total - prev_calculated;
if(prev_calculated > 0) limit++;
//--- Copy MACD and Signal values from the built-in handle
double macdArr[];
double signalArr[];
if(CopyBuffer(g_macdHandle, 0, 0, limit, macdArr) <= 0) return prev_calculated;
if(CopyBuffer(g_macdHandle, 1, 0, limit, signalArr) <= 0) return prev_calculated;
//--- Fill indicator buffers (most-recent bar = index 0 in CopyBuffer)
for(int i = 0; i < limit && (rates_total - 1 - i) >= 0; i++)
{
int dst = rates_total - 1 - i;
BufferMACD[dst] = macdArr[i];
BufferSignal[dst] = signalArr[i];
BufferHistogram[dst] = macdArr[i] - signalArr[i];
}
//--- Alert / EA logic on the most-recent completed bar (index 1 to avoid repainting)
if(rates_total >= 2 && prev_calculated > 0)
{
double macdPrev = BufferMACD[rates_total - 2];
double macdCurr = BufferMACD[rates_total - 1];
double signalPrev = BufferSignal[rates_total - 2];
double signalCurr = BufferSignal[rates_total - 1];
bool crossUp = (macdPrev < signalPrev) && (macdCurr >= signalCurr);
bool crossDown = (macdPrev > signalPrev) && (macdCurr <= signalCurr);
//--- Fire alert once per bar
if(InpAlerts && time[rates_total - 1] != g_lastAlert)
{
if(crossUp)
{
string msg = StringFormat("%s %s — MACD Bullish Crossover @ %.5f",
_Symbol, EnumToString(_Period), close[rates_total-1]);
Alert(msg);
SendNotification(msg);
g_lastAlert = time[rates_total - 1];
}
else if(crossDown)
{
string msg = StringFormat("%s %s — MACD Bearish Crossover @ %.5f",
_Symbol, EnumToString(_Period), close[rates_total-1]);
Alert(msg);
SendNotification(msg);
g_lastAlert = time[rates_total - 1];
}
}
//--- EA order management
if(InpEAMode)
{
if(crossUp && !g_lastCrossUp) OpenTrade(ORDER_TYPE_BUY, close[rates_total-1]);
if(crossDown && !g_lastCrossDown) OpenTrade(ORDER_TYPE_SELL, close[rates_total-1]);
}
g_lastCrossUp = crossUp;
g_lastCrossDown = crossDown;
}
return rates_total;
}
//+------------------------------------------------------------------+
//| Place a market order using MQL5 MqlTradeRequest |
//+------------------------------------------------------------------+
void OpenTrade(ENUM_ORDER_TYPE orderType, double price)
{
//--- Close any existing position in the opposite direction first
ClosePreviousTrades(orderType);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double pipVal = point * 10.0; // standard 5-digit broker
double sl, tp;
if(orderType == ORDER_TYPE_BUY)
{
sl = price - InpStopPips * pipVal;
tp = price + InpTakePips * pipVal;
}
else
{
sl = price + InpStopPips * pipVal;
tp = price - InpTakePips * pipVal;
}
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = InpLotSize;
request.type = orderType;
request.price = price;
request.sl = NormalizeDouble(sl, _Digits);
request.tp = NormalizeDouble(tp, _Digits);
request.deviation = 10;
request.magic = InpMagic;
request.comment = "MACD_Custom";
request.type_filling = ORDER_FILLING_FOK;
if(!OrderSend(request, result))
Print("OrderSend failed: ", result.retcode, " — ", result.comment);
else
Print("Order placed: ticket=", result.order, " type=",
EnumToString(orderType), " price=", price);
}
//+------------------------------------------------------------------+
//| Close all EA positions opposite to the new direction |
//+------------------------------------------------------------------+
void ClosePreviousTrades(ENUM_ORDER_TYPE newType)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
bool shouldClose = (newType == ORDER_TYPE_BUY && posType == POSITION_TYPE_SELL) ||
(newType == ORDER_TYPE_SELL && posType == POSITION_TYPE_BUY);
if(!shouldClose) continue;
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.position = ticket;
req.volume = PositionGetDouble(POSITION_VOLUME);
req.type = (posType == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
req.price = (req.type == ORDER_TYPE_SELL)
? SymbolInfoDouble(_Symbol, SYMBOL_BID)
: SymbolInfoDouble(_Symbol, SYMBOL_ASK);
req.deviation = 10;
req.magic = InpMagic;
req.comment = "MACD_Custom_Close";
req.type_filling = ORDER_FILLING_FOK;
OrderSend(req, res);
}
}
//+------------------------------------------------------------------+
//| OnTick — required entry point even when using OnCalculate |
//+------------------------------------------------------------------+
void OnTick()
{
// Main logic runs inside OnCalculate.
// OnTick is required for EAs compiled without indicator_separate_window.
}
//+------------------------------------------------------------------+
//| Clean up indicator handle on removal |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_macdHandle != INVALID_HANDLE)
{
IndicatorRelease(g_macdHandle);
g_macdHandle = INVALID_HANDLE;
}
Print("MACD_Custom: deactivated. Reason code=", reason);
}
//+------------------------------------------------------------------+Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.
Generate a Custom Multi-pair Momentum EA →
Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Bar-based, backtesting only | Tick-based, live trading |
| Deployment | TradingView alerts | Runs 24/5 on VPS/MT5 |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Built-in, no data download needed | Strategy Tester, tick data required |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
Pineify supports both platforms. Prototype your strategy visually in TradingView Pine Script, then generate the equivalent MQL5 EA for live MT5 trading.