AI 帮你生成了 Pine Script 策略
从代码到真正自动化交易还差多少
你把想法讲给 AI 听,它返回一段 Pine Script,粘贴进去一次就编译通过。这件事现在真的不难。 没有人提前告诉你的是:「代码能编译通过」跟「代码能自动下单」是两道完全不同的关卡, Pine Script 有一长串规则在决定你落在哪一边。
这篇文章不评判你用的是哪个 AI 工具好不好——我们没做过这种测试,这篇也不会做。 重点在代码底层这个语言和平台本身:如果生成出来的是 indicator() 而不是 strategy(),如果声明了版本却用了另一个版本的语法,或是不小心在回测里泄漏了 未来信息,代码照样能编译通过——只是上线之后不会做你以为它会做的事。这篇就按你实际会 遇到的顺序,把这些落差一段段拆开讲。
先贴这段:要代码时附在需求上面的规格
从这里开始,因为这篇剩下的内容基本上就是在解释这几条为什么要存在。 把下面这段贴在你自己那段白话策略描述的上面。每一条都是你事后可以自己核对的规则, 而要求它逐条编号回复,就是让核对变便宜的关键:
在你开始写之前,先把下面每一条都套用进去,并在回复里逐条编号确认:
1. 用 strategy() 声明,不要用 indicator()。我需要成交事件的 alert,
而带「strategy」前缀的 placeholder 只能用在 strategy 建立的 alert 里。
2. 第一行放 //@version=6。不要省略版本声明那一行。
3. 只用 v6 语法:下单函数不要用 "when =" 参数(改成用 "if" 包住调用);
任何会返回值的 switch / if 都必须有 default / else 分支;
需要 bool 的地方要用 bool() 显式转型。
4. 明确告诉我你把 margin_long 与 margin_short 设成多少,以及为什么。
5. 如果你用了 request.security() 搭配 lookahead = barmerge.lookahead_on,
序列必须用 [1] 位移。如果逻辑上根本不需要 lookahead,就完全不要用。
6. 开仓与平仓条件都用 barstate.isconfirmed 把关。
7. Alert message 必须是单行、合法的 JSON,不能有换行,
字段就这几个:[贴上你接收端要求的字段清单]
策略逻辑:[用白话描述你的开仓与平仓条件]第 4 条之所以要它「讲出来」而不是「设一下就好」,是因为默认值在版本之间变过—— 而一个被讲出来的数字,你才有办法跟它争论。 接下来几节,就是这几条各自的「为什么」,顺序按你实际会踩到的先后排。
落差一:你拿到的是 indicator,还是 strategy?
Pine Script 的脚本分成两种声明类型,差异不只是名字。TradingView 参考手册写得很直白: indicator() 是「一个声明语句,用来标识这个脚本是 indicator」,而 strategy()「将脚本标识为 strategy」,并解锁下单能力。官方 Strategies 文档说得更明确:strategy 是「用来在历史与实时 K 线上模拟交易的脚本」,能「下单、修改、取消假设性订单」—— 这些能力 indicator 完全没有。
如果你跟 AI 说「帮我写一个 RSI 超卖就买入的策略」,这句话有一种合理的理解方式, 会生成出一个「画出 RSI 交叉点」的脚本——语法没问题、画面看起来也对,但本质上就是 indicator。它会干净地编译通过,图表上看起来完全正常,但里面找不到任何一行 strategy.entry(),因为 indicator() 脚本根本没有这个命名 空间可以用。
| indicator() | strategy() | |
|---|---|---|
| 能在图表上画值 | ||
| 能模拟下单(strategy.entry/exit) | ||
| 有回测 / Strategy Tester 结果 | ||
| alertcondition() alert 有效 | ||
| 成交 alert 能带出 {{strategy.*}} 数据 |
最后一行才是真正会让自动化断掉的地方,值得直接引用官方原文。TradingView 官方的 alert placeholder 文档把 placeholder 分成几组,并直接写下这句话:
这句话正好解释了一个很常见的翻车场景:把 AI 生成的代码接上 webhook 时,alert 消息里写了像 {{strategy.order.action}} 或 {{strategy.order.contracts}} 这样的 placeholder,alert 正常触发、 没有任何报错,但这些 placeholder 换回来的是空字符串——因为这个脚本是 indicator, 根本没有下单、没有成交,这些 placeholder 自然没有数据可以替换。TradingView 的界面 不会阻止你在 indicator 的 alert 消息里输入这些字段,它只是不会把值填进去。
如果你需要这些成交 placeholder,官方 FAQ 直接给出了做法:把 indicator() 声明换成 strategy(),沿用原本画图用的触发条件(交叉点、突破条件), 再加一行下单指令——strategy.entry("Long", strategy.long)——去真正触发那笔之后 alert 要回报的模拟下单。
alertcondition() 跟 alert() 也容易搞混
跟 indicator/strategy 的分界无关,另一个容易踩的坑是 alertcondition() 只能用在 indicator。官方文档写的是:把它放进 strategy 脚本「不会造成编译错误,但无法从中创建 alert」——代码照常运行,只是「Create Alert」对话框里永远不会出现这个选项。相对地,alert() 两种脚本类型都能用,而且消息可以动态拼接。
落差二:你拿到的到底是哪个版本的语法?
Pine Script 目前最新版本是 v6,之前是 v5(2021 年 10 月)跟 v4(2019 年 6 月)。版本声明不是装饰用的,而是一个编译器指令,官方文档对「不写会 怎样」讲得很清楚:
不会弹出警告、不会报错——缺少 //@version= 的脚本,会悄悄按 version 1 的规则去编译,那个版本旧到会拒绝你以为理所当然能用的语法。就算版本行写了, v5→v6 的迁移指南也列出好几个「旧写法在新规则下悄悄跑偏,而不是直接报错」的变更。 挑几个最常见的:
| 变更项 | 旧版行为 | v6 行为 |
|---|---|---|
| switch / if 返回 unique type | 不需要 default/else,没覆盖到的情况悄悄返回 na | 必须有 default/else,否则编译失败 |
| 下单函数的 when 参数 | strategy.entry("Long", strategy.long, when = cond) | 已移除,要改写成 if cond 包裹 |
| 默认 margin_long / margin_short | 0——strategy 永远不会被强制平仓 | 100——资金不足时订单会被拒绝,亏损过大也可能触发强制平仓 |
| int/float 用在需要 bool 的地方 | 自动隐式转换成 bool | 必须显式转换:bool(x) |
margin_long/margin_short 的默认值是最容易被忽略的一项: 同一段 v5 代码,只换了版本声明为 v6、其他都没改,回测结果可能从「从来不会被强制 平仓」变成「一堆订单被拒绝、被强制平仓」——逻辑完全一样,只是默认值换了。如果 AI 的训练数据混杂了新旧版本示例,生成出来的代码在这些规则上落在哪一边都有可能, 而且你很可能要等到回测、甚至上线后 alert 表现跟预期不一样才会发现。
所以这一关不要靠自己读代码,直接问:「这支脚本的 margin_long 跟 margin_short 设成多少?如果没有明确设置, 那在它声明的那个版本底下,默认值是多少?」有把两个数字跟版本一起讲出来的回答,你对照上面那张表十秒就能验完; 回你「默认值没问题」的,等于没回答。
落差三:回测好看到不真实吗?
TradingView 官方文档对 repainting 的定义很直白:「script behavior causing historical vs realtime calculations or plots to behave differently」(脚本在历史 K 线与实时 K 线的计算或绘图表现不一致)。 下一句话值得停下来看,因为它把整个话题的框架换了一遍:「more than 95% of indicators in existence exhibit some form of repainting behavior」(超过 95% 现有的 indicator 都带有某种形式的 repainting)。 Repainting 本身不等于有 bug——它是一道光谱,官方文档把成因大致分成从 「普遍且通常没问题」到「不可接受」的等级。
「不可接受」这一级,才是你准备把 AI 生成的策略交给真实资金下单时真正要在意的地方: 把未来信息泄漏到过去。最常见的方式是 request.security() 用了 lookahead = barmerge.lookahead_on,却没有搭配对应的 [1] 偏移。TradingView 文档直接写出了后果:
官方文档甚至附了一段示例代码,注释直接写着 FUTURE LEAK! DO NOT USE!——这个坑常见到 TradingView 特地点名警告:
// FUTURE LEAK! DO NOT USE!
htfClose = request.security(syminfo.tickerid, "D", close, lookahead = barmerge.lookahead_on)
// 正确写法:用 [1] 偏移,确保只用已确认的数据
htfClose = request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on)这段是给你认形状用的,不用背。你要找的只有两样东西在不在同一行:lookahead_on 跟 [1]。第一个出现、第二个没出现,就是你该追问的点。 懒得自己看的话,「列出所有 request.security() 调用,逐一告诉我有没有开 lookahead、 序列有没有用 [1] 位移」可以直接换来一张表。
这个错误会让历史回测看到「还没发生」的价格——这正是它能好看到不合理的原因。 但实盘当下并不存在未来的 K 线,同一段代码上线之后行为完全不同。 TradingView 对这件事的态度很明确:「Using lookahead to produce misleading results is not allowed in script publications.」(用 lookahead 产生误导性结果,不允许出现在已发布的脚本中)
官方文档给出的解法,是接受一个小小的取舍:把 lookahead_on 跟 [1] 搭配使用、在触发条件里加上 and barstate.isconfirmed, 或是把 alert 触发频率设成只在 K 线收盘时触发。TradingView 自己也坦白这是有代价的:「You can’t have your cake and eat it too」——不 repaint 就意味着信号 一定会更晚出现。
落差四:把它变成 TradingView 真的会发出的 alert
就算策略回测逻辑正确,还需要正确配置 alert 才能让任何东西发到 webhook。 两个设置最关键:
| 设置项 | 含义 | 为什么在这里重要 |
|---|---|---|
| Alert 类型 | alertcondition() vs alert() vs strategy 成交事件 | 决定消息里哪组 placeholder 有效——用错组合不会报错,只会拿到空值 |
| 频率:Once per bar close | 等 K 线收盘才触发 | 是解决前面 repainting 问题最直接的做法 |
alert() 调用会自己把消息拼出来, 你不必在对话框里再打一次。下单函数的 alert_message= 参数只有在成交事件触发、且消息里用了 {{strategy.order.alert_message}} 时 才会被读出来。「Create Alert」对话框的 Message 栏则是唯一能用 {{strategy.*}} placeholder 的地方,前提是这个 alert 建立在 strategy 上。 界面上要点哪里(图表右上时钟 → Add Alert → Condition 选你的策略 → Notifications 勾 Webhook URL),在 用 Claude 写 Pine 那篇有逐步走过一遍。在你准备拿这个对接真实资金之前,有一个结构性细节值得知道:对 strategy 创建 alert 的当下,TradingView 会把整个脚本复制一份到服务器上。官方文档逐字写的是,这份副本“runs independently from the chart’s strategy in your browser, and changes to your chart’s strategy will have no effect”(在服务器上独立于你浏览器里那份图表策略运行,你之后改图表上的策略不会有任何影响)。 也就是说,之后回去改 AI 生成的代码,线上那个 alert 还是照着旧版逻辑 继续跑,除非你删除重建。
另外值得知道的是,通知只在实时成交时才会触发——官方逐字是“Notifications are not sent for orders on historical bars.”(历史 K 线上的下单不会发送任何通知)——而且单个 alert 有频率限制, 3 分钟内最多触发 15 次,超过 TradingView 会直接停止这个 alert。
落差五:AI 完全碰不到的那一段——webhook 到你的交易所
以上这一切,全都发生在 Pine Script 里面,运行在 TradingView 的服务器上。 没有一个环节知道你的交易所账户存在。策略的权益只是你自己填在 initial_capital 里的一个数字;TradingView 官方文档把整套模拟叫做「broker emulator」,成交的是「hypothetical orders(假设性订单)」。 这个语言里完全没有提到你真实的 API key、真实的余额,或是下单失败该怎么办—— 因为到了链路的最后一段,alert 就只是发一条消息到一个 URL。之后发生的事, 完全在 Pine Script 之外,也完全不是任何帮你写 Pine 的 AI 能够生成出来的东西。
接收 webhook、把它翻译成一笔经过身份验证的交易所订单、计算仓位大小、套用你自己的 风控限制、处理下单失败的情况——这一段是接收端服务要做的事。
这正是 TVSBot 在处理的部分:每个用户独立的 webhook URL(独立 token + payload secret 双重验证)、用 Fernet 加密存储的交易所 API key(AES-128-CBC + HMAC-SHA256)、 通过你自己的账户在支持的 7 家交易所下单,以及你自己配置的风控——单笔上限、最大持仓数、 日亏损熔断、一键 Kill Switch。新策略默认就勾好 dry-run,可以先让信号用模拟资金 跑完整条链路。
验收指令:把代码贴回去,问它这几题
下面那份检查清单里的每一项,也都可以直接变成问句。 如果你比较想「拿到答案列表」而不是自己去找,就发这一段。 刻意先不让它改写,是为了看出它到底有没有读过那支脚本:
这是一支我想拿去实盘交易的 Pine Script。先不要改写,先回答我:
1. 声明语句是 indicator() 还是 strategy()?把那一行原文引出来。
2. 它声明的 //@version= 是多少?如果根本没有,直接说没有。
3. margin_long 跟 margin_short 设成多少?如果没有明确设置,
在第 2 题那个版本底下的默认值是多少?
4. 列出所有 request.security() 调用,逐一说明有没有开 lookahead、
序列有没有用 [1] 位移。
5. 列出我的 alert message 会需要用到的所有 placeholder,
并逐一说明在第 1 题判定的脚本类型底下,它们是不是有效的。
6. Alert message 是不是单行、没有换行的合法 JSON?
以上都回答完,再给我修正版,并告诉我你改了什么。
[把你的脚本贴在这里]第 5 题抓的是最常见的上线翻车:strategy 类的 placeholder 被放进 indicator 的 alert 消息里—— 这种错不会报错,它会安静地坏掉。
检查清单:把 AI 生成的 Pine 交给真实资金之前
- 声明语句:写的是
indicator()还是strategy()?要拿到成交 alert 就一定要是strategy()。 - 版本行:有没有
//@version=, 且版本是你实际要求的那个?没写会悄悄退回 v1 规则。 - v5/v6 默认值:检查
margin_long/margin_short、任何缺 default/else 分支的switch/if,以及残留在下单函数上的when =参数。 - lookahead 是否成对:任何用了
lookahead = barmerge.lookahead_on的request.security(),都要搭配[1]偏移。 - Alert 类型与 placeholder 对得上:确认消息里的 placeholder (
{{strategy.*}}或{{plot_0}}这类) 真的对应你建的是 indicator alert 还是 strategy alert。 - 如果你要信号反映「已确认的 K 线」而不是进行中的,频率设为 Once per bar close。
- Alert 消息是合法的单行 JSON,格式对得上你的 webhook 接收端要解析的内容。
- 先跑 dry-run 再接入真实资金:完整跑一遍「alert → webhook → 模拟下单」的链路,确认无误再指向真实资金。
常见问题
AI 能写出真正能自动化交易的 Pine Script 吗?
strategy() 而不是 indicator()、版本语法对不对得上 你要的版本、逻辑有没有避开 repainting。这些都是你可以拿 TradingView 官方 文档自己核对的代码属性,不是在评判任何特定 AI 工具的好坏。为什么我的 TradingView alert 消息里没有下单信息?
indicator(),但 alert 消息用了 strategy 类的 placeholder,例如 {{strategy.order.price}}。 TradingView 官方 alert placeholder 文档明确说明:带「strategy」前缀的 placeholder 只能用在 strategy 创建的 alert 里——用在 indicator 的 alert 里, 它们会被替换成空值,而且不会有任何报错提示。使用 AI 生成的代码上线,我需要懂 Pine Script 吗?
request.security() 调用有没有把 lookahead_on 跟 [1] 偏移搭配使用。 上面的检查清单就是列给你核对用的。为什么回测看起来很漂亮,实盘却对不上?
request.security() 把未来的价格泄漏到了历史 K 线上。 TradingView 文档提到超过 95% 的 indicator 都有某种形式的 repainting, 而其中「不可接受」的那种(未来泄漏),可以让回测看起来远比实盘将来能 重现的结果好看。Pine Script 知道我在交易所的真实余额吗?
Get started
把 AI 生成好的 Pine Script 带过来,webhook 到交易所这一段交给 TVSBot——用你自己的 API key,先 dry-run,风控自己配置。
免费开始使用