◆ New — Strategy Validator, Code Fixer & Prop Compliance Checker are live. Try one free →
Home / Learn / alertcondition() vs alert() in Pine Script: Which One Should You Use?
Pine Script

alertcondition() vs alert() in Pine Script: Which One Should You Use?

A precise comparison of Pine Script's two alert mechanisms: alertcondition(), which registers a named, fixed-message alert option in TradingView's Create Alert dialog, and alert(), which fires events from inside script logic with dynamically built messages. Covers the const-string limitation, the unconfirmed-bar trap that makes alerts fire on signals that later disappear, and what to use in strategies. Educational material only, not financial advice — an alert is a notification mechanism, and no alerting choice makes a signal profitable; trading carries a real risk of loss.

What is the difference between alertcondition() and alert()?

*`alertcondition()` registers a named alert option that appears in TradingView's Create Alert dialog, and its message must be a compile-time constant string. `alert()` is a runtime call that fires an alert event from inside your script's logic, with a message you can build dynamically on each bar.* That is the whole distinction in two sentences — everything else follows from it.

With alertcondition(), your script advertises one or more named conditions ("Long cross", "RSI oversold"). A user opens the Create Alert dialog, picks your indicator, and selects one of those names from the condition dropdown. The message attached to it is frozen at compile time — the user can edit it in the dialog, and TradingView substitutes a small set of placeholders like {{ticker}} and {{close}} when it fires, but the script itself cannot compute the text.

With alert(), the user instead creates one alert on the condition "Any alert() function call", and from then on your script decides when an event fires and what the message says, using ordinary string logic — str.tostring(), concatenation, conditional text. Frequency is controlled in code via alert.freq_once_per_bar, alert.freq_once_per_bar_close, or alert.freq_all.

Two constraints apply to both: a script can never create a TradingView alert by itself — a human must always set one up in the dialog — and alertcondition() is only available in indicator() scripts; it does nothing in a strategy(). Strategies use alert() or order-fill alerts with alert_message instead, which we cover at the end.

Neither mechanism changes what your signal is. An alert delivers a message; whether the underlying condition has any merit is a separate question this article deliberately does not answer.

A side-by-side decision table

Here is the comparison most documentation spreads across several pages, condensed:

| | alertcondition() | alert() | |---|---|---| | Script type | indicator() only | indicator() and strategy() | | Message | Const string, fixed at compile time | Series string, built at runtime | | Dynamic values | Only dialog placeholders ({{close}}, {{ticker}}, {{time}}, {{interval}}...) | Anything via str.tostring() / str.format() | | User setup | Pick the named condition in the dropdown | Pick "Any alert() function call" | | Multiple distinct alerts | One dialog entry per alertcondition() call — user chooses which | All events funnel into one alert; distinguish them inside the message | | Frequency control | Chosen by the user in the dialog (Once Per Bar, Once Per Bar Close...) | Chosen by the author in code (alert.freq_*) | | Fires where | Only when a user has created an alert on that condition | Only when a user has created an "any alert() call" alert |

The practical decision rule: use alertcondition() when you are publishing an indicator and want users to pick from clean, named alert entries with predictable static messages. Use alert() when the message must carry computed values — a price level, a stop distance, a JSON payload for a webhook — or when the firing logic is more intricate than a single boolean.

One subtlety in the "multiple alerts" row deserves emphasis: because every alert() call in a script feeds the same "any alert() function call" alert, a user cannot subscribe to only your long signals and not your shorts. If per-signal opt-in matters to your users, that is a genuine argument for alertcondition(), or for exposing input.bool() toggles that gate each alert() call.

The dynamic-message trap: str.tostring() only works in alert()

The most common compile error in this area comes from trying to build a computed message inside alertcondition(). Its message parameter requires a const string — a value known at compilation, before any bar data exists. A concatenation involving a series value is not const, so this does not compile:

// ❌ WRONG — will not compile: alertcondition() requires a const string message.
// "RSI is " + str.tostring(rsiVal) is a series string, not a const string.
alertcondition(longSig, title = "Long", message = "RSI is " + str.tostring(rsiVal))

You have two correct escape hatches. The first stays with alertcondition() and leans on dialog placeholders, which TradingView substitutes at fire time rather than compile time. The message remains a const string; the values are injected by the alert engine:

//@version=6
// Educational only — validate before trading; not financial advice.
indicator("alertcondition with placeholders", overlay = true)
rsiVal  = ta.rsi(close, 14)
longSig = ta.crossover(rsiVal, 30)
plot(rsiVal, "RSI", display = display.none)
alertcondition(longSig, title = "RSI recross up",
     message = "{{ticker}} {{interval}}: RSI recrossed 30, close={{close}}")

Placeholders cover chart-level facts — symbol, interval, OHLC, time — but not your script's internal variables. There is no placeholder for rsiVal. The moment the message must contain a value your script computed, you need the second escape hatch: alert(), whose message is an ordinary series string:

if longSig and barstate.isconfirmed
    alert("RSI recrossed 30 at " + str.tostring(rsiVal, "#.##"),
         alert.freq_once_per_bar_close)

That barstate.isconfirmed gate is not decoration — it is the subject of the next section, because without it your dynamically messaged alert can fire on a signal that no longer exists by the time you read the notification.

The unconfirmed-bar trap: alerts on signals that later disappear

On a live chart, the current bar is not finished. Its close, high, and low keep changing with every tick, so any condition computed from them can be true at 10:03 and false again at 10:04 — before the bar ever closes. A ta.crossover() that flickers true intrabar and then un-crosses is the canonical case. If your alert fires on that flicker, you get notified about a signal that, on the finished chart, never happened. Users then compare their alert history against the historical chart, see signals with no corresponding markers, and conclude the script repaints. Functionally, it did.

//@version=6
// Educational only — validate before trading; not financial advice.
indicator("Confirmed-bar alerts", overlay = true)
fast = ta.sma(close, 10)
slow = ta.sma(close, 30)
plot(fast, "Fast SMA")
plot(slow, "Slow SMA")
longSig = ta.crossover(fast, slow)

// ❌ WRONG — fires on live ticks; the cross can vanish before the bar closes:
// if longSig
//     alert("Cross up on " + syminfo.ticker, alert.freq_once_per_bar)

// ✅ Fixed — only act on a signal the closed bar actually confirms:
if longSig and barstate.isconfirmed
    alert("Cross up on " + syminfo.ticker + " at " + str.tostring(close),
         alert.freq_once_per_bar_close)
plotshape(longSig and barstate.isconfirmed, title = "Long signal",
     style = shape.triangleup, location = location.belowbar)

For alert(), the fix is two-layered: gate the condition with barstate.isconfirmed and use alert.freq_once_per_bar_close. For alertcondition(), you cannot enforce this in code alone — the firing frequency lives in the user's dialog — so include barstate.isconfirmed in the condition and document that users should select "Once Per Bar Close". The honest trade-off: confirmed alerts arrive one bar later than tick alerts. That delay is the price of alerts that correspond to signals which actually survive. Whether the signal is worth acting on at all is a separate question no alert setting answers.

What about strategies — and how to verify any of this

alertcondition() has no effect in a strategy() script, so for strategies the choice collapses to two options. The first is alert(), which works exactly as in indicators and is the right tool for informational messages that are not tied to order fills. The second — usually better for automation — is order-fill alerts: pass an alert_message argument to strategy.entry(), strategy.exit(), or strategy.close(), then create an alert on the strategy with the order-fills event and put {{strategy.order.alert_message}} in the dialog's message box. Each simulated fill then emits your per-order message, which is the standard bridge to webhooks (covered in depth in our strategy-alerts guide).

A subtle behavioural difference worth knowing: order-fill alerts fire when the broker emulator fills an order, which can occur intrabar (a stop or limit being touched), whereas an alert() call you gate on barstate.isconfirmed fires only at bar close. Neither is wrong; they answer different questions — "my simulated order filled" versus "my condition confirmed".

Before trusting either mechanism, verify three things. First, that every alert-firing condition is gated on confirmed bars, or you accept and document tick-level behaviour. Second, that alert messages built with str.tostring() render what you expect — fire a few on a paper alert and read them. Third, that nothing upstream repaints: an alert pipeline is only as honest as the signal feeding it, and a request.security() call with lookahead problems will happily deliver perfectly formatted alerts about signals that never existed historically. ForexCodes' deterministic validator flags ungated alert calls and lookahead misuse for exactly this reason.

None of this is trading advice. Alerts are plumbing; they carry your signal's flaws with perfect fidelity, and trading any alert involves a real risk of loss.

Key takeaways

Educational only — not financial advice. Trading involves substantial risk of loss.

Catch the bug that compiles.Run auditGet Pro