A Pine Script indicator can plot a perfect signal on a chart and still be operationally useless, because "visible on a chart" and "actionable by a program" are two completely different problems. This post covers the actual bridge — TradingView alert payloads, a webhook receiver, payload validation, and a dispatch layer to the broker — that turns the Goldmine indicator from something you stare at into something that trades without you. Code included for every stage.
The gap nobody mentions between "the indicator works" and "the bot works"
Pine Script is genuinely good at one thing: visualizing structure on a chart. Order blocks, fair value gaps, CHoCH/BOS labels — all of it renders cleanly and updates in real time. What Pine Script is not is a place you can run arbitrary logic, hit a broker API, or maintain state across restarts. It runs inside TradingView's sandbox, full stop.
So the moment you want a visual indicator to become an acting system — placing real orders, not just drawing boxes — you hit a hard boundary: TradingView's only way out of that sandbox is alert() and a webhook payload. Everything past that point (receiving the alert, validating it, deciding whether to act on it, actually placing the order) is infrastructure you build yourself, outside Pine Script entirely.
This post is that infrastructure — the part that turns the Goldmine indicator's visual signal into the Goldmine Trading Bot's executed trade.
Stage 1: Designing the alert payload inside Pine Script
The temptation is to fire an alert with a vague message string. Don't — every field the downstream system needs to make a safe decision has to be in the payload itself, because there's no way to "go back and ask the chart for more context" once the webhook fires.
//@version=5
indicator("Goldmine Signal Engine", overlay=true)
// ... structure detection logic producing `signalDetected`, `direction`, `confidence`, `invalidationLevel` ...
if signalDetected
alertPayload = str.format(
'{{"symbol":"{0}","direction":"{1}","confidence":{2},"entry":{3},"invalidation":{4},"timeframe":"{5}","timestamp":"{6}"}}',
syminfo.ticker, direction, confidence, close, invalidationLevel, timeframe.period, str.tostring(time)
)
alert(alertPayload, alert.freq_once_per_bar_close)
Two details here matter more than they look like they should:
-
alert.freq_once_per_bar_close, notalert.freq_all— firing on every tick instead of on bar close produces duplicate/premature signals from repainting intermediate values. This single setting eliminated a whole category of phantom signals in early testing. - The timestamp is generated in Pine Script, not on receipt — if your webhook receiver timestamps on arrival instead of trusting the chart's bar-close time, network latency and retry delays silently corrupt your signal-to-execution latency measurements later.
Stage 2: The webhook receiver
TradingView POSTs the payload to a public HTTPS endpoint. This is the part with the most exposed attack surface in the whole pipeline, and it's the part homegrown bots most often build carelessly.
from flask import Flask, request, abort
import hmac, hashlib, json
app = Flask(__name__)
WEBHOOK_SECRET = load_secret() # never hardcode this
@app.route('/webhook/goldmine', methods=['POST'])
def receive_signal():
raw_body = request.get_data()
# TradingView doesn't sign requests natively — so validate a
# shared secret embedded in the payload itself, not just the URL
payload = json.loads(raw_body)
if not hmac.compare_digest(payload.get('secret', ''), WEBHOOK_SECRET):
abort(403)
if not _valid_schema(payload):
abort(400) # malformed payload — reject, don't guess
signal_queue.put(payload)
return '', 200
Why the shared secret matters: TradingView webhook URLs are, by design, public HTTPS endpoints. If your URL leaks — a screenshot, a log file, a misconfigured proxy — anyone can POST a fake signal to it. A secret embedded in the payload (not just relying on URL obscurity) is the minimum bar for a webhook that can trigger real trades.
Why validation happens before the queue, not after: A malformed payload that makes it into the execution pipeline is worse than one that gets rejected at the door. Fail loud and early here.
Stage 3: The dispatch layer — deciding whether to actually act
Receiving a valid signal is not the same as trading it. This is where the confidence threshold and risk governor from the bot's execution pipeline plug in:
def process_signal(payload):
if payload['confidence'] < MIN_EXECUTION_THRESHOLD:
log_skipped(payload, reason='below_threshold')
return
if not risk_governor.can_open_new_position(payload['symbol']):
log_skipped(payload, reason='risk_ceiling')
return
if is_duplicate(payload): # same structural level, different webhook retry
log_skipped(payload, reason='duplicate')
return
execution_engine.execute(build_order(payload))
Deduplication is the non-obvious one here. TradingView will retry a webhook delivery if it doesn't get a fast 200 response — meaning your endpoint needs to return quickly (queue and return, don't process synchronously) and your dispatch layer needs to recognize "this is the same signal arriving twice" rather than opening two positions from one structural event.
Stage 4: Latency — the metric nobody backtests
A backtest doesn't know that your webhook receiver is on a shared host with cold-start delay, or that your broker's order API has a 400ms round trip during high-volatility windows. In production, signal-to-execution latency on a fast-moving instrument like gold is a real, measurable variable — and it's invisible until you instrument it:
def execute(self, signal):
signal_time = parse_chart_time(signal['timestamp'])
received_time = time.time()
latency_to_receipt = received_time - signal_time
order = self.broker.place_order(...)
execution_time = time.time()
latency_to_fill = execution_time - received_time
metrics.record('signal_to_receipt_ms', latency_to_receipt * 1000)
metrics.record('receipt_to_fill_ms', latency_to_fill * 1000)
Once this was instrumented, the actual bottleneck wasn't the interesting part (Pine Script alert firing, or the webhook receiver) — it was broker order-confirmation round trips during news-driven volatility spikes, which is exactly when signal quality matters most and latency budget matters least.
What broke before this was hardened
Webhook retries created duplicate positions before deduplication was keyed to the structural level (the invalidation price) rather than a timestamp — two webhook deliveries a few hundred milliseconds apart looked like "different" signals under naive timestamp comparison.
A cold-start delay on the receiver caused TradingView to retry, and the retry arrived after the original had already been processed — meaning the dispatch layer had to handle out-of-order and duplicate delivery as a normal case, not an edge case.
Payload schema drift — a Pine Script update that changed a field name silently broke the receiver's validation, and because validation failed closed (rejecting the payload), signals just stopped executing with no obvious error until logs were checked. Schema versioning in the payload itself fixed this going forward.
Where the packaged version fits in
The signal detection and visualization layer described here is the Goldmine indicator; the receiver, dispatch, and execution layers are what make up the Goldmine Trading Bot. If you're building your own bridge, the priority order that mattered most in practice was: payload validation and deduplication before latency optimization — a fast pipeline that occasionally double-fires is more dangerous than a slightly slower one that doesn't.
Full disclosure: both are products I built and sell. I'm posting the actual bridge architecture because I think it's a useful pattern for anyone connecting a visual/chart-based signal system to a real execution layer, TradingView-based or not.
The Indicator in Action
FAQ
Why not just run everything inside Pine Script?
Pine Script has no persistent state across restarts, no outbound HTTP beyond alert webhooks, and no access to broker APIs directly. It's a charting/visualization sandbox by design — the alert-and-webhook bridge is the only sanctioned way out of it.
How do you handle TradingView webhook retries reliably?
Return a fast 200 immediately (queue the payload, process asynchronously) and deduplicate on the structural signal identity, not the delivery timestamp — retries are expected behavior, not a failure mode to prevent.
Is a shared secret in the payload actually secure, or should I use something stronger?
It's a practical minimum given TradingView's webhook model doesn't support native request signing. If your threat model warrants more, IP allowlisting TradingView's published webhook IP ranges adds a second layer, though it doesn't replace payload-level validation.
What's the typical signal-to-fill latency you're seeing?
It varies significantly by broker and session volatility — the point of instrumenting it (Stage 4) isn't a specific number to quote, it's making the bottleneck visible so you know where to actually spend optimization effort.
Can this pattern work with indicators other than Pine Script/TradingView?
Yes — the receiver/dispatch/execution layers are indicator-agnostic. Anything that can fire an HTTP webhook on a signal (custom Python indicator, MetaTrader alert, another charting platform) can plug into the same bridge.
Grab The Bot Here
Grab The Grid System with 90% Win Rate Here
Let Talk
If you've bridged a visualization tool (chart, dashboard, monitoring alert) into something that takes real action downstream, what was the failure mode you didn't see coming until production? Webhook retry handling in particular seems to be the thing everyone underestimates until it duplicates something expensive.
Top comments (0)