Pine Script v5 to v6: what changed and how to migrate
The breaking changes between Pine Script v5 and v6, with a before and after for each, a migration checklist, and the traps the editor's converter does not fix.
Updated 30 Aug 2026
Pine Script v6 is mostly the same language as v5 with a stricter type system, requests that can run anywhere in a script, and a handful of removals that had been deprecated for a while. Most scripts convert in a minute. The trouble is the small set of changes that alter behaviour without producing a compile error, because those show up as a different trade list, not as a red line in the editor.
This page follows the official migration guide and the release notes, and adds the items that matter most for strategies that feed a webhook.
The changes that break compilation
These are the ones the editor will point at. Each has a mechanical fix.
Numbers are no longer booleans
v5 silently treated 0, 0.0 and na as false and anything else as true. v6 refuses to cast. Wrap the number with bool() or write the comparison you meant.
// v5color c = bar_index ? color.green : color.redif ta.change(time("D")) entriesToday := 0
// v6color c = bool(bar_index) ? color.green : color.redif ta.change(time("D")) > 0 entriesToday := 0Booleans cannot be na
A v5 bool had three states: true, false and na. v6 has two. na(), nz() and fixnan() no longer accept a bool argument, a bool variable cannot start as na, and an if with no else that returns a bool now returns false where v5 returned na. If your logic depended on the third state, replace the boolean with an int (say -1, 0, 1) or a string.
when is gone from order functions
strategy.entry(), strategy.order(), strategy.exit(), strategy.close(), strategy.close_all(), strategy.cancel() and strategy.cancel_all() no longer take when. Wrap the call in an if.
// v5strategy.entry("L", strategy.long, when = fired)
// v6if fired strategy.entry("L", strategy.long)transp is gone
Every function that took a transp argument now expects the transparency inside the colour: color.new(color.red, 80).
One argument per parameter, no history on literals
A call cannot pass the same parameter twice. The [] operator cannot be applied to a literal or directly to a field of a user defined type; assign to a variable first.
plot() offset must not be a series
The offset parameter of plot() and related functions accepts input or simple values only. If you were shifting a plot by a runtime value, that plot has to be rebuilt with lines or labels.
Unique type parameters cannot be na
Parameters such as style in plot() expect one of a fixed set of constants. A switch feeding one needs a default branch and an if needs an else, so the expression can never evaluate to na.
The changes that alter behaviour silently
These compile fine and run differently. Check each one against your backtest.
| Change | v5 | v6 | What to look for |
|---|---|---|---|
| Default margin | margin_long = 0, margin_short = 0 | Both default to 100 | New margin calls, fewer or smaller entries |
const int / const int | Integer division, 5 / 2 is 2 | Fractional, 5 / 2 is 2.5 | Bar counts, lot maths, array indices |
and / or | Evaluate both sides | Lazy: right side skipped when the left decides | Side effects in the right operand stop running |
timeframe.period | "D", "W" | Always has a multiplier: "1D", "1W" | String comparisons against timeframe names |
| Trade limit | Error above 9000 orders | Oldest orders trimmed | Old strategy.closedtrades entries become na; use strategy.closedtrades.first_index |
strategy.exit() with both relative and absolute levels | Relative ignored | Whichever level triggers first wins | Exits that fire earlier than before |
for loop end boundary | Evaluated once | Evaluated before each iteration | Loops whose bound changes inside the body |
| Negative array index | Error | Counts from the end in some array.*() functions | Code that relied on the error as a guard |
| Colour constants | color.teal is #00897B | color.teal is #089981 | Purely visual |
The division change is the one that catches strategies. barsInSession = 375 / 5 was 75 in v5 and is still 75 in v6, but 375 / 15 / 2 was 12 and is now 12.5, and anything used as a bar offset or an array index needs wrapping in int() or math.floor().
Dynamic requests
In v5, request.security() needed simple arguments for its symbol and timeframe and had to sit in the global scope unless you set dynamic_requests = true. In v6 dynamic requests are on by default: the symbol and timeframe can be series strings, the call can live inside a loop or an if, and one call instance can serve many symbols (migration guide, Dynamic requests).
The compiler turns the feature off again when a script does not need it, so there is no cost for simple scripts. Two cautions from the guide:
- Chained requests, where the result of one
request.security()call feeds the expression of another, can return slightly different values in dynamic mode. If a converted script's trades shift and you cannot see why, adddynamic_requests = falseto the declaration and compare. - A v6 script that explicitly sets
dynamic_requests = falsecannot call a wrappedrequest.*()from a local scope at all, even inside a user function. Remove the explicit argument.
Nothing about lookahead changed. barmerge.lookahead_off is still the default and barmerge.lookahead_on still leaks the future unless you offset the expression. The repainting page covers that in detail.
Text formatting
str.format() behaves as it did in v5: it still groups thousands by default, so a webhook body that carries a price as {0} sends 57,400, which is not JSON. Use {0,number,#.##} and keep TradingView placeholders such as {{close}} outside the format string; the webhook alerts page has the full explanation.
Strategy arguments worth resetting
When you convert, reread the strategy() declaration rather than trusting the converter. The arguments that matter for a strategy that fires a webhook:
margin_longandmargin_short: 100 in v6 by default. Decide deliberately.calc_on_every_tick: keep itfalseso orders andalert()calls happen on confirmed bars.process_orders_on_close:truefills at the close of the bar that produced the signal, which matches an alert that fires on bar close.commission_type,commission_value,slippage: v6 did not change them, but a migration is a good moment to make sure the backtest pays realistic per order costs and a couple of ticks of slippage.dynamic_requests: leave it unset unless you are diagnosing a difference.
Migration checklist
- Duplicate the script and convert the copy, from Manage script, Convert code to v6. The original v5 keeps compiling.
- Fix every highlighted error using the sections above. Most are
when,transp, booleannaand numeric conditions. - Search the source for
/between integer literals and wrap the ones used as counts or indices. - Search for
timeframe.period ==and add the multiplier to the compared string. - Set
margin_longandmargin_shorton purpose. - Run the Strategy Tester on the same symbol, timeframe and date range for both versions and compare trade counts and the trade list. Any difference should trace back to one of the rows in the table above.
- Recreate every TradingView alert that pointed at the old script. Alerts are snapshots and do not follow edits.
- Fire a test alert at a request bin and confirm the body is still valid JSON with unformatted numbers. The webhook alerts page shows what to look for.
A before and after
The two snippets below are the same opening range breakout entry, first in v5 and then in v6. The differences are the when removal, an explicit else on the boolean helper, the margin arguments, and the number formatting in the webhook body.
//@version=5strategy("ORB 15m", overlay = true, process_orders_on_close = true, commission_value = 350, slippage = 2)
orHigh = ta.highest(high, orBars)[1]fired = ta.crossover(close, orHigh)body = '{"sym":"{{ticker}}","side":"BUY","type":"ORB",' + str.format('"sl":{0}', orLow) + '}'strategy.entry("L", strategy.long, when = fired and barstate.isconfirmed, alert_message = body)//@version=6strategy("ORB 15m", overlay = true, process_orders_on_close = true, commission_value = 350, slippage = 2, margin_long = 0, margin_short = 0)
orHigh = ta.highest(high, orBars)[1]fired = ta.crossover(close, orHigh)body = '{"sym":"{{ticker}}","side":"BUY","type":"ORB",' + str.format('"sl":{0,number,#.##}', orLow) + '}'if fired and barstate.isconfirmed strategy.entry("L", strategy.long, alert_message = body) alert(body, alert.freq_once_per_bar_close)Try it in Sarathi
The Studio writes Pine v6 from the start, so a strategy built there never needs this migration; it comes out with two state booleans, explicit if blocks around orders, calc_on_every_tick = false, and a webhook body formatted with {0,number,#.##}. Live Each alert from that script lands on your per-user webhook and becomes an order card in Telegram and on the web, waiting for your Confirm tap at ½, 1× or 2× size, or Skip. The paper broker fills confirmed orders today; Dhan and Zerodha adapters are planned. Planned Open the Studio.
Questions
No. v5 scripts keep compiling. Move when you want dynamic requests, the stricter boolean rules or new v6 functions, and treat it as a chance to re-test the strategy.
v6 defaults margin_long and margin_short to 100 percent. v5 defaulted to 0, which never checked available funds. Set both to 0 to reproduce the old behaviour, or size positions to the capital you set.
In v6 a bool is only ever true or false. Remove na(), nz() and fixnan() calls on booleans and model a third state with an int or a string.
Yes, from the Manage script menu, as long as the v5 code compiles. It handles most mechanical changes and leaves the rest highlighted as errors for you to fix by hand.
Related
- Repainting, lookahead and calc_on_every_tick explainedWhy a Pine strategy trades differently live than in the backtest: lookahead, request.security offsets, calc_on_every_tick, barstate.isconfirmed, how to test.
- alert() vs alertcondition(): which one fires your webhookThe four combinations of alert(), alertcondition(), strategy and indicator in Pine Script, what each can put in a webhook body, and which frequency to choose.
- The pre-live checklist for a Pine strategyTen checks before a TradingView strategy's alerts reach a broker: v6 compile, repainting, alert payloads, sessions, sizing, paper trades and the journal.