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.

macd mql5 indicatormql5 macd histogrammacd alert mql5macd ea mql5custom macd indicator mql5

Backtest Performance

50.7%
Win Rate
17.2%
Max Drawdown
1.08
Sharpe Ratio
2020–2025
Test Period

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

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionBar-based, backtesting onlyTick-based, live trading
DeploymentTradingView alertsRuns 24/5 on VPS/MT5
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingBuilt-in, no data download neededStrategy Tester, tick data required
Code complexitySimpler, functional syntaxC++-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.

Frequently Asked Questions

Related MQL5 Expert Advisors