目录
文章目录
  1. 1. 一条警报变成币安订单,中间经过哪几段
  2. 2. TradingView 对 Webhook 地址的 6 条要求
  3. 3. 警报消息怎么写,才能把交易对和方向带过去
  4. 4. 中转服务收到消息后按 5 步处理
  5. 5. 币安这边给中转服务单开一把 Key
  6. 6. 测试网上跑通之后,还要盯住两边的持仓
  7. 7. 接 Webhook 之前要弄清的几件事

用 Webhook 把 TradingView 警报变成币安现货订单

示意图:绿色铃铛、黄色服务器机架、红色菱形三个方框,由黄色虚线箭头从左到右依次相连
TradingView 警报先到你自己的服务器,签名后再调用币安 POST /api/v3/order 下单(示意图)

TradingView 只负责在警报触发时发一条 POST;签名和下单由你自己的服务器完成,币安那把 Key 也只给这台服务器用。

2026-11-08 发布 PromptDeck 撰 阅读约 10 分钟 约 3,295 字
范围:TradingView 警报经 Webhook 触发币安现货市价单,走 REST 接口 POST /api/v3/order。U 本位合约用的是另一套接口和参数,不在本文内。TradingView 的规则来自它的帮助中心,币安的规则来自开发者文档,均为 2026 年 11 月核对。
警报里不放任何密钥:币安的 API Key、Secret 和私钥只存在你自己的服务器上。TradingView 的 Webhook 说明页也写明,别把登录凭证、密码这类敏感数据放进 Webhook 消息体。

TradingView 的警报不能直接调用币安的下单接口。中间要有一台你自己控制的服务器:警报触发时 TradingView 向它发一个 HTTP POST,它检查消息内容,按币安的规则签名,再去下单。

卡在签名上。币安的下单接口属于 TRADE 类型,每个请求都要带当前时间戳和用密钥算出的 signature;TradingView 发出去的是一段你提前写好的固定文字,算不出签名。所以要搭的是三段:TradingView 警报里填 Webhook 地址和 JSON 消息,服务器上跑一个接收程序,币安那边开一把只给这台服务器用的 API Key。下面按这个顺序写,第 4 节给一段可以照着改的 Python 示例。

1. 一条警报变成币安订单,中间经过哪几段 #

币安开发者文档的请求鉴权页写明:TRADE 和 USER_DATA 两类接口是 SIGNED 请求(USER_STREAM 类只需带 API Key,不用签名),要带 signature 参数,还要带以毫秒或微秒计的当前 timestamp;recvWindow 不发时默认 5000 毫秒,最大 60000 毫秒。一条签好的请求过几秒就作废,没法事先签好塞进警报消息里,签名只能在警报到达之后现算。

段发生了什么规则看哪里
① TradingView → 你的服务器警报触发,TradingView 向 Webhook 地址发 POST,消息体就是你写的警报消息TradingView 帮助中心《How to configure webhook alerts》
② 你的服务器内部校验来源和内容,决定交易对、方向、金额你自己定
③ 你的服务器 → 币安带 X-MBX-APIKEY 请求头、timestamp 和 signature 调用 POST /api/v3/order币安开发者文档的交易接口页、请求鉴权页

第 ② 段也有第三方信号平台可以代办,用法是把币安 API Key 交给它们保管。这等于把下单权限放在另一家公司手里,出了问题按对方的规则处理。这篇只写自己搭的做法;要用第三方,至少别给那把 Key 开提现权限。

2. TradingView 对 Webhook 地址的 6 条要求 #

下表六条都出自 TradingView 帮助中心的 How to configure webhook alerts 页(2026 年 11 月核对)。服务器搭之前逐条对一遍,警报发不出去时要查的地方会少很多。

要求页面写的落到你的服务器上
双重验证账户开了 2FA 才允许用 Webhook 警报先在 TradingView 账户里开 2FA
端口只接受 80 和 443,发往其他端口的请求会被拒地址写成 https://你的域名/路径,不带 :8000 这类端口;程序跑在别的端口,前面加一层 Nginx 之类的反向代理
IPv6目前不支持域名要有指向 IPv4 地址的 A 记录
超时远端服务器处理超过 3 秒,请求会被取消收到消息先回 200,下单放到后台做
消息格式消息是合法 JSON 时带 application/json 请求头,否则用 text/plain消息写成 JSON,服务器按 JSON 解析
来源 IP列出 4 个可加白名单的地址:52.89.214.238、34.212.75.30、54.218.53.128、52.32.178.7服务器只接这 4 个地址发来的请求

这页没写发送失败后会不会自动重发,只说 Webhook 偶尔可能送不到目标地址,送达情况在警报日志的「Webhook status」一栏里看。所以它不是一定送达的通道:漏掉一条信号,账户里的仓位就和策略对不上了。怎么发现这种情况,第 6 节再说。

另有一项要自己确认:TradingView 定价页的方案对比表里有「Webhook notifications」这一行,你的订阅方案带不带这项功能,以那张表为准。

3. 警报消息怎么写,才能把交易对和方向带过去 #

消息正文里可以放双花括号的占位符,警报触发时 TradingView 把它替换成实际值。常用的几个,含义按 TradingView 帮助中心的 How to use a variable value in alert(2026 年 11 月核对):

占位符替换成什么能用在哪
{{exchange}}警报所用品种的交易所所有警报
{{ticker}}品种代码,页面举的例子是 AAPL、BTCUSD所有警报
{{close}}触发警报那根 K 线的收盘价所有警报
{{time}}那根 K 线的时间,UTC,格式 yyyy-MM-ddTHH:mm:ssZ所有警报
{{timenow}}警报实际触发的时间,精确到秒所有警报
{{strategy.order.action}}这笔成交单是 buy 还是 sell只限策略警报
{{strategy.order.contracts}}这笔成交单的合约数量只限策略警报
{{strategy.order.id}}这笔成交单的 ID只限策略警报

页面写明,带 strategy 前缀的占位符只能用在策略警报里。用指标做的警报拿不到 buy 还是 sell,方向就要写死在消息里:建两条警报,一条消息里写 "action": "buy",另一条写 "action": "sell"。

用策略的话,一条警报就能覆盖买卖两个方向,消息可以这样写:

{
  "tag": "btc-ema",
  "exchange": "{{exchange}}",
  "ticker": "{{ticker}}",
  "action": "{{strategy.order.action}}",
  "order_id": "{{strategy.order.id}}",
  "bar_time": "{{time}}",
  "tv_price": "{{strategy.order.price}}"
}

这段有三处要留意:

4. 中转服务收到消息后按 5 步处理 #

  1. 查来源 IP:不在 TradingView 那 4 个地址里的,直接回 403。
  2. 核对内容:exchange 是不是你预期的值,ticker 在不在你允许的交易对清单里。图表开的是别家交易所的 BTCUSD,币安现货没有这个交易对,这种消息直接丢掉,别让程序去猜对应哪个。
  3. 去重:ticker、action、order_id、bar_time 相同的消息只处理一次。
  4. 先回 200:下单交给后台线程,让 TradingView 在 3 秒内收到答复。
  5. 签名下单:后台线程按服务器配置的金额下单,币安的返回原样写进日志。

第 3 步别指望币安替你挡。币安交易接口页对 newClientOrderId 的说明是:它在未完成订单里唯一,同一个 ID 要等前一笔成交之后才会再被接受。市价单发出去通常就成交了,拿同一个 ID 再发一次,币安照样收下。重复消息只能在你自己的服务器上拦。

下单金额用 quoteOrderQty 最省事。币安现货交易接口页(2026 年 11 月核对)写明:市价单的 quantity 和 quoteOrderQty 必须给一个;用 quoteOrderQty 时,以 BTCUSDT 为例,BUY 是用这么多 USDT 能买多少 BTC 就买多少,SELL 是卖出足够的 BTC 换回这么多 USDT,而且这种单不会违反 LOT_SIZE 规则。改用 quantity 按币数下单,就得自己按步长取整,规则见币安 API 下单报 -1013 Filter failure 时按过滤器逐个排查。

下面是这 5 步的最小示例:Flask 收消息,签名用 Ed25519,私钥怎么生成、签名为什么要先转 base64 再做百分比编码,见把 AI 交易脚本用的币安 API 密钥从 HMAC 换成 Ed25519。接口地址默认指向现货测试网。

import base64, os, threading, time, urllib.parse

import requests
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from flask import Flask, abort, request

TV_IPS = {'52.89.214.238', '34.212.75.30', '54.218.53.128', '52.32.178.7'}
ORDER_USDT = {'BTCUSDT': '20'}   # 示例值:允许的交易对 → 每次花多少 USDT
BASE = os.environ.get('BINANCE_BASE', 'https://testnet.binance.vision')
API_KEY = os.environ['BINANCE_API_KEY']
with open(os.environ['BINANCE_PRIVATE_KEY_PATH'], 'rb') as f:
    PRIVATE_KEY = load_pem_private_key(f.read(), password=None)

app = Flask(__name__)
seen = set()
lock = threading.Lock()

def place_order(symbol, side):
    params = {'symbol': symbol, 'side': side, 'type': 'MARKET',
              'quoteOrderQty': ORDER_USDT[symbol],
              'timestamp': int(time.time() * 1000)}
    payload = urllib.parse.urlencode(params)
    sig = base64.b64encode(PRIVATE_KEY.sign(payload.encode('ascii'))).decode()
    body = payload + '&signature=' + urllib.parse.quote(sig, safe='')
    r = requests.post(BASE + '/api/v3/order', data=body, timeout=10,
                      headers={'X-MBX-APIKEY': API_KEY,
                               'Content-Type': 'application/x-www-form-urlencoded'})
    app.logger.warning('binance %s %s', r.status_code, r.text)

@app.post('/tv-hook')
def tv_hook():
    if request.headers.get('X-Real-IP') not in TV_IPS:
        abort(403)
    msg = request.get_json(silent=True)
    if not isinstance(msg, dict):
        abort(400)
    symbol = str(msg.get('ticker', ''))
    side = str(msg.get('action', '')).upper()
    if msg.get('exchange') != 'BINANCE' or symbol not in ORDER_USDT \
            or side not in ('BUY', 'SELL'):
        abort(400)
    key = (symbol, side, msg.get('order_id'), msg.get('bar_time'))
    with lock:
        if key in seen:
            return 'duplicate', 200
        seen.add(key)
    threading.Thread(target=place_order, args=(symbol, side), daemon=True).start()
    return 'ok', 200

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000)

示例里有几处是留给你改的:

5. 币安这边给中转服务单开一把 Key #

这把 Key 只给这台服务器用,不和别的脚本共用。币安请求鉴权页写明,API Key 默认不能交易,要下单得先在 API 管理里启用交易权限。这里只开现货交易,提现权限不开;各档权限的区别见给 AI 开 Binance API:只开只读权限,提现权限绝不能开。

整条链路上有两份 IP 白名单,方向相反,别填混:

白名单设在哪填什么
入口你的服务器(Nginx 或防火墙)TradingView 的 4 个发送地址
出口币安 API 管理里这把 Key 的 IP 访问限制你的服务器访问币安时用的公网 IPv4 地址

TradingView 的那 4 个地址不要填进币安的白名单。真正连币安的是你的服务器,不是 TradingView。出口地址要在服务器上查,别在自己电脑上查;填错了币安会报 -2015,按币安 API 报 -2015 是 Key、IP 还是权限没对上里的顺序排查。

6. 测试网上跑通之后,还要盯住两边的持仓 #

示例的 BINANCE_BASE 默认是 https://testnet.binance.vision。测试网要单独申请一把 Key,部分接口测试网上没有,数据还会不定期清空,接法和差别在AI 写的币安交易脚本先跑现货测试网:怎么接、和实盘差在哪里。在测试网上按这几步过一遍:

  1. 在 TradingView 建一条很容易触发的测试警报,勾上 Webhook,填你的 https 地址。
  2. 警报触发后,先看警报日志的「Webhook status」一栏,再看服务器日志有没有收到这条消息。
  3. 看测试网返回的订单,交易对、方向、金额是不是和你预期的一致。
  4. 把同一条消息手动再发一次,确认被去重拦下,没有第二笔订单。
  5. 从别的 IP 发一次,确认服务器回 403。

切实盘时只改 BINANCE_BASE 和那把 Key。上线以后要隔一段时间对一次:TradingView 策略报告里的持仓方向,和币安账户里实际拿着的币。前面说过 Webhook 偶尔送不到,漏掉一条 sell,策略里已经空仓,账户里还拿着币。对不上时先停掉警报,手动处理,不要写程序自动补单。

自动下单之前,亏损到哪一步离场也要事先定好,止损单怎么挂可以参考用 AI 算止损位和仓位,再到币安手动挂止损单。币安账户还没开的,看本站的币安注册开户教程;中国大陆用户能否注册,以注册页显示为准,风险自担。

7. 接 Webhook 之前要弄清的几件事

TradingView 警报能直接调用币安 API 下单吗?

不能直接调。币安下单接口的每个请求都要带当前时间戳和用密钥算出的签名,TradingView 发出的是提前写好的固定消息,算不出签名。中间需要一台你自己的服务器接收 Webhook,再签名调用币安的 POST /api/v3/order。

TradingView 的 Webhook 地址能带端口号吗?

不能。TradingView 帮助中心写明只接受 80 和 443 端口,发往其他端口的请求会被拒,也不支持 IPv6。程序跑在别的端口时,前面放一层反向代理,对外用 https 的 443 端口。

中转服务器为什么要在 3 秒内回复?

TradingView 写明,远端服务器处理请求超过 3 秒,这次请求会被取消。等币安返回也要花时间,所以收到消息先回 200,下单交给后台线程去做。

币安 API Key 的 IP 白名单要填 TradingView 的地址吗?

不用。连币安的是你的服务器,币安那把 Key 的 IP 访问限制填服务器访问币安时用的公网 IPv4 地址。TradingView 的 4 个发送地址 52.89.214.238、34.212.75.30、54.218.53.128、52.32.178.7 加在你服务器自己的入口白名单上。

指标警报能用 {{strategy.order.action}} 吗?

不能。TradingView 帮助中心写明,带 strategy 前缀的占位符只能用在策略警报里。指标警报要按方向分别建两条,在消息里直接写 buy 或 sell。

风险与链接说明:本页依据 TradingView 帮助中心的 Webhook 与警报占位符说明,以及币安开发者文档的现货交易接口页和请求鉴权页整理,规则会随官方页面更新而调整。代码是最小示例,上实盘前请自行审查。不构成投资建议;用程序在实盘自动下单可能造成资金损失,运行前请确认你所在地区能否使用。 本站部分评测页含币安推介链接,经这些链接注册,本站可能获得佣金;这篇文章没有放推介链接。 完整披露 →