TradingView webhook → Binance chart → Telegram topic
Find a file
Artemii Peretiachenko d6c539dca9 Initial TradingView→Telegram webhook service with Render Blueprint.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 16:35:47 +02:00
app Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00
.env.example Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00
.gitignore Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00
docker-compose.yml Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00
Dockerfile Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00
README.md Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00
render.yaml Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00
requirements.txt Initial TradingView→Telegram webhook service with Render Blueprint. 2026-07-24 16:35:47 +02:00

TradingView → Telegram setup service

Accepts TradingView webhook alerts, renders a Binance Futures candlestick setup chart, and posts photo + caption into a Telegram forum topic.

Quick start (Docker / VPS)

  1. Copy env and fill Telegram values:
cp .env.example .env
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHAT_ID=-100...
TELEGRAM_MESSAGE_THREAD_ID=...
HOST=0.0.0.0
PORT=8000
  1. Build and run:
docker compose up -d --build
  1. Health check:
curl http://127.0.0.1:8000/health
  1. Put HTTPS in front (nginx/Caddy) and point TradingView webhook to:

https://your-domain/webhook

Bot must be added to the group/forum and allowed to post in the target topic.

Local run (without Docker)

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # fill values
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

TradingView alert JSON

The webhook accepts a single-line or pretty-printed JSON body, including TradingViews common Content-Type: text/plain.

Static example (alert Webhook message body) — primary setup (signal_sequence: 1):

{
  "ticker": "{{ticker}}",
  "action": "long",
  "entry_price": "65034.7",
  "current_price": "65034.7",
  "stop_loss_price": "63904.9",
  "take_profit_1_price": "66085.9",
  "take_profit_2_price": "67209.2",
  "take_profit_3_price": "68355.5",
  "visual_timeframe": "15",
  "signal_sequence": 1,
  "signal_time": 1721736000
}

Control update (signal_sequence > 1) — Pine freezes entry_price / SL / TPs / signal_time from seq 1 and sends live current_price:

{
  "ticker": "{{ticker}}",
  "action": "short",
  "entry_price": "65034.7",
  "current_price": "64000.1",
  "stop_loss_price": "65896.8",
  "take_profit_1_price": "63904.9",
  "take_profit_2_price": "62781.6",
  "take_profit_3_price": "61635.3",
  "visual_timeframe": "15",
  "signal_sequence": 2,
  "signal_time": 1721736000
}

Build the JSON inside alert(). A continuous one-line string is fine.

In the TradingView alert dialog:

  • Webhook URL: https://your-domain/webhook
  • Message: only {{alert_message}} (do not paste a second JSON next to it)

On seq == 1: store entry_price = close, freeze SL/TPs, and signal_time = time / 1000 (bar open, unix seconds). On seq > 1: keep those frozen fields; only refresh current_price (= live close). Example shape:

alert('{"ticker":"' + syminfo.ticker + '","action":"long","entry_price":"' + str.tostring(long_entry_price, format.mintick) + '","current_price":"' + str.tostring(close, format.mintick) + '","stop_loss_price":"' + str.tostring(long_sl_price, format.mintick) + '","take_profit_1_price":"' + str.tostring(long_tp1_price, format.mintick) + '","take_profit_2_price":"' + str.tostring(long_tp2_price, format.mintick) + '","take_profit_3_price":"' + str.tostring(long_tp3_price, format.mintick) + '","visual_timeframe":"' + timeframe.period + '","signal_sequence":' + str.tostring(buyCount) + ',"signal_time":' + str.tostring(signal_buy_time) + '}', alert.freq_once_per_bar_close)

(Same idea for shorts with short_entry_price / sellCount / signal_sell_time.) Caption emoji/labels are built by the service from action + signal_sequence — do not put them in the webhook JSON.

Field notes:

Field Description
ticker any common TV form (BTCUSDT.P, BTCUSDT, BINANCE:ETHUSDT, BTC/USDT, …) → normalized to Binance Futures symbol for the chart; caption keeps the original
action long or short
entry_price trade entry from seq 1 (equals current_price on primary signal)
current_price live price (close at alert time)
*_price strings with your display precision
visual_timeframe 1, 3, 5, 15, 30, 60, 120, 240, D, W (also 15m, 1h, …)
signal_sequence 1 = primary setup; >1 = control update of that trade
signal_time unix seconds of the seq-1 bar open (UTC); chart draws Entry/SL/TP zones from that candle

Caption format

seq 1 (setup):

  • long: BTCUSDT.P 💚 Buy
  • short: BTCUSDT.P 💔 Sell
  • body: Price / SL (risk %) / TP13

seq >1 (control):

  • long: BTCUSDT.P 🌱 Buy Seq: N
  • short: BTCUSDT.P 🥀 Sell Seq: N
  • body: Entry price (from seq 1) / live Price / Current profit: +1.6% (RR 1:1.2)
  • no new SL/TP lines in the caption

Prices are shown with $ and thousand spaces (65034.7$65 034.7). For seq 1, SL includes distance from entry: SL: $63 904.9 (-2.37%) (risk %, negative for both long and short). For seq >1, profit % is signed vs entry; RR is |priceentry| / |entrySL| with the same sign as profit.

Behavior

  1. Validate payload
  2. Fetch ~90 klines from Binance USDT-M Futures (public, no API key)
  3. Render PNG: candles + Entry / SL / TP13 from the payload, starting at the signal_time candle (seq >1 reuses frozen seq-1 levels/time and also marks live Price)
  4. sendPhoto to TELEGRAM_CHAT_ID topic TELEGRAM_MESSAGE_THREAD_ID
  5. If chart/klines fail → text-only sendMessage fallback (still 200)
  6. If Telegram fails → 502

Endpoints

  • GET /health{"status":"ok"}
  • POST /webhook → signal payload above