Writing Pine Script Alerts for Automation

By Stax Team

For automation, use the alert() function rather than alertcondition(), because it allows messages built dynamically at runtime and lets the script set the trigger frequency in code. Set frequency to alert.freq_once_per_bar_close so signals fire on confirmed bars rather than intrabar conditions that can reverse. For strategies, the alert_message argument on order functions lets each order carry its own payload, which means one alert configuration can drive every order type your script produces.

Pine Script cannot place an order. It runs inside TradingView and has no path to a broker. What it can do is construct exactly the message your receiver needs, and doing that well removes most of the complexity from everything downstream.

alert() versus alertcondition()

alertcondition() is the older mechanism. It must sit at the top level of a script, its message is a static string, and the user selects which condition to use and at what frequency in the alert dialog. The script cannot choose for them.

alert() is called inside the execution flow, typically guarded by an if. Its message is built with ordinary string concatenation, so it can carry live values. It works in both indicators and strategies, and the frequency is set in code.

For automation, alert() wins on every dimension that matters. Dynamic payloads are the whole point: a static string cannot carry a computed position size or a calculated stop level. Keep alertcondition() only when you specifically want separately selectable named conditions in the alert dialog.

When using alert(), the user creates a single alert with the condition set to any alert function call, rather than choosing a named slot.

Frequency is a correctness decision

Three options, and the choice determines whether your live orders match your backtest.

alert.freq_all fires on every qualifying call within a bar. Noisy; use sparingly.

alert.freq_once_per_bar fires the first time the condition becomes true during the live bar. This is the default and it is the source of the repainting complaint: the condition can become true mid-bar, fire an order, and then be false by the time the bar closes. Your backtest evaluated on closed bars. Your live orders did not.

alert.freq_once_per_bar_close fires only when the realtime bar closes, and only if the condition still holds. This is the non-repainting choice, and for automation it should be the default rather than the exception.

Belt and braces: gate the condition on barstate.isconfirmed as well, so the logic itself cannot fire on an unconfirmed bar regardless of alert settings.

There is a genuine trade-off. With bar-close frequency, something that happened intrabar and resolved before the close does not generate an alert at all. If your strategy depends on intrabar events, you need a different design rather than a different frequency setting — some scripts accumulate intrabar messages and emit them together at bar close.

Building the payload in the script

The pattern worth adopting: construct the entire JSON payload inside Pine Script and pass it as the alert message. Your alert configuration in the TradingView dialog becomes a single placeholder, and all the logic lives in a script you can version.

For strategies, order functions accept an alert_message argument. Whatever string that argument holds is substituted into {{strategy.order.alert_message}} when the alert fires. Each order can therefore carry its own message, computed at signal time — different sizes, different order types, different exit instructions, all from one alert.

Use str.tostring() to interpolate numeric values. Keep the structure flat, because a single missing brace in a concatenated string produces malformed JSON, which silently changes the content type to text/plain — and most trading endpoints reject that outright. The failure is quiet and looks like the webhook never fired.

What to include

A shared secret, because TradingView does not sign requests and this is your only authentication.

A unique signal identifier, so the receiver can reject duplicates. Without acknowledgement there is no way to distinguish a replay from a genuine repeat.

Position context, not just an action. A sell can mean closing a long or opening a short. {{strategy.position_size}} and the market position placeholders distinguish them; the action alone cannot.

A strategy identifier, so one endpoint can serve several scripts.

The timeframe, which matters when the same logic runs on multiple charts.

What to leave out: credentials. TradingView's own documentation warns explicitly against including login credentials or passwords in a webhook body. A shared secret scoped to the receiver is one thing; a brokerage password is another and belongs nowhere near an alert.

Testing what you cannot replay

Alerts only fire in real time. They cannot be replayed across historical bars, which means you cannot see where an alert would have fired by scrolling back.

The workaround is to plot the same condition with plotshape so you can visually confirm the trigger points across history, then attach the alert once the markers look correct. This catches logic errors before any endpoint is involved.

Then point the alert at a request inspector and read what actually arrived — that the request came at all, that the content type is application/json rather than text/plain, and that every value resolved rather than appearing literally.

The honest limits

Everything the script reports about orders and positions describes TradingView's simulation, not your account. {{strategy.order.price}} is a simulated fill, not an execution price. Your real position lives at the broker, and a script that has drifted out of sync will keep sending signals based on a position it thinks it has.

Pine Script has execution time limits and memory constraints, and cannot perform operations outside TradingView except through alerts. Complex logic belongs in the receiver, which runs on infrastructure you control and is not constrained by the scripting environment.

A well-formed alert is not a good strategy. Bar-close frequency prevents repainting; it does not make a signal profitable. Position sizing is what bounds loss — capital divided by twenty as the ceiling per position, under the divide-by-20 rule — and it does not depend on any of this resolving correctly.

And a strategy that only works with intrabar entries may not be automatable at acceptable risk. That is worth knowing before building the pipeline rather than after, and running the receiver on self-hosted infrastructure does not change it.

Frequently asked questions

Should I use alert() or alertcondition()? alert() for automation. It supports dynamic messages, works inside conditional blocks, and sets frequency in code. alertcondition() takes a static message and lets the user choose settings.

What alert frequency should I use? alert.freq_once_per_bar_close, so signals fire on confirmed bars and match backtested logic.

Why do my alerts fire and then the signal disappears? Once-per-bar frequency fires the first time a condition becomes true intrabar, and the condition can be false by the close.

What is alert_message? An argument on strategy order functions holding a string that is substituted into {{strategy.order.alert_message}}, letting each order carry its own dynamic payload.

Can Pine Script place trades? No. It cannot reach a broker API. A receiver has to do that.

How do I test an alert on historical data? You cannot — alerts only fire in real time. Plot the condition with plotshape to verify trigger points across history first.


Disclaimer: This article is educational content about software engineering and trading automation. It is not investment advice, financial advice, tax advice, legal advice, or a recommendation to buy or sell any security. Any instruments named are used solely to illustrate mechanics. Options trading involves substantial risk of loss and is not suitable for all investors. Please read Characteristics and Risks of Standardized Options before trading options. Automated trading systems carry additional risks including software defects, missed or duplicated signals, network and connectivity failures, third-party service changes and outages, and unintended order behaviour that may prevent orders from being placed, modified, or cancelled. Past performance does not indicate future results, and no configuration, alert setup, position-sizing rule, or risk setting can guarantee a profit or prevent a loss.

StaxInvesting LLC sells self-hosted trading software. It is not a broker-dealer, investment adviser, or financial institution, and it does not manage accounts, hold member funds, place trades on behalf of members, or access member brokerage accounts. Members run the software in their own cloud environment, connect their own brokerage accounts under their own credentials, and are solely responsible for their configuration, their credential security, their compliance with third-party terms of service, and every trade executed in their account. Third-party platform details described here reflect publicly available documentation as of publication and are subject to change without notice; always verify against current official documentation. Consult a qualified financial adviser and tax professional regarding your individual circumstances.