Pine Script v6

How to Migrate Pine Script from v5 to v6 Without Bugs (2026 Step-by-Step Guide)

A practical pine script v6 migration from v5 guide: run the auto-converter, then fix the boolean, na, lazy and/or, request, and strategy.exit traps it silently misses.

Pine Script v6June 14, 202611 min read
About the author

Jayadev Rana has been building Pine Script systems since 2017 and writes these guides from the perspective of someone who has to make live behavior, alerts, and execution logic make sense together. If you want to check the public side of that work first, use the Work section, the Proof Hub, and the linked TradingView releases before you decide anything.

Algo Trading in India 2026

This article is written for traders who want the idea explained clearly enough to use, test, or challenge in real conditions.

Want examples before you message?

Use the Proof Hub and Work section if you want to see public examples first. If your main question is about your own setup, go straight to WhatsApp.

A clean pine script v6 migration from v5 is not just clicking "Convert code to v6" and shipping it. The auto-converter handles the mechanical renames fine. The problem is the handful of breaking changes that compile without errors and silently change what your script does — your booleans, your na defaults, your and/or chains, your request.*() calls, and your strategy.exit() levels. Those are the ones that turn a "working" v6 script into a strategy that backtests differently, repaints, or fires the wrong orders in production.

This guide is the hub for my v6 cluster. I'll walk through the exact order I migrate client scripts in: run the converter first, then go hunting for the five categories of bugs it gets wrong. I lead with the silent ones because those are the ones that cost real money.

First, run the auto-converter (and know its limits)

TradingView ships a built-in converter, and you should always start there. Open your v5 script in the Pine Editor — the //@version=5 line is highlighted in yellow — then use the Manage script dropdown and choose Convert code to v6. It rewrites //@version=5 to 6 and applies the safe mechanical changes.

Two things to understand before you trust the output:

  • It only converts code that already compiles. If your v5 script has errors, fix those in v5 first, then convert. A broken v5 script will not convert.
  • A clean conversion does not mean a correct conversion. In most cases the result compiles. But "compiles" is the trap — several v6 changes are behavioral, not syntactic, so the converter leaves your code looking identical while the runtime result differs. Those are the manual fixes below.

My workflow: convert, then keep the v5 version open in a second tab and compare outputs bar-by-bar on the same symbol/timeframe before I delete anything. If you want a deeper diagnostic routine for stubborn scripts, I keep one in how to fix Pine Script code.

The silent killers: breaking changes that change behavior without errors

These four come first because none of them throw a compile error. Your script runs. It just runs differently.

1. Numeric values no longer implicitly cast to bool

This is the single biggest source of post-migration bugs. In v5, an int or float in a boolean position was auto-cast: na, 0, or 0.0 counted as false, and any other value counted as true. In v6 that implicit cast is gone. You must compare explicitly.

The classic example that bites people uses bar_index, which is 0 on the first bar:

// v5: works "by accident" — first bar (bar_index == 0) is false, rest true
color c = bar_index ? color.green : color.red

// v6: this is now an error / wrong intent — bar_index is an int, not a bool
// Fix: compare explicitly
color c = bar_index != 0 ? color.green : color.red

The ternary operator ?:, if conditions, and and/or operands all expect a real bool now. Anywhere your v5 code wrote if myInt or if myFloat and leaned on "non-zero means true," you rewrite it as an explicit comparison:

// v5
if barssince_value
    doSomething()

// v6
if barssince_value != 0
    doSomething()

The converter catches the obvious cases but I have seen it miss conditions buried inside user-defined function bodies and inside switch arms. Grep your own code for any if, ?, and, or or that operates on something declared int or float and fix it by hand.

2. Booleans can no longer be na

In v6, a bool is strictly true or false — it can never hold na. This is a real semantic change, not cosmetic, and it breaks code in two specific ways:

  • You can no longer declare a bool with an na default: bool ready = na is invalid.
  • In an if or switch that returns a bool, any branch you didn't specify now returns false instead of na.

That second point is the silent one. If your v5 logic distinguished "this signal is false" from "this signal hasn't been evaluated yet" using na, that distinction is gone:

// v5: longSignal could be na (unknown) on early bars
bool longSignal = na
if barstate.isconfirmed
    longSignal := close > open

// v6: longSignal can't be na. Pick a real default and make
// the "not yet known" state explicit if you actually need it.
bool longSignal = false
bool evaluated  = false
if barstate.isconfirmed
    longSignal := close > open
    evaluated  := true

Also note: na(), nz(), and fixnan() no longer accept bool arguments. Any na(myBool) or nz(myBool) in your v5 code will not survive. I wrote a dedicated walkthrough of this exact failure mode — see fixing the boolean/na error in Pine Script v6 — because it's the most common one I get hired to clean up.

3. and / or now evaluate lazily (short-circuit)

v6 introduced short-circuit evaluation. With or, if the first operand is true, the second is never evaluated. With and, if the first is false, the second is skipped. Most of the time this is purely an upgrade — it's faster and it lets you guard array access safely:

// Safe in v6 — if the array is empty, the second operand never runs
if array.size(myArray) > 0 and array.get(myArray, 0) > threshold
    doSomething()

The trap is side effects in the second operand. If your v5 code relied on both sides of an and/or always executing — for example a function call that mutates a var, increments a counter, or appends to an array — that side effect may now be skipped on some bars. Symptoms look like a counter that's suddenly too low or an array that's missing elements. Move anything with a side effect out of the boolean expression and onto its own line.

4. Integer division can now return a float

Small change, nasty consequences for position sizing and index math. In v5, 1 / 2 returned 0 (integer truncation). In v6, dividing two const ints can return the fractional result — 1 / 2 is 0.5. If you used integer division deliberately to floor a value (lot sizing, array indexing, bar counting), wrap it to keep the old behavior:

// v5 gave 0; v6 gives 0.5
qtyPerLeg = totalQty / 2

// Preserve truncation in v6
qtyPerLeg = int(totalQty / 2)

The request.*() / dynamic_requests change

In v6, dynamic requests are on by default. The dynamic_requests parameter of indicator(), strategy(), and library() is true automatically, and request.*() calls can now live inside loops, conditional blocks, and exported library functions, and can take series string arguments that change the requested feed on any bar.

Two concrete migration actions:

  • Remove any explicit dynamic_requests = true. It's redundant now and can cause confusion. If the converter left it in, take it out.
  • If behavior changed after conversion, force it off. Because dynamic mode evaluates requests differently, a few multi-symbol scripts produce slightly different values after migration. If you see that, add dynamic_requests = false to your declaration to replicate most of the old v5 behavior, confirm the outputs match, then decide whether you actually want the new dynamic capability.
// v6 — dynamic by default, no flag needed
//@version=6
indicator("MTF example")

// Only if outputs drifted after migration and you want old behavior:
// indicator("MTF example", dynamic_requests = false)

The upside of this change is large if you write scanners or multi-symbol tools — you can finally put request.security() inside a loop. I cover that pattern properly in dynamic request.security for multi-symbol scanners and the library-specific angle in dynamic requests in Pine Script v6 libraries. If your script is multi-timeframe and you want to make sure it doesn't repaint after the change, pair this with my multi-timeframe indicator guide.

strategy.* changes that move your backtest

If you're migrating a strategy rather than an indicator, these four changes can shift your equity curve. Test before and after.

strategy.exit() now respects both relative and absolute parameters

In v5, strategy.exit() would ignore relative parameters (like profit / loss in ticks) when you also supplied absolute price levels (limit / stop). In v6 it evaluates both and acts on whichever level price reaches. If your v5 exit was secretly ignoring one set of parameters, your v6 backtest will fill at different prices. Audit every strategy.exit() call and make sure you're only passing the parameters you actually intend.

The when parameter is gone from all strategy.*() functions

This is the most common manual fix in strategy migration. when = no longer exists on strategy.entry(), strategy.exit(), strategy.close(), etc. Wrap the call in an if instead:

// v5
strategy.exit("TP/SL", "Long", profit = 100, loss = 50, when = inTrade)

// v6
if inTrade
    strategy.exit("TP/SL", "Long", profit = 100, loss = 50)

The converter usually handles this one, but check the result — when the when condition was a complex expression, I've seen it produce nested if blocks that change the order of evaluation. Read every converted exit.

Default margin is now 100%

v6 sets the default long and short margin to 100% (up from the old default). If you never set margin explicitly, your v5 backtest may have used hidden leverage. After migration, the same script trades smaller or runs out of buying power sooner. Either accept the more conservative (and more realistic) default or set your margin explicitly in strategy() to match your old test — just know which one you're choosing.

No more 9,000-trade halt

v5 strategies stopped at the 9,000-trade limit. v6 instead trims the oldest trades to make room for new ones, so a long backtest keeps running but loses its earliest history. Use strategy.closedtrades.first_index if you need to know which is the earliest retained trade. This mostly matters for high-frequency strategies on long histories.

My v5 → v6 migration checklist

StepWhat you're checkingRisk if skipped
1. ConvertRun "Convert code to v6"; confirm it compilesNone — but compiling ≠ correct
2. Bool castsEvery if/?/and/or on an int/float → explicit != 0Silent wrong logic, esp. on bar 0
3. na booleansbool x = na removed; unspecified branches now return false"Unknown" state collapses to false
4. Lazy and/orMove side effects out of boolean expressionsSkipped counters / missing array items
5. Int divisionWrap intended truncation in int()Wrong lot size / index math
6. RequestsDrop dynamic_requests = true; verify outputs, force false if driftedDifferent MTF values
7. strategy.exitBoth relative + absolute now evaluated; review every callFills at different prices
8. when removedReplace with if blocksWon't compile / reordered logic
9. MarginDefault is now 100%Backtest leverage changes silently
10. Side-by-sideCompare v5 vs v6 plots/trades on same dataYou ship a behavior change blind

Step 10 is non-negotiable. Run both versions on the same chart, same symbol, same timeframe, and confirm the plots and the strategy trade list match (or that any difference is one you chose). For a structured way to validate strategy output, my Pine Script backtesting guide covers what to actually compare. If you rely on alert webhooks downstream — Zerodha, Angel One, your own bot — re-verify the JSON after migration using alertcondition JSON payloads, because a silently changed boolean upstream can change which alerts fire.

A note on repainting after migration

The dynamic-request change and the stricter boolean handling can both interact with repainting. If your v5 script was non-repainting and you want it to stay that way, re-check it against the techniques in 7 non-repainting Pine Script techniques after you migrate — don't assume the property survived the conversion. A script that newly repaints because an unspecified branch now returns false instead of na is a subtle but real failure mode.

When to just hand it off

If the script is a simple indicator, the converter plus this checklist will get you there in an afternoon. If it's a live strategy wired to broker execution, a multi-symbol scanner, or a library other scripts depend on, the silent behavior changes are where money leaks — and that's exactly the kind of work I do daily as a Pine Script developer. If your migrated code compiles but behaves wrong, that's a textbook Pine Script audit and repair job: I diff v5 vs v6 behavior, isolate the breaking change responsible, and hand you back a verified script.

Pine Script versions and TradingView behavior change over time — verify the current details against TradingView's official migration guide and release notes before relying on any specific behavior here.

Soft CTA

Migrating a production strategy and want a second set of eyes before it goes live? I'll convert it, hunt the silent breaks, and verify v5-vs-v6 behavior side by side. Tell me about your script or see what's covered under my services.

FAQ

Does the Pine Script v6 auto-converter catch all the breaking changes?

No. It reliably handles syntactic and mechanical changes, but several v6 changes are behavioral — implicit bool casts, na booleans collapsing to false, lazy and/or evaluation, and integer division returning a float. These compile cleanly while changing what your script does, so they require manual review and a side-by-side comparison.

Why does my v6 script compile but produce different results than v5?

Almost always one of the silent changes. The most common culprits are a numeric value that used to be implicitly true/false now being treated strictly, an unspecified if/switch branch returning false instead of na, integer division now returning a fractional result, or strategy.exit() now honoring both relative and absolute parameters. Diff the two versions on identical data to find which one.

Do I need to remove dynamic_requests=true when migrating to v6?

Yes. In v6, dynamic requests are enabled by default, so an explicit dynamic_requests=true is redundant. If your multi-symbol or multi-timeframe outputs changed after conversion and you want the old behavior, add dynamic_requests=false to the declaration to replicate most of the v5 behavior, then verify the values match.

What replaces the when parameter in strategy functions?

An if block. The when parameter was removed from all strategy.*() functions in v6. Wrap the call in a condition instead — for example, replace strategy.exit(..., when = inTrade) with if inTrade followed by strategy.exit(...) on the next line.

Will my v6 strategy backtest match my v5 backtest?

Not automatically. The default margin changed to 100%, strategy.exit() now evaluates both relative and absolute levels, and very long backtests trim old trades instead of halting at 9,000. Any of these can move your equity curve. Set margin explicitly and review every exit call if you need the results to line up.

Fastest safe start

If you are just beginning, start with alert-assisted execution and strong logs before you move to fully automatic order placement. That sequence saves money.

WhatsApp for a 3-minute quote

How much capital and risk discipline make sense

A common beginner mistake is thinking algo trading becomes sensible only with huge capital. That is not true. What matters first is that your strategy has stable rules, your risk per trade is defined, and your automation cannot spiral because of a repeated alert or execution mismatch.

The capital question is really a risk-control question. Can you survive a bad streak? Can you keep sizing constant while you validate the system? Can you tell whether underperformance came from the strategy, the slippage, or your execution chain? If the answer is no, adding more capital only hides the problem temporarily.

I usually suggest a staged rollout: paper logic first, then tiny live size, then gradual scaling only after the logging and post-trade review prove the workflow is behaving the way you expect.

  • Do not scale a strategy you cannot explain trade by trade.
  • Validate entry timing, exit timing, and broker response behavior separately.
  • Use smaller size to test the system, not to chase excitement.
  • Judge the stack on repeatability, not on one unusually good day.

What the retail algo framework changes for Indian traders

The reason older beginner advice is weak in 2026 is that it often ignores the current Indian environment. SEBI’s February 4, 2025 circular created the safer participation framework, and the later September 30, 2025 circular gave a glide path before making the framework applicable to all stock brokers from April 1, 2026.

You do not need to become a lawyer to act sensibly, but you do need to stop treating automation like a loose collection of Telegram hacks. A clean retail workflow now means being able to identify the strategy, control how it reaches the broker, and keep enough records to understand what happened later.

This is also why broker-specific behavior matters. The same TradingView alert can sit inside very different operating models depending on which bridge, vendor, or in-house setup you use. Production quality now means broker-aware design, not just clever chart logic.

  • Know which part of your stack generates the signal and which part actually executes it.
  • Avoid black-box vendor promises you cannot inspect or log.
  • Keep strategy names, versions, and parameters explicit.
  • Choose a workflow that can be monitored in real time and audited later.

The best route from beginner to serious operator

The beginner-to-pro path is not linear, but it is predictable. Traders who succeed usually follow this order: first define the setup, then test the chart logic, then clean the alerts, then add execution controls, and only then trust full automation with meaningful size.

Traders who fail usually reverse that order. They buy a bridge first, copy a strategy second, and only ask what the rules are after the first bad fill or duplicate trade. That sequence is emotionally exciting, but it is operationally backwards.

If you want a sustainable edge in India in 2026, think like an operator. Your broker, your logs, your alerts, and your risk rules are part of the strategy. Not separate from it. That mindset shift saves a huge amount of pain.

  • Start with one market and one playbook before you add complexity.
  • Use alerts to create discipline before you use them to trigger execution.
  • Review every rejected order and every false trigger.
  • Scale only when the boring operational parts are stable.
Want a second pair of eyes on your setup?

Send the chart idea, broker, market, and goal on WhatsApp. I can usually tell you quickly whether it needs a custom indicator, a strategy audit, an alert fix, or a broker-ready automation layer.