Edgoro
Pine Script v6 → MQL5 custom indicator

ta.sma, ta.ema, ta.rsi and ta.atr in MQL5: indicator handles

Short answer

Built-in indicators are handles in MQL5: create them once in OnInit() with iMA() (MODE_SMA, MODE_EMA) or iRSI(), then copy values with CopyBuffer(). SMA and RSI follow Pine's formulas, the EMA matches once past the first bars, but iATR() is not ta.atr(): Pine smooths the true range with Wilder's average, MetaTrader 5 with a simple one, so compute that one yourself.

Pineta.smata.emata.rsita.atrdisplay.data_window

MQL5iMAiRSIiATRCopyBufferBarsCalculatedIndicatorRelease

  • MQL5: compiled in MetaEditor, 0 errors, 0 warnings
  • Pine: checked by Edgoro Pine Check, not run in TradingView

The Pine Script v6 code

An indicator in its own pane: the RSI is drawn, and the SMA, EMA and ATR of the same length appear in the Data Window so the numbers can be compared with MetaTrader 5.

Checked by Edgoro Pine Check with no errors and no warnings. Pine Check is a static checker, not TradingView's compiler, and this script was not run in TradingView.

Pine Script v6
//@version=6
indicator("SMA, EMA, RSI and ATR values", overlay = false)

len = input.int(14, "Length", minval = 1)

sma = ta.sma(close, len)
ema = ta.ema(close, len)
rsi = ta.rsi(close, len)
atr = ta.atr(len)

plot(rsi, "RSI", color.purple, 2)
hline(70, "Overbought", color.gray)
hline(30, "Oversold", color.gray)
// Shown in the Data Window only, to compare with MetaTrader 5
plot(sma, "SMA", color.gray, display = display.data_window)
plot(ema, "EMA", color.orange, display = display.data_window)
plot(atr, "ATR", color.teal, display = display.data_window)

The MQL5 program

A custom indicator with the same RSI line and, in the Data Window, the SMA, the EMA, a Wilder ATR equal to Pine's and MetaTrader 5's own iATR for comparison. It only draws.

Save it as MQL5/Indicators/ta-ema-sma-rsi-with-handles.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
//+------------------------------------------------------------------+
//| ta.sma, ta.ema, ta.rsi and ta.atr in MQL5                        |
//| RSI drawn; SMA, EMA and both ATRs in the Data Window (Ctrl+D).   |
//| Custom indicator: save in MQL5/Indicators and compile (F7).      |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_minimum 0
#property indicator_maximum 100
#property indicator_level1  30
#property indicator_level2  70
#property indicator_buffers 6
#property indicator_plots   5

#property indicator_label1  "RSI"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrMediumPurple
#property indicator_width1  2
#property indicator_label2  "SMA"
#property indicator_type2   DRAW_NONE
#property indicator_label3  "EMA"
#property indicator_type3   DRAW_NONE
#property indicator_label4  "ATR (Wilder, as ta.atr)"
#property indicator_type4   DRAW_NONE
#property indicator_label5  "ATR (MT5 iATR)"
#property indicator_type5   DRAW_NONE

input int InpLen = 14; // Length

double RsiBuf[], SmaBuf[], EmaBuf[], AtrBuf[], IatrBuf[], TrBuf[];
int    smaHandle  = INVALID_HANDLE;
int    emaHandle  = INVALID_HANDLE;
int    rsiHandle  = INVALID_HANDLE;
int    iatrHandle = INVALID_HANDLE;

int OnInit()
  {
   if(InpLen < 1)
      return INIT_PARAMETERS_INCORRECT;
   SetIndexBuffer(0, RsiBuf, INDICATOR_DATA);
   SetIndexBuffer(1, SmaBuf, INDICATOR_DATA);
   SetIndexBuffer(2, EmaBuf, INDICATOR_DATA);
   SetIndexBuffer(3, AtrBuf, INDICATOR_DATA);
   SetIndexBuffer(4, IatrBuf, INDICATOR_DATA);
   SetIndexBuffer(5, TrBuf, INDICATOR_CALCULATIONS);  // true range, not shown
   for(int p = 0; p < 5; p++)
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits + 1);
   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("SMA EMA RSI ATR (%d)", InpLen));

   smaHandle  = iMA(_Symbol, _Period, InpLen, 0, MODE_SMA, PRICE_CLOSE);  // ta.sma(close, len)
   emaHandle  = iMA(_Symbol, _Period, InpLen, 0, MODE_EMA, PRICE_CLOSE);  // ta.ema(close, len)
   rsiHandle  = iRSI(_Symbol, _Period, InpLen, PRICE_CLOSE);              // ta.rsi(close, len)
   iatrHandle = iATR(_Symbol, _Period, InpLen);                           // NOT ta.atr: simple average of TR
   if(smaHandle == INVALID_HANDLE || emaHandle == INVALID_HANDLE || rsiHandle == INVALID_HANDLE || iatrHandle == INVALID_HANDLE)
     {
      Print("Could not create an indicator handle, error ", GetLastError());
      return INIT_FAILED;
     }
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   IndicatorRelease(smaHandle);
   IndicatorRelease(emaHandle);
   IndicatorRelease(rsiHandle);
   IndicatorRelease(iatrHandle);
  }

// Copies the last `count` values of a handle's first buffer into dst[start..], oldest first
bool CopyTo(const int handle, double &dst[], const int start, const int count)
  {
   double tmp[];
   if(CopyBuffer(handle, 0, 0, count, tmp) != count)
      return false;
   for(int k = 0; k < count; k++)
      dst[start + k] = tmp[k];
   return true;
  }

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 < InpLen + 1)
      return 0;
   if(BarsCalculated(smaHandle) < rates_total || BarsCalculated(emaHandle) < rates_total ||
      BarsCalculated(rsiHandle) < rates_total || BarsCalculated(iatrHandle) < rates_total)
      return prev_calculated;

   int start = prev_calculated > 0 ? prev_calculated - 1 : 0;
   int count = rates_total - start;
   if(!CopyTo(smaHandle, SmaBuf, start, count) || !CopyTo(emaHandle, EmaBuf, start, count) ||
      !CopyTo(rsiHandle, RsiBuf, start, count) || !CopyTo(iatrHandle, IatrBuf, start, count))
      return prev_calculated;

   // ta.atr(len) = ta.rma(ta.tr(true), len): Wilder's smoothing, seeded with a simple average
   for(int i = start; i < rates_total; i++)
     {
      TrBuf[i] = i == 0 ? high[i] - low[i] : MathMax(high[i], close[i - 1]) - MathMin(low[i], close[i - 1]);
      if(i < InpLen - 1)
         AtrBuf[i] = EMPTY_VALUE;  // na in Pine
      else if(i == InpLen - 1)
        {
         double sum = 0.0;
         for(int j = 0; j < InpLen; j++)
            sum += TrBuf[j];
         AtrBuf[i] = sum / InpLen;
        }
      else
         AtrBuf[i] = (AtrBuf[i - 1] * (InpLen - 1) + TrBuf[i]) / InpLen;
     }
   return rates_total;
  }
//+------------------------------------------------------------------+

Line by line

Pine Script v6
sma = ta.sma(close, len)
MQL5
smaHandle = iMA(_Symbol, _Period, InpLen, 0, MODE_SMA, PRICE_CLOSE);

Arguments: symbol, timeframe, period, shift, method, price. The handle is a number that refers to an indicator the terminal keeps calculating.

Pine Script v6
ema = ta.ema(close, len)
MQL5
emaHandle = iMA(_Symbol, _Period, InpLen, 0, MODE_EMA, PRICE_CLOSE);

Same weight, 2 / (len + 1). Only the start of the series is seeded differently.

Pine Script v6
rsi = ta.rsi(close, len)
MQL5
rsiHandle = iRSI(_Symbol, _Period, InpLen, PRICE_CLOSE);

Wilder's RSI on both platforms.

Pine Script v6
// the value on each bar
MQL5
double tmp[];
CopyBuffer(handle, 0, 0, count, tmp);
dst[start + k] = tmp[k];

Buffer 0 is the line of a single-line indicator. Copied into a plain array, the values come oldest first, so tmp[k] belongs to bar start + k.

Pine Script v6
atr = ta.atr(len)
MQL5
TrBuf[i] = MathMax(high[i], close[i - 1]) - MathMin(low[i], close[i - 1]);
...
AtrBuf[i] = (AtrBuf[i - 1] * (InpLen - 1) + TrBuf[i]) / InpLen;

ta.atr() is ta.rma() of the true range: the first value is the simple average of the first len true ranges, then each bar takes (previous × (len − 1) + TR) / len. MT5's iATR() keeps a simple average of the last len true ranges instead, so it is also shown for comparison.

Pine Script v6
plot(sma, "SMA", color.gray, display = display.data_window)
MQL5
#property indicator_type2   DRAW_NONE

A DRAW_NONE plot draws nothing but shows its value in the Data Window: the easiest way to compare numbers with TradingView.

Pine Script v6
hline(70, "Overbought", color.gray)
MQL5
#property indicator_level2  70

Fixed horizontal levels of an indicator window are properties.

Pitfalls

  • iATR() is another ATR. After a volatility spike, the simple average drops the spike all at once len bars later while Wilder's fades it slowly, so the two can differ by a lot exactly when stops matter. A stop at 2 × ATR is then a different distance.
  • Handles created on every tick. Calling iMA() in OnTick() or OnCalculate() makes the terminal look up or build an indicator each time: slow, and in the Strategy Tester a common reason an EA crawls. Create handles in OnInit() and release them in OnDeinit(); a failed creation is error 4802.
  • Values not ready. Right after creation, BarsCalculated() is below the number of bars and CopyBuffer() fails (error 4806). In an indicator, return and wait for the next tick; in an EA, skip this tick.
  • Buffer numbers. Indicators with several lines number their buffers from 0: iMACD() buffer 1 is the signal line (a simple average, where Pine uses an EMA) and there is no histogram buffer; iBands() has BASE_LINE, UPPER_BAND and LOWER_BAND.
  • The first EMA values. They depend on how each platform starts the average. After about ten times the period the difference no longer shows in the displayed digits.

Checking that both give the same values

  1. Add both indicators with the same length to the same symbol and timeframe.
  2. Pick a bar far from the start of the history and read SMA, EMA, RSI and ATR in TradingView's Data Window and in MetaTrader 5's (Ctrl+D), on the same bar time.
  3. SMA and RSI should agree to the displayed digits when the closes agree; the EMA too, away from the start. "ATR (Wilder, as ta.atr)" should match TradingView's ATR; "ATR (MT5 iATR)" usually won't.
  4. If even the SMA differs, compare the closes of the last len bars: the feeds differ, not the code.

Reading one value in an Expert Advisor

In an Expert Advisor you rarely need whole buffers. Create the handle in OnInit(), then read the last closed bar with a series array: double v[]; ArraySetAsSeries(v, true); if(CopyBuffer(rsiHandle, 0, 1, 1, v) == 1) ..., where v[0] is Pine's rsi at the close of the previous bar. For Pine's ATR in an EA, compute the Wilder average from CopyRates(), as WilderAtr() does in the worked port.

These programs show one technique each; they are not trading systems, and none of this is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

The same topic in each language

All MQL5 errors

Rather not port it by hand?

Edgoro can generate the MQL5 version for you from a plain-English description of the rules, and compiles it in MetaEditor before you get it. Test it on a demo account: a clean build isn't proof that a strategy works.