Skip to content

Cross-Symbol & Multi-Timeframe

request.security() evaluates an expression in the context of a different symbol and/or timeframe, and returns the result aligned to the current chart bar. It is the primary way to access multi-timeframe (MTF) data and data from other instruments in Navi.

Basic Syntax

navi
request.security(symbol, timeframe, expression, gaps, lookahead, ignore_invalid_symbol, currency, calc_bars_count)
ParameterTypeDescription
symbolStringSymbol identifier, e.g. "AAPL.US" or syminfo.tickerid
timeframeStringTimeframe string, e.g. "D", "W", "60"
expressionany seriesExpression evaluated on the requested symbol/timeframe
gapsBarmergeGapsBarmergeGaps.Off (default): carry last value forward; BarmergeGaps.On: emit na between confirmations
lookaheadBarmergeLookaheadBarmergeLookahead.Off (default) or BarmergeLookahead.On
ignore_invalid_symbolboolIf true, return na instead of error for unknown symbols
currencyStringQuote the requested series in this currency instead of the symbol's own. Served by the data provider; syminfo.currency inside expression reports it
calc_bars_countintOptional positive limit for how much recent request history is loaded

Simple Examples

Higher timeframe close

navi
indicator("Weekly Close on Daily Chart", overlay: true);

let weekly_close = request.security(syminfo.tickerid, "W", close);
plot(weekly_close, "Weekly Close", color: color.BLUE);

Another symbol

navi
indicator("SPY on AAPL chart", overlay: false);

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

Higher timeframe indicator

navi
indicator("Weekly RSI");

let weekly_rsi = request.security(syminfo.tickerid, "W", ta.rsi(close, 14));
plot(weekly_rsi);
hline(70);
hline(30);

Timeframe Strings

StringMeaning
"1", "5", "15", "60"Minutes
"D"Daily
"W"Weekly
"M"Monthly
"3M", "6M"Multi-month

Use timeframe.period to reference the chart's own timeframe.

Warm-up

The requested expression is a series in its own right: it is evaluated bar by bar on the requested timeframe, with its own history. So ta.rsi(close, 14) on a weekly request needs fourteen weekly bars before it means anything — and those bars are normally older than the chart's first bar.

The engine asks for them. A weekly stream is requested as "cover the chart's first bar onward, and reach further back if it helps", together with how deep the expression reads, so a provider that honours it hands back the earlier weekly bars and the plot has a settled value from the chart's first bar.

Whether that happens is up to your data source. A provider that sends only bars at or after the chart's first bar is still correct — the expression simply warms up on its own, and the plot opens na for as long as it reads back. On a daily chart, a weekly ta.sma(close, 10) with no warm-up is about ten weeks of na before the first value. If you see that, the stream is being trimmed at the chart boundary.

The built-in providers, the playground and navi-chart all reach back.

calc_bars_count

Use calc_bars_count when a request only needs a short recent window.

  • A positive value asks the provider for at most that many recent bars for the requested stream.
  • na leaves the stream uncapped: it is anchored on the chart and the provider may reach back for warm-up, as above.

calc_bars_count is a cap, so it opts out of the warm-up above — the request is "the last N bars" and nothing older. Set it low and an indicator inside the expression may not have enough bars to settle.

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

Gaps

When the requested timeframe is higher than the chart timeframe, a new higher-TF bar closes less frequently than the chart advances.

  • BarmergeGaps.Off (default): the last known value is carried forward — the series has no na values between higher-TF bar closes.
  • BarmergeGaps.On: a na is emitted for every chart bar where the higher-TF bar has not yet closed.
MTF alignment and gapsMTF alignment and gaps
navi
// Off (default): weekly_close carries forward — always defined
let weekly_close = request.security(syminfo.tickerid, "W", close);

// On: na on every day except when the weekly bar closes
let weekly_close_gaps = request.security(syminfo.tickerid, "W", close, gaps: BarmergeGaps.On);

Lookahead

BarmergeLookahead.On makes the expression see the final value of the higher-TF bar from the very first chart bar within that period, rather than the still-forming value. This can introduce future leak into historical bars — only use it when intentional.

BarmergeLookahead.Off vs BarmergeLookahead.OnBarmergeLookahead.Off vs BarmergeLookahead.On
navi
// Default: sees the forming weekly close (updates throughout the week)
let weekly_open = request.security(syminfo.tickerid, "W", open);

// With lookahead: sees the confirmed weekly open immediately on Monday
let weekly_open_confirmed = request.security(syminfo.tickerid, "W", open, lookahead: BarmergeLookahead.On);

var and varip Variables

var and varip variables cannot be declared inside the expression argument. To accumulate state across bars on the requested timeframe, declare the variable at the top level of the script. The sub-instance runs the full program body on the requested symbol/timeframe, so top-level var state is maintained per call site independently of the main chart:

navi
indicator("Cumulative Volume (Weekly)");

// Declared at top level — the sub-instance accumulates this on weekly bars
var cum: float = 0.0;
cum += volume;

let weekly_cum_vol = request.security(syminfo.tickerid, "W", cum);
plot(weekly_cum_vol);

Each request.security call site has an isolated sub-instance — its var state is independent of the main script and of other request.security calls.

Tuples

An expression can return multiple values as a tuple:

navi
indicator("Weekly 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

Use this flag when the symbol might not exist in the data provider:

navi
let price = request.security("SOME.US", "D", close, ignore_invalid_symbol: true);
// price is na if the symbol is not recognised; no runtime error is raised

Without this flag, an unrecognised symbol raises a runtime error and halts execution.

request.security_lower_tf

For lower timeframes, use request.security_lower_tf. It returns an Array<T> containing every sub-bar value within the current chart bar, in ascending order:

security_lower_tf — sub-bars collected into an arraysecurity_lower_tf — sub-bars collected into an array
navi
indicator("Intraday highs on Daily chart");

// Returns an array of all 1-minute highs within each daily bar
let minute_highs = request.security_lower_tf(syminfo.tickerid, "1", high);

// Highest 1-minute high within the current daily bar
let intraday_high = minute_highs.max();
plot(intraday_high);

The array is empty (minute_highs.size() == 0) for bars where no sub-bars are available.

Ticker Expressions

A ticker expression is a string that combines multiple symbols using arithmetic operators. Navi decomposes it into individual DataProvider requests, evaluates the expression per bar, and returns the result as a single series.

Supported operators

OperatorExampleResult
*"AAPL*2"symbol value × scalar
/"AAPL/SPY"ratio between two symbols
+"AAPL+MSFT"sum of two symbols
-"AAPL-MSFT"difference of two symbols

Operands can be symbol strings ("TICKER.MARKET") or numeric literals. Standard operator precedence applies; use parentheses if needed.

Examples

Weighted blend (50/50 portfolio)

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

Relative performance (ratio)

navi
// AAPL price relative to SPY — how many SPY shares does one AAPL buy?
let ratio = request.security("AAPL.US/SPY.US", "D", close);
plot(ratio);

Spread (difference)

navi
// Gold/Silver spread
let spread = request.security("GC1!.US/SI1!.US", "D", close);
plot(spread);

Multi-symbol index

navi
// Equal-weight average of four tech stocks
let tech = request.security(
    "AAPL.US*0.25+MSFT.US*0.25+GOOGL.US*0.25+AMZN.US*0.25",
    "D",
    close
);
plot(tech);

How it works

For an expression like "AAPL*0.5+SPY*0.5", Navi:

  1. Extracts each symbol (AAPL, SPY) and fetches their candlestick data via DataProvider.
  2. On each bar, evaluates the arithmetic expression using the requested expression field (e.g. close) from each symbol's sub-instance.
  3. Returns the computed scalar result aligned to the chart bar.

Each symbol in the expression is subject to the same max_security_calls limit as a regular request.security call.

Limitations

  • Nesting: an expression inside request.security may itself call request.security, to any depth. There is no separate depth limit; what bounds nesting is the child limit below, since every level builds at least one child.

  • Circular expressions are refused: if a chain of requests comes back to a call site it has already passed through, nothing would ever end it. The run stops with an error naming the cycle instead of exhausting memory.

  • Child limit: ExecutionLimits::max_security_calls (default 40) caps how many children one run may build. A child is a whole sub-instance — its own state, series buffers and bar_index — and one call site can cost more than one:

    • naming a different symbol or timeframe on a later bar opens a child for that series too, and keeps the earlier one in case the call site returns to it;
    • an expression that reads another request's result makes that request part of what this child evaluates, against its own series, so a chain of four requests reading each other costs ten children rather than four.

    Call sites naming the same (symbol, timeframe) share the data that is fetched but not the count, so four fields of one symbol are four.