Pine Script v6 New Features Explained With Code Examples

jayadev rana Avatar

Pine Script v6 is the current major version of TradingView’s programming language, released in December 2024. Its headline new features are dynamic requests (call request.security() inside loops with series-string symbols), a stricter boolean type (no more na booleans, explicit int/float casting, and lazy and/or evaluation), enums with input.enum() dropdowns, text size and formatting for labels, boxes and tables, and negative array indices. As of July 2026, v6 receives every new Pine capability while v5 is frozen. In this guide I show each feature with before/after code you can paste straight into the Pine Editor.

In my 8 years coding Pine Script, no upgrade cleaned up client projects as fast as v6. The week it shipped I tore six stacked request.security() calls out of a sector-rotation dashboard and replaced them with a single loop — same output, roughly a third of the code. Below is exactly how the major v6 features work, each shown against its v5 ‘before’ so the difference is obvious. For a plain-language overview of where the language stands this year, see my Pine Script latest version guide; this article stays hands-on.

1. Dynamic requests: request.security() inside loops

Before v6, every request.*() call needed a simple (compile-time) string for the symbol and timeframe, and it had to sit in the global scope. Pulling three symbols meant three hand-written calls. In v6, request.*() accepts series strings and runs inside loops, conditionals and exported library functions, so one call can fetch many feeds.

Before (v5) — one call per symbol:

//@version=5
indicator('Multi-symbol closes (v5)')
aapl  = request.security('NASDAQ:AAPL',  timeframe.period, close)
msft  = request.security('NASDAQ:MSFT',  timeframe.period, close)
googl = request.security('NASDAQ:GOOGL', timeframe.period, close)
plot(aapl)
plot(msft)
plot(googl)

After (v6) — one dynamic call in a loop:

//@version=6
indicator('Multi-symbol closes (v6)')
symbols = array.from('NASDAQ:AAPL', 'NASDAQ:MSFT', 'NASDAQ:GOOGL')
closes  = array.new<float>()
for i = 0 to array.size(symbols) - 1
    sym = array.get(symbols, i)
    array.push(closes, request.security(sym, timeframe.period, close))
plot(array.get(closes, 0), 'AAPL',  color.aqua)
plot(array.get(closes, 1), 'MSFT',  color.orange)
plot(array.get(closes, 2), 'GOOGL', color.fuchsia)

One caveat I stress with every client: v6 still caps you at 40 unique request.*() calls per script (64 on the Ultimate plan), so a dynamic loop over a long symbol list can still hit the ceiling. Dynamic requests also make lookahead and repainting easier to get wrong, especially when you pull higher-timeframe data — read my note on request.security lookahead before you ship anything live.

2. The boolean overhaul: no na, explicit casts, lazy logic

v6 reworked the bool type. Three changes will break v5 scripts if you ignore them.

Numbers no longer implicitly cast to bool

In v5 a number could stand in for a condition — na, 0 and 0.0 meant false, and anything else meant true. v6 requires an explicit cast with bool().

// v5 - bar_index used directly as a condition (implicit cast)
color expr5 = bar_index ? color.green : color.red

// v6 - cast the number explicitly with bool()
color expr6 = bool(bar_index) ? color.green : color.red

Booleans can never be na

A v6 bool is only true or false. You can no longer assign na to a bool, and the na(), nz() and fixnan() functions reject bool arguments. A history reference on the first bar now returns false instead of na. Where you relied on a third ‘unknown’ state, switch to an int or an enum.

Lazy (short-circuit) and/or

v6 stops evaluating and/or as soon as the result is known. That lets you guard an array access on a single line — impossible in v5, where both sides always ran and the out-of-bounds access threw a runtime error.

//@version=6
indicator('Lazy evaluation demo')
array<bool> myArray = array.new<bool>()
if close > open
    array.push(myArray, true)
// array.first() runs ONLY if the size check passes - no out-of-bounds error
if array.size(myArray) != 0 and array.first(myArray)
    label.new(bar_index, high, 'Test')

Need these v6 upgrades wired into your own indicator or strategy? I convert v5 scripts to clean v6 and build custom tools from scratch. Message me on WhatsApp at +91 77352 68199, or see my Pine Script development service.

3. Enums and input.enum() dropdowns

Enums are a brand-new v6 type: a fixed set of named values. They replace the fragile ‘magic string’ inputs I used to litter through v5 code, and input.enum() builds the settings dropdown for you automatically.

//@version=6
indicator('Enum input demo (v6)', overlay = true)

enum Mode
    trend  = 'Trend following'
    revert = 'Mean reversion'
    off    = 'Disabled'

Mode userMode = input.enum(Mode.trend, 'Strategy mode')

float signal = switch userMode
    Mode.trend  => ta.sma(close, 20) - ta.sma(close, 50)
    Mode.revert => ta.sma(close, 50) - close
    => 0.0

plot(signal, 'Signal')

The compiler now enforces the allowed values, so a typo like ‘Trned’ becomes a compile-time error instead of a silent dead branch that only bites you at runtime.

4. Text size and formatting for labels, boxes and tables

v6 lets drawing text use typographic point sizes (a plain int) instead of only the old size.* constants, and adds a text_formatting parameter for bold, italic, or both via text.format_bold, text.format_italic and text.format_none.

//@version=6
indicator('Text formatting v6', overlay = true)
var t = table.new(position.bottom_center, 1, 2, bgcolor = color.yellow, frame_color = color.black, frame_width = 1)
if barstate.islastconfirmedhistory
    t.cell(0, 0, 'Bold header', text_size = 24, text_formatting = text.format_bold)
    t.cell(0, 1, 'Bold + italic, size 40', text_size = 40, text_formatting = text.format_bold + text.format_italic)

The same typographic sizing and text_formatting work on box.new() and label.new() (the label uses its size argument for the point size), so a dashboard can finally look like deliberate design instead of three fixed sizes.

5. Negative array indices

array.get(), array.set(), array.insert() and array.remove() now accept negative indices that count back from the end — a small change that strips a lot of array.size() – 1 noise out of collection code.

// v5 - count back from size manually
last5       = array.get(myArray, array.size(myArray) - 1)
secondLast5 = array.get(myArray, array.size(myArray) - 2)

// v6 - negative indices read from the end
last6       = array.get(myArray, -1)
secondLast6 = array.get(myArray, -2)

6. Bonus: strategies no longer die at 9,000 trades

Active strategies used to halt with an error after simulating 9,000 trades unless you ran Deep Backtesting. In v6 the strategy trims the oldest orders and keeps calculating; the new strategy.closedtrades.first_index variable tells you the index of the oldest order still on the books, which you can feed to strategy.closedtrades.*() calls. If you route those orders to a live account, this pairs well with my work on MT4/MT5 automation.

v5 vs v6 quick reference

Area Pine Script v5 Pine Script v6
request.security() context Simple strings, global scope only Series strings, runs in loops and conditionals
bool type true, false or na Only true or false
Numbers as conditions Implicitly cast to bool Must wrap with bool()
and / or evaluation Both sides always run Lazy / short-circuit
Named value sets String constants enum + input.enum()
Drawing text Fixed size.* constants Point sizes plus bold/italic
Array indexing 0 to size-1 only Negative indices allowed
Strategy trade cap Halts at 9,000 trades Trims oldest, keeps running

Frequently asked questions

What are the main new features in Pine Script v6?

Dynamic requests (request.security() with series strings inside loops), a stricter boolean type with no na and lazy and/or evaluation, enums with input.enum() dropdowns, typographic text sizes with bold/italic formatting, negative array indices, and strategies that trim old orders instead of halting at 9,000 trades.

Is Pine Script v5 still supported?

Yes. Existing v5 scripts keep running, but v5 is frozen — every new feature since December 2024 ships only to v6. Convert your scripts if you want access to the new tools.

How do I convert a v5 script to v6?

Open the Pine Editor, then use ‘Manage script’ and choose ‘Convert code to v6’. The auto-converter handles most changes, but boolean-na logic and implicit number-to-bool casts usually need a manual fix afterwards.

Why does my v6 script throw a boolean error after upgrading?

Almost always because a number is used directly as a condition (wrap it in bool()), or because a bool was assigned na or passed to na()/nz()/fixnan(). Replace any three-state bool with an int or an enum.

How many request.security() calls can a v6 script make?

Up to 40 unique request.*() calls, or 64 on the Ultimate plan. Identical calls are de-duplicated, so repeating the exact same request counts only once toward the limit.

Ready to ship on v6? Whether you need a v5-to-v6 migration, a new custom indicator, or a fully automated strategy, I can help. WhatsApp me at +91 77352 68199 and tell me what you’re building.

Risk disclaimer: Trading and investing carry a substantial risk of loss and are not suitable for every investor. The code in this article is provided for educational purposes only and is not financial advice or a recommendation to buy or sell any instrument. Backtested and past results do not guarantee future returns. Always test scripts on a demo account and consult a licensed financial advisor before trading real capital.

Leave a Reply

Your email address will not be published. Required fields are marked *