Edgoro
Pine Script v6 → MQL5 custom indicator

Pine var and varip in MQL5: values that persist between bars

Short answer

Pine's var keeps a value from bar to bar and rolls it back on each real-time tick, so the forming bar counts once; varip keeps every tick's change. MQL5 globals and static variables persist and are never rolled back, so they behave like varip. For var in an indicator, compute each bar's value from the previous bar's buffer element.

Pinevarvariptimeframe.changebarstate.isrealtime

MQL5SetIndexBufferTimeToStructMathMaxGlobalVariableSetstatic

  • 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 that counts up bars with var, tracks the day's high so far with a var reset each day, and counts real-time ticks with varip.

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("var and varip", overlay = false)

// var: initialized once, on the first bar, then keeps its value from bar to bar
var int upBars = 0
if close > open
    upBars += 1

// var with a reset: the day's high so far
var float dayHigh = na
if timeframe.change("D") or na(dayHigh)
    dayHigh := high
else
    dayHigh := math.max(dayHigh, high)

// varip: also keeps what each real-time tick changed (no rollback between ticks)
varip int ticks = 0
if barstate.isrealtime
    ticks += 1

plot(upBars, "Up bars so far", color.teal, 2)
plot(dayHigh, "Day high so far", color.orange, display = display.data_window)
plot(ticks, "Real-time ticks seen", color.gray, display = display.data_window)

The MQL5 program

A custom indicator with the same three values: the up-bar count drawn, the day's high and the tick count in the Data Window. It only draws.

Save it as MQL5/Indicators/var-persistent-variables.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
//+------------------------------------------------------------------+
//| var and varip in MQL5                                            |
//| var   -> a value carried from the previous bar's buffer element  |
//| varip -> a global variable changed on every tick                 |
//| Custom indicator: save in MQL5/Indicators and compile (F7).      |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   3

#property indicator_label1  "Up bars so far"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrTeal
#property indicator_width1  2
#property indicator_label2  "Day high so far"
#property indicator_type2   DRAW_NONE
#property indicator_label3  "Real-time ticks seen"
#property indicator_type3   DRAW_NONE

double UpBarsBuf[], DayHighBuf[], TicksBuf[];

// varip int ticks = 0: a global survives from one tick to the next, and nothing rolls it back
int ticks = 0;

int OnInit()
  {
   SetIndexBuffer(0, UpBarsBuf, INDICATOR_DATA);
   SetIndexBuffer(1, DayHighBuf, INDICATOR_DATA);
   SetIndexBuffer(2, TicksBuf, INDICATOR_DATA);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   ticks = 0;  // reset here too: an Expert Advisor keeps its globals when only the symbol or timeframe changes
   return INIT_SUCCEEDED;
  }

// True if the two times fall on different days (server time)
bool NewDay(const datetime prev, const datetime cur)
  {
   MqlDateTime a, b;
   TimeToStruct(prev, a);
   TimeToStruct(cur, b);
   return a.day != b.day || a.mon != b.mon || a.year != b.year;
  }

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 start = prev_calculated > 0 ? prev_calculated - 1 : 0;
   for(int i = start; i < rates_total; i++)
     {
      // var int upBars = 0 / if close > open: upBars += 1
      // Built from the PREVIOUS bar's value, so recalculating the forming bar on every tick
      // never counts it twice: that is Pine's rollback of var on real-time ticks
      double before = i > 0 ? UpBarsBuf[i - 1] : 0.0;
      UpBarsBuf[i] = before + (close[i] > open[i] ? 1.0 : 0.0);

      // var float dayHigh = na, reset on a new day
      if(i == 0 || NewDay(time[i - 1], time[i]))
         DayHighBuf[i] = high[i];
      else
         DayHighBuf[i] = MathMax(DayHighBuf[i - 1], high[i]);

      TicksBuf[i] = EMPTY_VALUE;
     }

   // varip: counts every real-time call (prev_calculated > 0 once the history has been calculated)
   if(prev_calculated > 0)
      ticks++;
   TicksBuf[rates_total - 1] = ticks;
   return rates_total;
  }
//+------------------------------------------------------------------+

Line by line

Pine Script v6
var int upBars = 0
if close > open
    upBars += 1
MQL5
double before = i > 0 ? UpBarsBuf[i - 1] : 0.0;
UpBarsBuf[i] = before + (close[i] > open[i] ? 1.0 : 0.0);

The previous bar's element holds the value as of that bar's close. The forming bar is recomputed from it on every tick, so it never counts twice: that is Pine's rollback.

Pine Script v6
var float dayHigh = na
if timeframe.change("D") or na(dayHigh)
    dayHigh := high
else
    dayHigh := math.max(dayHigh, high)
MQL5
if(i == 0 || NewDay(time[i - 1], time[i]))
   DayHighBuf[i] = high[i];
else
   DayHighBuf[i] = MathMax(DayHighBuf[i - 1], high[i]);

A reset is the same pattern with a condition. NewDay() compares the calendar dates of two bars in server time; Pine's timeframe.change("D") follows the exchange's sessions.

Pine Script v6
varip int ticks = 0
MQL5
int ticks = 0;  // global

A global keeps every change, and nothing undoes it between ticks.

Pine Script v6
if barstate.isrealtime
    ticks += 1
MQL5
if(prev_calculated > 0)
   ticks++;

After the first full calculation, each call comes from a new tick (or a history update): the closest MQL5 has to a real-time flag.

Pine Script v6
// Pine re-runs the script from the first bar on reload
MQL5
ticks = 0;  // in OnInit()

Reset globals in OnInit(). An Expert Advisor keeps its globals when only its symbol or timeframe changes; an explicit reset avoids carrying a count over.

Pine Script v6
plot(dayHigh, "Day high so far", color.orange,
  display = display.data_window)
MQL5
#property indicator_label2  "Day high so far"
#property indicator_type2   DRAW_NONE

Shown in the Data Window only, to compare numbers.

Pitfalls

  • A global counter in OnCalculate() counts ticks, not bars. upBars++ on every call where close > open adds one per tick on the forming bar: after a busy minute it is 40 too high. That is varip; var needs the buffer.
  • static is per program, not per bar. A static local in OnTick() keeps its value for the program's whole life, like a global. It doesn't reset each bar like a plain Pine variable, and it doesn't roll back like var.
  • Reloads. Changing inputs, recompiling or restarting the terminal re-runs an indicator from scratch: buffers are rebuilt from history, so var-style values come back, but tick counters restart from zero, as varip values do when a Pine script reloads.
  • State that must survive a restart (trades taken today, a partial close already done) can't live in a variable in either language. Rebuild it from the deal history (HistorySelect()) or keep it in a terminal global variable (GlobalVariableSet()).
  • Reading the previous element on bar 0. UpBarsBuf[i - 1] with i = 0 stops the indicator with array out of range: guard the first bar as the code does.

Checking that both give the same values

  1. Compare "Up bars so far" over a stretch of bars rather than as an absolute number: each platform counts from its own first loaded bar. The increase over the last 50 bars should be the same.
  2. "Day high so far" should match inside each day; around the day boundary it differs if the broker's server day and the exchange session don't start together.
  3. Watch the forming bar: on both, the up-bar count changes by at most one while the bar is open.
  4. The tick counters won't match, since each platform gets its own ticks, but both should rise with every price update.

var in an Expert Advisor

An Expert Advisor has no buffers and doesn't recalculate history, so a Pine var that changes once per bar becomes a global (or static) variable updated in the new-bar block of OnTick(): if(IsNewBar()) { if(iClose(_Symbol, _Period, 1) > iOpen(_Symbol, _Period, 1)) upBars++; }. Updated only there, it changes once per bar, as var does. Updated outside the new-bar block, it changes on every tick, as varip does.

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.