
简介这是一套基于TypeScript开发的自动化工具专为高频交易场景设计面向使用TradingView指标联动3Commas等机器人进行跨币对自动交易的开发者与量化实践者。它解决了TradingView官方无批量警报API导致需手动维护数十甚至数百个交易对警报的痛点通过Puppeteer驱动Chromium浏览器实现警报的全自动添加与配置。资源包共18个文件含5个核心TypeScript源码如add-tradingview-alerts.ts、tv-page-actions.ts、4个配置类JSON/YML文件含市场列表、黑名单、示例配置、1个Shell部署脚本、1个CSV黑名单及2个演示素材GIF操作录屏、PNG条件说明图辅以README、LICENSE等工程规范文件整体13.9MB结构清晰、开箱即用。目前已有1447人学习下载使用者可直接复用完整脚本、参考可视化操作流程、快速适配自身交易策略并基于源码二次扩展警报逻辑与多平台集成能力。1. 这不是 TradingView 官方功能一个用 TypeScript 写的「警报注入器」专治 3Commas 用户手动点到手抽筋的痛你有没有在 TradingView 上调好一套策略画完布林带、MACD 和 RSI 三重过滤信心满满地想把它塞进 3Commas 自动跟单结果卡在最后一步TradingView 的「添加警报」按钮得一个个点开图表、右键、选条件、填 webhook URL、勾选「仅发送一次」……一套操作下来10 个品种就得点 50 次稍一走神就漏掉一个。这不是效率问题是生理疲劳——我亲眼见过某开发者连续三天没睡够就因为要给 47 个 BTC/USDT、ETH/USDT、SOL/USDT 等不同时间周期的图表批量配警报最后导出的 CSV 里有 3 个 URL 少了个斜杠导致 3Commas 根本收不到触发信号策略静默了整整 12 小时。add-tradingview-alerts-tool就是为这个场景生的它不依赖 TradingView API官方压根没开放警报创建接口而是用 Puppeteer 驱动真实 Chromium 浏览器模拟人工点击全流程核心逻辑全用 TypeScript 编写类型安全强、IDE 补全准、错误提示直指变量定义行所有配置收口在config.json里支持按 symbol、timeframe、alertName 做组合匹配还能自动跳过已存在同名警报——不是“能跑就行”的脚本是能放进 CI/CD 流水线、被团队成员复用、改一行参数就能切换实盘/回测环境的工程化工具。适合正在用 3Commas 做网格、马丁、止盈止损联动的中高频交易者也适合需要把 TradingView 策略快速部署到多个子账户的量化运营岗。2. 从零启动TypeScript 环境搭建、Puppeteer 配置与 config.json 结构解析2.1 初始化项目并安装核心依赖我们不走 npm init -y 一键糊弄流。真实项目里tsconfig.json的strict必须开否则后期类型断言满天飞debug 成本翻倍。先建空目录初始化 TypeScriptmkdir tv-alert-batch cd tv-alert-batch npm init -y npm install --save-dev typescript types/node types/puppeteer npm install puppeteer npx tsc --init --strict --target es2020 --module commonjs --outDir dist --rootDir src提示types/puppeteer是关键。没有它TypeScript 不认识page.click()返回的是Promisevoid还是void后续.then()链式调用会报错且 IDE 无法智能提示page.waitForSelector()的 timeout 参数单位。生成的tsconfig.json需手动补两行{ compilerOptions: { strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, outDir: dist, rootDir: src, types: [node, puppeteer] } }types字段显式声明避免某些 TS 版本下types/puppeteer被忽略。2.2 Puppeteer 启动配置绕过检测、稳定加载、精准等待TradingView 页面大量使用动态渲染和防爬逻辑。直接puppeteer.launch()会触发 Cloudflare 验证或页面白屏。必须加三项硬核配置// src/browser.ts import puppeteer, { Browser, Page } from puppeteer; export async function launchBrowser(): PromiseBrowser { return puppeteer.launch({ headless: new, // 必须用 new旧版 headlesstrue 已废弃且易被识别 args: [ --no-sandbox, --disable-setuid-sandbox, --disable-blink-featuresAutomationControlled, --disable-web-security, --disable-featuresIsolateOrigins,site-per-process ], defaultViewport: { width: 1920, height: 1080 }, executablePath: /usr/bin/chromium // Linux 下指定系统 Chromium避免 Puppeteer 自带二进制体积过大 }); } export async function waitForTradingViewReady(page: Page): Promisevoid { // TradingView 加载分三阶段DOM 就绪 → 图表容器渲染 → 警报按钮可交互 await page.waitForSelector(.chart-container, { timeout: 30000 }); await page.waitForFunction(() window.TradingView ! undefined, { timeout: 20000 }); await page.waitForSelector(button[data-namealerts-button], { timeout: 15000 }); }--disable-blink-featuresAutomationControlled是核心它禁用 Blink 引擎的自动化特征标记让navigator.webdriver返回undefined而非truewaitForFunction等待window.TradingView全局对象存在比单纯等 DOM 更可靠——因为 TradingView 的 JS 是异步 chunk 加载的.chart-container出现时TradingView对象可能还没挂载所有timeout显式设值避免默认 30s 太长调试时等得心焦或太短网络抖动就失败。2.3 config.json 全字段详解为什么 alertName 必须带唯一前缀config.json是整个工具的控制中枢结构必须严格。以下是一个生产环境可用的最小完备配置{ tradingView: { email: your_tv_emailexample.com, password: your_strong_password, chartUrlTemplate: https://www.tradingview.com/chart/?symbol{{symbol}}interval{{interval}} }, alerts: [ { symbol: BTCUSDT, timeframe: 5, alertName: PROD_BTCUSDT_5m_MACD_CROSS, condition: MACD() signal() and MACD()[1] signal()[1], webhookUrl: https://api.3commas.io/webhooks/xxx?tokenyyy, isOnce: true, isSoundEnabled: false } ] }关键字段说明字段类型必填说明tradingView.emailstring✅TradingView 账号邮箱必须已登录并保存密码工具不处理二次验证chartUrlTemplatestring✅模板字符串{{symbol}}和{{interval}}会被自动替换interval值见 TradingView 文档11分钟55分钟D日线W周线alerts[].symbolstring✅交易所标准代码如BTCUSDTBinance、XBTUSDBitMEX大小写敏感alerts[].alertNamestring✅必须全局唯一重复名称会导致 TradingView 跳过创建它把 name 当主键建议用ENV_SYMBOL_TF_STRATEGY格式如STAGING_ETHUSDT_15m_RSI_OVERSOLDalerts[].conditionstring✅TradingView 警报条件表达式必须是合法 Pine Script v5 语法[1]表示前一根K线signal()是内置函数不能写成macd_signal()alerts[].webhookUrlstring✅3Commas Webhook 地址末尾必须带?tokenxxx否则 3Commas 拒绝接收注意condition字段是黑匣子。TradingView 不校验语法只在创建时执行一次。如果写错比如MACD()[1] signal()少了等号警报会创建成功但永不触发——你得去 TradingView 网页端手动编辑它才能看到报错。所以务必在config.json里先用 TradingView 编辑器验证好再粘贴进来。3. 核心逻辑拆解如何用 Puppeteer 精准定位并填写警报表单3.1 图表加载与时间周期切换用 CSS 选择器定位而非 XPathTradingView 的时间周期按钮 HTML 结构极不稳定XPath 容易因版本更新失效。我们改用>// src/alert-creator.ts export async function switchTimeframe(page: Page, timeframe: string): Promisevoid { // 先点开时间周期下拉框 await page.click(button[data-nameheader-toolbar-interval]); // 等待下拉菜单出现并查找匹配>export async function createAlert( page: Page, alertConfig: AlertConfig ): Promiseboolean { // 1. 点击警报按钮 await page.click(button[data-namealerts-button]); // 2. 等待弹窗出现TradingView 用 roledialog await page.waitForSelector(div[roledialog], { timeout: 10000 }); // 3. 填写警报名称aria-label 包含 Name await page.fill(input[aria-label*Name], alertConfig.alertName); // 4. 填写条件表达式aria-label 包含 Condition await page.fill(textarea[aria-label*Condition], alertConfig.condition); // 5. 勾选 Only once 复选框label 文本匹配 if (alertConfig.isOnce) { const onceLabel await page.$(label:has-text(Only once)); if (onceLabel) { await onceLabel.click(); } } // 6. 填写 Webhook URLaria-label 包含 Webhook await page.fill(input[aria-label*Webhook], alertConfig.webhookUrl); // 7. 点击 Create Alert 按钮用>// src/batch-runner.ts export async function runBatch(config: Config) { const browser await launchBrowser(); const page await browser.newPage(); try { // 1. 登录 TradingView await loginToTradingView(page, config.tradingView); // 2. 按 symbol 分组同一 symbol 的图表可复用页面减少 reload const groupedAlerts groupBy(config.alerts, symbol); for (const [symbol, alerts] of Object.entries(groupedAlerts)) { console.log(\n Processing symbol: ${symbol}); // 3. 为每个 symbol 加载一次图表 const chartUrl config.tradingView.chartUrlTemplate .replace({{symbol}}, symbol) .replace({{interval}}, alerts[0].timeframe); await page.goto(chartUrl, { waitUntil: networkidle0 }); await waitForTradingViewReady(page); // 4. 组内串行创建警报避免同一图表频繁切换 timeframe for (const alert of alerts) { console.log( ➕ Creating alert: ${alert.alertName}); try { await switchTimeframe(page, alert.timeframe); await createAlert(page, alert); console.log( ✅ Success); } catch (err) { console.error( ❌ Failed: ${(err as Error).message}); // 熔断单个警报失败不影响后续但记录日志供人工核查 } } } } finally { await browser.close(); } }groupBy是 Lodash 的groupBy(alerts, symbol)按 symbol 聚合减少页面 reload 次数TradingView reload 一次要 8~12 秒waitUntil: networkidle0比domcontentloaded更稳确保所有 JS/CSS 加载完毕不实现自动重试TradingView 对重复提交敏感重试可能触发风控。失败就记日志人来判断是网络问题还是配置问题。4. 避坑指南5 个血泪经验总结的「警报创建翻车现场」4.1 现象浏览器打开后停留在登录页控制台报net::ERR_CONNECTION_TIMED_OUT原因Puppeteer 默认使用自带 Chromium其证书库老旧访问 TradingView 时 SSL 握手失败尤其在企业网络或某些 Linux 发行版。解决强制使用系统 Chromium并添加--ignore-certificate-errors参数仅限内部可信环境# Ubuntu/Debian sudo apt install chromium-browser # 修改 launchBrowser() 中的 executablePath 为 /usr/bin/chromium-browser # 并在 args 中加入 --ignore-certificate-errors4.2 现象await page.click(button[data-namealerts-button])报错TimeoutError: waiting for selector button[data-name\alerts-button\] failed原因TradingView 页面未完全加载或当前视图是「市场概览」而非「图表页」。>await page.waitForSelector(.chart-container, { timeout: 30000 }); // 确保是图表页 await page.waitForSelector(button[data-namealerts-button], { timeout: 15000 }); // 再找按钮4.3 现象警报创建成功但在 TradingView 网页端查看时condition 显示为Invalid condition原因config.json中的condition字符串含不可见 Unicode 字符如 Word 复制来的全角括号、中文引号或 Pine Script 语法错误如MACD()[1] signal()应为。解决用 VS Code 打开config.json开启「显示不可见字符」CtrlShiftP → Toggle Render Whitespace在 TradingView 网页端新建一个临时警报把condition粘贴进去用编辑器右上角的「检查」按钮验证永远不要在微信/钉钉里复制 condition——它们会自动转中文标点。4.4 现象alertName相同的警报被跳过但实际想更新它的 condition原因TradingView 的设计逻辑是「name 即唯一标识」不存在「更新」操作只有「创建」和「删除后重建」。工具检测到同名即跳过防止误覆盖。解决方案 A推荐修改alertName如从BTCUSDT_5m_MACD改为BTCUSDT_5m_MACD_v2方案 B手动在 TradingView 网页端删除旧警报再运行工具方案 C高级扩展工具增加deleteAlertByName()函数用 Puppeteer 模拟点击删除图标需解析警报列表 DOM。4.5 现象运行时 CPU 占用 100%进程卡死无响应原因puppeteer.launch()未传headless: new旧版 headless 模式在某些内核版本下有渲染线程死锁 Bug。解决确认 Puppeteer 版本 ≥ 21.0.0launch()参数中headless必须为字符串new不能是布尔值true若仍卡死在args中加入--single-process仅开发调试用降低稳定性。5. 进阶技巧CI/CD 自动化部署、多账号轮询与警报健康度验证5.1 接入 GitHub Actions每日凌晨自动同步警报配置把config.json放进 Git 仓库用 GitHub Actions 实现无人值守更新。关键在于TradingView 密码不能明文写进 workflow必须用 Secrets# .github/workflows/deploy-alerts.yml name: Deploy TradingView Alerts on: schedule: - cron: 0 3 * * * # 每天凌晨 3 点 workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Run alert batch env: TV_EMAIL: ${{ secrets.TV_EMAIL }} TV_PASSWORD: ${{ secrets.TV_PASSWORD }} run: | # 注入 secrets 到 config.json jq --arg email $TV_EMAIL --arg pass $TV_PASSWORD \ .tradingView.email $email | .tradingView.password $pass \ config.json config.tmp mv config.tmp config.json npm run build node dist/index.js - name: Notify on failure if: ${{ failure() }} uses: dawidd6/action-send-mailv3 with: server_address: smtp.gmail.com server_port: 465 username: ${{ secrets.EMAIL_USER }} password: ${{ secrets.EMAIL_PASS }} subject: TV Alerts Deployment Failed body: Check GitHub Actions logs for ${{ github.repository }} to: your-emailexample.comjq命令动态注入密码避免 config.json 提交敏感信息npm run build调用tsc编译node dist/index.js运行失败时邮件通知比只看 Actions 日志更及时。5.2 多账号轮询用配置文件驱动避免硬编码当管理多个 TradingView 子账户如实盘/模拟/研究时不应复制多份代码。改为accounts/目录下放多个 JSONaccounts/ ├── live.json # 实盘账号 ├── paper.json # 模拟盘 └── research.json # 研究用每个文件结构同config.json但只含tradingView字段// accounts/live.json { tradingView: { email: livexxx.com, password: xxx, chartUrlTemplate: https://www.tradingview.com/chart/?symbol{{symbol}}interval{{interval}} } }主程序读取时动态合并// src/index.ts import { readFileSync } from fs; import { join } from path; function loadAccountConfig(accountName: string): TradingViewConfig { const accountPath join(__dirname, .., accounts, ${accountName}.json); const raw readFileSync(accountPath, utf8); return JSON.parse(raw).tradingView; } async function main() { const accountName process.argv[2] || live; // 默认 live const accountConfig loadAccountConfig(accountName); // 合并到主 config const fullConfig { ...JSON.parse(readFileSync(config.json, utf8)), tradingView: accountConfig }; await runBatch(fullConfig); }运行命令npm start live/npm start paper彻底解耦账号与策略。5.3 警报健康度验证用 Puppeteer 反向抓取已创建警报列表工具只管创建不管创建后是否有效。我们加一个verifyAlerts.ts登录后抓取 TradingView 的警报列表比对config.json中的alertName是否全部存在且 condition 一致// src/verify-alerts.ts export async function verifyAlerts(page: Page, expectedAlerts: AlertConfig[]) { // 1. 打开警报管理页 await page.goto(https://www.tradingview.com/alerts/, { waitUntil: networkidle0 }); await page.waitForSelector(div[data-widget-typealerts-list], { timeout: 20000 }); // 2. 提取所有警报的 name 和 condition const actualAlerts await page.evaluate(() { return Array.from(document.querySelectorAll(div[data-widget-typealerts-list] div[data-row-id])) .map(el { const nameEl el.querySelector(div[data-columnname] div); const conditionEl el.querySelector(div[data-columncondition] div); return { name: nameEl?.textContent?.trim(), condition: conditionEl?.textContent?.trim() }; }) .filter(a a.name a.condition); }); // 3. 比对 const missing expectedAlerts.filter(exp !actualAlerts.some(act act.name exp.alertName) ); const mismatch expectedAlerts.filter(exp actualAlerts.some(act act.name exp.alertName act.condition ! exp.condition ) ); if (missing.length 0) { console.error(❌ Missing alerts:, missing.map(m m.alertName)); } if (mismatch.length 0) { console.warn(⚠️ Condition mismatch:, mismatch.map(m m.alertName)); } if (missing.length 0 mismatch.length 0) { console.log(✅ All alerts verified); } }style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />