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

发布时间:2026/10/18 19:46:53
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 的私有 APIv1/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 TokenJWT的 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(): Promisestring { const res await this.axiosInstance.get(user/, { headers: { Cookie: tv_session${this.sessionCookie} }, }); return res.headers[x-csrf-token] || ; } // 创建警报的核心方法 async createAlert(payload: AlertPayload): PromiseAlertResponse { 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 而用 Webhook3Commas 提供两种接入方式一是调用其/api/v2/bots/创建交易机器人二是接收外部 Webhook 触发预设策略。本项目选后者原因很实在权限隔离TV 警报只需发 HTTP 请求无需申请 3Commas API Key含资金操作权限延迟可控Webhook 是 TV 主动推送比轮询 3Commas API 状态快 2~3 秒策略解耦你在 3Commas 后台只配置一次「当收到 /webhook/tv-alert 且 typersi_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: Recordstring, 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): Promisevoid { 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-nodenpm 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_sessionabc123def456... # 从浏览器 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?tokenxyz789关键字段说明sessionCookie不是document.cookie全串而是仅tv_sessionxxx这一段。打开 TV 网站 → F12 → Application → Cookies → 找tv_session值apiSecret必须去 TradingView Developer Portal 申请选「Alerts API」权限审核通过后邮件发送userId复制你的 TV 登录 JWTF12 → 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 有两道关Cloudflare 浏览器验证如果你用curl或 Postman 直接发请求大概率返回503 Service Temporarily Unavailable。解决方案必须用已登录的 Chrome 浏览器打开 TV 图表页F12 → Network → 刷一下页面 → 找任意一个/api/v1/请求如/api/v1/symbols/→ 右键 → Copy → Copy as cURLbash→ 粘贴到终端执行确认返回 200。这证明你的 cookie UA IP 组合被 CF 认可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是两个不同 IDBTCUSDT: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版本与类型定义不匹配如axios1.6.0需types/axios1.6.0。解决运行npm list axios types/axios确认两者主版本号一致若不一致执行npm install axios1.6.0 types/axios1.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命令逻辑是先 GET/api/v1/alerts/拿到当前账户所有警报解析每个警报的comment提取fingerprint对比config.yaml中的警报列表找出「新增的」fingerprint 不在现有列表和「已变更的」fingerprint 存在但 condition 不同对「新增的」调用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 Mapstring, 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的conditionsync命令自动识别变更并热更新。从那以后我每次提交策略变更都强制走一遍npm run sync -- --config prod.yaml再用git diff确认fingerprint变了才点「上线」。这套流程跑过 3 个月0 次误删、0 次漏更。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询