Quick Answer
barstate.isnew is true on the first update of a new bar. In MQL5, the same moment is detected inside OnTick() by comparing the open time of the current bar with the one you saved earlier. When the time changes, a new bar has started.
There is a second point that surprises many people. In Pine, barstate.isconfirmed is true when a bar closes. In an EA, the close of bar N and the start of bar N+1 happen at the same moment, so one new-bar check covers both. Run your logic there, and read the values of the bar that just closed with shift 1.
Key Facts
| Pine Script | MQL5 |
|---|---|
| barstate.isnew | A new-bar check in OnTick(): compare iTime(_Symbol, _Period, 0) with a saved time |
| barstate.isconfirmed | The same new-bar check, and then read closed-bar values with shift 1 |
| barstate.isfirst | A flag set once in OnInit() or on the first run |
| barstate.ishistory | No direct equivalent. An EA does not replay history |
| barstate.isrealtime | An EA on a live chart is always realtime. In the tester, check MQLInfoInteger(MQL_TESTER) |
| barstate.islast | The current bar is always shift 0, the last bar |
| Script runs on every update of the live bar | OnTick() runs on every tick |
The Pine Script Version
//@version=5
strategy("barstate example", overlay = true)
var int barCount = 0
if barstate.isnew
barCount += 1
fast = ta.ema(close, 10)
slow = ta.ema(close, 30)
longCond = ta.crossover(fast, slow)
if barstate.isconfirmed and longCond
strategy.entry("Long", strategy.long)
The counter grows once per bar, and the entry is only allowed when the bar is confirmed, which means closed.
Detect a New Bar in MQL5
OnTick() runs on every tick, not on every bar. To act once per bar, save the open time of the current bar and compare it on each tick:
datetime g_lastBarTime = 0;
int OnInit()
{
// Start from the current bar, so the first tick after attaching is not treated as a new bar
g_lastBarTime = iTime(_Symbol, _Period, 0);
return INIT_SUCCEEDED;
}
bool IsNewBar()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == 0)
return false; // history not ready yet
if(currentBarTime == g_lastBarTime)
return false;
g_lastBarTime = currentBarTime;
return true;
}
Initializing g_lastBarTime in OnInit() is a choice. With a static variable that starts at 0, the first tick after you attach the EA returns true, and the logic runs once on a bar that was already open. Decide which behavior you want. For most converted strategies, waiting for the next real new bar is safer.
barstate.isconfirmed: Use Closed-Bar Values
When IsNewBar() returns true, the previous bar has just closed. Its final values are at shift 1:
double closePrice = iClose(_Symbol, _Period, 1); // the bar that just closed
Do not read shift 0 for confirmed logic. Shift 0 is the new bar, and it has only one tick so far. The same rule is explained for close[1] in Pine Script close[1] in MQL5.
Full MQL5 Example
This EA counts bars and detects the same EMA cross as the Pine code, once per closed bar. It prints the signal and does not place trades.
input int FastPeriod = 10;
input int SlowPeriod = 30;
int fastHandle = INVALID_HANDLE;
int slowHandle = INVALID_HANDLE;
int g_barCount = 0;
datetime g_lastBarTime = 0;
int OnInit()
{
fastHandle = iMA(_Symbol, _Period, FastPeriod, 0, MODE_EMA, PRICE_CLOSE);
slowHandle = iMA(_Symbol, _Period, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(fastHandle == INVALID_HANDLE || slowHandle == INVALID_HANDLE)
return INIT_FAILED;
g_lastBarTime = iTime(_Symbol, _Period, 0);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(fastHandle != INVALID_HANDLE) IndicatorRelease(fastHandle);
if(slowHandle != INVALID_HANDLE) IndicatorRelease(slowHandle);
}
bool IsNewBar()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == 0 || currentBarTime == g_lastBarTime)
return false;
g_lastBarTime = currentBarTime;
return true;
}
// Reads the last two closed bars: v1 = shift 1, v2 = shift 2
bool ReadClosedValues(int handle, double &v1, double &v2)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(handle, 0, 1, 2, buf) != 2)
return false;
v1 = buf[0];
v2 = buf[1];
return true;
}
void OnTick()
{
if(!IsNewBar())
return;
g_barCount++; // like: if barstate.isnew -> barCount += 1
double fast1, fast2, slow1, slow2;
if(!ReadClosedValues(fastHandle, fast1, fast2) ||
!ReadClosedValues(slowHandle, slow1, slow2))
return;
// ta.crossover(fast, slow) on the closed bar
bool longCond = (fast1 > slow1 && fast2 <= slow2);
if(longCond)
PrintFormat("Bar %d: fast EMA crossed above slow EMA on the last closed bar", g_barCount);
}
The cross rule is fast1 > slow1 && fast2 <= slow2, the same idea as ta.crossover. For the details of that function, see Pine Script ta.crossover in MQL5.
What Does Not Translate
History. Pine runs the script over every historical bar and then continues in realtime, so var state is built from history. An EA starts at the current moment and does not replay old bars, so any state that Pine built from history, such as g_barCount, starts from zero when the EA starts. If you need it, rebuild it from the chart history or from open positions. Pine Script var and varip in MQL5 shows how.
Missed ticks. The new-bar check runs when a tick arrives. If the market is quiet and no tick comes at the start of a bar, the check runs a little later. Pine does not have this delay.
Strategy Tester. In the tester, every bar is history, so ishistory and isrealtime have no meaning there. If the Pine code behaves differently in realtime and on history, the results of the tester and the live account can differ. The causes are collected in Why a Pine Script Strategy Gives Different Results in MQL5. How much of each bar the tester models is explained in MT5 every tick vs real ticks.
Common Mistakes
- Running logic on every tick. Counters grow too fast and signals repeat. Use the new-bar check.
- Reading shift 0 for confirmed logic. Use shift 1 after a new bar is detected.
- Acting on the first tick after attaching. A
staticvariable that starts at 0 treats the open bar as new. Initialize the saved time inOnInit(). - Using
Bars()or the bar count as the new-bar test. When the chart has a maximum number of bars, the count can stay the same while a new bar appears, because the oldest bar drops out. Compare the bar time. - Keeping the saved time per symbol in one shared variable. If one EA handles several symbols, each symbol needs its own saved time.
- Expecting
barstate.ishistoryto exist. It has no equivalent. Rebuild the state instead. - Using
GetTickCountor seconds to guess the bar. Use the bar open time.
FAQ
Related Guides
- Pine Script close[1] in MQL5: closed-bar values and shift 1.
- Pine Script ta.crossover in MQL5: detect a cross on closed bars.
- Pine Script var and varip in MQL5: keep state between bars and after a restart.
- MQL5 CopyBuffer guide: read indicator values safely.
- Why a Pine Script Strategy Gives Different Results in MQL5: realtime versus history differences.
- MT5 every tick vs real ticks: what the tester models.
- What to check in a trading bot before going live: closed-bar signals and the other pre-live checks.
Convert Bar Logic Without Surprises
Bar events are easy to get wrong: one extra tick, one wrong shift, and a signal fires on the wrong bar. CodeFlowOS translates Pine Script into MQL5 and lets you review the generated code before you test it in the MT5 Strategy Tester. The platform compile check is optional, and outputs are not guaranteed, so always review the result. For the full process, see the Pine Script to MQL5 guide, or try the converter page. If the code does not compile, the Error Fixer can suggest a fix, and the free early-bird Code Review checks an EA for common mistakes such as trading on every tick.
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.