Edgoro
Guides

How to convert Pine Script v5 to v6: every breaking change, with before and after code

Convert a Pine Script from v5 to v6: the editor's converter, every breaking change with before/after code, and the checks that catch silent result changes.

Your v5 scripts keep running. TradingView doesn't switch old versions off, so there's no deadline. But new language features only arrive in v6, most code you'll find or generate from now on is v6, and mixing versions inside one project gets confusing quickly.

Converting is mostly mechanical. A few changes need a decision, and two of them can change a strategy's results without a single compiler error. This guide goes through every change that matters when moving from v5 to v6, with before and after code, then gives you a routine to check that the converted script still does what the old one did.

Start with the Pine Editor's converter

Open the v5 script in the Pine Editor and use the editor's menu option to convert the code to v6. The converter updates the version line and rewrites what has a mechanical equivalent. Whatever it can't rewrite shows up as compiler errors or warnings.

Two habits make the rest easier:

  • Keep the original. Save the converted script under a new name, so you can put the v5 and v6 versions on the same chart and compare them.
  • Go one version at a time. If the script is v4 or older, convert it to v5 first, fix that, then go to v6.

The changes at a glance

ChangeCompiler error?What to do
Numbers are no longer booleansYesCompare explicitly: volume > 0
Booleans can't be naYesStart at false; drop na() and nz() on bools
and / or are lazyWarning onlyCalculate ta.* calls on their own lines
when parameter removedYesWrap the order call in an if
Default strategy margin is 100%No, results can changeSet margin_long and margin_short yourself
strategy.exit() uses both price and tick levelsNo, results can changePass only one of each pair
timeframe.period always has a multiplierNo, logic can silently failCompare with "1D" or use timeframe.isdaily
Loop boundaries are re-evaluatedNoDon't change a loop's limit inside the loop
Oldest trades are trimmedNoStart loops at strategy.closedtrades.first_index
Requests are dynamic by defaultNoA new capability, not a break

1. Numbers are no longer booleans

v5 quietly treated any non-zero number as true. v6 refuses, and code like volume ? color.teal : color.gray stops compiling with this message:

Cannot call "operator ?:" with argument "expr0"="volume". An argument of "series float" type was used but a "const bool" is expected.

The fix is to say what you mean:

barColor = volume > 0 ? color.teal : color.gray

bool(x) also exists. It returns false for both 0 and na, but an explicit comparison is easier to read six months later. The same rule applies to if conditions: if bar_index becomes if bar_index > 0.

2. Booleans can't be na

In v6 a bool is always true or false. This v5 code no longer compiles:

var bool trendUp = na
trendUp := close > ta.sma(close, 200)
wasUp = nz(trendUp[1])

The first line fails with Cannot assign a value of the "simple na" type to the "trendUp" variable, and na() or nz() applied to a bool fails too. The v6 version:

var bool trendUp = false
trendUp := close > ta.sma(close, 200)
wasUp = trendUp[1]

On the first bar, trendUp[1] now returns false instead of na, so the nz() is no longer needed. If your script used na as a third state, meaning "not decided yet", switch to an int (for example 1, -1 and 0) or a separate bool flag. Don't just replace na with false without checking what the third state was doing.

3. and and or are lazy

In v6, and stops as soon as the left side is false, and or stops as soon as it's true. That's faster, but if the right side contains a function that depends on history, such as ta.ema(), ta.crossover() or ta.rsi(), that function no longer runs on every bar, and its internal state goes out of sync. The script compiles with a warning, and its results can differ from v5:

The "ta.crossover()" call inside the conditional expression might not execute on every bar, which can cause inconsistent calculations because the function depends on historical results.

In v5 you might have written longSignal = close > open and ta.crossover(fast, slow). In v6, calculate first and combine afterwards:

crossUp    = ta.crossover(fast, slow)
longSignal = close > open and crossUp

The same rule already applied to if blocks and the ternary operator, so make it a habit: every ta.* call on its own line, at the top level of the script.

4. The when parameter is gone

strategy.entry(), strategy.order(), strategy.exit(), strategy.close(), strategy.close_all(), strategy.cancel() and strategy.cancel_all() no longer accept when. The error reads The "strategy.entry" function does not have an argument with the name "when".

The v5 line strategy.entry("Long", strategy.long, when = longSignal) becomes:

if longSignal
    strategy.entry("Long", strategy.long)

5. Strategies default to 100% margin

This is the change most likely to move your numbers without any error. In v5, margin_long and margin_short defaulted to 0, so a strategy could open positions far larger than its equity. In v6 the default is 100: the position's value must be covered by equity, and when it isn't, the simulated broker issues a margin call and closes part of the position.

A v5 strategy that buys 100% of equity and pays commission, or uses large fixed quantities, can produce a different trade list in v6. Look for "Margin call" rows in the list of trades. Then decide on the margin deliberately:

strategy("My strategy", overlay = true, margin_long = 100, margin_short = 100)

Use 100 for an unleveraged account. With leverage, the margin percentage is 100 divided by the leverage: about 3.33 for 30:1. Setting 0 restores the v5 behaviour, but it lets the backtest take positions no broker would allow.

6. strategy.exit() reads both price and tick levels

strategy.exit() accepts levels as prices (stop, limit) or as distances in ticks (loss, profit). In v5, when you passed both members of a pair, the price won and the tick distance was ignored. In v6, the function evaluates both and uses whichever level the market would reach first.

Code that passed both "just in case", such as stop = stopPrice, loss = 200, can exit earlier in v6. Pass one of each pair:

strategy.exit("Exit", "Long", stop = stopPrice, limit = targetPrice)

For the full set of exit options, see strategy.exit() stop loss and take profit.

7. timeframe.period always has a multiplier

v6 returns "1D", "1W" and "1M" where v5 returned "D", "W" and "M". A comparison like timeframe.period == "D" compiles in both versions but is never true in v6. Search your code for timeframe strings in quotes. Intraday values such as "60" or "15" are unchanged.

isDaily = timeframe.isdaily and timeframe.multiplier == 1

timeframe.isdaily alone is true on any daily chart, including 2D or 3D; add the multiplier check if you need exactly one day. Comparing with "1D" works too.

8. Loop boundaries are re-evaluated

In v6, the end value of a for loop is evaluated again on every iteration. If the loop adds or removes array elements and its limit is array.size(...), or changes a variable used as the limit, the number of iterations can differ from v5. When you want a fixed boundary, copy it into a variable before the loop:

total = 0.0
n = array.size(levels)
if n > 0
    for i = 0 to n - 1
        total += array.get(levels, i)

The if n > 0 guard matters in every version: for i = 0 to -1 counts down and runs, it doesn't skip.

9. Old trades are trimmed instead of stopping the strategy

A v5 strategy that exceeded the trade limit stopped with an error. A v6 strategy keeps running and drops the oldest closed trades from its history. If your script loops over closed trades, start at strategy.closedtrades.first_index rather than 0.

10. Requests are dynamic by default

Not a break but a capability: in v6, request.security() and the other request.*() functions can be called inside if blocks, loops and your own functions, with symbols and timeframes that change from bar to bar. In v5 this needed dynamic_requests = true in the declaration. If your v5 script called every symbol at the top level as a workaround, you can simplify it. Non-repainting rules still apply, as covered in multi-timeframe Pine Script with request.security().

A complete before and after

A small v5 strategy that hits most of the changes above:

//@version=5
strategy("Trend pullback (v5)", overlay = true)

fastLen = input.int(20, "Fast EMA")
slowLen = input.int(50, "Slow EMA")

fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)

var bool trendUp = na
trendUp := close > ta.sma(close, 200)

hasVolume  = volume ? true : false
longSignal = trendUp and ta.crossover(fast, slow)

strategy.entry("Long", strategy.long, when = longSignal and hasVolume)
strategy.close("Long", when = ta.crossunder(fast, slow))

isDaily = timeframe.period == "D"
bgcolor(isDaily and not na(trendUp) and trendUp ? color.new(color.green, 92) : na)
plot(fast, "Fast EMA", color.teal)
plot(slow, "Slow EMA", color.orange)

The same strategy in v6:

//@version=6
strategy("Trend pullback (v6)", overlay = true, margin_long = 100, margin_short = 100)

fastLen = input.int(20, "Fast EMA")
slowLen = input.int(50, "Slow EMA")

fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)

// A bool can't be na in v6: start at false
var bool trendUp = false
trendUp := close > ta.sma(close, 200)

// No implicit number-to-bool cast
hasVolume = volume > 0

// `and` is lazy: run history-dependent calls on every bar first
crossUp    = ta.crossover(fast, slow)
crossDown  = ta.crossunder(fast, slow)
longSignal = trendUp and crossUp

// No `when` parameter: use if blocks
if longSignal and hasVolume
    strategy.entry("Long", strategy.long)
if crossDown
    strategy.close("Long")

// timeframe.period returns "1D", not "D"
isDaily = timeframe.isdaily and timeframe.multiplier == 1
bgcolor(isDaily and trendUp ? color.new(color.green, 92) : na)
plot(fast, "Fast EMA", color.teal)
plot(slow, "Slow EMA", color.orange)

The v5 compiler already warns about this script: the volume ? cast and both when arguments get deprecation-style warnings, a preview of what v6 turned into errors. Note also that the v5 version's trendUp and ta.crossover(fast, slow) was harmless in v5, where both sides always ran. Left as it was in v6, the crossover would be skipped on every bar where the trend filter is false, and its signals would no longer match.

After converting: check that nothing moved

  1. Zero errors, zero warnings. Treat lazy-evaluation warnings as errors; each one is a possible difference in results.
  2. Same chart, both versions. Add the v5 and v6 scripts to one chart. Plots should overlap exactly. Check a few bars in the Data Window, including early bars where na handling changed.
  3. Same Strategy Tester numbers. Compare the number of trades, net profit and maximum drawdown. If they differ, look at margin calls (change 5), exit pairs (change 6) and lazy evaluation (change 3), in that order.
  4. Recreate alerts. An alert stores a copy of the script when you create it, so existing alerts keep running the v5 code. See Pine Script alerts.
  5. Update any publication. If you published the script, publish the update and mention the move to v6 in the release notes.

If the converter leaves an error you don't recognise, how to add a Pine Script indicator to TradingView has a table of the most common messages, and the free error fixer linked below explains the rest.

Sources and further reading

Try it with Edgoro

Before you open the Pine Editor, paste the script into the free Pine Script checker: it lists the v4 and v5 code that has to change for v6, line by line, without uploading anything. If one error is holding up a conversion, paste the script and the exact message into the free Pine Script error fixer. If you'd rather start clean, describe what the script does and Edgoro's Pine Script generator writes it in v6 from the first line. New to the language? Pine Script v6 for beginners covers the concepts behind these changes.

Converted scripts can behave differently from the originals; compare both before relying on either. Backtested results are hypothetical, and trading involves substantial risk of loss.

Build it with Edgoro

Describe your strategy in plain English and get a TradingView indicator in Pine Script v6 or a MetaTrader 5 Expert Advisor in minutes. Every first purchase is covered by a 7-day money-back guarantee.

Build your indicator with Edgoro