โ† ะะฐะทะฐะด

TradingView Webhook โ†’ Automated Trading: Technical Guide


1. How TradingView Webhooks Work

Plan Requirements

Minimum for automation: Essential plan.

Setup Steps

  1. Create an alert on any chart (indicator, strategy, or drawing tool)
  2. In the alert dialog, check "Webhook URL" under Notifications
  3. Enter your server URL: https://your-server.com/webhook/tradingview
  4. Write the alert message (plain text or JSON) โ€” this is the POST body TV sends
  5. Set alert conditions (crossing, greater than, entering channel, etc.)
  6. Save the alert

How It Works Technically


2. Alert Message Format & JSON Payload

Placeholder Variables (Built-in)

Placeholder Description Example
{{ticker}} Symbol ticker BTCUSDT
{{exchange}} Exchange name BYBIT
{{close}} Current close price 67432.50
{{open}} Current open price 67100.00
{{high}} Current high 67500.00
{{low}} Current low 67000.00
{{volume}} Current volume 1234.56
{{time}} UTC timestamp 2025-01-15T14:30:00Z
{{timenow}} Current time 2025-01-15T14:30:05Z
{{interval}} Chart timeframe 15 (minutes)
{{plot_0}}, {{plot_1}} ... Indicator plot values 72.5 (RSI value)
{{strategy.order.action}} Strategy order direction buy or sell
{{strategy.order.id}} Strategy order ID Long Entry
{{strategy.order.price}} Strategy order price 67432.50
{{strategy.order.contracts}} Contracts/quantity 0.1
{{strategy.position_size}} Current position size 0.1 or -0.1
{{strategy.market_position}} Position type long, short, or flat
{{strategy.prev_market_position}} Previous position flat
{{strategy.order.alert_message}} Custom message from Pine Script (see below)

Recommended JSON Payload Template

{
  "secret": "YOUR_SECRET_KEY_HERE",
  "ticker": "{{ticker}}",
  "exchange": "{{exchange}}",
  "action": "{{strategy.order.action}}",
  "order_id": "{{strategy.order.id}}",
  "price": {{close}},
  "position_size": {{strategy.position_size}},
  "market_position": "{{strategy.market_position}}",
  "prev_market_position": "{{strategy.prev_market_position}}",
  "contracts": {{strategy.order.contracts}},
  "interval": "{{interval}}",
  "time": "{{time}}"
}

Pine Script alert_message Parameter (Strategy Only)

strategy.entry("Long", strategy.long, alert_message='{"action": "buy", "ticker": "' + syminfo.ticker + '", "price": ' + str.tostring(close) + '}')

When creating the alert, set message to: {{strategy.order.alert_message}}


3. Pine Script Examples for Signals

3.1 RSI Overbought/Oversold

//@version=5
strategy("RSI Webhook Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100)

rsiLength = input.int(14, "RSI Length")
overbought = input.int(70, "Overbought Level")
oversold = input.int(30, "Oversold Level")

rsiValue = ta.rsi(close, rsiLength)

longCondition = ta.crossover(rsiValue, oversold)
shortCondition = ta.crossunder(rsiValue, overbought)

if longCondition
    strategy.entry("RSI Long", strategy.long,
      alert_message='{"action":"buy","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"rsi":' + str.tostring(rsiValue) + ',"strategy":"rsi"}')

if shortCondition
    strategy.entry("RSI Short", strategy.short,
      alert_message='{"action":"sell","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"rsi":' + str.tostring(rsiValue) + ',"strategy":"rsi"}')

3.2 EMA Crossover

//@version=5
strategy("EMA Cross Webhook", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100)

fastLen = input.int(9, "Fast EMA")
slowLen = input.int(21, "Slow EMA")

fastEMA = ta.ema(close, fastLen)
slowEMA = ta.ema(close, slowLen)

if ta.crossover(fastEMA, slowEMA)
    strategy.entry("EMA Long", strategy.long,
      alert_message='{"action":"buy","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"strategy":"ema_cross"}')

if ta.crossunder(fastEMA, slowEMA)
    strategy.entry("EMA Short", strategy.short,
      alert_message='{"action":"sell","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"strategy":"ema_cross"}')

3.3 Volume Spike + Price Action

//@version=5
strategy("Volume Spike Webhook", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100)

volMultiplier = input.float(2.0, "Volume Spike Multiplier")
volSmaLen = input.int(20, "Volume SMA Length")

avgVol = ta.sma(volume, volSmaLen)
isVolumeSpike = volume > avgVol * volMultiplier
isBullish = close > open
isBearish = close < open

if isVolumeSpike and isBullish
    strategy.entry("Vol Long", strategy.long,
      alert_message='{"action":"buy","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"vol_ratio":' + str.tostring(volume/avgVol) + ',"strategy":"volume_spike"}')

if isVolumeSpike and isBearish
    strategy.entry("Vol Short", strategy.short,
      alert_message='{"action":"sell","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"vol_ratio":' + str.tostring(volume/avgVol) + ',"strategy":"volume_spike"}')

3.4 Multi-Condition (RSI + EMA + Volume)

//@version=5
strategy("Multi Signal Webhook", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=100)

rsi = ta.rsi(close, 14)
fast = ta.ema(close, 9)
slow = ta.ema(close, 21)
avgVol = ta.sma(volume, 20)

longSignal = rsi < 40 and ta.crossover(fast, slow) and volume > avgVol * 1.5
shortSignal = rsi > 60 and ta.crossunder(fast, slow) and volume > avgVol * 1.5

if longSignal
    strategy.entry("Multi Long", strategy.long,
      alert_message='{"action":"buy","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"rsi":' + str.tostring(rsi) + ',"strategy":"multi"}')

if shortSignal
    strategy.entry("Multi Short", strategy.short,
      alert_message='{"action":"sell","ticker":"' + syminfo.ticker + '","price":' + str.tostring(close) + ',"rsi":' + str.tostring(rsi) + ',"strategy":"multi"}')

4. Building a Webhook Receiver

Node.js (Express)

const express = require('express');
const app = express();

const SECRET = process.env.TV_WEBHOOK_SECRET || 'your-secret-key';
const ALLOWED_IPS = [
  '52.89.214.238', '34.212.75.30',
  '54.218.53.128', '52.32.178.7'
];

app.use('/webhook', express.text({ type: '*/*' }));

function checkIP(req, res, next) {
  const clientIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.ip;
  if (process.env.NODE_ENV === 'production' && !ALLOWED_IPS.includes(clientIP)) {
    console.log(`[${new Date().toISOString()}] Blocked IP: ${clientIP}`);
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
}

app.post('/webhook/tradingview', checkIP, async (req, res) => {
  const timestamp = new Date().toISOString();
  try {
    let payload;
    try {
      payload = JSON.parse(req.body);
    } catch (e) {
      console.log(`[${timestamp}] Non-JSON alert: ${req.body}`);
      return res.status(400).json({ error: 'Invalid JSON' });
    }

    if (payload.secret !== SECRET) {
      console.log(`[${timestamp}] Invalid secret from alert`);
      return res.status(401).json({ error: 'Unauthorized' });
    }

    console.log(`[${timestamp}] Alert received:`, JSON.stringify(payload));
    const result = await processSignal(payload);
    res.status(200).json({ success: true, result });
  } catch (err) {
    console.error(`[${timestamp}] Error:`, err.message);
    res.status(500).json({ error: 'Internal error' });
  }
});

const PORT = process.env.WEBHOOK_PORT || 3400;
app.listen(PORT, () => {
  console.log(`[${new Date().toISOString()}] Webhook receiver on port ${PORT}`);
});

5. Security

Defense Layers (use ALL)

Layer 1: Secret in Payload

{"secret": "a7f3b2c9d1e4...", "action": "buy", ...}

Layer 2: IP Whitelisting (Nginx)

location /webhook/tradingview {
    allow 52.89.214.238;
    allow 34.212.75.30;
    allow 54.218.53.128;
    allow 52.32.178.7;
    deny all;
    proxy_pass http://localhost:3400;
    proxy_set_header X-Forwarded-For $remote_addr;
}

Layer 3: HTTPS Only (Let's Encrypt โœ…)

Layer 4: Rate Limiting

const rateLimit = require('express-rate-limit');
app.use('/webhook', rateLimit({ windowMs: 60000, max: 30 }));

Layer 5: Non-Obvious URL Path https://your-server.com/webhook/tv-8f3a7b2c1d


6. Latency

Step Latency Notes
TV alert detection 0-15 sec Checked at bar close
TV sends webhook 50-500 ms TV server โ†’ your server
Server processes 5-50 ms Parse JSON + logic
Bybit API call 50-200 ms Order placement
Order fill 10-100 ms Market order, liquid markets
Total ~1-16 sec Dominant: TV bar close timing

Key: This is NOT HFT. Suits 5m-1D strategies. For sub-second, use direct exchange WebSocket.


7. Full Architecture

TradingView (Pine Script Strategy)
        |
        | HTTPS POST (JSON payload)
        v
Nginx (SSL + IP whitelist)
        |
        v
Webhook Receiver (Express :3400)
  - Parse JSON, verify secret, validate
        |
        v
Signal Router
  - Risk check (max daily loss, position sizing)
  - Cooldown (min time between trades)
  - Duplicate detection
        |
        +---> Bybit V5 API (place order)
        +---> Logger (trades.log)
        +---> Telegram notify (@Bender137_bot)

Cost

Item Cost Notes
TradingView Essential $12.95/mo Minimum for webhooks
TradingView Plus $24.95/mo 100 alerts, multi-pair
Bybit API Free
Server (existing) $0 Already on szhub.space
Total $13-25/mo