|
|
||
|---|---|---|
| app | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
| render.yaml | ||
| requirements.txt | ||
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)
- 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
- Build and run:
docker compose up -d --build
- Health check:
curl http://127.0.0.1:8000/health
- 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 TradingView’s 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
}
PineScript alert() (recommended)
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 %)/TP1–3
seq >1 (control):
- long:
BTCUSDT.P 🌱 Buy Seq: N - short:
BTCUSDT.P 🥀 Sell Seq: N - body:
Entry price(from seq 1) / livePrice/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 |price−entry| / |entry−SL| with the same sign as profit.
Behavior
- Validate payload
- Fetch ~90 klines from Binance USDT-M Futures (public, no API key)
- Render PNG: candles + Entry / SL / TP1–3 from the payload, starting at the
signal_timecandle (seq>1reuses frozen seq-1 levels/time and also marks livePrice) sendPhototoTELEGRAM_CHAT_IDtopicTELEGRAM_MESSAGE_THREAD_ID- If chart/klines fail → text-only
sendMessagefallback (still200) - If Telegram fails →
502
Endpoints
GET /health→{"status":"ok"}POST /webhook→ signal payload above