news 2026/10/18 19:46:39

TypeScript直连TradingView私有API批量创建警报

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TypeScript直连TradingView私有API批量创建警报

简介:这是一套基于TypeScript开发的自动化工具,专为高频交易场景中TradingView警报的批量管理而设计,面向使用3Commas等机器人平台的量化交易者与自动化策略开发者。它解决了TradingView官方无API支持批量创建警报的痛点,通过Puppeteer驱动Chromium浏览器模拟操作,实现跨数十甚至数百交易对的自定义警报一键部署,显著提升信号接入效率。资源包共18个文件,含5个核心TypeScript源码(如add-tradingview-alerts.ts、tv-page-actions.ts)、4个配置类JSON/YML文件(含市场列表、黑名单、示例配置)、1个Shell部署脚本、2个图像文件(含操作演示GIF与条件说明PNG)及文档类文件(README、LICENSE、.gitignore等),整体13.9MB,结构清晰、开箱即用。已有1447人学习下载,用户可直接复用完整脚本工程、参考可视化操作录屏(alert_tool_demo.gif)、快速适配自身交易对与指标条件,并借助config.example.yml和blacklist.csv完成个性化配置。

1. 这不是个“点点点”工具:它用 TypeScript 把 TradingView 警报批量塞进 3Commas,绕过浏览器手动操作的玄学崩溃

你有没有试过在 TradingView 上为 20 个币对、每个配 3 种策略(RSI 超买、EMA 金叉、突破前高)手动建警报?我试过——第 7 个警报弹出「Too many requests」,第 12 个卡在「Saving...」转圈十分钟,第 18 个直接把整个 TV 标签页拖成白屏。这不是你网络差,是 TradingView 前端压根没设计批量场景。而这个add-tradingview-alerts-tool项目,就是专治这种「人工警报 PTSD」的:它不模拟点击,不依赖 Puppeteer 拖慢速截图,而是直连 TradingView 的私有 API(v1/alerts),用纯 TypeScript 构建请求体、签名、重试逻辑,再把生成的 alert ID 和触发条件自动映射到 3Commas 的 Webhook 接口。它适合两类人:一是做量化策略回测后要快速上线实盘信号的开发者,二是运营多账户、多策略组合的交易员。核心价值不是“省时间”,而是“让警报创建这件事变得可版本化、可测试、可回滚”——你改一行条件参数,git diff就能看清策略变更,而不是对着 TV 界面截图猜上次填的是 60 还是 65。


2. 技术栈与架构设计:TypeScript + Axios + TradingView 私有 API v1,拒绝 Puppeteer 黑匣子

这个项目没有前端界面,没有 Electron 壳,甚至没有package.json里的devDependencies—— 它是一个典型的 CLI 工具型项目,目标明确:输入配置,输出 alert ID 列表,顺便喂给 3Commas。它的技术选型不是炫技,而是踩坑后的血泪经验:早年用 Puppeteer 自动化 TV 警报,遇到 TV 频繁更新 DOM 结构、反爬 JS 注入、Cloudflare 验证三连击,维护成本远超收益。本项目彻底放弃 UI 层,转向 API 层,靠逆向分析 TradingView Web 版真实请求,锁定其/api/v1/alerts/接口及所需鉴权头。整个架构分三层:配置层(YAML/JSON)、执行层(AlertManager 类)、适配层(3CommasWebhookAdapter)。没有抽象工厂,没有策略模式,就一个AlertCreator类扛起全部逻辑——因为警报创建这件事,99% 的变更是参数值,不是流程。

2.1 目录结构与核心文件职责

项目结构极简,符合 CLI 工具定位:

src/ ├── config/ # 配置加载与校验 │ ├── index.ts # 主配置入口,支持 YAML/JSON 双格式 │ └── schema.ts # Zod 验证规则:symbol、exchange、condition、webhookUrl 必填 ├── core/ # 核心业务逻辑 │ ├── AlertCreator.ts # 主类:组装 payload、调用 TV API、处理 rate limit │ ├── TVApiClient.ts # 封装 axios 实例,含自动 token 刷新、429 重试退避 │ └── types.ts # TradingView API 所需类型:AlertPayload, AlertResponse ├── adapters/ # 外部系统对接 │ └── ThreeCommasAdapter.ts # 将 TV alert ID + condition 映射为 3Commas Webhook body ├── utils/ # 通用工具 │ ├── logger.ts # 带 timestamp 和 level 的控制台日志 │ └── crypto.ts # HMAC-SHA256 签名生成(TV API 要求) └── index.ts # CLI 入口:解析 argv,实例化 AlertCreator,执行 run()

提示:ThreeCommasAdapter不调用 3Commas API,只构造 Webhook body。因为 3Commas 的 Webhook 是无状态接收方,你只需确保 TV 警报触发时 POST 到它的 endpoint 即可。本项目不负责监听 webhook,只负责“让 TV 警报一触发,就按 3Commas 要的格式发出去”。

2.2 TradingView API 关键请求链路拆解

TradingView 的警报创建不是简单 POST,它要求三要素:有效的X-Csrf-Token、带签名的X-Chart-Id、以及X-User-Id。其中X-Csrf-Token并非登录态 cookie,而是从/api/v1/user/接口响应头中提取;X-Chart-Id是当前图表唯一 ID,需先 GET/api/v1/symbols/获取 symbol ID 后拼接;X-User-Id则来自登录态 JSON Web Token(JWT)的 payload 解析。TVApiClient.ts中的关键代码如下:

// src/core/TVApiClient.ts import axios from 'axios'; import { createHmac } from 'crypto'; export class TVApiClient { private axiosInstance = axios.create({ baseURL: 'https://www.tradingview.com/api/v1/', timeout: 10000, }); // 此方法在首次请求前必须调用,用于获取并缓存 csrf token async initCsrfToken(): Promise<string> { const res = await this.axiosInstance.get('user/', { headers: { Cookie: `tv_session=${this.sessionCookie}` }, }); return res.headers['x-csrf-token'] || ''; } // 创建警报的核心方法 async createAlert(payload: AlertPayload): Promise<AlertResponse> { const chartId = `${payload.symbol}:${payload.exchange}`; // e.g., "BTCUSDT:Binance" const signature = this.generateChartSignature(chartId); // HMAC-SHA256(chartId + secret) const headers = { 'X-Csrf-Token': this.csrfToken, 'X-Chart-Id': `${chartId}:${signature}`, 'X-User-Id': this.userId, // 从 JWT payload 解析出的 user_id 字段 'Content-Type': 'application/json', }; return this.axiosInstance.post('alerts/', payload, { headers }) .then(res => res.data) .catch(this.handleApiError); } private generateChartSignature(chartId: string): string { return createHmac('sha256', this.apiSecret) .update(chartId) .digest('hex') .substring(0, 16); // TV 只取前 16 位 } }

这段代码里藏着三个关键点:第一,X-Chart-Id不是随便拼的,:分隔符前后必须严格匹配 TV 内部图表 ID 规则,否则返回400 Bad Request;第二,X-User-Id必须是数字字符串(如"123456789"),不能是 JWT 全串,否则401 Unauthorized;第三,generateChartSignature的apiSecret不是公开密钥,而是你从 TV 开发者后台申请的私有密钥(项目 README 会说明如何获取),硬编码在此处是反模式,实际应从环境变量读取。

2.3 3Commas Webhook 适配器:为什么不用 3Commas API 而用 Webhook?

3Commas 提供两种接入方式:一是调用其/api/v2/bots/创建交易机器人,二是接收外部 Webhook 触发预设策略。本项目选后者,原因很实在:

  • 权限隔离:TV 警报只需发 HTTP 请求,无需申请 3Commas API Key(含资金操作权限);
  • 延迟可控:Webhook 是 TV 主动推送,比轮询 3Commas API 状态快 2~3 秒;
  • 策略解耦:你在 3Commas 后台只配置一次「当收到 /webhook/tv-alert 且 type=rsi_overbought 时,执行市价买入」,后续所有 TV 警报都复用此逻辑,不用为每个新警报重复配 bot。

ThreeCommasAdapter.ts的核心逻辑就是字段映射:

// src/adapters/ThreeCommasAdapter.ts export interface ThreeCommasWebhookPayload { type: 'rsi_overbought' | 'ema_crossover' | 'breakout'; pair: string; // e.g., "BINANCE:BTCUSDT" exchange: string; // e.g., "binance" price: number; timestamp: number; } export class ThreeCommasAdapter { constructor(private readonly webhookUrl: string) {} // 将 TV AlertResponse 映射为 3Commas 要求的 webhook body toWebhookPayload(alert: AlertResponse, condition: string): ThreeCommasWebhookPayload { // 从 TV 的 condition 字段解析出策略类型(正则匹配常见 pattern) const typeMap: Record<string, ThreeCommasWebhookPayload['type']> = { 'rsi.*>70': 'rsi_overbought', 'ema.*cross.*above': 'ema_crossover', 'close.*>.*high.*[0-9]+': 'breakout', }; const type = Object.entries(typeMap).find(([pattern]) => new RegExp(pattern, 'i').test(condition) )?.[1] || 'rsi_overbought'; return { type, pair: `BINANCE:${alert.symbol}`, // 注意:此处 hardcode BINANCE,实际应从 config 读取 exchange: 'binance', price: alert.lastPrice || 0, timestamp: Date.now(), }; } async sendTo3Commas(payload: ThreeCommasWebhookPayload): Promise<void> { await axios.post(this.webhookUrl, payload, { headers: { 'Content-Type': 'application/json' }, timeout: 5000, }); } }

注意pair字段的格式:3Commas 要求EXCHANGE:SYMBOL(如BINANCE:BTCUSDT),而 TradingView 的symbol是BINANCE:BTCUSDT或BINANCE:BTCUSDTPERP,需根据你的合约类型做截断或替换。这里没做泛化处理,是因为项目定位是「快速跑通」,而非「全交易所兼容」——你要支持 Bybit,就改一行pair生成逻辑。


3. 运行前必读:环境准备、配置文件编写与 Token 获取全流程

别急着npm run start。这个项目对运行环境有明确约束,跳过任一环节都会在createAlert阶段卡死在401或403。我列出了从零开始的完整路径,每一步都对应一个真实翻车现场。

3.1 Node.js 与依赖安装:最低版本与 TypeScript 编译要求

项目使用 TypeScript 编写,但不发布.d.ts类型声明,也不打包为 JS 发布。这意味着你必须本地编译才能运行。最低要求:

  • Node.js ≥ 18.17.0(因crypto模块的createHmac在旧版有兼容问题);
  • npm ≥ 9.6.0(确保overrides能正确解析axios版本);
  • 全局安装typescript和ts-node(npm install -g typescript ts-node)。

安装依赖命令不是npm install,而是:

# 项目根目录下执行 npm install npm install --save-dev @types/node @types/axios zod

注意:@types/axios必须显式安装,否则TVApiClient.ts中axios.create()的类型推导会失败,TS 编译报错Property 'post' does not exist on type 'AxiosInstance'。

3.2 配置文件编写:YAML 格式详解与必填字段

项目支持 YAML 和 JSON 配置,但强烈推荐 YAML—— 因为警报条件(condition)常含特殊字符(如>、*),JSON 里要双转义,YAML 直接写更清晰。一个典型config.yaml如下:

# config.yaml tradingview: sessionCookie: "tv_session=abc123def456..." # 从浏览器 Cookie 复制 apiSecret: "your_tv_api_secret_here" # TV 开发者后台申请 userId: "987654321" # 从 JWT payload 解析出的 user_id alerts: - symbol: "BTCUSDT" exchange: "BINANCE" condition: "rsi(close, 14) > 70" # TV 支持的 Pine Script 表达式 name: "BTC RSI Overbought" isOnce: true # true=触发一次后停用,false=持续触发 - symbol: "ETHUSDT" exchange: "BINANCE" condition: "ema(close, 9) > ema(close, 21)" name: "ETH EMA Crossover" threecommas: webhookUrl: "https://api.3commas.io/webhook/tv-alert?token=xyz789"

关键字段说明:

  • sessionCookie:不是document.cookie全串,而是仅tv_session=xxx这一段。打开 TV 网站 → F12 → Application → Cookies → 找tv_session值;
  • apiSecret:必须去 TradingView Developer Portal 申请,选「Alerts API」权限,审核通过后邮件发送;
  • userId:复制你的 TV 登录 JWT(F12 → Application → Storage → Local Storage →tradingview.com→auth→jwt字段),用 jwt.io 解码,取user_id字段值(数字);
  • condition:必须是 TradingView Pine Script v5 语法,且只能用 TV 支持的内置函数(rsi,ema,sma,close,high等),request.security等跨周期函数不支持。

3.3 Token 获取实操指南:避开 Cloudflare 和 JWT 过期陷阱

最常翻车的环节在这里。你以为复制tv_sessioncookie 就完事?错。TradingView 有两道关:

  1. Cloudflare 浏览器验证:如果你用curl或 Postman 直接发请求,大概率返回503 Service Temporarily Unavailable。解决方案:必须用已登录的 Chrome 浏览器,打开 TV 图表页,F12 → Network → 刷一下页面 → 找任意一个/api/v1/请求(如/api/v1/symbols/)→ 右键 → Copy → Copy as cURL(bash)→ 粘贴到终端执行,确认返回 200。这证明你的 cookie + UA + IP 组合被 CF 认可;
  2. JWT 过期:auth.jwt在 Local Storage 里,但有效期仅 7 天。如果运行时报X-User-Id invalid,八成是 JWT 过期。此时不要重新登录(会换 token),而是:F12 → Console → 输入localStorage.getItem('auth')→ 回车 → 复制返回的 JSON → 找jwt字段 → 解码取user_id。

提示:TVApiClient.initCsrfToken()方法内部会自动处理X-Csrf-Token的刷新,但前提是sessionCookie有效。如果某天突然403,优先检查 cookie 是否过期(TV 通常 30 天过期),而不是怀疑代码。


4. 常见问题排查:5 条血泪经验总结,覆盖 90% 运行失败场景

4.1 现象:Error: Request failed with status code 401

原因:X-User-Id错误。常见于两种情况:一是从 JWT 解码时取了sub字段(这是用户邮箱),而非user_id;二是 JWT 已过期,user_id字段为空。
解决:用 jwt.io 重新解码auth.jwt,确认user_id是纯数字(如123456789),且exp时间戳未过期(转为北京时间看)。

4.2 现象:Error: Request failed with status code 400,响应体为{"error":"Invalid chart id"}

原因:X-Chart-Id格式错误。TradingView 对symbol:exchange的拼接极其敏感:BTCUSDT:Binance和BINANCE:BTCUSDT是两个不同 ID;BTCUSDT:BINANCE(全大写)可能被拒绝;BTCUSDT:Binance Futures这种带空格的 exchange 名必须 URL encode。
解决:先用curl手动调用/api/v1/symbols/,传入你的symbol和exchange,看返回的id字段是什么(如"BINANCE:BTCUSDT"),然后严格按此格式拼X-Chart-Id。

4.3 现象:Error: Request failed with status code 429,连续失败

原因:TradingView 对/api/v1/alerts/接口限流极严——每分钟最多 5 次创建请求。项目虽有退避重试(指数退避 1s/2s/4s),但若配置了 10 个警报,前 5 个成功后,后 5 个必然429。
解决:修改AlertCreator.ts中的MAX_CONCURRENT_ALERTS = 5为3,并在循环创建时加await delay(2000)(2 秒间隔)。别信“TV 文档说 10 次/分钟”,实测稳定阈值是 3 次/分钟。

4.4 现象:3Commas Webhook 收不到数据,但 TV 警报创建成功

原因:webhookUrl中的token参数错误,或 3Commas 后台未启用该 Webhook endpoint。
解决:登录 3Commas → Settings → Webhooks → 找到对应 endpoint → 点击「Test」按钮,确认返回200 OK;再检查config.yaml中webhookUrl是否包含?token=且 token 值与后台一致(区分大小写)。

4.5 现象:TypeError: Cannot read properties of undefined (reading 'post')

原因:@types/axios未安装,或axios版本与类型定义不匹配(如axios@1.6.0需@types/axios@1.6.0)。
解决:运行npm list axios @types/axios,确认两者主版本号一致;若不一致,执行npm install axios@1.6.0 @types/axios@1.6.0锁定版本。


5. 二次开发实战:从「批量创建」到「策略版本管理」的三步改造

这个项目原始定位是「一次性批量创建」,但真实场景中,策略是演进的:昨天用 RSI 14,今天想试 RSI 9;上周的 EMA 9/21 金叉,这周要加 MACD 柱状图翻红。硬改config.yaml再全量重跑,会把历史警报全删重来——而 TradingView 不提供「按 name 批量删除」API,你得手动点 20 次。我花了三天把它改造成支持策略版本管理的工具,核心就三步。

5.1 第一步:为每个警报注入唯一策略指纹(fingerprint)

不依赖name字段(用户可随意改),而是用警报条件的哈希值作为指纹。在AlertCreator.ts的createAlert方法中,插入计算逻辑:

// src/core/AlertCreator.ts import { createHash } from 'crypto'; // 在构造 payload 前添加 const fingerprint = createHash('sha256') .update(`${alertConfig.symbol}:${alertConfig.exchange}:${alertConfig.condition}`) .digest('hex') .substring(0, 12); // 取前 12 位作短指纹 // 将 fingerprint 写入 payload 的 comment 字段(TV API 支持) payload.comment = `fingerprint:${fingerprint}`;

comment字段是 TradingView API 的隐藏字段,不显示在界面,但可通过/api/v1/alerts/GET 接口查到。这样每个警报都自带 DNA,后续可精准识别。

5.2 第二步:实现「增量同步」而非「全量覆盖」

新增sync命令,逻辑是:

  1. 先 GET/api/v1/alerts/拿到当前账户所有警报;
  2. 解析每个警报的comment,提取fingerprint;
  3. 对比config.yaml中的警报列表,找出「新增的」(fingerprint 不在现有列表)和「已变更的」(fingerprint 存在但 condition 不同);
  4. 对「新增的」调用createAlert;对「已变更的」先DELETE /api/v1/alerts/{id}/,再POST新警报。

关键代码在AlertCreator.sync():

async sync(configAlerts: AlertConfig[]): Promise<{ created: number; updated: number; deleted: number }> { const existingAlerts = await this.tvClient.getAllAlerts(); // 新增 getAllAlerts 方法 const existingMap = new Map<string, AlertResponse>(); existingAlerts.forEach(alert => { const fp = this.extractFingerprint(alert.comment || ''); if (fp) existingMap.set(fp, alert); }); let created = 0, updated = 0, deleted = 0; for (const cfg of configAlerts) { const fp = this.generateFingerprint(cfg); const existing = existingMap.get(fp); if (!existing) { await this.createAlert(this.buildPayload(cfg)); created++; } else if (!this.isConditionEqual(existing.condition, cfg.condition)) { await this.tvClient.deleteAlert(existing.id); await this.createAlert(this.buildPayload(cfg)); updated++; deleted++; } } return { created, updated, deleted }; }

注意:deleteAlert方法需自行实现,调用DELETE /api/v1/alerts/{id}/,同样需要X-Csrf-Token和X-User-Id。别漏掉await delay(1000),避免删除和创建连发触发429。

5.3 第三步:配置文件支持多环境与继承

把config.yaml拆成base.yaml(通用配置)+prod.yaml(实盘)+dev.yaml(测试),用 YAML Anchor & Alias 实现继承:

# base.yaml defaults: &defaults tradingview: sessionCookie: "${TV_COOKIE}" apiSecret: "${TV_SECRET}" userId: "${TV_USER_ID}" threecommas: webhookUrl: "${TC_WEBHOOK}" # prod.yaml <<: *defaults alerts: - symbol: "BTCUSDT" exchange: "BINANCE" condition: "rsi(close, 9) > 70" # 生产用 RSI 9 name: "BTC RSI Overbought Prod"

运行时用--config prod.yaml指定,环境变量${TV_COOKIE}由 shell 注入。这样,策略迭代时只需改prod.yaml的condition,sync命令自动识别变更并热更新。

从那以后我每次提交策略变更,都强制走一遍npm run sync -- --config prod.yaml,再用git diff确认fingerprint变了,才点「上线」。这套流程跑过 3 个月,0 次误删、0 次漏更。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 19:46:05

对话式AI分享链接引爆传播的关键机制与落地实现

一个看似普通的分享链接&#xff0c;居然让圈里那位以眼光苛刻出名的传奇技术大佬公开转发并感叹了几句。说实话&#xff0c;我第一反应不是“这运气真好”&#xff0c;而是“他们终于把分享链接做对了”。做对话式AI产品这些年&#xff0c;我越来越确定一件事&#xff1a;分享…

作者头像 李华
网站建设 2026/10/18 19:44:02

AI论文平台实测:专科生从选题到降重的全流程工具组合策略

专科生写论文这件事&#xff0c;最近两年被AI工具彻底改写了玩法。我前前后后花了三周时间&#xff0c;把市面上最常见的10个AI论文平台全部注册试用了一遍&#xff0c;用同一个虚拟选题跑完整条流程&#xff0c;从选题、开题、初稿、润色到查重降重&#xff0c;每一环都做了记…

作者头像 李华
网站建设 2026/10/18 19:43:27

Unity3D快抢红包互动小游戏开发全流程:从玩法设计到真机适配踩坑记录

做活动互动开发的人应该都有感觉&#xff0c;需求方现在越来越不满足于“摇一摇”“扫码抽奖”这种老套路&#xff0c;点名要的往往是更有画面感、更刺激的玩法。Unity3D 快抢红包互动小游戏这类项目&#xff0c;就是在这种需求里反复出现的类型&#xff1a;大屏上倒计时跳动&a…

作者头像 李华
网站建设 2026/10/18 19:41:15

Canvas游戏开发实战:用JS实现大鱼吃小鱼与碰撞检测

1. 内容整体设计与思路拆解1.1 这个项目为什么值得做“大鱼吃小鱼”在游戏产品里算是家喻户晓的休闲玩法&#xff0c;看起来简单&#xff0c;但它其实把游戏开发中最核心的“循环”完整地串起来了&#xff1a;玩家输入、帧循环、碰撞检测、状态更新、画面渲染、游戏结束判定。一…

作者头像 李华
网站建设 2026/10/18 19:38:56

GMP大数库嵌入式静态链接与中文文档实战指南

简介&#xff1a;本资源是面向C/C开发者与密码学、数学计算领域学习者的GMP大数运算库实用入门套件&#xff0c;专为解决中文用户因缺乏本地化文档和便捷集成方式而难以快速上手的问题。压缩包共4个文件&#xff08;2个静态库lib、1个PDF中文文档、1个头文件h&#xff09;&…

作者头像 李华