Quick Answer
ta.macd(close, 12, 26, 9) in Pine Script maps to iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE) in MQL5. Like every MQL5 indicator, iMACD returns a handle, and you read the values with CopyBuffer. It has two buffers: buffer 0 is the MACD line and buffer 1 is the signal line. There is no histogram buffer, so calculate it as macd - signal.
The catch is the signal line. Pine's ta.macd smooths the signal with an EMA, while the built-in MT5 MACD smooths it with a simple moving average. The MACD line is the same idea in both, but the signal line and the histogram can differ, and so can the bar where a cross happens.
Key Facts
| Pine Script | MQL5 |
|---|---|
| ta.macd(close, 12, 26, 9) | iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE) plus CopyBuffer |
| macdLine | Buffer 0 |
| signalLine | Buffer 1 (SMA signal in MT5). Calculate an EMA of buffer 0 to match Pine |
| histLine | No buffer. Use macd - signal |
| ta.crossover(macdLine, signalLine) | macd1 > signal1 && macd2 <= signal2 on closed bars |
| Source close | PRICE_CLOSE. Other sources use ENUM_APPLIED_PRICE |
| Shift 0 is the live bar | Use shift 1 for closed-bar logic |
The Pine Script Version
//@version=5
strategy("MACD cross", overlay = false)
[macdLine, signalLine, histLine] = ta.macd(close, 12, 26, 9)
if ta.crossover(macdLine, signalLine)
strategy.entry("Long", strategy.long)
The strategy enters long when the MACD line crosses above the signal line.
iMACD and CopyBuffer
Create the handle once in OnInit(), read the buffers with CopyBuffer, and release the handle in OnDeinit():
int macdHandle = INVALID_HANDLE;
int OnInit()
{
macdHandle = iMACD(_Symbol, _Period, 12, 26, 9, PRICE_CLOSE);
if(macdHandle == INVALID_HANDLE)
return INIT_FAILED;
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(macdHandle != INVALID_HANDLE)
IndicatorRelease(macdHandle);
}
bool GetMacd(int shift, double &macd, double &signal)
{
double m[1], s[1];
if(CopyBuffer(macdHandle, 0, shift, 1, m) != 1) return false;
if(CopyBuffer(macdHandle, 1, shift, 1, s) != 1) return false;
macd = m[0];
signal = s[0];
return true;
}
The histogram is macd - signal. For error handling and bar shifts in CopyBuffer, see MQL5 CopyBuffer guide.
The Signal Line: SMA in MT5, EMA in Pine
Pine's ta.macd takes the difference of two EMAs, and then smooths it with another EMA to make the signal line. The built-in MT5 MACD makes the same difference of two EMAs, but smooths it with a simple moving average. Because of that:
- The MACD line (buffer 0) is very close to Pine's.
- The signal line (buffer 1) is slower to react and does not match Pine's value.
- A cross can happen one or more bars earlier or later, and the histogram changes sign at different bars.
If your strategy trades the cross of the MACD line and the signal line, this difference matters. You can compare it yourself: print both values for a few bars and check them against TradingView.
Match Pine: Calculate the EMA Signal Yourself
Keep iMACD for the MACD line, and calculate the signal as an EMA of the MACD line:
// Pine-style signal: EMA of the MACD line.
// Returns the MACD value, the EMA signal and the histogram at 'shift'.
bool GetMacdEmaSignal(int shift, int signalLen, int lookback,
double &macd, double &signal, double &hist)
{
double main[];
ArraySetAsSeries(main, true); // main[0] = value at 'shift', main[lookback-1] = oldest
if(CopyBuffer(macdHandle, 0, shift, lookback, main) != lookback)
return false;
double alpha = 2.0 / (signalLen + 1.0);
double ema = main[lookback - 1]; // start from the oldest value
for(int i = lookback - 2; i >= 0; i--)
ema = alpha * main[i] + (1.0 - alpha) * ema;
macd = main[0];
signal = ema;
hist = macd - signal;
return true;
}
The first value of an EMA depends on where you start, so a longer lookback (for example 300 bars) gets closer to Pine's value. Compare a few bars against TradingView before you rely on it. The same EMA, SMA and RMA differences are explained in Pine Script ta.rsi and ta.ema vs MQL5.
Full MQL5 Example
This EA detects the MACD cross on the last closed bar using the EMA signal, and prints it. It does not place trades.
input int FastEMA = 12;
input int SlowEMA = 26;
input int SignalLen = 9;
input int Lookback = 300;
int macdHandle = INVALID_HANDLE;
datetime g_lastBarTime = 0;
int OnInit()
{
macdHandle = iMACD(_Symbol, _Period, FastEMA, SlowEMA, SignalLen, PRICE_CLOSE);
if(macdHandle == INVALID_HANDLE)
return INIT_FAILED;
g_lastBarTime = iTime(_Symbol, _Period, 0);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(macdHandle != INVALID_HANDLE)
IndicatorRelease(macdHandle);
}
bool IsNewBar()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == 0 || currentBarTime == g_lastBarTime)
return false;
g_lastBarTime = currentBarTime;
return true;
}
bool GetMacdEmaSignal(int shift, int signalLen, int lookback,
double &macd, double &signal, double &hist)
{
double main[];
ArraySetAsSeries(main, true);
if(CopyBuffer(macdHandle, 0, shift, lookback, main) != lookback)
return false;
double alpha = 2.0 / (signalLen + 1.0);
double ema = main[lookback - 1];
for(int i = lookback - 2; i >= 0; i--)
ema = alpha * main[i] + (1.0 - alpha) * ema;
macd = main[0];
signal = ema;
hist = macd - signal;
return true;
}
void OnTick()
{
if(!IsNewBar())
return;
double macd1, sig1, hist1;
double macd2, sig2, hist2;
// shift 1 = last closed bar, shift 2 = the bar before it
if(!GetMacdEmaSignal(1, SignalLen, Lookback, macd1, sig1, hist1)) return;
if(!GetMacdEmaSignal(2, SignalLen, Lookback, macd2, sig2, hist2)) return;
// ta.crossover(macdLine, signalLine) on the closed bar
if(macd1 > sig1 && macd2 <= sig2)
PrintFormat("MACD crossed above signal. MACD %.6f, signal %.6f, hist %.6f",
macd1, sig1, hist1);
}
The cross uses two closed bars, the same idea as in Pine Script ta.crossover in MQL5, and the new-bar check is explained in Pine Script close[1] in MQL5.
Common Mistakes
- Looking for a histogram buffer.
iMACDhas only two buffers. Calculate the histogram. - Expecting the signal line to match Pine. The MT5 signal is an SMA, and Pine's is an EMA.
- Swapping the buffers. Buffer 0 is the MACD line, buffer 1 is the signal.
- Mixing the parameter order.
iMACDtakes the fast period, then the slow period, then the signal period. - Reading shift 0. The live bar changes on every tick. Use closed bars for signals.
- Using a short lookback for the EMA signal. The start of the EMA affects the result. Use a longer lookback and compare with TradingView.
- Creating the handle on every tick. Create it once in
OnInit().
FAQ
Related Guides
- Pine Script ta.bb and ta.stdev in MQL5: another indicator with several buffers and a formula detail to match.
- Pine Script ta.rsi and ta.ema vs MQL5: why EMA, SMA and RMA values differ.
- Pine Script ta.crossover in MQL5: detect a cross on closed bars.
- MQL5 CopyBuffer guide: read indicator buffers safely.
- Pine Script close[1] in MQL5: closed-bar values and the new-bar check.
- Pine Script ta.barssince in MQL5: count the bars since the last MACD cross.
- Why a Pine Script Strategy Gives Different Results in MQL5: indicator differences are one of the causes.
Convert Indicator Logic Without Surprises
A signal line that smooths differently can move a cross by a bar and change a whole backtest. CodeFlowOS translates Pine Script into MQL5 and lets you review the generated code before you test it in the MT5 Strategy Tester. The platform compile check is optional, and outputs are not guaranteed, so always review the result. For the full process, see the Pine Script to MQL5 guide, or try the converter page. If the code does not compile, the Error Fixer can suggest a fix, and the free early-bird Code Review checks an EA for common mistakes.
This article is educational. It does not describe trading performance and is not financial advice. Always compile and test code in MetaEditor and the Strategy Tester before using it on a live account.