Part 3: Closing the Loop
In Part 1, I laid out the idea — using TradingView’s alert system to report a strategy’s trades to a dashboard. In Part 2, I went through how the Pine Script itself builds those events: the JSON envelope, manual position tracking across bars, and the warmup replay. This last post covers the part that actually makes it all show up somewhere: the one-time setup, the full event payloads, and how to adapt this to your own strategy.
One-time setup
Unlike the NinjaTrader version — where the webhook URL and system name were just two properties on the strategy itself — getting events flowing from TradingView takes a few more steps, because the delivery mechanism (an alert) is configured separately from the script:
- In ScalpTrader, open the system you want this strategy to report to and copy its Webhook URL — the same
https://tradeevents.scalptrader.com/events/user_xxxx/tl-xxxxxxxxformat as the NinjaTrader series, since both send to the same backend. - In the script’s inputs, set TradeEvents system name to match the name shown in the ScalpTrader dashboard exactly. As with the NinjaTrader version, a mismatch gets the events rejected with a 401.
- Add the strategy to a chart on a small timeframe — 1 to 5 minutes. This isn’t a trading decision; it’s because card updates fire on bar close, and the dashboard marks a system offline after roughly 7.5 minutes without an event. A 1-hour chart would mean long, misleading gaps in the dashboard’s idea of whether the strategy is even running.
- Create the alert: Condition — this strategy — “Any alert() function call.” Under Notifications, set the Webhook URL to the URL from step 1. Leave the alert’s message box as-is — the script fills in the actual payload at the point it calls
alert(), so there’s nothing to type there. Click Create.
That’s the entire setup. No server, no separate auth token to manage — the webhook URL itself carries the authentication, the same as on the NinjaTrader side.
The event payloads
Three event types come out of this script, matching the ones covered at a code level in Part 2.
position_opened
Fired the moment a new position opens — either from flat, or as the “new” half of a same-bar reversal:
{
"system_name": "YOUR_SYSTEM_NAME",
"event_type": "position_opened",
"timestamp": "2026-06-30T14:32:00Z",
"data": {
"strategy": "YOUR_SYSTEM_NAME",
"symbol": "BTCUSD",
"direction": "long",
"quantity": 1,
"entry_price": 64250.5,
"position_id": "BTCUSD-14-18230"
}
}position_id is built from the ticker, an internal trade counter, and the bar index at the time of entry — not anything TradingView assigns itself, since Pine doesn’t expose a stable position identifier the way a broker API typically would.
position_closed
Fired when a position closes out, whether from a flat-out exit or as the “old” half of a reversal:
{
"system_name": "YOUR_SYSTEM_NAME",
"event_type": "position_closed",
"timestamp": "2026-06-30T16:05:00Z",
"data": {
"strategy": "YOUR_SYSTEM_NAME",
"symbol": "BTCUSD",
"direction": "long",
"quantity": 1,
"exit_price": 64810.0,
"profit_loss": 559.5,
"pnl_dollars": 559.5,
"close_type": "full",
"position_id": "BTCUSD-14-18230"
}
}The position_id here matches the one from the corresponding position_opened event, which is how TradeEvents groups the two into a single trade on the dashboard rather than treating them as unrelated events.
card
The recurring status snapshot. Shape depends on whether the strategy is currently in a position:
{
"system_name": "YOUR_SYSTEM_NAME",
"event_type": "card",
"data": {
"strategy": "YOUR_SYSTEM_NAME",
"symbol": "BTCUSD",
"status": "in_position",
"direction": "long",
"quantity": 1,
"entry_price": 64250.5,
"unrealized_pnl": 312.0
}
}{
"system_name": "YOUR_SYSTEM_NAME",
"event_type": "card",
"data": {
"strategy": "YOUR_SYSTEM_NAME",
"symbol": "BTCUSD",
"status": "active",
"state_detail": "Waiting for EMA crossover"
}
}As covered in Part 2, this fires once per realtime bar regardless of whether anything changed, plus immediately on any position change — so the dashboard’s “last seen” stays current bar-to-bar, not just on trades.
Warmup events (EMACrossover_Full.pine only)Intermediate
Anything replayed from trade history on go-live carries “warmup”: true on the open/close payloads, and “is_warmup”: true inside data as well:
{
"system_name": "YOUR_SYSTEM_NAME",
"event_type": "position_closed",
"warmup": true,
"timestamp": "2026-06-30T09:15:00Z",
"data": {
"strategy": "YOUR_SYSTEM_NAME",
"symbol": "BTCUSD",
"direction": "short",
"quantity": 1,
"exit_price": 63900.0,
"profit_loss": -120.25,
"pnl_dollars": -120.25,
"is_warmup": true,
"close_type": "full",
"position_id": "BTCUSD-warmup-7"
}
}The double-tagging (top-level warmup and data.is_warmup) gives TradeEvents the option to filter or visually distinguish replayed history from genuinely live trades, however the dashboard chooses to handle that on its end.
Customizing this for your own strategy
The whole point of structuring the script this way is that the section below the // TradeEvents plumbing comment shouldn’t need to change. Everything it relies on is generic to any Pine strategy:
strategy.position_size,strategy.position_avg_price— read on every bar, regardless of what logic decided to enter or exitstrategy.closedtradesand its accessors — populated automatically by the strategy engine for warmup replaysyminfo.ticker,syminfo.pointvalue— read from the chart, not from the strategy logic
To plug in your own strategy, the only things to touch are above that line: replace the EMA calculations and the longCondition/shortCondition logic with whatever strategy.entry/strategy.close calls represent your actual edge. As long as your entries and exits go through strategy.entry/strategy.exit/strategy.close the normal way, the position-size diffing in the plumbing section picks them up without modification.
A couple of things to double-check when adapting it:
- If your strategy holds multiple simultaneous positions (pyramiding) or uses partial exits, the size-diffing logic here assumes a single net position per symbol — it would need rework to track partial closes correctly rather than treating every change as a full open/close.
pnl_dollarsin this script usessyminfo.pointvalue, which is meaningful for futures-style instruments; for equities or spot crypto you may want to double-check that math produces the number you expect.
What it looks like on the dashboard
Once the alert is live and the first events start arriving, here’s what to expect. The account row on the main dashboard (in this example the account is named TV) shows the strategy status and a cumulative equity sparkline:
Clicking through to the account opens the event log, where each position_opened, position_closed, and card event appears as it arrives:
Wrapping up
This series mirrors the NinjaTrader one in structure, but the constraints turned out to be genuinely different: no fill callbacks, no direct HTTP calls, a hard string-length cap, and a “liveness” signal that’s coupled to chart timeframe instead of a timer. What stayed the same is the underlying approach — a small number of well-defined events, a stable ID tying opens to closes, and a clean separation between “decide what to trade” and “report what happened.” That separation is what makes this version usable as a drop-in pattern rather than a one-off example: anyone running their own Pine strategy can keep the plumbing exactly as written and just point their own entries and exits at it.

