
If you write custom indicators or strategies on TradingView, you have likely encountered the question of Pine Script v5 vs v6. Released in late 2024, Pine Script v6 is the biggest language update since v4 to v5, introducing new features, stricter type rules, and several breaking changes that affect how existing scripts behave1. This guide walks through every major difference between the two versions so you can decide whether to upgrade and know exactly what to expect when you do.
Pine Script v6 is not a minor patch. It changes how booleans work, how operators evaluate, how data requests execute, and how strategies handle trades. Some of these changes will break your v5 code. Others unlock capabilities that were simply not possible before. Below, we compare every significant difference side by side.
Before diving into the details, here is a high-level feature comparison showing the pine script v5 vs v6 differences at a glance.
| Feature | Pine Script v5 | Pine Script v6 |
|---|---|---|
| Bool can be na | Yes (three-state: true, false, na) | No (strictly true or false) |
| Implicit int/float to bool | Yes (0 = false, nonzero = true) | No (explicit bool() cast required) |
| and/or evaluation | Eager (both sides always evaluated) | Lazy (short-circuit evaluation) |
| request.*() arguments | Static only (simple string) | Dynamic (series string) |
| Integer division (1/2) | Returns 0 (integer division) | Returns 0.5 (fractional result) |
| Enums | Not available | Full support with input.enum() |
| Runtime logging | Not available | log.info(), log.warning(), log.error() |
| Polyline drawing | Not available | polyline.new() with curved/closed paths |
| strategy when parameter | Available | Removed |
| Default strategy margin | Varies by broker | 100% (long and short) |
| Trade limit (9000) | Error thrown, script halts | Oldest orders trimmed automatically |
| Negative array indexing | Not supported | Supported in array.get(), array.set(), etc. |
| Text formatting | Plain text only | Bold, italic via text_formatting parameter |
| timeframe.period format | "D" (no multiplier) | "1D" (always includes multiplier) |
| Max scopes | 550 | Unlimited (Feb 2025 update) |
| String max length | 4,096 characters | 40,960 characters (Aug 2025 update) |
| Bid/ask variables | Not available | Available on tick charts (Feb 2025) |
| Plot line styles | Solid only | Solid, dashed, dotted (Sep 2025) |
| Footprint data | Not available | request.footprint() (Jan 2026) |
The most impactful breaking changes in the pine script v5 vs v6 comparison involve the type system. Pine Script v6 enforces stricter rules around how types convert and interact, which catches subtle bugs but also breaks scripts that relied on v5's lenient behavior2.
In v5, any integer or float value could be used directly in a conditional expression. Zero and na evaluated to false, and any nonzero value evaluated to true2. This was convenient but error-prone.
In v6, you must explicitly cast numeric values to bool using the bool() function3.
v5 (implicit cast works):
//@version=5
indicator("Implicit cast demo")
color bg = bar_index ? color.green : color.red
bgcolor(bg)
v6 (explicit cast required):
//@version=6
indicator("Explicit cast demo")
color bg = bool(bar_index) ? color.green : color.red
bgcolor(bg)
Without the bool() wrapper in v6, the script throws a compilation error2. This change forces you to be explicit about when a number should be treated as a boolean, reducing a class of bugs where na values or unexpected zeros silently produced wrong results.
This is one of the most significant changes in the pine script v5 vs v6 migration. In v5, a bool variable had three possible states: true, false, or na2. The na state created subtle behavior differences because na was not equal to false when compared with ==, yet it evaluated as false in conditional expressions.
In v6, bool is strictly two-state. It is always true or false, never na. This means:
v5 (three-state boolean):
//@version=5
strategy("Bool na demo v5", overlay=true)
bool isLong = if strategy.position_size > 0
true
else if strategy.position_size < 0
false
// When position_size == 0, isLong is na
// This works in v5
if na(isLong)
label.new(bar_index, high, "No position")
v6 (two-state boolean):
//@version=6
strategy("Bool na demo v6", overlay=true)
// Use an int or string to represent three states instead
int positionState = strategy.position_size > 0 ? 1 : strategy.position_size < 0 ? -1 : 0
if positionState == 0
label.new(bar_index, high, "No position")
If your v5 scripts rely on the three-state boolean pattern, you need to refactor them to use a different type (such as int or an enum) to represent the third state.
In v5, the and and or operators always evaluate both sides of the expression, regardless of the first operand's value. This is called eager evaluation2.
In v6, these operators use lazy (short-circuit) evaluation. If the first operand of or is true, the second operand is not evaluated. If the first operand of and is false, the second operand is skipped2.
v5 (both sides always evaluated):
//@version=5
indicator("Eager eval demo")
// Both array.get() calls execute even if the first condition is true
bool result = array.size(myArray) > 0 or array.get(myArray, 0) > 100
v6 (short-circuit evaluation):
//@version=6
indicator("Lazy eval demo")
// If array.size(myArray) > 0 is true, array.get() is NOT called
// This can change behavior if the second operand had side effects
bool result = array.size(myArray) > 0 or array.get(myArray, 0) > 100
This change matters most when the second operand has side effects, such as modifying a variable or calling a function that updates state. In v5, those side effects always occurred. In v6, they may be skipped. Most of the time, lazy evaluation is what you want because it is more efficient, but review your code if you depend on both sides always executing.
In v5, dividing two const int values performs integer division. The expression 1 / 2 returns 0 because both operands are integers and the result is truncated2.
In v6, the same expression returns 0.5. The compiler now allows fractional results from integer division of const values2.
v5:
//@version=5
indicator("Int division v5")
plot(1 / 2) // Plots 0
v6:
//@version=6
indicator("Int division v6")
plot(1 / 2) // Plots 0.5
If you relied on the truncating behavior of integer division, you need to explicitly use int() or math.floor() to get the same result in v6.
Dynamic requests are arguably the most powerful new capability in v6 and a major reason to upgrade. This is one of the clearest advantages in the pine script v5 vs v6 comparison.
In v5, all request.*() functions require "simple string" arguments for the ticker and timeframe parameters. This means the symbol and timeframe must be known on the very first bar and cannot change2. Requests must also execute globally and cannot be placed inside loops or conditional blocks unless you enable dynamic_requests = true in the declaration statement.
//@version=5
indicator("Static requests v5")
// Each symbol needs its own request.security() call
float aapl = request.security("NASDAQ:AAPL", "1D", close)
float msft = request.security("NASDAQ:MSFT", "1D", close)
float googl = request.security("NASDAQ:GOOGL", "1D", close)
plot((aapl + msft + googl) / 3)
In v6, request.*() functions accept "series string" arguments by default. You can pass variables, use them inside loops, place them in conditional blocks, and even export library functions containing request calls2. The compiler automatically optimizes performance when dynamic requests are not needed.
//@version=6
indicator("Dynamic requests v6")
var array<string> symbols = array.from("NASDAQ:AAPL", "NASDAQ:MSFT", "NASDAQ:GOOGL")
array<float> closes = array.new<float>()
for [i, sym] in symbols
float reqClose = request.security(sym, "1D", close)
closes.push(reqClose)
plot(closes.avg())
This is a fundamental shift. In v5, building a multi-symbol scanner or dynamic portfolio tool required workarounds like hard-coding every ticker. In v6, you can loop through an array of symbols and request data for each one dynamically. This opens the door to custom indices, portfolio rotation strategies, and correlation matrices that were previously impractical.
If you find that a converted v6 script behaves differently with dynamic requests enabled, you can add dynamic_requests = false to your declaration statement to replicate the v5 behavior.
Beyond the breaking changes, Pine Script v6 introduces several entirely new capabilities that have no equivalent in v5.
Enums let you define a custom type with a fixed set of named values3. They provide type safety and make your code more readable by replacing magic numbers and strings with meaningful names.
//@version=6
indicator("Enum demo")
enum Signal
buy = "Buy Signal"
sell = "Sell Signal"
neutral = "Neutral"
Signal currentSignal = close > ta.sma(close, 20) ? Signal.buy : close < ta.sma(close, 20) ? Signal.sell : Signal.neutral
color plotColor = switch currentSignal
Signal.buy => color.green
Signal.sell => color.red
Signal.neutral => color.gray
bgcolor(plotColor, title="Signal Background")
Enums work with the new input.enum() function, which creates a dropdown menu in the script's settings panel3. They can be stored in arrays, matrices, and maps, and used as map keys for strict key control. Each enum is a unique type, so members of different enums cannot be compared or interchanged, preventing accidental misuse4.
Pine Script v6 adds log.info(), log.warning(), and log.error() functions that output messages to the "Pine Logs" menu in TradingView3. This is a game-changer for debugging.
//@version=6
indicator("Logging demo")
float rsi = ta.rsi(close, 14)
if ta.crossover(rsi, 70)
log.warning("RSI crossed above 70: {0}", rsi)
if ta.crossunder(rsi, 30)
log.info("RSI crossed below 30: {0}", rsi)
In v5, debugging meant plotting values on the chart or creating label objects, both of which cluttered the display and had limited usefulness. Runtime logging gives you a dedicated output channel with severity levels, formatted string support, and the ability to inspect values at specific bars without modifying your chart.
The polyline.new() function creates multi-point drawings that connect a series of chart.point objects. Polylines can be straight or curved, open or closed, and filled with color3.
//@version=6
indicator("Polyline demo", overlay=true)
if barstate.islast
var points = array.new<chart.point>()
points.clear()
for i = 0 to 9
points.push(chart.point.from_index(bar_index - 9 + i, low[9 - i]))
polyline.new(points, curved=true, line_color=color.blue, line_width=2)
This enables complex visualizations like support/resistance zones, custom channel drawings, and multi-segment patterns that previously required multiple line.new() calls and careful coordinate management.
Labels, boxes, and table cells in v6 support a text_formatting parameter with options for bold and italic text3. Text sizes can also be defined using integer values for more granular control.
//@version=6
indicator("Text format demo", overlay=true)
if barstate.islast
label.new(bar_index, high, "Strong Signal",
text_formatting=text.format_bold, size=size.large)
Combined with integer-based text sizing, this gives you precise control over how text appears on charts, making published indicators and strategies look more professional.
If you use Pine Script for backtesting strategies, several v6 changes directly affect how strategies behave and report results.
In v5, strategy functions like strategy.entry(), strategy.exit(), and strategy.close() accept a when parameter that conditionally executes the function. In v6, this parameter is removed entirely2.
v5 (using when):
//@version=5
strategy("When param v5", overlay=true)
longCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))
strategy.entry("Long", strategy.long, when=longCondition)
v6 (using if instead):
//@version=6
strategy("When param v6", overlay=true)
longCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))
if longCondition
strategy.entry("Long", strategy.long)
The Pine Editor converter handles this change automatically by wrapping strategy calls in if blocks. The logic is identical, but the code is more explicit about when trades execute.
In v5, the default margin for strategy.entry() varies depending on the broker simulation settings. In v6, both margin_long and margin_short default to 100%, meaning strategies use no leverage unless you explicitly set it2.
If your v5 strategy relied on the default margin being less than 100% for leveraged positions, you need to explicitly set the margin parameters in your v6 strategy() declaration to match the previous behavior.
In v5, strategies that exceed the 9,000 trade limit throw a runtime error, halting the script. In v6, the strategy automatically trims the oldest orders to make room for new ones2. The new strategy.closedtrades.first_index variable lets you retrieve the index of the earliest non-trimmed order3.
This is a significant improvement for high-frequency strategies or long backtesting periods. Your strategy no longer crashes when it generates many trades. It simply discards the oldest records to continue running.
In v5, the strategy.exit() function ignores relative take-profit and stop-loss parameters (profit, loss, trail_points, trail_offset) when the corresponding absolute parameters (limit, stop, trail_price) are also provided. In v6, both relative and absolute parameters are respected simultaneously, and the closest price level to the current price takes effect2.
Review any strategy.exit() calls that include both absolute and relative parameters. The behavior may differ between v5 and v6.
Beyond the major categories above, several smaller changes in the pine script v5 vs v6 transition can affect your code.
The history-referencing operator [] can no longer reference the history of literal values or fields of user-defined types directly. In v5, expressions like true[1] were allowed. In v6, you must store the value in a variable first, then reference that variable's history2.
The value of timeframe.period now always includes a multiplier. Where v5 returned "D" for daily, v6 returns "1D". Similarly, "W" becomes "1W" and "M" becomes "1M"2. If your script compares timeframe.period against hard-coded strings like "D", you need to update those comparisons.
The offset parameter of plot() and related functions no longer accepts "series" values. It requires a "simple int" value that is known at the first bar2. If your v5 script used a dynamic offset, you need to refactor the logic.
The transp parameter, already deprecated in v5, is fully removed in v62. Use color.new() to set transparency instead.
In v6, for loops re-evaluate their end boundary before every iteration. In v5, the end boundary was evaluated once2. If you modify a variable used as the loop's end boundary inside the loop body, the behavior changes in v6.
Since the initial v6 release, TradingView has shipped monthly updates that further widen the gap between v5 and v6. These features are only available in v6 scripts.
These ongoing improvements mean the feature gap between v5 and v6 grows every month. New capabilities are being built exclusively for v6, and v5 is effectively in maintenance mode.
The answer depends on your situation. Here is a practical framework for deciding.
Whether you are running Pine Script v5 or v6 strategies, TradersPost connects your TradingView alerts to live broker accounts for automated trade execution5. Both versions generate the same webhook alert format, so your automation setup works identically regardless of which Pine Script version you use.
TradersPost supports automated execution through brokers including TradeStation, Alpaca, Interactive Brokers, Tradier, and many others5. When your Pine Script strategy fires an alert, TradersPost receives the webhook and places the corresponding order in your connected brokerage account within seconds.
If you are migrating a v5 strategy to v6, you do not need to change anything on the TradersPost side. The alert message format and webhook URL remain the same. Just update your script, verify the backtest results match expectations, and your automation continues seamlessly.
To get started with automating your Pine Script strategies, visit TradersPost and connect your TradingView account to your broker.
The pine script v5 vs v6 comparison reveals a clear trajectory: v6 is a stricter, more powerful, and more capable language. The breaking changes around booleans, operator evaluation, and integer division enforce better coding practices. The new features like dynamic requests, enums, runtime logging, and polylines unlock capabilities that were impossible or impractical in v5.
For new development, there is no reason to start with v5. For existing scripts, plan your migration carefully, use the built-in converter as a starting point, and test thoroughly. The ongoing monthly updates to v6 mean the feature gap will only continue to grow.
If you want to learn more about specific v6 features, check out our guides on Pine Script v6: What's New and Why It Matters, Adapting to Type System Changes in Pine Script v6, and Pine Script Strategy Automation.
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