Skip to main content

Strategy-Based Alerts (with Position Tracking)

When you can edit your strategy's Pine source, a strategy() tracks positions internally. It knows you are long 0.05 BTC and can fire strategy.exit() when SL hits. Combined with TradingView's {{strategy.order.alert_message}} placeholder, a single alert dispatches the right JSON for every event: entry, SL, TP, manual close. This is the cleanest way to live-trade a Pine strategy you control.

Who this page is for

This page is for traders who can edit the Pine source of their strategy and add alert_message= to each order. If you run a closed-source or invite-only strategy that you cannot edit, you can't attach alert_message. Jump to How to automate a closed-source TradingView strategy below, which uses {{strategy.order.action}} instead.

TL;DR
  • Attach a JSON payload to every order via alert_message=.
  • In TradingView's alert Message box, paste {{strategy.order.alert_message}}. TV substitutes the right body per order at fire time.
  • One alert covers entry, exit, and close, with no per-direction setup.
  • strategy.exit() and strategy.close() both accept alert_message. Include a close-command JSON or rely on the auto-close behaviour.
Pine strategy alert setup walkthrough. The video predates the GitHub boilerplate; the written docs are canonical.

How strategy.entry/exit/close interact with TVH

The sequence on a long entry:

  1. Pine fires strategy.entry("Long", strategy.long, alert_message=buy_command) on a confirmed bar.
  2. TradingView attaches buy_command to that specific order.
  3. The alert (set to "Order fills only") triggers.
  4. TradingView replaces {{strategy.order.alert_message}} with buy_command.
  5. TVH receives the resolved JSON, runs its auto-mapping, dispatches the buy.

Same loop for exits and closes. The only thing that changes is which alert_message is attached to which order. TradingView routes the right body to TVH automatically.

TradingView alert flow: one alert resolves Pine strategy entry, exit and close events and posts the resolved command JSON to the TVH webhook

Pine v5 strategy template (compact)

//@version=5
strategy("EMA Cross + TVH", overlay=true,
default_qty_type=strategy.percent_of_equity, default_qty_value=10,
pyramiding=0, calc_on_order_fills=false, process_orders_on_close=true)

fast = ta.ema(close, 9)
slow = ta.ema(close, 21)
plot(fast, "Fast", color.new(color.aqua, 0))
plot(slow, "Slow", color.new(color.orange, 0))

// Trade commands — generated by the Trade Command Builder UI then pasted here.
buy_command = '{"exchange":"binance","pair":"{{ticker}}","apiKey":"binance-main","isBuy":true,"unitsType":"percent","unitsPercent":5,"isMarket":true,"token":"YOUR_TOKEN"}'
sell_command = '{"exchange":"binance","pair":"{{ticker}}","apiKey":"binance-main","isSell":true,"unitsType":"percent","unitsPercent":5,"isMarket":true,"token":"YOUR_TOKEN"}'
close_command = '{"exchange":"binance","pair":"{{ticker}}","apiKey":"binance-main","isClose":true,"token":"YOUR_TOKEN"}'

long_cond = ta.crossover(fast, slow)
short_cond = ta.crossunder(fast, slow)

if long_cond
strategy.entry("Long", strategy.long, alert_message=buy_command)
strategy.exit("Long-Exit", from_entry="Long", loss=200, profit=400, alert_message=close_command)

if short_cond
strategy.entry("Short", strategy.short, alert_message=sell_command)
strategy.exit("Short-Exit", from_entry="Short", loss=200, profit=400, alert_message=close_command)

The strategy.exit(..., loss=200, profit=400) line gives Pine an internal SL of 200 integer tick distances and TP of 400 integer tick distances. Those numbers drive Pine's bookkeeping (the equity curve in Strategy Tester). The actual broker-side SL/TP is what TVH places — typically defined inside the buy_command JSON via stopLoss / targets.

The same pattern with broker-side SL/TP:

buy_command = '{"exchange":"binance-futures","pair":"{{ticker}}","apiKey":"bin-fut","leverage":5,"marginType":"cross","isBuy":true,"unitsType":"percent","unitsPercent":5,"isMarket":true,"stopLossType":"percent","stopLossPercent":2,"targetType":"percent","targetAmountInPercent":true,"targets":[{"takeProfitPercent":4,"amount":100}],"token":"YOUR_TOKEN"}'

Now the exchange holds the SL and TP. Pine still has strategy.exit(loss=200, profit=400) for the backtest, but the live trade is managed broker-side. This separation is intentional: Pine's exits are notional, TVH's are real.

The {{strategy.order.alert_message}} placeholder

In TradingView's alert dialog:

  • Condition: your strategy.
  • Alert actions: tick Webhook URL, paste the TVH endpoint.
  • Message: literally {{strategy.order.alert_message}} and nothing else.

The trigger frequency for strategy alerts is "Order fills only" — TradingView fires one alert per order placed by the strategy. There is no per-bar firing; the alert is event-driven.

What TradingView posts to TVH:

  • On strategy.entry("Long", ..., alert_message=buy_command)buy_command body.
  • On strategy.exit(..., alert_message=close_command)close_command body.
  • On strategy.close(..., alert_message=close_command)close_command body.

One alert, three different payloads. No per-direction setup.

Single quotes inside Pine, double quotes in JSON

Same rule as for indicators — wrap JSON in single Pine quotes so the inner " survive:

// Good
buy_command = '{"exchange":"binance","pair":"{{ticker}}","isBuy":true,"token":"abc"}'

// Compile error — Pine sees the first inner " as the end of the string.
buy_command = "{"exchange":"binance",...}"

Details on Alert Message Format.

strategy.exit vs strategy.close

Both can carry alert_message. They serve different patterns:

FunctionWhen it firesUse it for
strategy.exit(loss=..., profit=...)SL/TP tick distances are hitPine-tracked stop-loss and take-profit (Pine bookkeeping; the real broker SL/TP is in your JSON).
strategy.exit(stop=..., limit=...)A specific price is reachedPrice-based exits.
strategy.close(when=...)A boolean condition is trueSignal-driven exit (e.g. opposite crossover, RSI rotation).

Pattern that combines both — fixed risk plus opposite-signal exit:

if long_cond
strategy.entry("Long", strategy.long, alert_message=buy_command)
strategy.exit("L-SL/TP", from_entry="Long", loss=200, profit=400, alert_message=close_command)
strategy.close("L-Reverse", when=short_cond, alert_message=close_command)

Same close_command body is attached to both the SL/TP exit and the reverse-signal close. TVH receives identical JSON either way and closes the position. The orderType:"close" field is what tells TVH "exit, do not enter".

How to automate a closed-source TradingView strategy

Invite-only and closed-source strategies do not expose their Pine code, so you cannot add alert_message= to their orders. The author already wrote the strategy.entry/exit/close calls and you cannot change them. The only field you control is the alert's Message. TVH bridges this with two placeholders TradingView emits for any strategy, with no author cooperation needed.

Enable Strategy Alerts in the Builder

  1. Open any exchange's Trade Command Builder.
  2. Configure the trade as usual: exchange, pair, sizing, SL, TP.
  3. Tick Enable strategy alerts.

Enable strategy alerts toggle in the Trade Command Builder

The toggle adds two placeholders to the generated command:

"orderType": "{{strategy.order.action}}",
"strategyComment": "{{strategy.market_position}}"

TradingView fills both at runtime. {{strategy.order.action}} becomes buy or sell for the order that fired. {{strategy.market_position}} becomes long, short, or flat. Copy the command, create a TradingView alert on the strategy, set the trigger to Order fills only, and paste it into the Message box. One alert covers entries and exits.

How TVH decides entry vs close

After it parses your JSON, TVH's auto-mapping does this in order:

  1. orderType buy or sell sets the direction (isBuy / isSell).
  2. It then scans strategyComment for the substrings close, exit, or flat (case-insensitive). A match overrides the direction into a close: isClose = true, isMarket = true.

This is why {{strategy.market_position}} works so cleanly. When the strategy closes a position, market position becomes flat, the flat substring triggers the close, and entries (long / short) stay entries. One command handles both, with no reliance on the author's wording. Full rule: Trade Command Anatomy.

Example payload

A complete closed-source payload for Binance Futures USD-M:

{
"exchange": "binance-futures",
"pair": "BTCUSDT",
"apiKey": "bin-fut-main",
"token": "YOUR_TOKEN",
"isMarket": true,
"leverage": 10,
"marginType": "cross",
"unitsType": "percent",
"unitsPercent": 5,
"stopLossType": "percent",
"stopLossPercent": 2,
"targetType": "percent",
"targetAmountInPercent": true,
"targets": [{ "takeProfitPercent": 4, "amount": 100 }],
"orderType": "{{strategy.order.action}}",
"strategyComment": "{{strategy.market_position}}",
"alertTimestamp": "{{ticker}}-{{timenow}}"
}

Everything except the last three lines is yours to set. The placeholders make the alert reusable across entries and exits.

Keying off the author's order comments

{{strategy.market_position}} is the simplest path. If you would rather react to the author's own exit labels, set strategyComment to {{strategy.order.comment}} (the comment="..." text attached to each order). TVH still scans for close, exit, or flat. When the author uses other words, override the close keywords with strategyCloseValue:

{
"orderType": "{{strategy.order.action}}",
"strategyComment": "{{strategy.order.comment}}",
"strategyCloseValue": "Ex Short|TP|SL Reached|Trail Done"
}

Any one match (case-insensitive substring) triggers the close. The standard close|exit|flat keywords are not kept once you set strategyCloseValue, so list every keyword you want, including the defaults if you still need them: "close|exit|flat|TP|SL".

Hedge mode

When a closed-source strategy reverses (long to short or back), dual-position accounts (Binance Futures hedge, Bybit Hedge) need to know which side to close. TVH derives it from orderType plus the close trigger:

  • orderType: "sell" plus a close trigger closes the long side.
  • orderType: "buy" plus a close trigger closes the short side.

The reconciliation runs server-side; you do not set the boolean flags. Details on Close & Cancel.

Closed-source pitfalls

  • Empty comment. If strategyComment resolves to an empty string, TVH falls back to orderType only and opens an entry. Prefer {{strategy.market_position}} (never empty) over a comment the author might leave blank.
  • Custom token collisions. strategyCloseValue: "TP" matches any comment containing TP, even "Setup pending TP". Pick distinctive tokens like TP_HIT or SL_FIRED.
  • Reversal vs close. A comment like "Reverse to Short" carries no close keyword, so TVH treats it as an entry. If you need close-then-reenter, key off {{strategy.market_position}} (which goes flat between sides) or chain two alerts.

The same comment-based auto-close works when you do control the Pine code: instead of maintaining a separate close_command, put close / exit / flat in the order comment and TVH closes the position. An explicit close_command is cleaner when you have the choice.

Useful TradingView placeholders

Beyond {{strategy.order.alert_message}}, TradingView replaces a set of tokens with live values before it posts the webhook. Drop them straight into your JSON to send dynamic data. The ones you reach for most in strategy alerts:

PlaceholderResolves toTypical JSON field
{{ticker}}The chart symbol, e.g. BTCUSDTpair
{{strategy.order.action}}buy or sell for the order that firedorderType
{{strategy.order.contracts}}Order size in contracts / base unitsunits
{{strategy.order.price}}Fill (entry) price of the orderprice
{{strategy.order.comment}}The order's comment textstrategyComment
{{strategy.position_size}}Position size after the ordersizing logic
{{close}}Current bar closeprice, math expressions
{{time}} / {{timenow}}Bar time / current timealertTimestamp

Full dynamic strategy alert trade command

Putting the placeholders together, here is a single command where almost every value is filled by TradingView at runtime:

{
"pair": "{{ticker}}",
"exchange": "bybit",
"apiKey": "YourKeyName",
"token": "YOUR-TOKEN",
"isMarket": true,
"orderType": "{{strategy.order.action}}",
"strategyComment": "{{strategy.market_position}}",
"unitsType": "absolute",
"units": "{{strategy.order.contracts}}",
"leverage": "5",
"marginType": "ISOLATED",
"stopLossType": "absolute",
"stopLoss": "{{plot("Strategy SL Price")}}",
"targetType": "absolute",
"targetAmountInPercent": true,
"targets": [
{ "idx": 1, "price": "{{plot("Strategy TP Price")}}", "amount": "100" }
],
"closeCurrentPosition": true,
"alertTimestamp": "{{ticker}}-{{timenow}}"
}

What each dynamic piece does:

  • "pair": "{{ticker}}" makes the command work on whatever chart the alert runs on.
  • "orderType" + "strategyComment" let one command handle entries and exits (see How TVH decides entry vs close).
  • "unitsType": "absolute" with "units": "{{strategy.order.contracts}}" feeds the size straight from the strategy. TVH places exactly the quantity your Pine strategy sized for that order, so the live position matches the backtest one-to-one.
  • "stopLoss" and targets[0].price pull the exact SL and TP levels your strategy plots via {{plot("...")}} (see Dynamic SL and TP price via plot() below). Both are absolute prices, so stopLossType and targetType are "absolute".
  • "closeCurrentPosition": true flattens an opposite position before entering, for clean reversals.
  • "alertTimestamp": "{{ticker}}-{{timenow}}" is the dedup key, so a resent webhook never double-fires.

Prefer risk-based sizing instead? Swap those two lines for "unitsType": "risk" and "unitsPercent": "2", and TVH derives the size from the distance to the stop (2% of balance at risk per trade).

Dynamic SL and TP price via plot()

TradingView can also inject any plotted value into the message. Plot your stop-loss and take-profit prices as a series, then reference each one by its plot title with {{plot("title")}}:

slPrice = strategy.position_avg_price * 0.98
tpPrice = strategy.position_avg_price * 1.04
plot(slPrice, "SL")
plot(tpPrice, "TP")

buy_command = '{"exchange":"binance-futures","pair":"{{ticker}}","apiKey":"bin-fut","isBuy":true,"isMarket":true,"stopLossType":"absolute","stopLoss":{{plot("SL")}},"targetType":"absolute","targets":[{"price":{{plot("TP")}},"amount":100}],"token":"YOUR_TOKEN"}'

{{plot("SL")}} and {{plot("TP")}} resolve to plain numbers at fire time, so TVH places the exact SL and TP prices your strategy computed. You can also reference plots by index, for example {{plot_0}} and {{plot_1}}.

Limit order at the strategy's price

To mirror a strategy that enters with limit orders, send a limit at the price the order fired on with {{strategy.order.price}}:

{
"pair": "{{ticker}}",
"exchange": "bybit",
"apiKey": "YourKeyName",
"token": "YOUR-TOKEN",
"orderType": "{{strategy.order.action}}",
"strategyComment": "{{strategy.market_position}}",
"isLimit": true,
"price": "{{strategy.order.price}}",
"limitPriceType": "fixedPrice",
"postOnly": false,
"unitsType": "absolute",
"units": "{{strategy.order.contracts}}",
"alertTimestamp": "{{ticker}}-{{timenow}}"
}

{{strategy.order.price}} resolves to the price of the order that fired, so TVH places the limit at exactly that level. Keep postOnly: false if it must fill when the price is marketable (the default is true, which rejects a crossing limit, see Gotchas #1). To chase the order book instead of pinning a fixed level, set limitPriceType: "bestPrice".

For the complete list and the rules on which fields accept placeholders, see Pine Placeholders.

Strategy Tester mode

Before going live, run the strategy through TradingView's Strategy Tester tab. The tester executes strategy.entry/exit/close on historical data and produces:

  • Equity curve.
  • Profit factor, Sharpe ratio, max drawdown.
  • List of every simulated trade.

Important: Strategy Tester does not fire alerts. No webhook reaches TVH while you backtest. You can run a strategy as many times as you want with default_qty_value tweaks and never place a real trade.

To go live: keep the same strategy on the chart, then create the TV alert with {{strategy.order.alert_message}} in the Message box. Tick "Webhook URL", paste the TVH endpoint. Click Create. From that moment on, every order Pine places also reaches TVH.

A walkthrough with screenshots is in the Strategy Cookbook → Strategy Tester Setup.

Common pitfalls

  • Forgetting alert_message=close_command on strategy.exit. TVH receives an empty Message body and fails to parse it — your position never closes. Always attach an explicit close_command.
  • Hardcoded pair in the JSON. If you write "pair":"BTCUSDT" literally, the strategy only works on BTCUSDT. Use "{{ticker}}" so the same strategy template works on every chart.
  • Pine strategy fires intra-bar. Strategies with process_orders_on_close=false can place orders on a tick that later reverses. Set process_orders_on_close=true (or check barstate.isconfirmed inside your conditions) for non-repainting behaviour.
  • pyramiding=N with N > 0. Each new entry signal stacks another position. TVH executes every stack. Set pyramiding=0 unless you actually want to pyramid.
  • calc_on_every_tick=true combined with calc_on_order_fills=true. Causes recursive intra-bar fills that flood the webhook. Keep both off unless you have a specific reason.
  • Token leaked in a public strategy. When you publish a strategy to TV's public library, the JSON string is visible to everyone. Use a separate token for public scripts and rotate it on schedule.

Next