Complete .mq5 source code

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.

Historical test results

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.

How the Supertrend signal works

Bullish flip

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.

Bearish flip

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 Custom Indicator 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, save it in the MQL5/Indicators folder, and compile with F7.

Change the settings or add your own signal filter

Describe the ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.

Pine Script vs MQL5: Same Strategy, Different Platforms

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionSeries evaluated on chart updatesEvent handlers with explicit buffers
DeploymentRuns in TradingView with alertsRuns in the MT5 terminal or on a VPS
Broker accessVia TradingView broker integrationDirect broker connectivity
BacktestingStrategy Tester for strategy scriptsStrategy Tester for Expert Advisors
Code complexitySimpler, functional syntaxC++-like, more powerful

The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.

Frequently Asked Questions

Related MQL5 pages

Risk and testing note

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.