Edgoro
Expert Advisor · MQL5

Detect a new bar in MQL5 (once per bar, any timeframe)

Short answer

Store the open time of the current bar, iTime(_Symbol, PERIOD_CURRENT, 0), and compare it on every tick: when it changes, a new bar has opened and bar 1 is the one that just closed. Keep one stored time per timeframe, and ignore a result of 0, which means that timeframe's history isn't loaded yet.

UsesiTimeOnTickiCloseEnumToString

The code

An Expert Advisor that trades nothing: it prints a line in the Experts tab when a bar opens on the chart's timeframe and on a second timeframe you choose.

Save it as MQL5/Experts/detect-new-bar.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
input ENUM_TIMEFRAMES InpHigherTf = PERIOD_H4; // Second timeframe to watch

// Says when a new bar opens on one symbol and timeframe. One object per timeframe, so they never share state.
class CNewBar
  {
private:
   string            m_symbol;
   ENUM_TIMEFRAMES   m_tf;
   datetime          m_last;    // open time of the last bar seen
public:
                     CNewBar(void) : m_symbol(""), m_tf(PERIOD_CURRENT), m_last(0) {}
   void              Init(const string symbol, const ENUM_TIMEFRAMES tf, const bool skipCurrent)
     {
      m_symbol = symbol;
      m_tf     = tf;
      // skipCurrent: the bar already open when the EA starts doesn't count as new
      m_last   = skipCurrent ? iTime(symbol, tf, 0) : 0;
     }
   bool              IsNew(void)
     {
      datetime open = iTime(m_symbol, m_tf, 0);
      if(open == 0)            // history not ready yet: try again on the next tick
         return false;
      if(open == m_last)
         return false;
      m_last = open;
      return true;
     }
  };

CNewBar chartBar;
CNewBar higherBar;

int OnInit()
  {
   chartBar.Init(_Symbol, PERIOD_CURRENT, true);
   higherBar.Init(_Symbol, InpHigherTf, true);
   return INIT_SUCCEEDED;
  }

void OnTick()
  {
   if(chartBar.IsNew())
     {
      // Runs once per bar, on its first tick. Bar 1 is the bar that just closed.
      PrintFormat("New %s bar at %s. The last bar closed at %s", EnumToString(_Period),
                  TimeToString(iTime(_Symbol, PERIOD_CURRENT, 0)), DoubleToString(iClose(_Symbol, PERIOD_CURRENT, 1), _Digits));
     }
   if(higherBar.IsNew())
      PrintFormat("New %s bar at %s", EnumToString(InpHigherTf), TimeToString(iTime(_Symbol, InpHigherTf, 0)));
  }

How it works

  • iTime(m_symbol, m_tf, 0). The open time of the bar that's forming. It changes exactly once per bar, on the first tick of the new one, which makes it a better key than any price or bar count.
  • open == 0. The terminal hasn't built that timeframe's history yet: common in the first seconds after start, and for a timeframe that isn't open on any chart. Returning false without updating m_last means the check runs again on the next tick.
  • One object per timeframe. Each CNewBar keeps its own m_last. A single static datetime inside a shared function would be updated by the first timeframe that calls it and hide the other's new bars.
  • skipCurrent. Attach the EA ten minutes into an H1 bar and, without it, the first tick reports a "new bar" that opened ten minutes ago, so a signal on bar 1 would be acted on late. With true, the first event is the next real bar open.
  • Bar 1 inside the block. On the new bar's first tick, bar 0 has barely started (its close is the current Bid). Signals belong on bar 1, the bar that just closed, and the ones before it.

Pitfalls

  • Counting bars with Bars() or iBars(). The count also changes when the terminal loads more history, so a jump isn't always a new bar. The open time of bar 0 has no such ambiguity.
  • No tick, no event. OnTick() only runs when a tick arrives, so the check fires on the first tick of the bar, which on a quiet symbol can come well after the bar's official open time. A period with no ticks has no bar at all. To act at an exact time, use a timer.
  • Marking the bar as seen before the data is ready. If the code updates its stored time and then CopyBuffer() fails with 4806, that bar's signal is lost. Update the stored time only after the indicator values are read, as in reading indicator values.
  • Restarts. Changing the inputs or the chart's timeframe re-initializes the EA and resets the stored time. Don't rely on the new-bar check alone to prevent a second trade on the same bar: check the deal history.
  • Reading bars that don't exist. Indexing a series array past the bars copied stops the EA with array out of range. Copy what you need, and check how many values came back.

Variations

  • Without a class. For a single timeframe, two lines in OnTick() do it: static datetime last = 0; datetime t = iTime(_Symbol, PERIOD_CURRENT, 0); if(t == 0 || t == last) return; last = t;.
  • Another property. (datetime)SeriesInfoInteger(_Symbol, tf, SERIES_LASTBAR_DATE) returns the same open time as iTime(..., 0).
  • Several symbols. One CNewBar per symbol works, but OnTick() only runs on ticks of the chart's own symbol: poll the others from OnTimer().
  • In an indicator. OnCalculate() already tells you: a new bar has opened when rates_total is greater than prev_calculated (after the first call).

Questions

How do I run an MQL5 EA only once per candle?

Keep the open time of bar 0 in a static or global variable and return from OnTick() while iTime(_Symbol, PERIOD_CURRENT, 0) still equals it. When it differs, store the new time and run your logic on bar 1, the candle that just closed.

Why does my new-bar code trigger as soon as I attach the EA?

Because the stored time starts at 0, so the bar already open looks new. Initialize it with the current bar's open time in OnInit() (the skipCurrent option in this example) if you only want bars that open after the EA starts.

Do bars open at the same time for every broker?

Not for H4 and daily bars. Bar times follow the broker's server clock, so an H4 or D1 bar on a server at UTC+2 opens at a different moment than on a server at UTC+0, and the bars contain different prices. Minute and hourly bars line up as long as the offset is a whole number of hours.

This program is a minimal example written to show one technique, not a trading system, and none of it 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.

All MQL5 errors

Want it inside a complete EA?

Every EA from Edgoro's MT5 EA generator evaluates its signals once per bar, on closed bars, unless you ask for tick-by-tick logic. A clean build proves the code compiles, not that a strategy works: test it on a demo account.