Skip to content

跨标的与多时间框架

request.security() 在不同标的和/或时间框架的上下文中对表达式求值,并将结果对齐到当前图表 bar。它是 Navi 中访问多时间框架(MTF)数据和其他标的数据的主要方式。

基本语法

navi
request.security(symbol, timeframe, expression, gaps, lookahead, ignore_invalid_symbol, currency, calc_bars_count)
参数类型说明
symbolstring标的标识符,如 "AAPL.US"syminfo.tickerid
timeframestring时间框架字符串,如 "D""W""60"
expression任意 series在请求的标的/时间框架上求值的表达式
gapsBarmergeGapsBarmergeGaps.Off(默认):延续最后值;BarmergeGaps.On:无新值时发出 na
lookaheadBarmergeLookaheadBarmergeLookahead.Off(默认)或 BarmergeLookahead.On
ignore_invalid_symbolbool若为 true,未知标的返回 na 而不报错
currencystring价格转换的目标货币
calc_bars_countint可选的正整数,用于限制请求流加载的最近历史窗口大小

基础示例

高时间框架收盘价

navi
indicator("日线图上的周线收盘价", overlay: true);

let weekly_close = request.security(syminfo.tickerid, "W", close);
plot(weekly_close, "周线收盘", color: color.BLUE);

其他标的

navi
indicator("AAPL 图上的 SPY", overlay: false);

let spy_close = request.security("SPY.US", "D", close);
plot(spy_close);

高时间框架指标

navi
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 来收敛。

navi
let recent_weekly = request.security(syminfo.tickerid, "W", close, calc_bars_count: 2);

gaps(空缺填充)

当请求的时间框架高于图表时间框架时,高时间框架 bar 收盘的频率低于图表 bar 推进的频率。

  • BarmergeGaps.Off(默认):最后已知值向前延续——序列中不会出现 na
  • BarmergeGaps.On:每个高时间框架 bar 尚未收盘的图表 bar 都会发出 na
MTF 对齐与 gapsMTF 对齐与 gaps
navi
// 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 中引入未来数据泄漏——仅在明确需要时使用。

BarmergeLookahead.Off 与 BarmergeLookahead.On 对比BarmergeLookahead.Off 与 BarmergeLookahead.On 对比
navi
// 默认:看到正在形成的周线开盘价(本周内持续更新)
let weekly_open = request.security(syminfo.tickerid, "W", open);

// 使用 lookahead:周一即可看到已确认的周线开盘价
let weekly_open_confirmed = request.security(syminfo.tickerid, "W", open, lookahead: BarmergeLookahead.On);

varvarip 变量

varvarip 变量需在脚本顶层声明,不能在 expression 参数内部声明。子实例会在请求的标的/时间框架上执行完整程序体,因此顶层的 var 状态会在请求的时间框架的各个 bar 之间独立累积:

navi
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 调用互相独立。

元组

表达式可以以元组形式返回多个值:

navi
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

当标的可能不存在于数据提供者中时使用此参数:

navi
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 的值,按升序排列:

security_lower_tf — 子 bar 收集为数组security_lower_tf — 子 bar 收集为数组
navi
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 投资组合)

navi
let blend = request.security("AAPL.US*0.5+SPY.US*0.5", "D", close);

相对表现(比值)

navi
// AAPL 相对于 SPY 的价格——买一股 AAPL 需要多少股 SPY?
let ratio = request.security("AAPL.US/SPY.US", "D", close);
plot(ratio);

价差(差值)

navi
// 黄金/白银价差
let spread = request.security("GC1!.US/SI1!.US", "D", close);
plot(spread);

多标的等权指数

navi
// 四只科技股等权平均
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 的处理步骤:

  1. 提取各个标的(AAPLSPY),通过 DataProvider 获取对应的 K 线数据。
  2. 每个 bar 上,使用各标的子实例中请求的 expression 字段(如 close)对算术表达式求值。
  3. 将计算得到的标量结果对齐到图表 bar 后返回。

表达式中的每个标的都计入 max_security_calls 限制,与普通 request.security 调用相同。

限制

  • 嵌套request.security 的表达式内部可以再调用 request.security,深度不限。没有单独的深度限制;约束它的是下面的子实例上限,因为每一层至少会建一个子实例。

  • 循环依赖会被拒绝:如果一串请求绕回了已经经过的调用点,这个过程不会结束。运行会以一条指明成环的错误停止,而不是耗尽内存。

  • 子实例上限ExecutionLimits::max_security_calls(默认 40)限制一次运行最多可以建多少个子实例。子实例是一个完整的实例 —— 有自己的状态、序列缓冲和 bar_index —— 而一个调用点可能不止花掉一个

    • 后续 bar 上换了 symboltimeframe,就会为那条序列另开一个子实例,并保留原来的以备该调用点回到它;
    • 表达式读了另一个请求的结果,那个请求就成为本子实例要求值的一部分,并且是在它自己的序列上求值 —— 所以四个互相读取的请求组成的链条要花十个子实例,而不是四个。

    命名同一 (symbol, timeframe) 的调用点共享取到的数据,但不共享计数 —— 同一标的的四个字段算四次。

基于 MIT 许可证发布。