跳到正文

一行跨品种的 request.security:回测需要什么

约 6 分钟阅读

交易一个市场、却拿另一个市场做过滤的策略,需要第二个市场的 K 线,而你图表的 CSV 里没有这些数据。本文说明 PineForge 引擎运行这类脚本需要什么、缺少 K 线时会怎么做,以及 Docker 镜像和 MCP 服务器能提供什么。

2026-10-04 更新:自发布版 1.1.0 起,pineforge-release 镜像会按 PINEFORGE_SYMBOL_FEEDS 指定的 JSON 索引,为 request.security 装入其他品种的 K 线,每个请求的周期各一份,均不做聚合(引擎 #327、pineforge-release #23;详见引擎的 1.1.0 变更日志)。本文其余内容保持 2026-10-02 发布时的原样,对应发布版 1.0.1。

你的策略交易一只股票,但只在纳斯达克处于上升趋势时买入。在 TradingView 上,这只是一行代码,而且能跑,因为 TradingView 有所有品种的 K 线。基于你的 CSV 运行的回测工具,手里只有图表这一个品种的 K 线。向它要 QQQ,它只有三种选择:拒绝、中止,或者悄悄地在错误的市场上计算这个过滤条件。

PineForge 选的是第二种,而且只在不得不这样做的时候:把 QQQ 自己的 K 线交给引擎,脚本就能运行。

需要第二个市场的那一行

像下面这个脚本,只在 QQQ 位于 50 日均线之上时做多:

//@version=6
strategy("QQQ regime filter", overlay=true)
qqqUp = request.security("NASDAQ:QQQ", "D", close > ta.sma(close, 50))
fast = ta.sma(close, 20)
if ta.crossover(close, fast) and qqqUp
    strategy.entry("L", strategy.long)
if ta.crossunder(close, fast)
    strategy.close("L")

问题出在第 3 行。它的表达式 close > ta.sma(close, 50) 运行在 QQQ 自己的日线上:QQQ 的收盘价,QQQ 的 50 日均线。图表的 K 线不管怎么聚合,都得不到这些数据。直到不久前,PineForge 还会直接拒绝这个脚本。现在它可以运行,前提是运行引擎的人把 QQQ 的 K 线交给它。

引擎需要什么

引擎公开的 C 头文件里,有一个专门做这件事的调用(pineforge.h):

PF_API int strategy_set_symbol_feed(pf_strategy_t s, const char* key, const char* timeframe,
                                    const pf_bar_t* bars, const int64_t* close_ms, int n);

可以把它看成三条要求。key 就是脚本传入的那个字符串,一字不差:NASDAQ:QQQ,这样 K 线才能对应到请求它的那个调用。timeframe 必须是请求的那个周期:日线请求需要日线 K 线,同一品种其他周期的数据不算数。还有 close_ms,它给每根 K 线各自的收盘时间,因为「不会为该品种猜测交易日历」:除非 K 线自己写明,否则引擎不知道纳斯达克几点收盘。品种的交易时段、货币和最小变动价位等信息是可选的,通过一个配套的调用传入。

按谁的时钟

收盘时间之所以要紧,是因为两个市场的收盘时间不一样。在每一根图表 K 线上,引擎交给请求的,是另一个品种在这根图表 K 线收盘时已经收盘的最后一根 K 线;如果开启了 lookahead,则是在这根图表 K 线开盘时已经开盘的最后一根(pineforge.h)。所以,「收盘晚于图表交易时段收盘的日线 K 线,会晚一个图表交易日才被看到,和 TradingView 看到的一样」(MTF 指南)。

这是 TradingView 的规则,不是 PineForge 图省事的做法。引擎的测试里保留了一份 TradingView 导出:一张请求美元指数的黄金日线图。黄金在周日晚上开盘的那根日线,周一 17:00 ET 收盘;美元指数周一那根 K 线 19:00 ET 收盘,晚了两个小时。所以在 TradingView 上,那根黄金 K 线读到的是周五的美元指数 K 线,周一那根要等到下一根黄金 K 线才读得到(fixture 说明)。回测工具如果在另一个市场的 K 线收盘之前就把它交给图表 K 线,就是在读取未来。

缺少 K 线时

脚本仍然可以转译,但会给出警告:这个请求读取的是「另一个品种的 K 线」,并且「如果没有装入任何数据,运行会在读取其值的地方报错停止」(codegen)。第一次读取时,运行就会停止,报错信息会点名是哪个调用(源码):

request.security("NASDAQ:QQQ", "D", ...) at line 3: no data is pinned for this request, and its value was read

它绝不会拿图表的 K 线顶替 QQQ 的(变更日志)。在错误的市场上算出来的过滤条件,会生成一条看似合理、实则毫无意义的权益曲线,所以停下来是更安全的失败方式。有两种情况不会停止:一是请求的值从未被读取(比如藏在一个关闭的输入选项后面);二是请求的值只流向绘图、警报或表格,这时它会变成 na,并附带一条「交易不受影响」的警告(codegen)。

Docker 镜像和 MCP 服务器提供什么

它们都只回测一个市场的 K 线:图表自己的。

  • Docker 镜像接收 strategy.pine 和一个 ohlcv.csv(README),它的 harness 不会装入任何其他品种的 K 线。QQQ 那个脚本在这里会带着上面那条信息停止,镜像以退出码 4 退出。
  • 本地 MCP 服务器基于这个镜像构建,只回测一个 CSV。它的 README 也写明了:它「不会装入任何其他品种的 K 线……例如,引擎支持对其他品种调用 request.security,但在这里,只要请求的值可能影响到交易,运行就会停止」(README)。
  • 托管 MCP 服务器接收品种、周期和交易场所,自己去拉取这一个市场的 K 线:只支持加密货币,所以像 QQQ 示例这样的股票策略在那里根本跑不起来(同一份 README)。

这里有个容易踩的坑:Docker 镜像和本地服务器从不告诉引擎图表是哪个市场,所以用字符串写出的品种都算作其他品种,哪怕它指的就是图表自己的市场(覆盖范围说明)。引用图表自己的品种,请写 syminfo.tickerid。对它发起的高周期请求不需要额外数据,因为引擎会用图表的 K 线合成那些 K 线(MTF 指南)。但有一个限制:在美股或 CME 期货的日内图表上,TradingView 的日线是交易所自己的日线,带官方收盘价,任何聚合都还原不出来(pineforge.h)。

这对你意味着什么

  • 用图表自己的市场做过滤? 用 syminfo.tickerid,脚本只靠你的 CSV 就能运行。
  • 用另一个市场做过滤? 自己运行引擎,在运行前装入那个市场的 K 线及其收盘时间。引擎仓库的测试 harness 就是一个现成的例子:它根据每个脚本各自的请求清单装入这些数据,清单字段由 scripts/run_strategy.py 定义。
  • 要和 TradingView 对比? 在 lookahead 保持默认关闭时,收盘比你的市场晚的市场,其日线会晚一天才到。这是 TradingView 的行为,引擎与之一致。

本文内容于 2026-10-02 对照 PineForge 引擎和 codegen 1.0.1(当天发布的版本)核对;这一行为首次随 1.0.0 发布。

延伸阅读