Quick Answer
In Pine Script, var declares a variable that is set once on the first bar and then keeps its value from bar to bar. In MQL5, a global variable or a static local variable does the same job, because it keeps its value between calls to OnTick().
The difference is in how often the code runs. Pine evaluates the script on every bar (and on every update of the live bar), while an EA's OnTick() runs on every tick. To get Pine's once-per-bar behavior, update your state only when a new bar opens. And because an EA starts with empty variables after a restart, you may need to rebuild the state from open positions or save it.
Key Facts
| Pine Script | MQL5 |
|---|---|
| var x = 0 (set once, kept across bars) | Global variable int x = 0; or static int x = 0; inside a function |
| x := x + 1 | x++ in your once-per-bar code |
| A normal variable (reset on every bar) | A plain local variable inside the function |
| varip (kept across live updates of the same bar) | A global variable updated on every tick |
| State lives as long as the script runs | State lives until the EA is removed, the terminal restarts or the inputs change |
| State is rebuilt when the script runs on history | State must be rebuilt after a restart, from positions or saved values |
The Pine Script Version
//@version=5
strategy("var example", overlay = true)
var int barsInTrade = 0
var float highestSinceEntry = na
if strategy.position_size > 0
barsInTrade += 1
highestSinceEntry := na(highestSinceEntry) ? high : math.max(highestSinceEntry, high)
else
barsInTrade := 0
highestSinceEntry := na
This counts the bars since a long entry and keeps the highest high since entry. Without var, both variables would reset on every bar and the counter would never go above 1.
Normal Variables vs var vs varip
Pine has three behaviors, and each maps to a different place in MQL5:
- Normal variable. It is calculated again on every bar. In MQL5, this is a local variable inside the function that you call each bar.
var. It keeps the value from the previous bar. On a live bar, Pine rolls it back to the last closed-bar value before each new update, so the value from an unfinished bar does not stay. In MQL5 a global does not roll back. If you change it on every tick, the tick changes stay.varip. It keeps changes made during live updates of the bar and does not roll back. This is closest to a normal MQL5 global that you change on every tick.
Check the Pine documentation for the exact rules of your version, because varip only matters on live bars and is not applied to historical bars.
The consequence for an EA: if you want the behavior of var, update the variable once, when a new bar opens, and use the closed-bar values (shift 1). If you update it on every tick, you get something closer to varip.
Full MQL5 Example
This EA tracks the number of bars since a position was opened and the highest high since entry, and prints them once per bar. It only reads positions, it does not place trades.
input long MagicNumber = 123456;
// State, equivalent to Pine "var"
int barsInTrade = 0;
double highestSinceEntry = 0.0;
bool inTrade = false;
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;
}
bool HasPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber)
continue;
return true;
}
return false;
}
void ResetState()
{
barsInTrade = 0;
highestSinceEntry = 0.0;
inTrade = false;
}
void OnTick()
{
if(!IsNewBar())
return;
if(HasPosition())
{
double lastHigh = iHigh(_Symbol, _Period, 1); // last closed bar
if(lastHigh == 0.0)
return;
if(!inTrade)
{
// First bar we see with a position
inTrade = true;
barsInTrade = 1;
highestSinceEntry = lastHigh;
}
else
{
barsInTrade++;
highestSinceEntry = MathMax(highestSinceEntry, lastHigh);
}
PrintFormat("Bars in trade: %d, highest since entry: %.5f",
barsInTrade, highestSinceEntry);
}
else
{
ResetState();
}
}
All three variables are globals, so they keep their values between ticks, like Pine var. They change only inside the IsNewBar() branch, so each closed bar is counted once. The same new-bar pattern and shift 1 rule are explained in Pine Script close[1] in MQL5.
Static Locals: State Inside One Function
If only one function needs the value, a static local variable keeps it private to that function:
int CountNewBars()
{
static int counter = 0; // initialized once, kept between calls
counter++;
return counter;
}
A normal local variable would be created again on each call and always start from its initial value, like a Pine variable without var.
What Happens When the EA Restarts
Pine keeps its state as long as the script runs, and recalculates from history when it is reloaded. An EA does not do that. When you remove the EA, change its inputs, change the symbol or timeframe, or restart the terminal, OnInit() runs again and every global starts from its initial value. Your barsInTrade would be 0 even if a trade has been open for an hour.
There are two ways to deal with it:
- Rebuild from the position. The open position holds the entry time, so you can work out the state again:
void RestoreStateFromPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber)
continue;
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
int entryShift = iBarShift(_Symbol, _Period, openTime, false);
if(entryShift < 0)
return;
inTrade = true;
barsInTrade = entryShift; // closed bars since the entry bar
// Highest high from the entry bar up to the last closed bar
int count = entryShift + 1;
int idx = iHighest(_Symbol, _Period, MODE_HIGH, count, 1);
if(idx >= 0)
highestSinceEntry = iHigh(_Symbol, _Period, idx);
return;
}
}
int OnInit()
{
RestoreStateFromPosition();
return INIT_SUCCEEDED;
}
This is a simple version. The result can differ by one bar from what a continuous run would give, depending on how you count the entry bar, so decide on a rule and keep it the same in both places.
- Save the value. For values that cannot be rebuilt, such as a counter of trades today, store them with terminal global variables (
GlobalVariableSet,GlobalVariableGet,GlobalVariableCheck) or in a file. Terminal global variables survive an EA restart. They may behave differently in the Strategy Tester, so test that part on its own.
For a position that belongs to your EA, use the magic number filter described in how to filter positions by magic number in MQL5, so that the restore code does not pick up other positions.
Using State in Signals
State is most useful when it combines with an entry or an exit rule. For example, you can close a trade after a number of bars, or trail a stop from highestSinceEntry. For the signal side, see Pine Script ta.crossover in MQL5, and for the closing side, see Pine Script strategy.close to MQL5.
Common Mistakes
- Updating state on every tick. The counter grows many times per bar. Use a new-bar check.
- Using a local variable and expecting it to persist. It is created again on each call. Use a global or
static. - Using the forming bar's values. Use shift 1, the last closed bar, for the state update.
- Forgetting to reset. When the position closes, set the state back to its start value.
- Ignoring restarts. After a restart the globals are empty. Rebuild or save what matters.
- Sharing one global between several symbols. If the EA runs on different charts, each chart has its own copy, but code that mixes symbols inside one EA needs a separate state for each symbol.
- Expecting
varipbehavior from a once-per-bar update. If you need intrabar state, update on every tick and accept that it can change before the bar closes.
FAQ
Convert Stateful Strategies Without Surprises
State is easy to get wrong: one update on the wrong event, and a counter or a trailing level drifts. CodeFlowOS translates Pine Script into MQL5 and lets you review the generated code before you test it in the MT5 Strategy Tester. For the full process, see the Pine Script to MQL5 guide, or try 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.