Edgoro
Pine Script v6 → MQL5 custom indicator

request.security in MQL5: higher-timeframe data without repainting

Short answer

The non-repainting Pine form, request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on), returns the last completed daily bar. In an Expert Advisor that is simply iClose(_Symbol, PERIOD_D1, 1). In an indicator, find the daily bar that contains each chart bar with iBarShift() and read the one before it, at shift + 1, using a handle created on the daily timeframe for indicator values.

Pinerequest.securitybarmerge.lookahead_oninput.timeframeclose[1]

MQL5iBarShiftCopyCloseiMACopyBufferBarsArraySetAsSeries

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

The Pine Script v6 code

An overlay indicator that shows, on an intraday chart, the previous daily close and the 20-period EMA of the daily chart as of the previous day.

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("Higher-timeframe close and EMA, no repainting", overlay = true)

htf = input.timeframe("D", "Higher timeframe")
emaLen = input.int(20, "HTF EMA length", minval = 1)

// [1] with lookahead_on: the last COMPLETED higher-timeframe bar, the same on history and in real time
htfClose = request.security(syminfo.tickerid, htf, close[1], lookahead = barmerge.lookahead_on)
htfEma = request.security(syminfo.tickerid, htf, ta.ema(close, emaLen)[1], lookahead = barmerge.lookahead_on)

plot(htfClose, "Previous HTF close", color.gray, 2, plot.style_stepline)
plot(htfEma, "HTF EMA (closed bar)", color.orange, 2, plot.style_stepline)

The MQL5 program

A custom indicator drawing the same two lines from the timeframe chosen in its inputs. It only draws.

Save it as MQL5/Indicators/request-security-higher-timeframe.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
//+------------------------------------------------------------------+
//| Higher-timeframe close and EMA without repainting                |
//| = request.security(..., close[1], lookahead = lookahead_on)       |
//| Custom indicator: save in MQL5/Indicators and compile (F7).      |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_label1  "Previous HTF close"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGray
#property indicator_width1  2

#property indicator_label2  "HTF EMA (closed bar)"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrOrange
#property indicator_width2  2

input ENUM_TIMEFRAMES InpHtf    = PERIOD_D1; // Higher timeframe
input int             InpEmaLen = 20;        // HTF EMA length

double CloseBuf[], EmaBuf[];
int    emaHandle = INVALID_HANDLE;

int OnInit()
  {
   if(PeriodSeconds(InpHtf) <= PeriodSeconds(_Period) || InpEmaLen < 1)
     {
      Print("Pick a timeframe higher than the chart's, and an EMA length of at least 1");
      return INIT_PARAMETERS_INCORRECT;
     }
   SetIndexBuffer(0, CloseBuf, INDICATOR_DATA);
   SetIndexBuffer(1, EmaBuf, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   // ta.ema() evaluated on the higher timeframe = an iMA handle on that timeframe
   emaHandle = iMA(_Symbol, InpHtf, InpEmaLen, 0, MODE_EMA, PRICE_CLOSE);
   if(emaHandle == INVALID_HANDLE)
     {
      Print("Could not create the higher-timeframe EMA, error ", GetLastError());
      return INIT_FAILED;
     }
   return INIT_SUCCEEDED;
  }

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

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[])
  {
   int htfBars = Bars(_Symbol, InpHtf);
   if(htfBars < 2 || BarsCalculated(emaHandle) < htfBars)
      return prev_calculated;  // higher timeframe still loading: try again on the next tick

   int start = prev_calculated > 0 ? prev_calculated - 1 : 0;

   // How far back on the higher timeframe the oldest bar to calculate goes, plus the bar before it
   int oldest = iBarShift(_Symbol, InpHtf, time[start]);
   int need   = oldest < 0 ? htfBars : MathMin(oldest + 2, htfBars);

   double htfClose[], htfEma[];
   ArraySetAsSeries(htfClose, true);  // [0] = forming HTF bar, [1] = last completed one
   ArraySetAsSeries(htfEma, true);
   if(CopyClose(_Symbol, InpHtf, 0, need, htfClose) != need || CopyBuffer(emaHandle, 0, 0, need, htfEma) != need)
      return prev_calculated;

   for(int i = start; i < rates_total; i++)
     {
      CloseBuf[i] = EMPTY_VALUE;
      EmaBuf[i]   = EMPTY_VALUE;
      int shift = iBarShift(_Symbol, InpHtf, time[i]);  // the HTF bar that contains this chart bar
      if(shift < 0 || shift + 1 >= need)
         continue;
      // shift + 1 is close[1] in Pine: the HTF bar before it, already closed
      CloseBuf[i] = htfClose[shift + 1];
      EmaBuf[i]   = htfEma[shift + 1];
     }
   return rates_total;
  }
//+------------------------------------------------------------------+

Line by line

Pine Script v6
htf = input.timeframe("D", "Higher timeframe")
MQL5
input ENUM_TIMEFRAMES InpHtf = PERIOD_D1; // Higher timeframe
...
if(PeriodSeconds(InpHtf) <= PeriodSeconds(_Period))
   return INIT_PARAMETERS_INCORRECT;

Timeframes are an enum. The check refuses a timeframe that isn't higher than the chart's, which this program isn't written for.

Pine Script v6
htfClose = request.security(syminfo.tickerid, htf,
  close[1], lookahead = barmerge.lookahead_on)
MQL5
int shift = iBarShift(_Symbol, InpHtf, time[i]);
CloseBuf[i] = htfClose[shift + 1];

iBarShift() returns the higher-timeframe bar that contains the chart bar; shift + 1 is the one before it, closed for good. That is what [1] with lookahead_on returns, on history and in real time alike.

Pine Script v6
ta.ema(close, emaLen)[1]  // inside request.security
MQL5
emaHandle = iMA(_Symbol, InpHtf, InpEmaLen, 0, MODE_EMA, PRICE_CLOSE);
...
CopyBuffer(emaHandle, 0, 0, need, htfEma);
EmaBuf[i] = htfEma[shift + 1];

The expression Pine evaluates on the daily chart becomes a handle created on the daily timeframe. Its buffer is indexed by daily bars, so it's read at the same shift + 1.

Pine Script v6
// Pine loads the other timeframe before the script runs
MQL5
int htfBars = Bars(_Symbol, InpHtf);
if(htfBars < 2 || BarsCalculated(emaHandle) < htfBars)
   return prev_calculated;

MetaTrader 5 loads it on demand. Until it's there, copies fail, so the indicator returns and tries again on the next tick.

Pine Script v6
// [1] reaches back from the current bar
MQL5
ArraySetAsSeries(htfClose, true);  // [0] = forming HTF bar
int need = MathMin(oldest + 2, htfBars);

As series, the copied daily values are indexed like shifts, so htfClose[shift + 1] reads directly. Only the daily bars the calculation needs are copied: all of them on the first call, two on later ticks.

Pine Script v6
plot(htfClose, "Previous HTF close", color.gray, 2,
  plot.style_stepline)
MQL5
#property indicator_type1  DRAW_LINE

MT5 has no step style, but the value is constant through each day, so the line is flat with a jump at each new day.

Pitfalls

  • shift instead of shift + 1. Reading the daily bar that contains the chart bar gives, on history, the day's final close, known only at the end of that day: the indicator, and any backtest built on it, sees the future, like lookahead_on without [1]. In real time it is today's moving price, so history and live disagree: repainting.
  • iClose(_Symbol, PERIOD_D1, 0) in an Expert Advisor is the current price of today's bar, not yesterday's close. Pine's request.security(..., close) with no offset is the same moving value in real time.
  • Different days. MetaTrader 5 builds daily bars from the broker's server midnight; TradingView builds them from the session of its feed. When the boundaries differ, "the previous daily close" is a different price on each platform. H4 bars have the same problem.
  • Sunday bars. Some brokers have a short Sunday session that adds a sixth daily bar to the week. On Monday, "the previous day" is then that stub in MetaTrader 5 and Friday on a feed without it.
  • Not loaded yet. Right after attaching, the other timeframe often isn't available and copies fail with error 4806. Never treat a failed copy as zero.

Checking that both give the same values

  1. Open an H1 chart of the same symbol on both platforms and add both indicators.
  2. Pick a bar in the middle of a day and read "Previous HTF close" in each Data Window (converting TradingView's time zone to server time).
  3. Both values should equal the previous daily bar's close, which you can read on a daily chart of each platform. If the daily closes differ, the day boundaries differ: the code isn't the cause.
  4. Check the first bar of a new day: both lines should step on that bar, not before it.

In an Expert Advisor

An Expert Advisor runs on the latest tick only, so there is no history to line up: iClose(_Symbol, PERIOD_D1, 1) is yesterday's completed close, and a daily EMA is CopyBuffer(dailyEmaHandle, 0, 1, 1, v) on a handle created with iMA(_Symbol, PERIOD_D1, 20, 0, MODE_EMA, PRICE_CLOSE) in OnInit(). Check the return values: 0 from iClose() or a count below 1 from CopyBuffer() means the data isn't loaded yet.

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.