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.
//@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.
//+------------------------------------------------------------------+
//| 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
barstate.isconfirmedbool 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.
sma = ta.sma(close, len)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].
crossUp = ta.crossover(close, sma)
signal = crossUp and barstate.isconfirmeddouble 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.
alert("Close crossed above the SMA on " + syminfo.ticker,
alert.freq_once_per_bar_close)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 alerts only fire in real timelastBarTime = iTime(_Symbol, _Period, 0); // OnInitStarting 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 waits for the dataif(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.isconfirmedfires 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_calculatedin an indicator is also true on the first call and whenever history is reloaded. Compare bar times instead, as the alert indicator does.- Restarts.
lastBarTimelives 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
- 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.
- Both should fire after the same bar closes, MetaTrader 5 on the next tick.
- 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.