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.
//@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.
//+------------------------------------------------------------------+
//| 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
htf = input.timeframe("D", "Higher timeframe")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.
htfClose = request.security(syminfo.tickerid, htf,
close[1], lookahead = barmerge.lookahead_on)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.
ta.ema(close, emaLen)[1] // inside request.securityemaHandle = 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 loads the other timeframe before the script runsint 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.
// [1] reaches back from the current barArraySetAsSeries(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.
plot(htfClose, "Previous HTF close", color.gray, 2,
plot.style_stepline)#property indicator_type1 DRAW_LINEMT5 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
shiftinstead ofshift + 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, likelookahead_onwithout[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'srequest.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
- Open an H1 chart of the same symbol on both platforms and add both indicators.
- 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).
- 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.
- 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.