
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.
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.
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.
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.
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.
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.
//@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.
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.
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.
//@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.
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.
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.
//@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.
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.
Pine Script v6 makes three fundamental changes to how booleans work, and these changes affect nearly every existing script.
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")
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.
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.
Beyond booleans, Pine Script v6 tightens the type system in several ways that improve code safety.
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().
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.
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.
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.
Pine Script v6 makes several changes to strategy behavior that affect backtesting results and script structure.
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.
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.
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.
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.
Pine Script v6 upgrades the visual capabilities of drawing objects with new formatting options and sizing controls.
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.
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.
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
Pine Script v6 adds new built-in variables that provide information about the chart's symbol and timeframe context.
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Upgrading from v5 to v6 does not need to be painful if you follow a systematic approach.
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.
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.
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