Strategy Alerts & Automation

Send Pine Script signals to external systems via webhook-formatted alert messages — the bridge between a validated strategy and automated execution.

Once a strategy is validated through backtesting, the next question is how to act on its signals consistently — without manually watching the chart. Pine Script's alert system provides the answer: `alert()` and `alertcondition()` let you send structured messages from your script to any system that accepts a webhook.

The architecture is straightforward. Your Pine Script strategy runs on TradingView's servers, evaluating every bar as it closes. When a signal fires, an alert message is dispatched to a URL you specify. That URL belongs to an automation platform or broker API that receives the message, parses it, and executes the trade. The message itself is a string you control — typically formatted as JSON so the receiving system can extract the relevant values.

Understanding the timing of when alerts fire — and ensuring they fire only on confirmed, closed bars — is the most important technical consideration. An alert that fires mid-bar may represent a signal that reverses before the bar closes.

alertcondition() vs alert() — which to use

### alertcondition() — for indicators `alertcondition(condition, title, message)` is used in `indicator()` scripts. It registers a named alert that the user manually creates in TradingView's alert dialog (the "Create Alert" button on the chart). The alert fires whenever `condition` becomes true, sending `message` to the configured destination.

The limitation is that the message is a static string with optional placeholders (`{{ticker}}`, `{{close}}`, `{{time}}`). You cannot compute dynamic values like ATR-based stop levels inside an `alertcondition` message.

### alert() — for strategies and dynamic messages `alert(message, freq)` is called directly inside your script's logic — typically inside an `if` block alongside a `strategy.entry()`. It fires immediately when that code block executes. The message is a computed string, meaning you can embed any Pine Script expression, including ATR stops, percentages, equity values, and formatted prices.

This makes `alert()` the right choice for automated trading, where the receiving system needs precise numbers — not just "a signal fired."

The critical frequency setting

The second parameter of `alert()` controls when it fires:

Always use `alert.freq_once_per_bar_close` for strategy signals. Combined with `barstate.isconfirmed` on the entry condition, this guarantees the alert fires exactly once per signal, on bar close.

Formatting JSON for webhooks

Most automation platforms expect a JSON-formatted alert message. JSON is just a structured string — but building it correctly in Pine Script requires string concatenation.

The pattern from the code above: `'{"action":"buy","symbol":"' + syminfo.ticker + '","qty":1}'`

Common fields to include in an automation payload: `action` (buy/sell/close), `symbol` (syminfo.ticker), `qty` or `percent` (position size), `stop` (stop loss price), `target` (take profit price), `comment` (signal description).

TradingView alert configuration

After adding `alert()` calls to your script:

  1. Right-click the strategy on the chart → "Add alert on [Strategy Name]"
  2. Set the condition to "Order fills only" or the specific alert message
  3. In the "Notifications" section, enter your webhook URL
  4. Leave "Once Per Bar Close" checked — this adds a second layer of protection beyond the `alert.freq_once_per_bar_close` already in the code

The alert is now active. Every time a bar closes with a matching signal, TradingView sends the JSON payload to your webhook URL. The receiving system parses the payload and executes the corresponding order.

Automation platforms

Several platforms bridge TradingView alerts to broker APIs. Each expects a slightly different payload format, so check their documentation before going live. The general pattern is consistent: a JSON message arrives at a webhook endpoint, the platform parses the action, symbol, and quantity, and submits the order to the connected broker account.

//@version=6
strategy("Strategy Alerts Demo", overlay=true,
         default_qty_type=strategy.percent_of_equity,
         default_qty_value=10)

// ── Entry logic ───────────────────────────────────────────────────────
fastEMA = ta.ema(close, 9)
slowEMA = ta.ema(close, 21)
atrVal  = ta.atr(14)

longEntry  = ta.crossover(fastEMA, slowEMA)  and barstate.isconfirmed
shortEntry = ta.crossunder(fastEMA, slowEMA) and barstate.isconfirmed

// ── Strategy entries with exit targets ────────────────────────────────
if longEntry
    strategy.entry("Long", strategy.long)
    strategy.exit("Long Exit", "Long",
                  stop=close - atrVal * 2,
                  limit=close + atrVal * 4)
    // JSON payload for webhook automation
    alert('{"action":"buy","symbol":"'   + syminfo.ticker +
          '","qty":1,"stop":'             + str.tostring(close - atrVal * 2, format.mintick) +
          ',"target":'                   + str.tostring(close + atrVal * 4, format.mintick) + '}',
          alert.freq_once_per_bar_close)

if shortEntry
    strategy.entry("Short", strategy.short)
    strategy.exit("Short Exit", "Short",
                  stop=close + atrVal * 2,
                  limit=close - atrVal * 4)
    alert('{"action":"sell","symbol":"'  + syminfo.ticker +
          '","qty":1,"stop":'             + str.tostring(close + atrVal * 2, format.mintick) +
          ',"target":'                   + str.tostring(close - atrVal * 4, format.mintick) + '}',
          alert.freq_once_per_bar_close)

// ── For indicator scripts: use alertcondition() instead ───────────────
// alertcondition(longEntry,  title="Long Signal",
//                message="Long entry on {{ticker}} at {{close}}")
// alertcondition(shortEntry, title="Short Signal",
//                message="Short entry on {{ticker}} at {{close}}")

plot(fastEMA, "Fast EMA", color=color.blue)
plot(slowEMA, "Slow EMA", color=color.orange)
plotshape(longEntry,  style=shape.triangleup,   location=location.belowbar, color=color.green, size=size.small)
plotshape(shortEntry, style=shape.triangledown, location=location.abovebar, color=color.red,   size=size.small)