Guide

Why Your Pine Script Alert Fires on the Wrong Bar

alertcondition fires on every tick the condition is true, including every update of the current live candle. The confirmed-bar fix makes it fire exactly once, at the close.

9 min read

You set up a Pine Script alert for an RSI crossover. The first notification arrives. Then another. Then six more before the candle closes. You check the chart: there is a single signal on a single bar. The alert fired eight times for it.

This is the most common Pine Script alert problem, and it is not a bug in the alert system. It is a direct consequence of how Pine Script executes and a one-line fix once you understand what is happening.

Why alerts fire repeatedly on the same bar

Pine Script re-executes your entire script on every tick of the current live bar. Every condition you evaluate is checked on every price update. An alertcondition call checks its condition on every one of those executions and fires whenever it is true.

If RSI drops below 30 at 2pm and stays there until the bar closes at 4pm, your condition is true on every price update during those two hours. Depending on the symbol and session, that could be dozens or hundreds of ticks. Every one of them triggers the alert.

pine
//@version=6
indicator("Alert fires on every tick", overlay=true)

rsiValue  = ta.rsi(close, 14)
condition = rsiValue < 30

// PROBLEM: alertcondition fires on every tick where condition is true.
// If RSI is below 30 for 45 minutes on the live bar, you get a new
// alert every time price updates — potentially dozens per bar.
alertcondition(condition, "RSI oversold", "RSI below 30")

For a crossover condition, the situation is slightly different but still problematic. ta.crossover() returns true on the exact bar where the cross occurs, but during the live bar the cross can occur and then reverse multiple times as price updates. Each time the condition briefly becomes true, the alert fires.

The fix: barstate.isconfirmed

barstate.isconfirmed is true only on a bar's final tick — the close. Adding it to any alertcondition with and means the alert can only fire once per bar, at the moment the bar closes, when the condition's value is final and will not change.

pine
//@version=6
indicator("Alert fires once per bar at close", overlay=true)

rsiValue  = ta.rsi(close, 14)
condition = rsiValue < 30

// FIXED: barstate.isconfirmed is true only on the bar's final tick (the close).
// The alert fires exactly once per bar — only when the bar is confirmed closed
// and the condition is true at that moment.
alertcondition(condition and barstate.isconfirmed, "RSI oversold", "RSI below 30 on close")

This is identical to the pattern used to prevent signal repainting in indicators, which is covered in the barstate.isconfirmed guide. The same logic applies here: you want your alert to fire on the confirmed, closed bar — the same bar it will appear on permanently in the chart history. An alert that fires mid-bar is effectively notifying you of a condition that might reverse before the bar finishes.

The correct pattern for crossover alerts

For crossover-based conditions, the combination of ta.crossover() and barstate.isconfirmed is doubly useful. The crossover already restricts the event to a single bar, and isconfirmed restricts it further to the final tick of that bar. The result is one notification per genuine confirmed crossover.

pine
//@version=6
indicator("Crossover alert — once per signal", overlay=false)

fastMa = ta.ema(close, 9)
slowMa = ta.ema(close, 21)

// ta.crossover returns true only on the bar where the cross happens.
// It is already a one-bar event, but combining with isconfirmed ensures
// it fires on the confirmed close rather than potentially mid-bar if
// the cross occurs and then reverses before the bar finishes.
crossUp   = ta.crossover(fastMa,  slowMa)
crossDown = ta.crossunder(fastMa, slowMa)

alertcondition(crossUp   and barstate.isconfirmed, "Bullish cross", "9 EMA crossed above 21 EMA")
alertcondition(crossDown and barstate.isconfirmed, "Bearish cross", "9 EMA crossed below 21 EMA")

plot(fastMa, "Fast EMA", color=color.aqua)
plot(slowMa, "Slow EMA", color=color.orange)

How strategy alerts work differently

Strategy alerts behave differently from alertcondition. When you create an alert on a strategy in TradingView, the alert dialog offers several trigger options. "Order fills only" fires when strategy.entry or strategy.exit executes a simulated order, which is the most precise option for trade automation. "Once per bar close" fires once per bar when the script finishes its close-bar calculation, which matches the isconfirmed behavior for indicators.

pine
//@version=6
strategy("Strategy alert — order-fill confirmation", overlay=true)

rsiValue = ta.rsi(close, 14)
crossUp  = ta.crossover(rsiValue, 30)

if crossUp
    strategy.entry("Long", strategy.long)

// Strategy alerts work differently from alertcondition.
// When you create an alert on a strategy, TradingView offers options:
// "Order fills only" — fires when strategy.entry/exit executes.
// "Once per bar close" — fires once per bar on the strategy's close-bar calc.
// Use "Once per bar close" to match the indicator alert behavior above.
plot(rsiValue, "RSI", color=color.aqua)
hline(30)

If you are using alerts to trigger webhook-based order execution, "Order fills only" is almost always the right choice. It fires exactly when the strategy executes an order, and the alert message can include the order direction and size using TradingView's placeholder syntax. The full alert message format and webhook JSON structure is covered in the Pine Script alerts guide.

A note on once() for alertcondition

Pine Script also offers alert() (distinct from alertcondition) with a freq parameter that accepts alert.freq_once_per_bar_close. This is an alternative way to achieve the same result for scripts that use the newer alert() function rather than the older alertcondition(). The two functions have different capabilities:alert() can be called conditionally inside an if block and can dynamically construct its message string, while alertcondition() must be called at global scope and uses a static message. For new scripts, alert() with alert.freq_once_per_bar_close is the cleaner choice.

If you are building a complex indicator with multiple alert conditions across different timeframes and want to ensure all of them fire at the right moment, PineScripter applies barstate.isconfirmed and correct alert patterns when generating indicators from a plain-English description. Asking it to "add an alert when RSI crosses above 30 on a confirmed close" produces the correct pattern without you needing to remember the exact function syntax.


Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.