Pine Script v6 Complete Guide

Fact checked by
Mike Christensen, CFOA
March 11, 2026
The complete guide to Pine Script v6 covering every new feature, from enums and dynamic requests to runtime logging and polyline drawing.

Bottom Line

  • Pine Script v6 is TradingView's most significant scripting update, adding enums, dynamic requests, runtime logging, and polylines
  • Stricter boolean handling eliminates na booleans and implicit casting, reducing bugs in trading scripts
  • Dynamic request functions let scripts pull data from any symbol or timeframe inside loops and conditionals
  • The enum type provides type-safe named constants with dropdown input support for professional strategy settings
  • Runtime logging with log.info(), log.warning(), and log.error() replaces plot-based debugging

Pine Script v6 is the most substantial upgrade TradingView has released to its scripting language since the jump from version 4 to version 5. Launched in November 20241, Pine Script v6 introduces entirely new language constructs like enums, unlocks dynamic data requests that were previously impossible, adds runtime logging for real debugging, and tightens the type system to catch bugs before they reach your charts2. If you write indicators or strategies on TradingView, this is the version that changes how you build.

This guide covers every major Pine Script v6 feature in depth. Whether you are migrating existing scripts or starting fresh, bookmark this page as your definitive reference. We walk through enums, dynamic requests, runtime logging, polyline drawing, boolean logic changes, type system improvements, strategy enhancements, visual upgrades, new symbol variables, post-launch additions, and how to automate your v6 strategies for live trading.

Enums: Type-Safe Named Constants

The enum keyword is one of the headline additions in Pine Script v6. Enumerations let you define a fixed set of named values that a variable, function parameter, or collection can accept. Instead of passing raw strings or integers to control script behavior, you declare an enum type once and the compiler enforces that only valid members are used throughout your code3.

Declaring an Enum

An enum declaration lists its fields on indented lines below the enum keyword. Each field represents a unique member of the enum type. You can optionally assign a string title to each field, which serves as the display label in input dropdown menus and is retrievable through str.tostring().

//@version=6
indicator("Enum demo")

//@enum An enumeration of oscillator choices.
enum OscType
    rsi = "Relative Strength Index"
    mfi = "Money Flow Index"
    cci = "Commodity Channel Index"

OscType oscInput = input.enum(OscType.rsi, "Oscillator type")

calcOsc(float src, simple int len, OscType sel) =>
    switch sel
        OscType.rsi => ta.rsi(src, len)
        OscType.mfi => ta.mfi(src, len)
        OscType.cci => ta.cci(src, len)

plot(calcOsc(close, 20, oscInput))

In the example above, the OscType enum defines three members. The input.enum() function creates a dropdown in the Settings/Inputs tab showing the field titles. The calcOsc() function accepts only OscType members for its sel parameter, so passing a raw string or a member from a different enum type causes a compilation error.

Enum Inputs and Dropdowns

The input.enum() function is the primary way to expose enum choices to script users. When a script calls this function, TradingView renders a dropdown menu in the script's settings panel, populated with the titles of all fields in the enum type. If a field has no explicit title, its name is used as the display text.

This feature replaces the common v5 pattern of using input.string() with a static options array. Enums are safer because the compiler validates every reference at compile time, whereas string comparisons only fail at runtime if you mistype a value.

Enums in Collections

Arrays, matrices, and maps can all store enum members. Maps with enum keys are especially powerful because they restrict the set of allowed keys to exactly the members of the enum, plus na. This gives you a fixed-key dictionary where the compiler prevents invalid key insertion.

//@version=6
indicator("Enum map demo", overlay = true)

enum Signal
    strongBuy  = "Strong buy"
    buy        = "Buy"
    neutral    = "Neutral"
    sell       = "Sell"
    strongSell = "Strong sell"

var map<Signal, float> counters = map.new<Signal, float>()

if barstate.isfirst
    counters.put(Signal.strongBuy, 0)
    counters.put(Signal.buy, 0)
    counters.put(Signal.neutral, 0)
    counters.put(Signal.sell, 0)
    counters.put(Signal.strongSell, 0)

float ema  = ta.ema(close, 50)
float rank = ta.percentrank(close, 50)

Signal state = switch
    close > ema => rank > 70 ? Signal.strongBuy : rank > 50 ? Signal.buy : Signal.neutral
    close < ema => rank < 30 ? Signal.strongSell : rank < 50 ? Signal.sell : Signal.neutral
    => Signal.neutral

counters.put(state, counters.get(state) + 1)

The counters map above can hold at most six key-value pairs because the Signal enum has five members and maps also allow na as a key. The compiler ensures you never accidentally insert an unrelated key type.

Dynamic Requests

In Pine Script v5, every request.security() call required its ticker and timeframe arguments to be known on the first script execution and remain constant across all subsequent bars2. This "simple string" restriction prevented you from changing the data source conditionally or requesting data from different symbols inside a loop.

Pine Script v6 removes this restriction entirely. All request.*() functions now accept "series string" arguments by default, meaning a single request call can change its target data feed on any bar. Furthermore, request calls can now live inside loops, conditional structures, and exported library functions2.

Multi-Symbol Comparison

//@version=6
indicator("Dynamic requests demo", overlay = false)

symbols = array.from("AAPL", "MSFT", "GOOGL", "NVDA")
var symCloses = array.new<float>(4, 0.0)

for i = 0 to 3
    symCloses.set(i, request.security(symbols.get(i), timeframe.period, close))

plot(symCloses.get(0), "AAPL", color.blue)
plot(symCloses.get(1), "MSFT", color.red)
plot(symCloses.get(2), "GOOGL", color.green)
plot(symCloses.get(3), "NVDA", color.orange)

This pattern was impossible in v5 because the symbol string inside the loop changes with each iteration, making it a "series" value. In v6, the script resolves each request dynamically, pulling close data from four different tickers in a single loop.

Controlling Dynamic Behavior

Dynamic requests are enabled by default in v6 through the dynamic_requests parameter of the indicator(), strategy(), and library() declaration functions. If you need backward-compatible behavior for a specific script, you can set dynamic_requests = false. However, most new scripts benefit from leaving it enabled.

Keep in mind that dynamic requests are more resource-intensive than static ones. Each unique ticker/timeframe combination counts against TradingView's request limits, and requests inside loops can multiply quickly. Plan your data access patterns accordingly.

Runtime Logging

Before Pine Script v6, debugging a script meant plotting values on the chart, writing text to labels, or using bgcolor() as a visual flag. These approaches were slow, limited to visual inspection, and cluttered your chart with debugging artifacts.

Pine Script v6 introduces a proper logging system through three functions in the log.* namespace: log.info(), log.warning(), and log.error()3. Messages appear in the dedicated Pine Logs pane in the TradingView editor, where you can filter by severity level and click on log entries to jump to the bar that generated them.

Using the Log Functions

//@version=6
indicator("Logging demo")

float ratio = (close - open) / (high - low)
float avg   = ta.sma(ratio, 10)

plot(avg, "Average ratio", color.purple, 3)

if barstate.isconfirmed
    switch (high - low)
        0.0 => log.error("Division by zero on confirmed bar.")
        => log.info(
            "Bar values:\nratio: {0,number,#.####}\navg: {1,number,#.####}",
            ratio, avg
        )

The log functions accept the same formatting patterns as str.format(), so you can interpolate variables directly into your messages. Logs created during historical bar execution persist in the Pine Logs pane. During realtime execution, logs are created for every tick and are not subject to rollback, so they remain visible even after the bar closes4.

Filtering and Navigation

The Pine Logs pane displays messages with color-coded severity indicators. You can filter by level (info, warning, error) using the menu above the log list. Each log entry includes the bar index and timestamp, and clicking an entry navigates the chart to the corresponding bar. This makes runtime logging far more powerful than any plot-based debugging technique.

Polyline Drawing

Pine Script v6 introduces the polyline drawing type for rendering multi-point connected shapes on the chart1. While v5 offered lines and boxes, creating complex shapes like polygons, spirals, or custom profiles required dozens of individual line objects that consumed drawing limits quickly. A single polyline can connect an array of chart.point objects into one continuous drawing.

Creating Polylines

//@version=6
indicator("Polyline demo", overlay = true, max_polylines_count = 50)

var pivotPoints = array.new<chart.point>(0)
float pHi = ta.pivothigh(5, 5)
float pLo = ta.pivotlow(5, 5)

if not na(pHi)
    pivotPoints.push(chart.point.from_index(bar_index - 5, pHi))
if not na(pLo)
    pivotPoints.push(chart.point.from_index(bar_index - 5, pLo))

if barstate.islastconfirmedhistory
    polyline.new(pivotPoints, line_color = color.purple, line_width = 2)

The polyline.new() function accepts parameters for closed (connect the last point back to the first), curved (smooth interpolation between points), line color, width, style, and fill color. This single function call replaces what would have been dozens of line.new() calls in v5.

Curved and Filled Shapes

Setting curved = true tells the polyline to interpolate smooth curves between its points rather than drawing straight line segments. Combined with closed = true and a fill color, you can create complex filled shapes like N-sided polygons, custom envelopes, or even spirals. Each polyline can hold up to 10,000 points, and scripts can display up to 100 polylines on the chart by setting max_polylines_count in the declaration statement.

Boolean Logic Changes

Pine Script v6 makes three fundamental changes to how booleans work, and these changes affect nearly every existing script.

No Implicit Int/Float to Bool

In v5, any numeric value could be used directly in a conditional expression. Zero and na evaluated to false, and any nonzero value evaluated to true. In v6, this implicit casting is removed. You must explicitly compare numeric values or wrap them with the bool() function.

// v5: implicit casting worked
if bar_index
    label.new(bar_index, high, "OK")

// v6: explicit comparison required
if bar_index != 0
    label.new(bar_index, high, "OK")

// Or use the bool() function
if bool(bar_index)
    label.new(bar_index, high, "OK")

Bool Cannot Be na

In v5, a bool variable had three possible states: true, false, or na. This three-state behavior was a frequent source of bugs because na was not equal to false when compared with ==, yet it evaluated as false in conditional expressions.

In v6, booleans are strictly two-state. Assigning na to a bool variable is a compilation error. The na(), nz(), and fixnan() functions no longer accept bool arguments. Any if or switch that returns a bool will return false instead of na for unhandled branches. And using the history-referencing operator [] on the first bar returns false (not na) for bool values2.

Lazy Evaluation for and/or

The and and or operators now use short-circuit (lazy) evaluation2. If the first operand of an and expression is false, the second operand is never evaluated. If the first operand of an or expression is true, the second operand is skipped.

This improves efficiency but introduces a subtle behavior change. In v5, both operands were always evaluated, which meant side-effect functions like ta.rsi() ran on every bar. In v6, if the first operand short-circuits, the second operand's function call is skipped on that bar, potentially disrupting its internal history. If your logic depends on a function maintaining its bar-by-bar state, call the function outside the boolean expression and store the result in a variable first.

Type System Improvements

Beyond booleans, Pine Script v6 tightens the type system in several ways that improve code safety.

Integer Division Returns Floats

In v5, dividing two const int values produced an integer result: 1 / 2 returned 0. In v6, the same expression returns 0.5. This change eliminates a common source of silent math errors in strategies that relied on integer division for position sizing or lot calculations. If you specifically need integer division, wrap the result with int().

History Referencing Restrictions

The history-referencing operator [] can no longer be applied directly to literal values or to fields of user-defined types. In v5, expressions like myUDT.fieldName[10] were valid. In v6, you must reference the object's history first, then access its field: (myUDT[10]).fieldName. This change makes the order of operations explicit and prevents ambiguity about whether you are referencing the history of the field or the object.

na Value Restrictions

In v5, na could be used in place of built-in constants of unique types (such as color.red or label.style_label_up). In v6, the compiler requires explicit types. If a function expects a value of a specific built-in constant type, you must either pass a valid constant or use an explicitly typed na cast. This prevents accidental misuse of na in places where it could silently produce wrong behavior.

Plot Offset Restriction

The offset parameter of plot() and similar functions no longer accepts "series" values. In v6, this parameter requires a "simple" or weaker qualifier, meaning the offset must be known at script startup and cannot change on each bar. This prevents visually misleading charts where plotted data shifts position bar by bar.

Strategy Enhancements

Pine Script v6 makes several changes to strategy behavior that affect backtesting results and script structure.

when Parameter Removed

The when parameter has been removed from all strategy.entry(), strategy.exit(), strategy.close(), strategy.close_all(), strategy.order(), and strategy.cancel() functions2. In v5, you could pass a boolean condition directly to when to control order execution. In v6, wrap the strategy call in an if block instead.

// v5: used the when parameter
strategy.entry("Buy", strategy.long, when = buyCondition)

// v6: use an if block
if buyCondition
    strategy.entry("Buy", strategy.long)

This change improves code readability and makes the execution flow explicit. The automatic converter handles this transformation when you upgrade from v5 to v6.

Default Margin Set to 100%

In v6, the default margin_long and margin_short values in the strategy() declaration are both 100%. In v5, these values varied depending on the broker emulator settings. The v6 default means strategies operate with no leverage unless you explicitly configure margin percentages, which produces more conservative and realistic backtesting results by default.

Trade Limit Trimming

In v5, strategies that exceeded the 9,000-trade limit outside of Deep Backtesting mode threw an error and halted execution. In v6, the strategy engine automatically trims the oldest closed trades to make room for new ones. The new strategy.closedtrades.first_index variable returns the index of the earliest non-trimmed trade, so you can still reference valid trade data without encountering out-of-range errors.

strategy.exit() Parameter Changes

In v5, strategy.exit() silently ignored relative parameters (like profit and loss in ticks) when absolute parameters (like limit and stop) were also specified. In v6, both relative and absolute parameters are respected simultaneously. If you set both profit and limit, the strategy uses whichever is hit first. This change can alter backtesting results for scripts that previously included both parameter types, so review your exit logic carefully during migration.

Text and Visual Improvements

Pine Script v6 upgrades the visual capabilities of drawing objects with new formatting options and sizing controls.

Text Formatting Parameter

Labels, boxes, and table cells now accept a text_formatting parameter that controls typographic emphasis. The available constants are text.format_bold, text.format_italic, and text.format_none3. You can combine bold and italic using the addition operator: text.format_bold + text.format_italic.

//@version=6
indicator("Text formatting demo", overlay = true)

if barstate.islast
    label.new(bar_index, high, "Bold Label",
         textcolor = color.white, color = color.blue,
         text_formatting = text.format_bold)

This is a significant improvement over v5, where all drawing text was rendered in a single style. Bold and italic formatting lets you create dashboards and annotations that visually separate headings from data, improving readability for end users.

Integer Text Sizes

In v5, text sizes were limited to string constants like size.small, size.normal, and size.large. Pine Script v6 adds support for integer-based text sizing, so you can set precise font sizes as int values. A table cell or label can use text_size = 25 to set exactly 25-point text, giving you granular control over dashboard layouts.

Negative Array Indices

Functions like array.get(), array.set(), array.insert(), and array.remove() now accept negative indices that reference elements from the end of the array1. Index -1 refers to the last element, -2 to the second-to-last, and so on. This eliminates the common array.get(arr, array.size(arr) - 1) pattern.

//@version=6
indicator("Negative index demo")
a = array.from(10.0, 20.0, 30.0, 40.0, 50.0)
plot(a.get(-1)) // Plots 50.0
plot(a.get(-2)) // Plots 40.0

Symbol and Data Variables

Pine Script v6 adds new built-in variables that provide information about the chart's symbol and timeframe context.

syminfo.mincontract

The syminfo.mincontract variable returns the minimum tradeable contract size for the current symbol3. For most stocks this is 1, but for forex pairs or crypto assets it can be a fractional value. This variable is essential for strategies that need to calculate position sizes that comply with exchange rules.

syminfo.main_tickerid

The syminfo.main_tickerid variable always returns the ticker ID of the main chart symbol, regardless of the execution context. This is critical when your script runs inside a request.security() call on a different symbol. In v5, you had to pass the main ticker as a function argument. In v6, this variable gives you a reliable reference back to the chart's primary symbol from any context.

timeframe.main_period

Similarly, timeframe.main_period returns the main chart's timeframe string regardless of execution context. If your indicator runs a request.security() call on a different timeframe, timeframe.period inside that context returns the requested timeframe. The new timeframe.main_period variable always points back to the chart's original timeframe.

timeframe.period Format Change

In v5, timeframe.period returned "D" for a daily chart and "W" for a weekly chart, omitting the multiplier when it was 1. In v6, the multiplier is always included: "1D", "1W", "1M"2. This change makes string comparisons and concatenation more predictable, but scripts that hardcoded checks like timeframe.period == "D" need to be updated to timeframe.period == "1D".

Post-Launch Updates (2025-2026)

Since the initial Pine Script v6 release in November 2024, TradingView has shipped monthly updates that continue to expand the language. Here are the most significant additions through early 2026.

February 2025: Bid/Ask Variables

New bid and ask built-in variables provide real-time bid and ask prices on tick-based charts. These variables enable spread analysis, microstructure studies, and more accurate execution modeling in strategies. The scope limit was also removed, allowing unlimited local scopes in v6 scripts (previously capped at 550 in v5)1.

March 2025: Dynamic For Loops

The for loop to value can now be a "series int" expression, meaning the loop count can change on each bar1. This unlocks patterns like iterating over a dynamically sized array without workaround techniques. Previously, the loop boundary had to be a "simple" value known at script startup.

July 2025: Active Inputs

The active parameter was added to input functions, allowing scripts to programmatically enable or disable inputs based on the values of other inputs. This creates cascading settings panels where selecting one option reveals or hides related fields, producing a cleaner user experience for complex scripts.

August 2025: Longer Strings

The maximum string length was increased from 4,096 to 40,960 characters. This is particularly useful for scripts that build large tooltip texts, export CSV-style data through labels, or construct complex formatted logging messages.

September 2025: Plot Line Styles

The plot() function gained a linestyle parameter supporting line.style_solid, line.style_dashed, and line.style_dotted. In v5, all plots rendered with solid lines. This addition lets you visually distinguish multiple data series without relying solely on color.

January 2026: Footprint Requests

The largest post-launch addition is the request.footprint() function along with the footprint and volume_row types. These features enable scripts to retrieve and analyze volume footprint data for each bar, including Point of Control, Value Area boundaries, and per-row volume deltas1. This functionality is available to Premium and Ultimate plan subscribers.

For a detailed breakdown of every monthly update, see our Pine Script Updates 2025-2026 post.

Automating v6 Strategies

All of the new features in Pine Script v6 work seamlessly with existing automation workflows. The alert() function is unchanged in v6, so strategies that send webhook alerts to platforms like TradersPost continue to work without modification.

Webhook Integration

To automate a Pine Script v6 strategy, you add alert() calls inside your entry and exit logic, format the message as JSON, and point the TradingView alert's webhook URL to your automation platform. TradersPost receives the alert, parses the trade instructions, and routes orders to your connected broker account5.

//@version=6
strategy("Automated v6 Strategy", overlay = true)

enum Direction
    longOnly  = "Long Only"
    shortOnly = "Short Only"
    both      = "Both Directions"

Direction dirInput = input.enum(Direction.both, "Trade direction")
int lenInput = input.int(20, "EMA Length", minval = 1)

float ema = ta.ema(close, lenInput)
bool goLong  = ta.crossover(close, ema)
bool goShort = ta.crossunder(close, ema)

if goLong and (dirInput == Direction.longOnly or dirInput == Direction.both)
    strategy.entry("Long", strategy.long)
    alert('{"action": "buy", "ticker": "' + syminfo.ticker + '"}', alert.freq_once_per_bar_close)

if goShort and (dirInput == Direction.shortOnly or dirInput == Direction.both)
    strategy.entry("Short", strategy.short)
    alert('{"action": "sell", "ticker": "' + syminfo.ticker + '"}', alert.freq_once_per_bar_close)

Notice how the strategy above uses a v6 enum to control trade direction. The user selects "Long Only," "Short Only," or "Both Directions" from a dropdown, and the enum type ensures no invalid value can be assigned. The webhook alerts themselves are standard JSON payloads that any automation platform can parse.

What Changes for Automation

From an automation perspective, the only v6 change that might affect your setup is the removal of the when parameter from strategy functions. If your v5 strategy used when to control order placement, the conversion to if blocks does not change the alert behavior. Your webhooks and broker connections remain intact. For a complete guide to connecting TradingView strategies with live brokers, see our Pine Script Strategy Automation Guide.

Migration Tips

Upgrading from v5 to v6 does not need to be painful if you follow a systematic approach.

  • Use the Pine Editor's built-in converter first. It handles many changes automatically, including the when parameter removal, timeframe.period format, and some bool casting fixes.
  • Search your code for any bare numeric value used in a conditional (if count, if volume, etc.) and add explicit comparisons or bool() wrappers.
  • Check for na assigned to bool variables and replace with false or restructure your logic to avoid the na state.
  • Review strategy.exit() calls that specify both relative and absolute take-profit or stop-loss parameters, since both are now respected.
  • Test integer division expressions. If you relied on 1/2 returning 0, wrap with int() to restore the old behavior.
  • Move any ta.*() function calls that appear inside lazy-evaluated and/or expressions to separate lines above the boolean expression.

For a line-by-line breakdown of every breaking change, see our Pine Script v6 Breaking Changes guide. For a side-by-side comparison of both versions, see our Pine Script v5 vs v6 Comparison.

Conclusion

Pine Script v6 is not an incremental update. It is a structural overhaul that adds professional-grade language features while closing long-standing safety gaps in the type system. Enums bring type-safe configuration to every script. Dynamic requests unlock multi-symbol and multi-timeframe analysis patterns that were impossible before. Runtime logging replaces clumsy plot-based debugging with a real diagnostic tool. Polylines simplify complex chart visuals. And the ongoing monthly updates through 2025 and into 2026 have continued to expand what the language can do, from footprint data to dynamic loops to plot line styles.

The stricter boolean handling and type system changes will break some existing v5 scripts during migration, but they eliminate entire categories of subtle bugs that plagued earlier versions. The trade-off is clearly worth it for anyone building production strategies or publishing scripts to the TradingView community.

If you are building automated trading strategies with Pine Script v6, TradersPost connects your TradingView alerts to live broker accounts for stocks, options, futures, and crypto5. The alert and webhook system works identically in v6, so your automation pipeline requires no changes beyond the script itself. Start building your v6 strategy today and let the platform handle the execution.

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 ->