Learning Center

How Pine Script history maps to MQL arrays

Why close[1] means different things in each language, and how to index bars in MQL5 without off-by-one bugs.

The core difference

In Pine Script every variable is a series. Your script runs once per bar from left to right, and close[1] always means "the close one bar before the bar being calculated". You never manage arrays yourself.

In MQL5 an indicator receives whole arrays in OnCalculate. By default index 0 is the oldest bar on the chart, not the newest. Most conversion bugs start here: code that reads close[1] in MQL5 without changing the indexing reads the second-oldest bar in history.

Option 1: series indexing (newest bar = 0)

Calling ArraySetAsSeries(array, true) flips the indexing so 0 is the current bar, which matches Pine. Then loop from older bars toward 0 and only recalculate what changed since the last call, using prev_calculated.

Pine Script

//@version=5
indicator("Close change")
change = close - close[1]
plot(change)

MQL5

double Buf[];
int OnInit(){ SetIndexBuffer(0, Buf, INDICATOR_DATA); return INIT_SUCCEEDED; }

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[])
{
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(Buf, true);
   int limit = (prev_calculated == 0) ? rates_total - 2 : rates_total - prev_calculated;
   for(int i = limit; i >= 0; i--)
      Buf[i] = close[i] - close[i + 1];   // i+1 is the previous (older) bar
   return rates_total;
}

Checklist

Set series indexing on every array you read, including your own buffers. Leave enough room for the lookback: if you read close[i + 1], start at rates_total - 2, not rates_total - 1. When prev_calculated is 0 the chart is new, so calculate everything once; after that, only the newest bars.

Related code snippets