Blog
Pine Script close[1] in MQL5: Bar Indexing Explained
Quick Answer
In Pine Script, close[1] is the close of the previous bar. MQL5 has no [] history operator on close. You read a bar by its shift: iClose(_Symbol, _Period, shift). Shift 0 is the bar that is still forming, and shift 1 is the last completed bar.
A Pine strategy normally evaluates a bar after it closes. So when your EA runs once per new bar, Pine close usually maps to MQL5 shift 1, and Pine close[1] maps to shift 2. Mapping close[1] to shift 1 is the most common off-by-one mistake in a Pine Script to MQL5 conversion.
Key Facts
| Concept | Pine Script | MQL5 |
|---|---|---|
| Current bar | close (bar index 0) | iClose(_Symbol, _Period, 0) (still forming) |
| Last closed bar | close[1] while the bar is open | iClose(_Symbol, _Period, 1) |
| History reference | x[n] operator | shift argument or ArraySetAsSeries array |
| Evaluation | Once per bar close by default in strategies | Every tick, unless you add a new-bar check |
| Index 0 means | The bar the script is calculating | The newest bar, which is still forming |
What close[1] Means in Pine Script
The square brackets in Pine Script are the history-referencing operator. close[0] is the same as close, close[1] is one bar back, and close[2] is two bars back.
longCond = close > close[1]
This asks whether the current bar's close is higher than the previous bar's close. The key point is which bar is "current" when the condition is checked. In a Pine strategy with default settings, the script calculates when a bar closes. That bar is the "current" bar, and close[1] is the one before it.
How MQL5 Indexes Bars
MQL5 gives you timeseries access functions such as iClose(), iOpen(), iHigh(), iLow() and iTime(). They take a shift argument:
- Shift 0: the newest bar, which is still forming and changes on every tick.
- Shift 1: the last fully closed bar.
- Shift 2: the bar before that.
An EA's OnTick() runs on every tick, not on every bar close. Reading shift 0 inside OnTick() therefore gives a value that keeps changing while the bar is open.
The Off-by-One Mapping
Here is the mapping to use when your EA evaluates its logic once per new bar, after the previous bar has closed:
| Pine Script | MQL5 shift | MQL5 call |
|---|---|---|
| close | 1 | iClose(_Symbol, _Period, 1) |
| close[1] | 2 | iClose(_Symbol, _Period, 2) |
| open | 1 | iOpen(_Symbol, _Period, 1) |
| high[2] | 3 | iHigh(_Symbol, _Period, 3) |
| low[1] | 2 | iLow(_Symbol, _Period, 2) |
The reason is simple. At the moment a new bar opens, the bar Pine would call "current" has just closed, and in MQL5 it now sits at shift 1.
Translating a Pine Condition to MQL5
Pine Script:
longCond = close > close[1]
MQL5 using iClose:
double pineClose = iClose(_Symbol, _Period, 1); // Pine: close
double pineClose1 = iClose(_Symbol, _Period, 2); // Pine: close[1]
if(pineClose == 0.0 || pineClose1 == 0.0)
return; // history not available yet
bool longCond = (pineClose > pineClose1);
iClose() returns 0 when the data is not available, so it is worth checking before you compare.
Using CopyClose and ArraySetAsSeries
When you need several bars at once, copy them into an array. Set the array as a series first, so that index 0 is the newest copied bar.
double closes[];
ArraySetAsSeries(closes, true);
// start_pos = 1 skips the forming bar, count = 3 copies shifts 1, 2 and 3
int copied = CopyClose(_Symbol, _Period, 1, 3, closes);
if(copied < 3)
return;
// closes[0] = Pine close
// closes[1] = Pine close[1]
// closes[2] = Pine close[2]
Because start_pos is 1, closes[0] is already the last closed bar. This keeps the array index aligned with the Pine offset, which makes the converted code easier to read. For indicator values, the same idea applies to CopyBuffer. See the MQL5 CopyBuffer guide for the safe-read pattern.
Run the Logic Once Per Bar
If your Pine strategy only acts on bar close, your EA should not re-run the same logic on every tick. Add a new-bar check:
bool IsNewBar()
{
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == 0)
return false;
if(currentBarTime == lastBarTime)
return false;
lastBarTime = currentBarTime;
return true;
}
void OnTick()
{
if(!IsNewBar())
return;
// Run signal logic here using shift 1 and shift 2
}
With this in place, the shift 1 / shift 2 mapping above applies.
Common Mistakes
- Mapping
close[1]to shift 1. This reads the wrong bar by one position, so the EA signal lags the Pine signal. - Reading shift 0 for a closed-bar rule. The value changes every tick, so signals can appear and disappear while the bar is open.
- Forgetting
ArraySetAsSeries. Without it, index 0 of a copied array is the oldest element, not the newest. - Not checking the
CopyClosereturn value. If fewer bars are copied than requested, reading the array can cause an array out of range error. - Mixing timeframes.
_Periodfollows the chart. If the Pine script usedrequest.security()on another timeframe, pass that timeframe explicitly.
Why Results Can Still Differ
Even with the right shifts, Pine and MQL5 results can differ because of fill timing, spreads, data feeds and order handling. Pine's strategy tester has its own fill assumptions, and the MT5 Strategy Tester uses broker data. Our guide on why a Pine Script strategy gives different results in MQL5 covers these differences, and the Pine Script to MQL5 step-by-step guide covers the full conversion workflow. For entry orders specifically, see the strategy.entry() to CTrade mapping.
FAQ
What is close[1] in MQL5?
There is no direct close[1] syntax. Use iClose(_Symbol, _Period, 1) for the last closed bar, or copy values with CopyClose into an array set as a series. If your EA evaluates on a new bar and the Pine script used close[1], the equivalent shift is usually 2.
Is shift 0 the current bar or the last closed bar?
Shift 0 is the current bar, which is still forming. Shift 1 is the last closed bar.
Why does my converted EA signal one bar late or one bar early?
The most likely cause is an off-by-one shift. Check whether your EA runs on a new bar, and whether you mapped Pine close to shift 1 and Pine close[1] to shift 2.
Do I need ArraySetAsSeries?
Only when you use arrays filled by CopyClose, CopyHigh, CopyBuffer and similar functions. If you set the array as a series, index 0 is the newest copied element. The iClose family of functions does not need it.
Convert Without Counting Shifts by Hand
Getting every bar offset right across a long Pine script takes care. CodeFlowOS translates Pine Script history references into MQL5 and lets you review the generated code before you test it in the MT5 Strategy Tester. You can try it on the converter page.
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.