Closed Bar or Forming Bar: Which Candle Your EA's Signals Should Read

Closed Bar or Forming Bar: Which Candle Your EA's Signals Should Read

28 September 2026, 19:11
Boris Armenteros
0
6

Every indicator-based Expert Advisor has to decide which candle it reads: the one that is still forming, or the last one that closed. The same moving-average crossover or RSI threshold behaves differently depending on the answer. It changes how many signals the EA sees, how many of them turn out to be false, and how early it acts on them.

Neither option is right for every system. What matters is making the choice on purpose, in code, and knowing what each side costs.

What the choice trades off

Closed candle Live candle
Signals Fewer, which lowers the risk of false signals More, which raises the risk of false signals
Basis Closing prices of finished candles The most recent price action
Timing Later: the signal waits for the close Earlier and more responsive to short-term moves
Weak spot Can lag current market conditions More sensitive to short-term moves, less reliable in choppy markets

Take a 20-period and a 50-period moving average on a daily chart. On closed candles, a cross of the 20 above the 50 is read as a buy signal and a cross below as a sell signal, once the candle has finished. Put the same pair on a 5-minute chart and read it on the live candle, and the signal arrives sooner, from the price action still in progress. An RSI reading above 70 or below 30 follows the same split: on a closed candle the reading is final, while on the live candle it can still change before the close.

What "closed" and "live" mean in MQL5

In a timeseries, index 0 is the current bar. The documentation on indexing direction says the element with index 0 "contains the information about the latest quote of a symbol", and that the data of the current, not yet completed period sit at the zero position. Index 1 is the last bar that closed.

CopyBuffer()  counts the same way. In its documentation, a "starting position of 0 means the current bar (indicator value for the current bar)". So an EA that copies from position 0 is trading on a live candle, whether its author meant to or not: the value at index 0 is recalculated as new quotes arrive, until the bar closes. From position 1 it reads bars that have finished.

Evaluating once per closed bar

One way to read closed candles is to run the signal logic once, when a new bar opens, and look at the bar that has just finished. iTime() returns the opening time of the bar at the given shift, or 0 on error, so a change in the opening time of bar 0 marks a new bar:

datetime lastBarTime = 0;

bool IsNewBar()
  {
   datetime barTime = iTime(_Symbol, _Period, 0);   // opening time of the forming bar
   if(barTime == 0 || barTime == lastBarTime)
      return false;                                 // no data yet, or still the same bar
   lastBarTime = barTime;
   return true;
  }

The first tick of each new bar returns true, as does the first tick after the EA starts; every other tick of that bar returns false.

Reading a crossover from closed bars

The crossover itself only needs the last two values of each average. The start position decides which two:

int fastHandle = INVALID_HANDLE;   // iMA(_Symbol, _Period, 20, 0, MODE_SMA, PRICE_CLOSE) in OnInit()
int slowHandle = INVALID_HANDLE;   // iMA(_Symbol, _Period, 50, 0, MODE_SMA, PRICE_CLOSE) in OnInit()

// startPos = 1: the last two closed bars. startPos = 0: the forming bar and the last closed one.
bool CrossedUp(const int startPos)
  {
   double fast[], slow[];
   ArraySetAsSeries(fast, true);   // index 0 = the newest bar copied
   ArraySetAsSeries(slow, true);
   if(CopyBuffer(fastHandle, 0, startPos, 2, fast) != 2 ||
      CopyBuffer(slowHandle, 0, startPos, 2, slow) != 2)
     {
      printf("%s: WARNING: CopyBuffer failed, error %d", __FUNCTION__, GetLastError());
      return false;
     }
   return fast[1] <= slow[1] && fast[0] > slow[0];   // not above before, above now
  }

Two details carry the logic. The arrays are dynamic, because ArraySetAsSeries() cannot set the flag on a static array. And the flag is what makes index 0 the newest value: CopyBuffer()  stores the oldest element first whatever the flag, so without it fast[0]  would be the older of the two bars and the comparison would run backwards. CopyBuffer()  returns the number of elements copied, or -1 on error, so the code compares the result with the 2 values it needs: a short copy fails the same way an error does.

What the live version does differently

Called with a start position of 0, the same function compares the forming bar with the last closed one. The value at index 0 moves with each new quote, so a crossover can appear during a bar and be gone by its close. Called on every tick, the function reports each of those. That is the mechanism behind "more signals, more false signals", and also the reason the live version reacts earlier: it is looking at the price action still in progress.

A live-candle EA also has to stop itself acting on the same crossover on every tick while it holds. That needs a latch of its own, which is not shown here.

Making the choice testable

The choice depends on the indicator, the strategy, and how much risk of a false signal the system can absorb, so it is worth testing both rather than settling it by habit. Making it an input keeps the two versions in one EA:

input bool InpClosedBarOnly = true;   // Signals on closed bars only

void OnTick()
  {
   if(InpClosedBarOnly && !IsNewBar())
      return;                           // closed-bar mode: evaluate once per bar
   if(CrossedUp(InpClosedBarOnly ? 1 : 0))
     {
      // entry logic
     }
  }

Run it over the same period once with each setting and compare the signal counts, not only the equity curve. Combining different indicators may also help the reliability of either mode. What we would rather not see is the choice made by accident, because the code happened to read index 0.

The trader-side comparison, with the moving-average and RSI examples, is on our blog.