Pine Script v6 Dynamic Requests

Fact checked by
Mike Christensen, CFOA
March 11, 2026
How to use Pine Script v6 dynamic requests for multi-symbol analysis, with request.security() inside loops, conditionals, and library functions.

Bottom Line

  • Pine Script v6 allows request.security() and other request functions to accept series string arguments instead of simple string only
  • Request calls can now be placed inside loops conditionals and library functions for the first time
  • This enables multi-symbol dashboards watchlist scanners and dynamic sector rotation strategies that were impossible in v5
  • Scripts are limited to 40 dynamic request calls per execution to prevent performance issues

What Changed in Pine Script v6 Requests

Pine Script v6 introduced dynamic requests, one of the most significant upgrades to the language in years1. For the first time, request.security() and all other request.*() functions can accept series string arguments, execute inside loops and conditional blocks, and work within exported library functions2. This unlocks multi-symbol dashboards, watchlist scanners, and dynamic sector rotation strategies that were simply impossible in v5.

If you have ever tried to build a Pine Script indicator that loops through a list of symbols and pulls data for each one, you know the frustration. In v5, the symbol and timeframe arguments required simple string values, meaning they had to be known at compile time and could never change during execution2. Every symbol needed its own hardcoded request.security() call. That limitation is now gone.

In this guide, we will walk through exactly how dynamic requests work, demonstrate practical code patterns for loops, conditionals, and libraries, and build two real-world examples: a multi-symbol dashboard and a dynamic sector rotation strategy. We will also cover the 40-call execution limit and performance best practices.

What Are Dynamic Requests

In Pine Script v5, all request.*() function calls required simple string arguments for the symbol and timeframe parameters2. A simple string must be known on the first script execution and cannot change afterward. This meant every request.security() call was permanently bound to one specific symbol and timeframe combination, and it had to execute in the global scope.

Pine Script v6 changes the default behavior so that all request.*() calls are dynamic2. Dynamic requests can:

  • Accept series string arguments for symbol, timeframe, and other context-defining parameters3
  • Execute within the local scopes of conditional structures, loops, and exported library functions2
  • Change their requested data feed on any historical bar4
  • Execute nested requests where one request.*() call evaluates another4

Pine v6 scripts enable dynamic requests by default. If you need backward-compatible behavior for any reason, you can disable it by adding dynamic_requests = false to your indicator() or strategy() declaration3.

Series String vs Simple String

The core technical change is the type qualifier upgrade for request parameters. Here is the difference at a glance:

  • v5 (simple string): The value must be a constant or an input. It cannot change across bars. Example: request.security("AAPL", "1D", close)2
  • v6 (series string): The value can be any string expression, including one that changes on every bar. Example: request.security(myDynamicSymbol, timeframe.period, close)3

This means a single request.security() instance in your code can fetch data from different symbols or timeframes on different bars, which is exactly what makes loops and conditionals possible.

Using Requests in Loops

The most common use case for dynamic requests is iterating over an array of symbols and pulling data for each one. In v5, you would need a separate request.security() call for every symbol, hard-coded in the global scope2. In v6, a single call inside a for...in loop handles everything4.

Here is a working example that requests volume data for ten forex pairs and calculates aggregate statistics:

//@version=6
indicator("Multi-Symbol Loop Demo", format = format.volume)

//@variable An array of symbol strings to request data for.
var array<string> symbols = array.from(
     "EURUSD", "USDJPY", "GBPUSD", "AUDUSD", "USDCAD",
     "USDCHF", "NZDUSD", "EURJPY", "GBPJPY", "EURGBP"
 )

//@variable An array to hold retrieved volume for each symbol.
array<float> requestedData = array.new<float>()

// Loop through symbols and request volume for each one.
for symbol in symbols
    float data = request.security("OANDA:" + symbol, timeframe.period, volume)
    requestedData.push(data)

// Calculate aggregate statistics from the collected data.
float avgVolume = requestedData.avg()
float maxVolume = requestedData.max()
float minVolume = requestedData.min()

// Plot the results.
plot(avgVolume, "Average volume", color.gray, 3)
plot(maxVolume, "Highest volume", color.teal, 3)
plot(minVolume, "Lowest volume", color.maroon, 3)

This pattern is clean and scalable. You define your symbol list once, loop through it, and collect the results. The request.security() call inside the loop executes once per symbol per bar, and each iteration pulls data from a different data feed.

Expression Limitation in Loops

There is one important constraint to understand: the expression argument of a request.*() call inside a loop cannot depend on the loop variable or any mutable variable that changes within the loop scope4.

For example, this code causes a compilation error because the expression volume * (10 - i) depends on the loop index i:

// This causes a compilation error.
for [i, symbol] in symbols
    float data = request.security("OANDA:" + symbol, timeframe.period, volume * (10 - i))
    requestedData.push(data)

The symbol and timeframe parameters can depend on loop variables, but the expression parameter cannot. If you need to weight or transform the returned data, do so after the request returns.

Using Requests in Conditionals

Dynamic requests also work inside if and switch blocks4. This means a request.*() call can activate zero or one time per execution, depending on a condition. In v5, every request call ran on every bar regardless of any surrounding logic.

Here is an example that switches the data source based on a user-selected asset class:

//@version=6
indicator("Conditional Request Demo", overlay = true)

//@variable User input to select the asset class.
string assetClass = input.string("Equities", "Asset Class",
     options = ["Equities", "Crypto", "Forex"])

//@variable The requested close price from a benchmark symbol.
float benchmarkClose = switch assetClass
    "Equities" => request.security("AMEX:SPY", "1D", close)
    "Crypto"   => request.security("BINANCE:BTCUSDT", "1D", close)
    "Forex"    => request.security("OANDA:EURUSD", "1D", close)

plot(benchmarkClose, "Benchmark", color.orange, 2)

Each branch of the switch contains a different request.security() call. Only the branch matching the user's input executes on any given bar. This keeps your code organized and avoids unnecessary data requests.

Series String Conditional Patterns

You can also use a single request.security() call with a series string symbol that changes based on conditions:

//@version=6
indicator("Series Symbol Demo")

//@variable Switches between two symbols every 20 bars.
string dynamicSymbol = bar_index % 40 < 20 ? "NASDAQ:AAPL" : "NASDAQ:MSFT"

float reqClose = request.security(dynamicSymbol, timeframe.period, close)
plot(reqClose, "Dynamic close", color.purple, 3)

// Label each symbol change for clarity.
if dynamicSymbol != dynamicSymbol[1]
    label.new(bar_index, reqClose, dynamicSymbol, textcolor = color.white)

This pattern is useful when you want to rotate through symbols programmatically based on bar conditions, time of day, or indicator signals.

Using Requests in Libraries

Libraries with dynamic requests enabled can export functions containing request.*() calls4. This is a powerful feature for building reusable data-fetching modules that other scripts can import.

Here is a library that exports a function to fetch confirmed higher-timeframe OHLC prices:

//@version=6
library("DynamicRequests")

//@function        Requests confirmed HTF OHLC data for a specified ticker and timeframe.
//@param tickerID  The ticker identifier to request data for.
//@param timeframe The timeframe of the requested data.
//@returns         A tuple of the last confirmed open, high, low, and close.
export htfPrices(string tickerID, string timeframe) =>
    if timeframe.in_seconds() >= timeframe.in_seconds(timeframe)
        runtime.error("The timeframe argument must be higher than the chart timeframe.")
    request.security(tickerID, timeframe, [open[1], high[1], low[1], close[1]],
         lookahead = barmerge.lookahead_on)

Any script that imports this library can call htfPrices("NASDAQ:AAPL", "1D") to get daily OHLC data without writing its own request.security() logic. The [1] offset combined with barmerge.lookahead_on is the recommended method to avoid repainting in higher-timeframe requests.

Key constraint: The expression argument inside an exported library function cannot depend on the function's parameters4. The symbol and timeframe parameters can use the function arguments, but the evaluated expression must be independent of them.

Multi-Symbol Dashboard

Let us build a practical multi-symbol dashboard that displays the current price and daily change for a list of stocks. This combines dynamic requests in a loop with table drawing:

//@version=6
indicator("Multi-Symbol Dashboard", overlay = true)

//@variable A user-defined list of symbols.
string symbolListInput = input.text_area(
     "NASDAQ:AAPL,NASDAQ:MSFT,NASDAQ:GOOGL,NASDAQ:NVDA,NASDAQ:AMZN",
     "Symbols (comma-separated)")

//@variable An array created by splitting the input string.
var array<string> symbols = str.split(symbolListInput, ",")

//@variable A dashboard table displayed in the top-right corner.
var table dashboard = table.new(position.top_right, 3, symbols.size() + 1, chart.fg_color)

// Initialize header row on the first bar.
if barstate.isfirst
    dashboard.cell(0, 0, "Symbol", text_color = chart.bg_color, text_size = size.small)
    dashboard.cell(1, 0, "Price", text_color = chart.bg_color, text_size = size.small)
    dashboard.cell(2, 0, "Change %", text_color = chart.bg_color, text_size = size.small)

// Update data on the last bar only to minimize resource usage.
if barstate.islast
    for [i, symbol] in symbols
        float symClose = request.security(symbol, timeframe.period, close)
        float symPrevClose = request.security(symbol, "1D", close[1],
             lookahead = barmerge.lookahead_on)
        float changePercent = (symClose - symPrevClose) / symPrevClose * 100
        color changeColor = changePercent >= 0 ? color.green : color.red

        dashboard.cell(0, i + 1, symbol,
             text_color = chart.bg_color, text_size = size.small)
        dashboard.cell(1, i + 1, str.tostring(symClose, format.mintick),
             text_color = chart.bg_color, text_size = size.small)
        dashboard.cell(2, i + 1, str.tostring(changePercent, "#.##") + "%",
             text_color = changeColor, text_size = size.small)

This dashboard loops through each symbol, requests the current close and previous daily close, calculates the percentage change, and color-codes it green or red. The table updates only on the last bar to minimize drawing operations.

Dynamic Sector Rotation Strategy

Dynamic requests are especially powerful for sector rotation strategies, where you compare relative strength across multiple instruments and allocate to the strongest performer. Here is a strategy skeleton that ranks symbols by momentum:

//@version=6
strategy("Dynamic Sector Rotation", overlay = false)

//@variable An array of sector ETF symbols to compare.
var array<string> sectors = array.from(
     "AMEX:XLK", "AMEX:XLF", "AMEX:XLE", "AMEX:XLV",
     "AMEX:XLY", "AMEX:XLP", "AMEX:XLI", "AMEX:XLU"
 )

int lookback = input.int(20, "Momentum Lookback", minval = 5)

//@variable Tracks the highest momentum value found.
var float bestMomentum = na
//@variable Stores the symbol with the highest momentum.
var string bestSymbol = na

float currentBest = -999.0
string currentBestSym = ""

// Loop through sectors and calculate momentum for each.
for sector in sectors
    float sectorClose = request.security(sector, "1D", close)
    float sectorPast = request.security(sector, "1D", close[lookback],
         lookahead = barmerge.lookahead_on)
    float momentum = (sectorClose - sectorPast) / sectorPast * 100

    if momentum > currentBest
        currentBest := momentum
        currentBestSym := sector

bestMomentum := currentBest
bestSymbol := currentBestSym

// Plot the best momentum and display the leading sector.
plot(bestMomentum, "Best Momentum %", color.teal, 2)

if barstate.islast
    label.new(bar_index, bestMomentum,
         "Leader: " + bestSymbol + "\n" + str.tostring(bestMomentum, "#.##") + "%",
         color = color.teal, textcolor = color.white)

This strategy requests daily close data for eight sector ETFs on every bar, calculates the percentage momentum over a user-defined lookback period, and identifies the strongest sector. In a full implementation, you would add strategy.entry() calls to rotate into the leading sector and exit lagging positions.

Limitations and the 40-Call Limit

Dynamic requests are powerful, but they come with important constraints that every Pine Script developer should understand.

Execution Call Limit

A script can execute a maximum of 40 unique request.*() calls per bar3. If a loop iterates 50 times and each iteration calls request.security() with a different symbol, the script will raise a runtime error on the 41st iteration. Identical calls (same symbol, timeframe, and expression) are counted only once, so redundant requests do not count toward the limit4.

Here is what triggers the error:

// This raises a runtime error at i == 41.
for i = 1 to 50
    reqSum += request.security(syminfo.tickerid, str.tostring(i), close)

Each iteration requests a different timeframe string, producing 50 unique calls and exceeding the 40-call limit.

Historical Bar Requirement

All datasets that a dynamic request accesses on realtime bars must have been previously requested on historical bars4. You cannot request a new symbol or timeframe for the first time on a realtime bar. If you try, the script raises a runtime error. This means your loop or conditional logic must cover all possible data feeds during the historical execution phase.

Expression Independence

The expression argument of any request.*() call inside a loop cannot depend on the loop variable or mutable variables modified within the loop4. The symbol and timeframe parameters can use loop-dependent values, but the expression must remain loop-invariant.

Tuple Element Limit

All request.*() calls combined cannot return more than 127 tuple elements3. If you need more values, use user-defined types (UDTs) to bundle fields into a single object, which counts as one tuple element.

Performance Tips

Working with dynamic requests efficiently requires some planning. Here are best practices to keep your scripts fast and within the limits.

  • Minimize unique calls: Each unique symbol-timeframe-expression combination counts toward the 40-call limit3. Request only what you need.
  • Update tables on the last bar: Wrap table-drawing logic in if barstate.islast to avoid redrawing on every historical bar.
  • Use calc_bars_count: If you only need recent data, set calc_bars_count to limit how far back the request fetches3. This reduces memory usage.
  • Bundle expressions with tuples: Request [open, high, low, close] as a single tuple instead of making four separate calls for the same symbol.
  • Avoid nested requests unless necessary: Nested requests (one request.security() evaluating another) add complexity and can produce unexpected results4. Use them deliberately.
  • Use ignore_invalid_symbol: When looping through user-provided symbol lists, set ignore_invalid_symbol = true to prevent runtime errors from typos or delisted tickers3.

Automating Multi-Symbol Strategies with TradersPost

Dynamic requests open up multi-symbol analysis inside TradingView, but executing trades across multiple symbols still requires automation infrastructure. This is where TradersPost bridges the gap between your Pine Script signals and live broker execution.

Here is how the workflow fits together:

  • Signal generation: Your Pine Script v6 strategy uses dynamic requests to scan multiple symbols and generate buy or sell signals for the strongest candidates.
  • Alert forwarding: TradingView alerts fire when your strategy identifies a trade, sending the signal to TradersPost via webhook5.
  • Order execution: TradersPost receives the webhook, applies your risk management rules, and routes the order to your connected broker, whether that is a stock broker, futures platform, or crypto exchange5.
  • Multi-account support: A single Pine Script strategy scanning eight sector ETFs can route trades to multiple accounts simultaneously through TradersPost, including prop firm accounts with specific risk parameters5.

The combination of Pine Script v6 dynamic requests for analysis and TradersPost for execution gives you a complete automated pipeline. Your script handles the intelligence, scanning and ranking symbols in real time, while TradersPost handles the execution logistics across any number of brokers and accounts.

Conclusion

Dynamic requests are the most impactful feature in Pine Script v6 for traders who work with multiple symbols. The ability to use series string arguments, place request.security() inside loops and conditionals, and export request-containing functions from libraries removes years of workarounds and code duplication.

The key points to remember are:

  • Pine v6 enables dynamic requests by default; no special flag is needed
  • A single request.security() call inside a loop can fetch data for an entire watchlist
  • The expression argument cannot depend on loop variables, but symbol and timeframe can
  • You are limited to 40 unique request.*() calls per bar execution
  • All datasets must be requested on historical bars before they can be accessed on realtime bars

Whether you are building multi-symbol dashboards, sector rotation strategies, or cross-market correlation scanners, dynamic requests give you the tools to do it cleanly and efficiently within Pine Script.

References

1 Pine Script Release Notes
2 Pine Script v5 to v6 Migration Guide
3 Pine Script v6 Language Reference
4 Pine Script User Manual
5 TradersPost - Automated Trading Platform

Ready to automate your trading? Try a free 7-day account:
Try it for free ->