Pine Script 的 Alert 没有记忆
为什么简单的触发逻辑会让自动化出问题
策略触发了 alert,webhook 发出去,结果没几秒同一根 K 线上又冒出一笔几乎一样的订单。 或者反过来:你改了开仓条件、保存了脚本,线上那个 alert 却还是照上周的逻辑跑,好像 你什么都没改过一样。这两个都不是随机出问题。
它们的根源是同一件事:Pine Script 的 alert 不会记得自己已经发送过什么——而且更容易被 忽略的是,它跑的根本不是你现在图表上正在看的那份脚本。
如果这支策略是 AI 帮你生成的,它不会主动提醒你这些事。因为这不是语法问题——代码照样编译通过、 回测照样好看,出问题的是 TradingView 执行脚本的方式,以及 alert 的存储机制。 所以这篇不是要教你写 Pine,而是给你三件事:出问题背后的机制、让 AI 生成代码时该贴给它的指令原文, 以及拿到代码后该回头问它哪几个问题。
alert() 和 alertcondition() 的触发频率设置,只负责限流「同一根 K 线内这个条件最多可以触发几次」——alert() 是靠代码里的 freq 参数,alertcondition() 本身没有 freq 参数,是在建立 alert 的对话框里选 Frequency。它们不会确认 webhook 有没有送达, 不会帮你去重,也不知道上一根 K 线发生过什么——除非你自己用 var/varip 存起来。而且就算存了,这份记忆也只属于跑在 TradingView 服务器上的 那一个特定 alert 实例,不属于你在图表编辑器里看到的那份脚本。脚本为什么没有记忆:它每根 K 线都从头跑一次
Pine 不是把你的脚本对着整张图表算一次就结束。TradingView 官方对执行模型的描述是: 脚本会「在历史 K 线与实时 tick 的序列上重复执行……针对每一根 K 线各自计算一次」。 在已经收盘的历史 K 线上,这件事很简单:从最早的 K 线到最新的,依次每根执行一次。
图表最右边那根还没收盘的 K 线——实时 K 线——就不一样了。因为它的 最高/最低/收盘价都还没定案,脚本会在每一个新进来的 tick 上重新跑一次,用最新价格重新算。 每次重跑之前,Pine 都会把这根 K 线上的变量「重置」(rollback)回这个 tick 发生前的状态, 确保上一次的临时性结果不会渗进下一次计算。只有这根 K 线最终、收盘那一个 tick 的结果, 才会真正写进永久的历史数据里。
Strategy 是部分例外:默认情况下,strategy 跟 indicator 不同,即使是实时 K 线也只在收盘后执行一次。要开启 calc_on_every_tick 或 calc_on_order_fills,才会逐 tick 重新计算。
这件事对你下指令的影响是:当你跟 AI 说「同一个形态只准开仓一次」, 它必须自己造一个「记忆」出来,因为这个语言本身没有给它。 它会抓什么来当记忆——一个标志位、一个计数器、记住某根 K 线的编号——都是为了绕过这个执行模型。 下一节就是教你认出你拿到的是哪一种绕法。
AI 给的代码用了 varip,说明它想解决什么
有两个声明关键字决定变量的值会不会跨执行保留下来,区别正好对应上面讲的 rollback 机制。 你看这张表不是为了自己写得出来,而是为了:这两个词出现在生成的代码里时, 你知道 AI 当时在试图解决什么。
| 声明方式 | 什么时候第一次设定 | 会不会跨 K 线保留 | 实时 K 线收盘前改动会不会留住 |
|---|---|---|---|
| (不加关键字) | 每次执行 | 不会——每次都重新初始化 | 不会 |
var | 该 K 线收盘 tick 第一次执行时 | 会 | 不会——会被还原回上一次确认收盘的状态 |
varip | 第一次执行,即使是收盘前 | 会 | 会——不受 rollback 影响 |
这张表是给你对照用的,不用背。
在历史 K 线上这个区别完全看不出来,因为每根本来就只执行一次——var 跟 varip 在那里表现一模一样。区别只会在实时 K 线上体现出来:你在收盘前用 var 改的标志位,下一个 tick 一进来就悄悄还原,只有收盘那一刻的版本才会真正定下来。 而 strategy 因为默认每根 K 线(含实时)只执行一次,除非开启 calc_on_every_tick 或 calc_on_order_fills,否则 varip 的表现会跟 var 一样——这点很容易让人误以为 varip 永远比 var 更「实时」。
所以你该做什么?看到生成的代码里有 varip, 通常说明 AI 想跨 tick 记住某个状态——而在 alert 这个场景下,「跨 tick 记东西」 常常是用错的方法回答错的问题。直接问它:「这个状态在 alert 触发之后还可靠吗?我把 alert 删掉重建之后, 它在第一根 K 线上的值是多少?」如果回答完全没提到「会归零」,那就是你该追问下去的点。 任何拿 var 标志位来当「不要重复触发」防护的写法,同样问这一题。
如果对声明和执行的基础还不熟,可以先看我们的 Pine Script 入门教程——不过这篇剩下的内容,不看那篇也读得完。
直接贴这段指令,多数坑就不会被写进去
把下面这段当作「需求区块」,贴在你自己的策略描述上面。它刻意要求 AI 逐条回报, 而不是让它默默按自己的想法做:
帮我写一支 TradingView 用的 Pine Script v6 策略。这支策略会接 webhook 去下真实订单,
所以「可靠」比「漂亮」重要。
策略逻辑:[用白话描述你的开仓与平仓条件]
以下是硬性要求。全部照做,然后逐条编号回报你怎么处理的:
1. 用 strategy() 声明,不要用 indicator()。
2. 所有开仓与平仓条件,只能在「已确认的 K 线」上判断。
用 barstate.isconfirmed 把关。然后列出脚本里还有哪些条件是在读
「还没收盘那根 K 线」的 close / high / low。
3. 不要用 var 或 varip 来记住「这个信号我已经发过了」。
如果你认为逻辑上非要这种记忆不可,先停下来跟我说明:
当我把 alert 删掉、重新建立一个新的之后,那个标志位的值会是多少。
4. Alert message 必须是「单行、合法的 JSON」,不能有换行,
字段就这几个:[贴上你接收端要求的字段清单]
5. 最后列出脚本里所有「实时 K 线行为跟历史 K 线行为不一样」的地方。第 3 条是最有价值的一条。好的回答会告诉你「新建 alert 之后那个标志位会从零开始」; 如果它只回你一句「对,var 会保留」——这句话字面没错,但实务上会害你, 而且正好告诉你:去重这件事得放到脚本以外的地方做。
一、代码里的
alert() 调用——消息由程序当场拼出来, 你不必在对话框里再打一次。二、下单函数的
alert_message= 参数——这段文字只有在成交事件 触发、且消息里用了 {{strategy.order.alert_message}} 时才会被读出来。三、TradingView「Create Alert」对话框的 Message 栏——这是唯一能用
{{strategy.*}} placeholder 的地方,前提是这个 alert 建立在 strategy 上。界面上要点哪里(图表右上时钟 → Add Alert → Condition 选你的策略 → Notifications 勾 Webhook URL),在 用 Claude 写 Pine 那篇有逐步走过一遍。
线上跑的 alert,不是你现在看到的那份代码
这是最容易让人意外的地方——很多人以为「alert 就等于我的脚本」,但其实不是。 当你点击 Create Alert 的那一刻,TradingView 会把脚本、输入参数、图表标的/周期做成一份快照, 之后这份快照就在它自己的服务器上独立运行。官方文档说得很明确:「之后对脚本输入或图表的 修改,不会影响已经根据它们创建的、正在运行的 alert。」FAQ 被问到「改脚本会不会让正在跑 的 alert 跟着变」时回答更直接:「不会,除非你重新创建一个 alert……要让修改生效,必须删除 现有的 alert、重新创建一个新的。」
这对任何你用 var/varip 存起来追踪状态的东西(比如一个「这个 信号是不是已经发过」的标志位)有直接影响:这份记忆属于 alert 正在跑的那份冻结快照, 不属于你在图表面板里持续修改的脚本。你尽管改脚本——线上那个 alert 内部的计数器还是照旧逻辑跑, 直到你把它删掉重建。而你重建之后,那就是一份全新的快照:它的 var/varip 状态从零开始,重新用历史数据算一遍,完全不会继承旧 alert 实例累积过的 任何记忆。(这最后一句结论官方文档没有用单独一句话直接讲出来——它是把上面「快照机制」跟 下一节「脚本重启」两段官方陈述结合起来推导出的结果。)
| 你改了什么 | 正在跑的 alert 会跟着变吗? | 官方依据 |
|---|---|---|
| 图表编辑器里的脚本代码 | 不会 | 「Subsequent changes to your script's inputs or the chart will thus not affect running alerts previously created from them.」 |
| 脚本的输入参数 / 图表品种或周期 | 不会 | 同一句官方原文——输入参数与图表本来就在创建当下被一并快照起来。 |
| 把 alert 删掉重建 | 会——但那是一份全新的快照 | 「To update an alert after making changes, delete the existing alert and create a new one.」它的 var/varip 状态会从零开始。 |
引用内容为 TradingView 官方 alerts 文档与 FAQ 的逐字原文。
这一段是最多人跟 AI 耗掉时间的地方。你把脚本贴回去、它找出问题、 你改好了、盯着线上看——结果什么都没变。不是因为改错了, 而是因为那个修改根本没有送到「正在跑的那个东西」上。再怎么下指令也到不了一个已经在跑的 alert。 唯一的路径是:改脚本 → 把 alert 删掉 → 重建一个新的。 这件事值得写在便签上,因为它看起来实在太像「AI 没修好」了。
「触发了、信号又不见了」:先检查这个
TradingView 对 repainting 的定义很直白:「脚本在历史计算跟实时计算/绘图之间表现不同的行为」。 这不代表它自动就是坏事——官方把某些 repainting(比如 close 在还没收盘的 K 线上会浮动)归类为「普遍但通常可接受」,但另一些类型则被标为「可能误导」甚至「不可接受」 (用实时的 K 线内数据去触发 alert 或下单,就属于不可接受的那一类)。
「触发了、又消失」这个现象背后的机制,官方称之为 fluid data(浮动数据):历史 K 线只会 存最终的 OHLC,但实时 K 线的最高/最低/收盘价在收盘前会随每个 tick 变动。如果你的条件式检查的是 当下的 close 或 high,它可能在某个 tick 成立、alert 触发了, 但等 K 线真正收盘时条件已经不成立——alert 已经发出去了,下次刷新画面时,条件却看起来 「根本没发生过」。
官方给的正式解法是强制等 K 线收盘才判断:把 alert 频率设成Once Per Bar Close,或在代码里用 barstate.isconfirmed 挡住条件。TradingView 也坦白说这是有代价的取舍:「这些方法虽然能避免 repainting,但也会让 信号比会 repaint 的脚本更晚触发……鱼与熊掌不可兼得。」这种「收盘 vs 实时」的张力,也是同一个 alert 有时候测起来几乎瞬间送达、有时候又明显变慢的部分原因——我们的webhook 延迟拆解文讲清楚了哪些是你能调的、哪些是 TradingView 自身基础设施的部分。
落到实务:Frequency 那个下拉菜单是你自己在浏览器里点的,十秒钟的事。 其他部分则是「要交代给写代码的那一方」的指令——「所有条件都用 barstate.isconfirmed 把关,然后告诉我你改了哪几条」——这也正是上面那段指令第 2 条的内容。
把代码贴回去,问它这四个问题
不管脚本是 AI 生成的、论坛抄的,还是你自己几个月前写的,这一轮都要跑。 刻意先不让它改,是为了让你看出它到底有没有搞懂情况,再让它动手:
这是一支 Pine Script 策略。我要拿它建 TradingView alert 去接 webhook 实盘下单。
先不要改写,先回答我这四题:
1. 这支脚本里,有哪些条件可能「K 线中途成立、收盘时又不成立」?逐行列出来。
2. 这里面有没有用 var 或 varip 来避免重复触发?
如果有:我把 alert 删掉、重新建一个之后,那个变量在第一根 K 线上的值是多少?
3. 如果我把 alert 的 Frequency 设成「Once Per Bar Close」,
这支脚本的哪些逻辑行为会改变、哪些不会?
4. 这支脚本里有没有任何地方,是假设「我的服务器已经收到上一条 alert」?
回答有或没有,并指出是哪一行。
以上都回答完,再给我修正版。
[把你的脚本贴在这里]第 4 题是故意设的陷阱,正确答案是「没有」——Pine 脚本根本无从知道这件事。 如果它回答有,或是主动提议在脚本里加一套「送达确认」机制, 那这条回复里的其他内容你都要多留一分怀疑。 这篇最后一节会说明为什么 Pine 里没有东西能回答这个问题。
对照你的症状,找出对应的机制
把上面几件事串起来——逐根 K 线执行、var/varip 的作用范围、被冻结的 alert 快照、repainting—— 多数「我的自动化莫名其妙出问题」的反馈,其实都能对应到下面几种机制之一。
上线前的自检清单,以及脚本永远做不到的那一件事
在你把这套东西指向真钱之前,把下面这张清单走一遍。 每一项都是你自己就能确认的——不是在浏览器上点一下,就是直接问 AI 一个问题, 完全不需要你会写 Pine:
- alert 的 Frequency 设成 Once Per Bar Close,不是只设 Once Per Bar——光是这一点就能消掉多数由 repainting 造成的重复触发。
- 任何会用到
close/high/low的条件式, 都用barstate.isconfirmed挡住。 - 你知道 alert 上次是什么时候重建的,能拿这个时间点跟你最后一次改脚本的时间对比。
- 用
var/varip标志位做「不要重复触发」的防护时,清楚知道 alert 重建就会归零——不是只有图表刷新才会归零。 - 你的接收端/执行端自己保留每一笔实际收到的 payload 记录,因为 TradingView 不会帮你留。
有一件事 Pine 真的给不了你:确认你发出的 webhook 有没有被下游收到并处理。 alert() 和 alertcondition() 从设计上就是 fire-and-forget—— TradingView 的文档详细描述了频率跟触发机制,但完全没有任何一句话说 alert 引擎会跟踪 或确认接收端服务器对这条消息做了什么。同一个信号在下游是不是被处理了两次,不是 Pine 执行环境设计上会知道的事。
这其实是执行端的问题,不是 Pine 的问题。TVSBot 的做法是把收到的信号丢进后台异步处理, 并把每一条信号连同原始 payload 与完整处理记录存下来,逐条可查、可 Replay—— 让你能实际核对是 TradingView 真的发了两次,还是重复发生在链路的其他环节。
但要讲清楚:那是事后查得到,不是自动去重。真正要修复 alert 逻辑本身不可靠的问题, 还是得从上面那份 Pine 端清单开始。
常见问题
为什么 alert 在 K 线中途触发了,条件后来又消失了?
close 或 high 这种在 K 线收盘前还会变动的值——这是一种 repainting。条件在 K 线中途看起来成立、 触发了 alert,等最后一个 tick 落地后又不成立了。这种情况下 Once Per Bar 并不会发两次, 官方文档写的是「同一根实时 K 线只有第一次调用会触发 alert」;问题在于那唯一一次发出, 依据的是一个撑不到收盘的数值。把 Frequency 改成 Once Per Bar Close,或在脚本里用 barstate.isconfirmed 把关,可以强制等收盘确认才判断。我明明改了 Pine 脚本,为什么线上的 alert 还是照旧逻辑跑?
var 变量存的计数器,是不是永久安全不会丢失?
var 就会针对历史数据集从头重新初始化一遍。AI 在我的策略里用了 varip,需要担心吗?
var 的更改只有在 K 线收盘那个 tick 才会真正生效——在还没收盘的 实时 K 线中途做的改动,下一个 tick 一来就会被还原。varip 则是设定当下就立刻保留,逐 tick 都不受 rollback 影响。在历史 K 线上两者没有可见的区别, 因为那里每根本来就只执行一次。所以生成的代码里出现 varip, 通常是想跨 tick 保留某个状态——去问它保留的是什么状态、alert 重建之后那个值还剩多少。我能确定 TradingView 只发了一次 webhook 吗?
Get started
TVSBot 把 TradingView 的 alert 转成跨 7 家交易所的自动下单——非托管、用你自己的 API key,搭配后台异步信号处理与逐条可查的 Replay 记录,让你清楚知道每一笔信号实际收到了什么。
免费开始使用