Edgoro
Pine Script v6 → MQL5 expert advisor

barstate.isconfirmed in MQL5: act once per closed bar

Short answer

barstate.isconfirmed is true on a bar's last tick, when its values are final. An Expert Advisor gets that moment one tick later: on the first tick of the next bar, detected when iTime(_Symbol, _Period, 0) changes, it reads the bar that just closed at shift 1. In an indicator, base signals on the bar at rates_total - 2.

Pinebarstate.isconfirmedbarstate.isnewalert.freq_once_per_bar_closeta.crossover

MQL5iTimeiCloseCopyBufferAlertSendNotification

  • MQL5: compiled in MetaEditor, 0 errors, 0 warnings
  • Pine: checked by Edgoro Pine Check, not run in TradingView

The Pine Script v6 code

An overlay indicator that marks the bars whose close crossed above a 20 SMA, only once the bar has closed, and sends an alert at that moment.

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("Close above SMA, closed bars only", overlay = true)

len = input.int(20, "SMA length", minval = 1)

sma = ta.sma(close, len)
crossUp = ta.crossover(close, sma)
// Only once the bar has closed, so a signal can't appear and then vanish in real time
signal = crossUp and barstate.isconfirmed

plot(sma, "SMA", color.orange, 2)
plotshape(signal, "Close crossed above SMA", shape.labelup, location.belowbar, color.teal, size = size.tiny)

if signal
    alert("Close crossed above the SMA on " + syminfo.ticker, alert.freq_once_per_bar_close)

The MQL5 program

An Expert Advisor that checks the same cross once per bar, on the closed bar, and alerts with a pop-up and optionally a push notification. It never trades.

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

MQL5
//+------------------------------------------------------------------+
//| barstate.isconfirmed in an Expert Advisor: act once per bar,     |
//| on the bar that just closed. This EA only alerts, it never       |
//| trades. Save in MQL5/Experts and compile (F7).                   |
//+------------------------------------------------------------------+
input int  InpSmaLen = 20;    // SMA length
input bool InpPopup  = true;  // Pop-up alert
input bool InpPush   = false; // Push notification to the MetaTrader app

int      smaHandle   = INVALID_HANDLE;
datetime lastBarTime = 0;

int OnInit()
  {
   if(InpSmaLen < 1)
      return INIT_PARAMETERS_INCORRECT;
   smaHandle = iMA(_Symbol, _Period, InpSmaLen, 0, MODE_SMA, PRICE_CLOSE);
   if(smaHandle == INVALID_HANDLE)
      return INIT_FAILED;
   // Like a Pine alert, start with the next bar that closes, not with history
   lastBarTime = iTime(_Symbol, _Period, 0);
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   IndicatorRelease(smaHandle);
  }

// True on the first tick of a new bar: the previous bar (shift 1) has just closed
bool IsNewBar()
  {
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == 0 || t == lastBarTime)
      return false;
   lastBarTime = t;
   return true;
  }

void OnTick()
  {
   if(!IsNewBar())
      return;

   double sma[];
   ArraySetAsSeries(sma, true);  // [0] = bar 1 (just closed), [1] = bar 2
   double c1 = iClose(_Symbol, _Period, 1);
   double c2 = iClose(_Symbol, _Period, 2);
   if(CopyBuffer(smaHandle, 0, 1, 2, sma) != 2 || c1 == 0.0 || c2 == 0.0)
     {
      lastBarTime = 0;  // data not ready: evaluate this bar again on the next tick
      return;
     }

   // ta.crossover(close, sma) on the closed bar; the closed bar is barstate.isconfirmed
   if(!(c1 > sma[0] && c2 <= sma[1]))
      return;

   string msg = StringFormat("Close crossed above the SMA(%d) on %s, bar of %s", InpSmaLen, _Symbol,
                             TimeToString(iTime(_Symbol, _Period, 1), TIME_DATE | TIME_MINUTES));
   Print(msg);
   if(InpPopup)
      Alert(msg);
   if(InpPush && !SendNotification(msg))
      Print("Push notification failed, error ", GetLastError());
  }
//+------------------------------------------------------------------+

Line by line

Pine Script v6
barstate.isconfirmed
MQL5
bool IsNewBar()
  {
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == 0 || t == lastBarTime)
      return false;
   lastBarTime = t;
   return true;
  }

MQL5 has no last-tick-of-the-bar event: a bar is known to be closed only when the next one starts. iTime() of shift 0 is the open time of the forming bar; when it changes, a new bar has begun.

Pine Script v6
sma = ta.sma(close, len)
MQL5
ArraySetAsSeries(sma, true);
CopyBuffer(smaHandle, 0, 1, 2, sma);

Starting at shift 1 skips the forming bar: sma[0] is the bar Pine is confirming and sma[1] its [1].

Pine Script v6
crossUp = ta.crossover(close, sma)
signal = crossUp and barstate.isconfirmed
MQL5
double c1 = iClose(_Symbol, _Period, 1);
double c2 = iClose(_Symbol, _Period, 2);
if(!(c1 > sma[0] && c2 <= sma[1]))
   return;

The crossover on the closed bar. Nothing here reads shift 0, so no tick can change the result.

Pine Script v6
alert("Close crossed above the SMA on " + syminfo.ticker,
  alert.freq_once_per_bar_close)
MQL5
Alert(msg);
if(InpPush && !SendNotification(msg))
   Print("Push notification failed, error ", GetLastError());

It runs once per bar because IsNewBar() is true once per bar.

Pine Script v6
// Pine alerts only fire in real time
MQL5
lastBarTime = iTime(_Symbol, _Period, 0);  // OnInit

Starting with the current bar's time means the first check happens when the next bar closes, not on a bar that closed before the EA started.

Pine Script v6
// Pine waits for the data
MQL5
if(CopyBuffer(...) != 2 || c1 == 0.0 || c2 == 0.0)
  {
   lastBarTime = 0;  // evaluate again on the next tick
   return;
  }

If the values aren't available yet, forget that the bar was seen so the next tick tries again instead of skipping the bar.

Pitfalls

  • The first tick can be late. The new bar is noticed when a tick arrives. On a quiet market that can be seconds or minutes after the close; Pine's barstate.isconfirmed fires on the bar's last tick. Same values, later moment.
  • Bars without ticks. If no tick arrives during a whole bar, that bar doesn't exist in MetaTrader 5, so shift 2 can be further back in time than the clock suggests.
  • The tester's "Open prices only". That mode only runs the first tick of each bar. An EA that reads only closed bars behaves the same there and live; one that reads shift 0 doesn't, which is how an MT5 backtest repaints. See MT5 Strategy Tester modelling modes.
  • rates_total > prev_calculated in an indicator is also true on the first call and whenever history is reloaded. Compare bar times instead, as the alert indicator does.
  • Restarts. lastBarTime lives in memory: after a restart the EA starts again from the current bar. Fine for alerts; for trades, check the open positions or the deal history rather than a variable.

Checking that both give the same values

  1. In TradingView, add the Pine indicator and create an alert on "Any alert() function call"; in MetaTrader 5, attach the EA to the same symbol and timeframe. A 1-minute chart gives signals quickly.
  2. Both should fire after the same bar closes, MetaTrader 5 on the next tick.
  3. On history, run the EA in the Strategy Tester with visual mode on: the Journal prints each signal with its bar time, to compare with the labels on the TradingView chart.

The same idea in a custom indicator

An indicator recalculates the forming bar on every tick, at index rates_total - 1. Values there can still change, like Pine's real-time bar. To keep only confirmed signals, loop over closed bars, for(int i = start; i < rates_total - 1; i++), and decide alerts on rates_total - 2, remembering its time[] so later ticks don't repeat them. The pivot indicator confirms pivots that way.

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.