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
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
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-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.