同一个 Pine 脚本的两次回测很少能分毫不差,所以在说清比的是什么、差多少才算够接近之前,「与 TradingView 一致」这句话没有意义。本文说明一个评级回测由什么构成、它的交易如何与 TradingView 的配对、五档评级各自要求什么,以及如何评判一次代码改动。
很多回测工具都说自己和 TradingView 一致。可同一个 Pine 脚本的两次回测几乎不可能分毫不差,所以在弄清比的是什么、差多少才算接近之前,这句话说明不了什么。我们的评级总表给 7,989 个回测各打上五个词中的一个,其中 7,905 个是 excellent。本文解释这个词,以及另外四个词分别是什么意思。
一个回测,一份交易列表
评级的单位是探针:「从 TradingView 导出、附带交易列表,并在同样的 K 线上逐笔重放的策略」(codegen README)。脚本保留它自己的输入参数和默认值,评级比较的是两份交易列表:TradingView 的那份,以及 PineForge 在同样的 K 线上生成的那份。
同一个脚本可以在多个市场上分别作为探针。这 7,989 个回测包括:公开语料库 312 个策略中的 309 个,以及 741 个 TradingView 脚本的 7,680 个探针,分布在九个市场(加密货币、美国期货和股票、一个印度指数、一个货币对和黄金),每个市场都跑 15 分钟和日线两种 K 线(评级总表)。
交易配对
评分器就是一个公开脚本:verify_corpus.py。对 TradingView 的每一笔交易,它会在还没配对的 PineForge 交易里,找方向相同、入场时间相差不超过一小时、入场价相差不超过 3.00 的最接近的一笔,价格按该市场自己的价格单位计(配对、阈值)。然后测量两件事:TradingView 的列表被复现了多少,以及复现出来的交易有多接近。
前一项叫覆盖率,它以 TradingView 的全部已平仓交易为基数,而不只是两份列表重叠的那一段。代码里写明了原因:其他指标都是在匹配结果自己划定的窗口里计算的,「所以如果没有这道门槛,一次几乎什么都没复现的运行,也可能凭碰巧复现出来的那一小段拿到最高一档的评级(生产环境中真实出现过:真实覆盖率 0.9% 却评为 excellent)」(评分器)。覆盖率成为门槛后,语料库里有两个策略降到了 moderate:它们分别复现了 TradingView 224 笔交易中的 209 笔、241 笔中的 228 笔,这些缺口此前都被旧的窗口掩盖了。
五个词
| 评级 | 复现的 TradingView 交易 | 交易笔数 | 入场价、出场价(p90) | P&L(p90) |
|---|---|---|---|---|
| excellent | ≥ 99% | 完全相同,且当 TradingView 在完全相同的时间、价格和方向上显示两个或以上不同的入场 Signals 时,引擎也在该处显示相同数量的独立入场(独立入场识别) | 0.01% 以内 | 1% 以内 |
| strong | ≥ 95% | 6% 以内 | 0.1%、0.5% | 100% 以内 |
| moderate | ≥ 75%,且重叠区间内 ≥ 90% | |||
| weak | 至少一笔 | |||
| minimal | 一笔也没有 |
「p90」的意思是,十笔交易里有九笔至少这么接近(阈值、评级阶梯)。TradingView 的交易里如果最多只有一笔没匹配上,就能通过 excellent 和 strong 的覆盖率门槛,所以一份较短的导出边缘上的一笔交易,不会让评级掉档。excellent 完全不允许交易笔数有任何差异。代码给出的理由是:「在交易很多的策略里,百分比阈值可能掩盖几十笔缺失或多出的交易」(评分器)。出场使用移动止损的策略,按更宽松的阈值评级:出场价在 0.05% 以内,P&L 在 100% 以内,因为移动止损在一根 K 线内的什么位置成交,取决于 TradingView 的 K 线内价格路径(评分器)。
最差的那笔交易:只打印,不评级
评级看的是第 90 百分位,而不是最差的那一笔。一个公开策略可以说明原因。下面是评分器在 order-harbor-stop-limit-01 上的输出(有删节):
Matched: 471 (100.0% of TV)
Count delta: 0.0000% (OK; abs=0)
PnL p90 delta: 0.0381% (OK)
-- report-only (not gated; MAE/MFE are intrabar-path-limited) --
Entry/Exit/PnL p100: 0.0011% / 0.0084% / 4.9658%
-> excellent
看评级之前的最后一个数字:有一笔交易的 P&L 偏差接近 5%。让评分器列出最差的几笔交易(--show-diffs 3),会看到这一笔 TradingView 的入场价是 1,970.36,PineForge 是 1,970.35,而整笔交易只亏了约 51 美分。约 2.5 个单位、每单位差一美分,合计 2.5 美分;而 2.5 美分是 51 美分亏损的 5%。如果按最差的一笔来评级,这个策略会因为一次入场差了一美分而拿不到 excellent;按百分位评级,它通过了,报告里也照样列出了这个离群值。
如何评判一次改动
评级描述的是代码当下的状态。一次改动则看它带来了什么变动:同一批探针在改动前后各评一次。规则要求硬约束范围内没有任何退步。其余的市场和周期则计入 excellent 档和 excellent+strong 档的合计得分:得分大于零即可通过,前提是探针跌出某个档时最多只降一级;得分恰好为零,则要没有任何地方变差才能通过(README)。
之所以看两个档,是因为每个档都能看到另一个档漏掉的变动。一个策略从 strong 升到 excellent,只改变前一个档的计数;从 moderate 升到 strong,只改变后一个档的计数。9 月的一次改动把 32 个探针升进了 excellent;其中两个原本低于 strong,因此在两个档里都算数,这次改动得分 +34,而且没有任何探针降级。硬约束范围是 15 分钟 ETH 这条测试线:1,009 个探针,公开语料库的 309 个策略也在其中。无论改动在别处带来多少提升,这里都不允许任何探针降级。
什么都没变的时候
很多好的改动什么都不会改变:一次重构、一个新 API、修复一个评测集里没有任何策略会走到的情形。只改文档的改动不需要跑全量评测(示例)。代码改动如果没有让任何探针进入或离开任何一个档,得分就是零。按照 README 里的规则,只要没有任何地方变差,它就能通过:如果没有任何评级发生变化,标记为「no-improve-no-regression」(无提升、无退步);如果有地方变好了,只是两个档都看不到,例如一个策略从 weak 升到 moderate,则标记为「improved」(有提升)。但得分为零也可能掩盖退步,这时改动就不通过:比如一个探针从 moderate 降到 minimal,始终在两个档之下;或者一个探针的降级正好抵消了另一个探针的提升。
对这类改动来说,零变动正是预期的结果,但它证明不了别的地方没有变化,因为五个词的粒度太粗。所以引擎的 CI 还会锁定引擎在公开语料库上生成的每一份交易列表的哈希值:某个策略的滑点改动一个 tick,就「会改变它的交易和基线哈希,而 verify_corpus.py 仍报告 excellent=311, strong=0, anomaly=1」(CI 文档)。
这对你意味着什么
- 在画廊页面上,
excellent意味着:交易笔数与 TradingView 相同,TradingView 的交易至少复现了 99%,并且十笔交易里有九笔的价格和 P&L 落在上表的阈值内。对于excellent,当 TradingView 在完全相同的时间、价格和方向上显示两个或以上不同的入场 Signals 时,引擎也在该处显示相同数量的独立入场(独立入场识别)。除了一个已声明的 TradingView 端异常,画廊里的每个策略都是excellent;strong只出现在非公开测试中。 - 如果你要拿自己的回测工具和 TradingView 比较,也用同样的方法:按入场配对交易,以 TradingView 的全部交易计算覆盖率,看百分位,而不是看最差的那一笔。
- 公开的部分可以自己重跑:README 里的命令会构建、运行语料库的 312 个策略并为其评级。非公开测试是我们自己对未公开脚本的测量,其中大部分是社区脚本,根据 TradingView 的条款不予公开。
评级测于 2026-09-29,所用的引擎和 codegen 代码已包含在 1.0.0 版本中。2026-10-02 发布的引擎和 codegen 1.0.1,其代码也在同一批 7,989 个探针上评过级,没有任何探针的评级发生变化。
延伸阅读
- 我们如何衡量一致性:最新结果及其数据来源。
- 浏览策略画廊:每个语料库策略的评级,以及两份交易列表。
- 一则保证金一致性的排查故事:策略达不到
excellent时,评分器的输出是什么样子,以及我们如何把其中一个追查到根源。