Guide

alertcondition vs alert() in Pine Script: Which to Use

alertcondition() registers a named option you pick in the alert dialog. alert() fires from code. Here is how scope, messages, and frequency differ.

•10 min read

You have a condition that works. A moving average cross, a breakout, a volume spike, whatever the script already plots correctly. Now you want TradingView to tell you when it happens, and you open the Pine Editor to find two functions that both claim to do it: alertcondition() and alert(). They look interchangeable in the reference manual, so you pick one, and an hour later the Create Alert dialog shows nothing, or the message says "{{plot_0}}" instead of a number, or the alert fires four times on one bar.

The two functions are not interchangeable. They hook into the alert system in different ways, they have different rules about scope, message text, and frequency, and one of them does not work in strategies at all. This post walks through both, shows the same condition wired up each way, and ends with a plain rule for choosing. Every code block is complete Pine Script v6 that you can paste into the Pine Editor.

What each function actually does

alertcondition(condition, title, message) is a declaration. It tells TradingView "this script can offer an alert when this condition is true." Nothing fires on its own. The function registers a named option, and the option shows up in the Condition dropdown of the Create Alert dialog on the chart. You, the human, then open the dialog, pick the script, pick the named condition, choose a trigger and expiry, and save. The alert only exists after that manual step.

alert(message, freq) is a call that runs at runtime. When execution reaches it on a bar, it asks TradingView to fire an alert right then. You still create an alert from the dialog, but you choose "Any alert() function call" as the condition, and the script itself decides when to fire and what to say. One alert covers every alert() call in the script.

That difference in who is in charge explains almost everything else. alertcondition() puts the decision about frequency and timing in the dialog, so you configure it by hand each time. alert() puts the decision in the code, so the behaviour travels with the script.

The same condition, both ways

Start with a simple condition: a fast EMA crossing above a slow EMA. Here it is with alertcondition().

pine
//@version=6
indicator("EMA Cross: alertcondition", overlay = true)

fastLen = input.int(9, "Fast EMA length", minval = 1)
slowLen = input.int(21, "Slow EMA length", minval = 1)

fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)

crossUp   = ta.crossover(fast, slow)
crossDown = ta.crossunder(fast, slow)

plot(fast, "Fast EMA", color = color.teal)
plot(slow, "Slow EMA", color = color.orange)

alertcondition(crossUp,   title = "Fast crossed above slow", message = "{{ticker}}: fast EMA crossed above slow EMA on {{interval}}")
alertcondition(crossDown, title = "Fast crossed below slow", message = "{{ticker}}: fast EMA crossed below slow EMA on {{interval}}")

After you add this to a chart, the Create Alert dialog lists two named conditions under this script. Pick one, save it, and you have an alert. If you want both directions, you create two alerts. The {{ticker}} and {{interval}} pieces are placeholders that TradingView replaces when the alert fires, which is the only way to get live values into an alertcondition() message.

Now the same logic with alert().

pine
//@version=6
indicator("EMA Cross: alert()", overlay = true)

fastLen = input.int(9, "Fast EMA length", minval = 1)
slowLen = input.int(21, "Slow EMA length", minval = 1)

fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)

crossUp   = ta.crossover(fast, slow)
crossDown = ta.crossunder(fast, slow)

plot(fast, "Fast EMA", color = color.teal)
plot(slow, "Slow EMA", color = color.orange)

if crossUp
    alert(syminfo.ticker + ": fast EMA crossed above slow EMA at " + str.tostring(close, format.mintick), alert.freq_once_per_bar_close)

if crossDown
    alert(syminfo.ticker + ": fast EMA crossed below slow EMA at " + str.tostring(close, format.mintick), alert.freq_once_per_bar_close)

Here one alert, created with "Any alert() function call", covers both directions. The message is built in code, so it can contain the actual close price, and the frequency is fixed by the second argument. Notice that alert() sits inside an if block. That is allowed for alert() and not for alertcondition().

Scope: where each function may be called

alertcondition() has to be called from the global scope of the script. You cannot put it inside an if block, a loop, or a function body. The condition you pass in is a series bool computed anywhere, but the call itself sits at the top level. If you try to wrap it in a conditional, the compiler objects.

alert() has no such restriction. You can call it from an if, a for loop, a user-defined function, or a method. This matters when your logic is naturally structured as branches. A script that tracks several states, say "zone created", "zone touched", and "zone broken", can fire a differently worded alert from each branch without computing a separate bool for every case.

If you are building a long script with many event types, this scope rule is often what pushes people toward alert(). If you have a handful of conditions that already exist as plain bools, alertcondition() is the lighter touch.

Messages: static templates versus built strings

The message argument of alertcondition() has to be a constant string. You cannot concatenate str.tostring(close) into it, because the value is fixed when the script compiles. To get dynamic content you use placeholders such as {{close}}, {{ticker}}, {{interval}}, {{time}}, and {{plot_0}}, which TradingView substitutes at the moment the alert fires. The placeholder list is documented in TradingView's alert help, and it is worth checking there for the full set, since only some placeholders apply to a given alert type.

The message argument of alert() is a series string, so you build it with ordinary string functions. You can include any variable the script holds at that moment: a computed stop distance, a level taken from an array, the name of the branch that fired. The tradeoff is that you are now responsible for formatting. str.tostring(close, format.mintick) rounds to the symbol's tick size, which is usually what you want, while a bare str.tostring(close) will show more decimals than the market uses.

This difference becomes important if the alert goes to a webhook. A webhook receiver, whether it is a script of your own or a logging tool, gets the message text as the request body. A message with unresolved {{plot_0}} text, or one that is missing the value you wanted, is a common reason a downstream parser fails. If you plan to send alerts to a webhook, the alert webhook builder helps you lay out the message, and the guide on connecting Pine Script to a webhook or broker covers what Pine can and cannot do on that side.

Frequency: dialog setting versus function argument

An alertcondition() alert gets its trigger setting from the Create Alert dialog. The usual choices are once, once per bar, or once per bar close. You pick it each time you create the alert,.

An alert() call carries its frequency in code through the freq argument. There are three constants: alert.freq_once_per_bar, which is the default and fires the first time the call executes on a bar; alert.freq_once_per_bar_close, which waits until the bar closes; and alert.freq_all, which fires on every execution, including every real-time tick.

The default is the one that surprises people. On a real-time bar, alert.freq_once_per_bar fires the moment the condition becomes true on a tick, even though the bar may still change and the condition may be false by the close. This is the same mechanism behind alerts that seem to fire on the wrong bar, covered in why your Pine Script alert fires on the wrong bar. If you want a signal that cannot be withdrawn after it fires, use alert.freq_once_per_bar_close, or gate the condition on barstate.isconfirmed, as explained in barstate.isconfirmed and signals that stop repainting.

Strategies: a hard dividing line

alertcondition() is not available in strategies. If your script is declared with strategy() instead of indicator(), the call will not work, and you need alert() instead. In a strategy, TradingView also has its own order-fill alerts through the alert dialog, but those report orders the strategy placed in the Strategy Tester's simulation. They are separate from alert() and from alertcondition(), and which one you want depends on whether you need to report an order event or a custom condition.

So if you are converting an indicator into something backtestable, expect to rewrite the alertcondition() lines as alert() calls. The walkthrough in from indicator to backtestable strategy covers the rest of that conversion.

Comparing them at a glance

Questionalertcondition()alert()
Where can it be called?Global scope onlyAnywhere, including if, for, and functions
Message textConstant string with placeholdersSeries string you build in code
Frequency set byThe Create Alert dialogThe freq argument
Works in strategiesNoYes
Alerts per scriptOne per named condition, each created separatelyOne alert covers every call
Condition shown in dialogNamed entries you can pick"Any alert() function call"

Using both in one script

Nothing stops you from using both in the same indicator, and sometimes it is the cleanest design. You might expose two or three headline conditions with alertcondition() so that other people who add your script can pick them from a familiar dropdown with sensible titles, and also include an alert() call for a richer, code-built message on a particular event.

pine
//@version=6
indicator("Breakout alerts: both functions", overlay = true)

lookback = input.int(20, "Lookback", minval = 2)

hi = ta.highest(high, lookback)[1]
lo = ta.lowest(low, lookback)[1]

breakUp   = close > hi
breakDown = close < lo

plot(hi, "Prior high", color = color.green, style = plot.style_stepline)
plot(lo, "Prior low", color = color.red, style = plot.style_stepline)

alertcondition(breakUp,   title = "Close above prior high", message = "{{ticker}} closed above its {{interval}} prior high")
alertcondition(breakDown, title = "Close below prior low",  message = "{{ticker}} closed below its {{interval}} prior low")

if barstate.isconfirmed and breakUp
    alert("Breakout above " + str.tostring(hi, format.mintick) + " on " + syminfo.ticker, alert.freq_once_per_bar_close)

Be careful with this pattern. If you create a dialog alert from the alertcondition() entry and also an "Any alert() function call" alert for the same script, the same event can notify you twice. That is easy to forget when you come back to a chart after a month.

Choosing: a short rule

Use alertcondition() when the script is an indicator, the conditions already exist as plain bools, you want named options in the dialog, and a constant message with placeholders is enough. It is the lowest-effort choice and it reads well to anyone who adds your script.

Use alert() when the script is a strategy, when the alert logic lives inside branches or functions, when you need values computed by the script in the message text, or when you want the trigger frequency to be part of the code instead of a dialog setting someone has to remember. For complex indicators with many event types, alert() is usually the better fit. The longer treatment of building a repaint-free alert system is in building a repaint-free alert system in Pine Script, and adding alerts to a complex indicator safely covers retrofitting an existing script.

Common mistakes

The first mistake is expecting alertcondition() to create the alert. It only creates the option. Until you open the dialog and save an alert on it, nothing will ever notify you. The second is calling it inside an if and wondering why the compiler complains. The third is putting str.tostring(close) into an alertcondition() message; the message must be a constant, so use {{close}}.

For alert(), the common mistake is forgetting that the default frequency can fire on an unconfirmed real-time bar. Another is creating the alert in the dialog from a named condition when you meant "Any alert() function call", then wondering why the alert() text never arrives. A last one, in both cases, is testing only on historical bars. Alerts fire on the real-time bar, so a script that looks right in the chart history can still behave differently live. Stepping through a run one bar at a time in PineScripter's Replay helps you see when the condition turns true before you rely on it.

Checking what actually fired

Once the alerts exist, the next question is whether they are doing what you think. TradingView shows an alert log, but it is easy to lose track when you run several scripts across several symbols. PineScripter's Alert Log gives each script a unique webhook address. You paste it into the webhook URL field of a TradingView alert, and each time the alert fires, it is recorded with its name, price, and exact time. It also notices when an alert has gone unusually silent, which helps you tell "has not fired lately" apart from "may have expired or broken". It needs a TradingView plan that supports webhooks, and it records alerts, it does not place trades. You can read more on the alerts tracker page.

If you would rather not hand-edit the conversion between the two functions, PineScripter writes Pine Script v6, checks every script for compile errors the way TradingView does, and fixes them on its own. Ask it in plain English to turn your alertcondition() lines into alert() calls with a closed-bar frequency and it changes only the affected lines, with a line-by-line view of what moved. You still paste the result into TradingView yourself and create the alert there.

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.

Frequently asked questions

Can I use alertcondition() in a strategy?

No. alertcondition() is only available in indicators. In a strategy, call alert() instead.

Why does my alertcondition() message show {{plot_0}} literally?

The message is a constant string, so placeholders are substituted by TradingView when the alert fires. Check that the placeholder name is valid for your alert type and that the plot exists. If you need a computed value, use alert() and build the string in code.

Can alertcondition() go inside an if block?

No. It must be called from the global scope. Compute the condition as a bool and pass it in. alert() can be called from if blocks, loops, and functions.

Which alert() frequency should I use?

Use alert.freq_once_per_bar_close if the signal must not be withdrawn after it fires. The default, alert.freq_once_per_bar, can fire on an unconfirmed real-time tick, and alert.freq_all fires on every execution.

How do I create an alert from an alert() call?

Open the Create Alert dialog, choose your script as the condition source, and select Any alert() function call. One alert then covers every alert() call in the script.

Keep a log of every alert your script fires →


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.