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
nadefault:bool ready = nais invalid. - In an
iforswitchthat returns abool, any branch you didn't specify now returnsfalseinstead ofna.
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 = falseto 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
| Step | What you're checking | Risk if skipped |
|---|---|---|
| 1. Convert | Run "Convert code to v6"; confirm it compiles | None — but compiling ≠ correct |
| 2. Bool casts | Every if/?/and/or on an int/float → explicit != 0 | Silent wrong logic, esp. on bar 0 |
| 3. na booleans | bool x = na removed; unspecified branches now return false | "Unknown" state collapses to false |
| 4. Lazy and/or | Move side effects out of boolean expressions | Skipped counters / missing array items |
| 5. Int division | Wrap intended truncation in int() | Wrong lot size / index math |
| 6. Requests | Drop dynamic_requests = true; verify outputs, force false if drifted | Different MTF values |
| 7. strategy.exit | Both relative + absolute now evaluated; review every call | Fills at different prices |
| 8. when removed | Replace with if blocks | Won't compile / reordered logic |
| 9. Margin | Default is now 100% | Backtest leverage changes silently |
| 10. Side-by-side | Compare v5 vs v6 plots/trades on same data | You 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.
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 quoteHow 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.
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.