跨标的与多时间框架
request.security() 在不同标的和/或时间框架的上下文中对表达式求值,并将结果对齐到当前图表 bar。它是 Navi 中访问多时间框架(MTF)数据和其他标的数据的主要方式。
基本语法
request.security(symbol, timeframe, expression, gaps, lookahead, ignore_invalid_symbol, currency, calc_bars_count)| 参数 | 类型 | 说明 |
|---|---|---|
symbol | string | 标的标识符,如 "AAPL.US" 或 syminfo.tickerid |
timeframe | string | 时间框架字符串,如 "D"、"W"、"60" |
expression | 任意 series | 在请求的标的/时间框架上求值的表达式 |
gaps | BarmergeGaps | BarmergeGaps.Off(默认):延续最后值;BarmergeGaps.On:无新值时发出 na |
lookahead | BarmergeLookahead | BarmergeLookahead.Off(默认)或 BarmergeLookahead.On |
ignore_invalid_symbol | bool | 若为 true,未知标的返回 na 而不报错 |
currency | string | 价格转换的目标货币 |
calc_bars_count | int | 可选的正整数,用于限制请求流加载的最近历史窗口大小 |
基础示例
高时间框架收盘价
indicator("日线图上的周线收盘价", overlay: true);
let weekly_close = request.security(syminfo.tickerid, "W", close);
plot(weekly_close, "周线收盘", color: color.BLUE);其他标的
indicator("AAPL 图上的 SPY", overlay: false);
let spy_close = request.security("SPY.US", "D", close);
plot(spy_close);高时间框架指标
indicator("周线 RSI");
let weekly_rsi = request.security(syminfo.tickerid, "W", ta.rsi(close, 14));
plot(weekly_rsi);
hline(70);
hline(30);时间框架字符串
| 字符串 | 含义 |
|---|---|
"1"、"5"、"15"、"60" | 分钟 |
"D" | 日线 |
"W" | 周线 |
"M" | 月线 |
"3M"、"6M" | 多月 |
使用 timeframe.period 引用图表自身的时间框架。
预热
被请求的表达式本身就是一条序列:它在被请求的周期上逐 bar 求值,拥有自己的历史。 所以周线请求里的 ta.rsi(close, 14) 需要十四根周线才有意义 —— 而这些 bar 通常比图表的第一根还要早。
引擎会去要它们。周线流的请求含义是「覆盖图表首根 bar 之后,并且可以往前多取」, 同时附带该表达式读多深,因此遵守这一点的 provider 会把更早的周线一并返回, plot 从图表第一根 bar 起就有稳定的值。
是否真的如此取决于你的数据源。只返回图表首根 bar 之后数据的 provider 依然是正确的 —— 表达式只是自己慢慢预热,plot 会 na 到它读满为止。在日线图上,一个没有预热的 周线 ta.sma(close, 10) 大约要十周才出第一个值。看到这种现象,就说明数据流在图表 边界处被截断了。
内置 provider、playground 和 navi-chart 都会往前多取。
calc_bars_count
当请求只需要最近一小段历史时,可以使用 calc_bars_count。
- 传入正整数后,provider 最多为该请求流加载这么多最近 bar。
na表示不设置上限:请求锚定在图表上,provider 可以按上文所述往前多取用于预热。
calc_bars_count 是一个上限,因此它会绕开上文的预热 —— 请求含义变成「最近 N 根」, 不含更早的。设得过小时,表达式里的指标可能没有足够的 bar 来收敛。
let recent_weekly = request.security(syminfo.tickerid, "W", close, calc_bars_count: 2);gaps(空缺填充)
当请求的时间框架高于图表时间框架时,高时间框架 bar 收盘的频率低于图表 bar 推进的频率。
BarmergeGaps.Off(默认):最后已知值向前延续——序列中不会出现na。BarmergeGaps.On:每个高时间框架 bar 尚未收盘的图表 bar 都会发出na。
// Off(默认):weekly_close 始终有值
let weekly_close = request.security(syminfo.tickerid, "W", close);
// On:除周五(周线 bar 收盘)外的所有交易日均为 na
let weekly_close_gaps = request.security(syminfo.tickerid, "W", close, gaps: BarmergeGaps.On);lookahead(超前读取)
BarmergeLookahead.On 使表达式在周期内的第一个图表 bar 就能看到高时间框架 bar 的最终值,而不是正在形成中的值。这会在历史 bar 中引入未来数据泄漏——仅在明确需要时使用。
// 默认:看到正在形成的周线开盘价(本周内持续更新)
let weekly_open = request.security(syminfo.tickerid, "W", open);
// 使用 lookahead:周一即可看到已确认的周线开盘价
let weekly_open_confirmed = request.security(syminfo.tickerid, "W", open, lookahead: BarmergeLookahead.On);var 和 varip 变量
var 和 varip 变量需在脚本顶层声明,不能在 expression 参数内部声明。子实例会在请求的标的/时间框架上执行完整程序体,因此顶层的 var 状态会在请求的时间框架的各个 bar 之间独立累积:
indicator("累计成交量(周线)");
// 在顶层声明——子实例在周线 bar 上累积此变量
var cum: float = 0.0;
cum += volume;
let weekly_cum_vol = request.security(syminfo.tickerid, "W", cum);
plot(weekly_cum_vol);每个 request.security 调用点都有独立的子实例——其 var 状态与主脚本及其他 request.security 调用互相独立。
元组
表达式可以以元组形式返回多个值:
indicator("周线 OHLC");
let (w_open, w_high, w_low, w_close) =
request.security(syminfo.tickerid, "W", (open, high, low, close));
plot_candle(w_open, w_high, w_low, w_close);ignore_invalid_symbol
当标的可能不存在于数据提供者中时使用此参数:
let price = request.security("SOME.US", "D", close, ignore_invalid_symbol: true);
// 标的未识别时 price 为 na,不会触发运行时错误不使用此参数时,未识别的标的会触发运行时错误并中止执行。
request.security_lower_tf
获取低时间框架数据时,使用 request.security_lower_tf。它返回一个 Array<T>,包含当前图表 bar 内所有子 bar 的值,按升序排列:
indicator("日线图上的分钟级最高价");
// 返回每根日线 bar 内所有 1 分钟 bar 的最高价
let minute_highs = request.security_lower_tf(syminfo.tickerid, "1", high);
// 当前日线 bar 内的最高 1 分钟最高价
let intraday_high = minute_highs.max();
plot(intraday_high);当某个 bar 内没有子 bar 数据时,数组为空(minute_highs.size() == 0)。
Ticker 表达式
Ticker 表达式是一个将多个标的通过算术运算符组合的字符串。Navi 会将其分解为独立的 DataProvider 请求,逐 bar 对表达式求值,并将结果作为单一序列返回。
支持的运算符
| 运算符 | 示例 | 结果 |
|---|---|---|
* | "AAPL*2" | 标的值 × 标量 |
/ | "AAPL/SPY" | 两个标的的比值 |
+ | "AAPL+MSFT" | 两个标的之和 |
- | "AAPL-MSFT" | 两个标的之差 |
操作数可以是标的字符串("TICKER.MARKET")或数字字面量。运算符优先级与标准数学一致,必要时可使用括号。
示例
加权组合(50/50 投资组合)
let blend = request.security("AAPL.US*0.5+SPY.US*0.5", "D", close);相对表现(比值)
// AAPL 相对于 SPY 的价格——买一股 AAPL 需要多少股 SPY?
let ratio = request.security("AAPL.US/SPY.US", "D", close);
plot(ratio);价差(差值)
// 黄金/白银价差
let spread = request.security("GC1!.US/SI1!.US", "D", close);
plot(spread);多标的等权指数
// 四只科技股等权平均
let tech = request.security(
"AAPL.US*0.25+MSFT.US*0.25+GOOGL.US*0.25+AMZN.US*0.25",
"D",
close
);
plot(tech);执行原理
对于 "AAPL*0.5+SPY*0.5" 这样的表达式,Navi 的处理步骤:
- 提取各个标的(
AAPL、SPY),通过DataProvider获取对应的 K 线数据。 - 每个 bar 上,使用各标的子实例中请求的
expression字段(如close)对算术表达式求值。 - 将计算得到的标量结果对齐到图表 bar 后返回。
表达式中的每个标的都计入 max_security_calls 限制,与普通 request.security 调用相同。
限制
嵌套:
request.security的表达式内部可以再调用request.security,深度不限。没有单独的深度限制;约束它的是下面的子实例上限,因为每一层至少会建一个子实例。循环依赖会被拒绝:如果一串请求绕回了已经经过的调用点,这个过程不会结束。运行会以一条指明成环的错误停止,而不是耗尽内存。
子实例上限:
ExecutionLimits::max_security_calls(默认 40)限制一次运行最多可以建多少个子实例。子实例是一个完整的实例 —— 有自己的状态、序列缓冲和bar_index—— 而一个调用点可能不止花掉一个:- 后续 bar 上换了
symbol或timeframe,就会为那条序列另开一个子实例,并保留原来的以备该调用点回到它; - 表达式读了另一个请求的结果,那个请求就成为本子实例要求值的一部分,并且是在它自己的序列上求值 —— 所以四个互相读取的请求组成的链条要花十个子实例,而不是四个。
命名同一
(symbol, timeframe)的调用点共享取到的数据,但不共享计数 —— 同一标的的四个字段算四次。- 后续 bar 上换了

