TradingView alerts are genuinely powerful. You can write a Pine Script strategy, backtest it, and then fire an alert the moment a condition is met. The problem is what happens after that alert fires — by default, nothing. You still have to manually place the trade.
Most people solve this in one of two ways: they pay $49+/month for a SaaS bridge, or they find a GitHub script that uses Flask, has no error handling, and breaks silently. Neither is satisfying. Here is how to build the bridge yourself in about 100 lines of Python.
The Architecture
The flow is simple:
TradingView alert → HTTP POST → FastAPI server → Alpaca order
Four components:
- A FastAPI app with a single
/webhookendpoint - A Pydantic model that validates the incoming payload
-
alpaca-pyfor order execution
4. A .env file for secrets, loaded with python-dotenv
Why FastAPI Over Flask
Flask works, but FastAPI gives you three things that matter here:
- Async by default. Webhook handlers are I/O-bound (network calls to Alpaca). Async lets you handle bursts without blocking.
-
Automatic docs. Hit
/docsand you get an interactive Swagger UI. Useful when debugging why your payload is being rejected.
- Pydantic integration. Your payload model is also your validation layer. If TradingView sends a malformed body, FastAPI returns a 422 before your handler even runs.
The Pydantic Model
from pydantic import BaseModel
from typing import Literal
class WebhookPayload(BaseModel):
secret: str
ticker: str
action: Literal["buy", "sell", "close"]
qty: float | None = None
The secret field is the key decision. You are putting this server on the public internet, and TradingView does not support request signing. Anyone who finds your URL can POST to it. The payload secret is a shared token you set in both your .env and inside the TradingView alert message. If it does not match, you return a 403 and log the attempt.
import os
WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")
@app.post("/webhook")
async def webhook(payload: WebhookPayload):
if payload.secret != WEBHOOK_SECRET:
raise HTTPException(status_code=403, detail="Invalid secret")
...
Order Execution
from alpaca.trading.client import TradingClient
from alpaca.trading.requests import MarketOrderRequest
from alpaca.trading.enums import OrderSide, TimeInForce
client = TradingClient(
os.getenv("ALPACA_API_KEY"),
os.getenv("ALPACA_SECRET_KEY"),
paper=True # flip to False for live
)
async def execute_order(payload: WebhookPayload):
if payload.action == "close":
client.close_position(payload.ticker)
return
side = OrderSide.BUY if payload.action == "buy" else OrderSide.SELL
order = MarketOrderRequest(
symbol=payload.ticker,
qty=payload.qty,
side=side,
time_in_force=TimeInForce.DAY
)
client.submit_order(order)
Two decisions worth explaining:
Market orders by default. Limit orders introduce slippage decisions — what price, how long to wait, what if it doesn't fill? For automated signals, market orders execute immediately and you know you're in. If your strategy is latency-sensitive enough to care about the spread, you probably should not be using a webhook bridge anyway.
close_position vs. market sell. If you sell a fixed quantity and your position size is not exactly that quantity (because of partial fills, fractional shares, etc.), you end up with a residual position. close_position flattens whatever you actually hold. Use action: "close" for exits.
The TradingView Side
In your alert message, send JSON:
{
"secret": "{{your_secret_here}}",
"ticker": "{{ticker}}",
"action": "buy",
"qty": 1
}
Set the webhook URL to your server's /webhook endpoint.
Security Checklist
- Secret token in payload (covered above)
- API keys in
.env, never in source code -
.envin.gitignorebefore the first commit, not after - Use Alpaca paper trading until you have tested this extensively
- Log every incoming request (at minimum: timestamp, ticker, action, whether it succeeded)
Deploying Cheaply
A one-file Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Railway and Render both have free tiers that will run this. Railway's free tier gives you $5/month of compute credit — enough for a lightweight FastAPI server. Set your environment variables in the dashboard, not in the Dockerfile.
What I Learned
The webhook secret feels obvious in retrospect but it is the thing most people skip in tutorials. Silent failures are the other thing — if your order fails (market closed, insufficient buying power, invalid symbol), you want to know immediately. Log everything, even successful orders. Add an /health endpoint that returns 200 so you can ping it and confirm the server is running before TradingView fires a live alert.
I packaged this webhook server alongside a backtester and portfolio dashboard as AlgoKit — a Python trading infrastructure bundle for people who want to own their stack: algokit-dev.lemonsqueezy.com
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.