v6 changes that stop the script
The compiler finds all of these, so they cost time rather than money. Every row is from the official migration guide [1].
| v5 code | Why v6 rejects it | Fix |
|---|---|---|
if someNumber | numbers no longer cast to bool implicitly | if bool(someNumber) or if someNumber != 0 |
na(someBool), nz(someBool) | bool can no longer be na | remove — a bool is true or false |
plot(close, style = na) | unique-type parameters reject na | give a real style |
a switch returning a unique type with no default | the missing branch would be na | add a => default branch |
strategy.entry("id", strategy.long, when = cond) | when was removed | wrap in if cond |
6[1], true[10], color.red[3] | no history on literals or built-in constants | remove the [] |
myObject.field[10] | no direct history on a UDT field | (myObject[10]).field — parentheses required |
plot(close, color = a, color = b) | a parameter cannot repeat | keep one |
plot(close, offset = seriesValue) | offset must be simple, not series | use a const or input value |
plot(close, linewidth = 0) | minimum is 1 | use 1 or more |
plot(close, transp = 80) | transp was removed | color = color.new(myColor, 80) |
ta.ema(close, mutableLen) | a mutated variable is now series, and ta.ema wants simple | use a fixed or input length |
Two of these deserve a note.
when was the standard way to place a conditional order in v5, so it
appears in a very large share of published v5 strategies. Every one of them
needs the if rewrite. It is mechanical, but it is not optional.
The mutable-variable rule is the one people argue with. A variable that is
reassigned during execution qualifies as series, and functions like
ta.ema() accept only simple for their length [1]. In v5 this slipped
through. It was never sound — an indicator length that changes bar to bar has no
consistent history — so v6 is refusing something that was silently wrong rather
than removing a feature.
The colour constants changed value
Not an error, but it belongs here because it surprises people comparing screenshots [1]:
| Constant | v5 | v6 |
|---|---|---|
color.red | #FF5252 | #F23645 |
color.teal | #00897B | #089981 |
color.yellow | #FFEB3B | #FDD835 |
label.new() also changed its default text colour from color.black to
color.white.
v6 changes that do not error
These are the migration changes that let the script compile and run, and change what it returns. They are the reason a converted strategy can backtest differently while looking identical, and they are all documented [1] — just not grouped this way.
| What changed | v5 | v6 | Consequence |
|---|---|---|---|
and / or evaluation | both sides always evaluated | short-circuits | an indicator call after a false condition is skipped, corrupting its history |
margin_long / margin_short default | 0 — no margin checking | 100 — strict enforcement | margin calls where there were none |
strategy.exit() with both relative and absolute levels | absolute won, relative ignored | whichever triggers first | profit = 0, limit = price now exits at entry |
| Orders beyond 9,000 | runtime error, execution halted | oldest orders silently trimmed | trimmed trades return na from strategy.closedtrades.* |
timeframe.period on a daily chart | "D" | "1D" | timeframe.period == "D" is now always false |
5 / 2 with two const ints | 2 | 2.5 | thresholds and lookback lengths shift |
| A bool referenced before it exists | na | false | first-bar logic takes the other branch |
for i = 0 to expr | expr evaluated once | re-evaluated every iteration | a loop whose bound mutates can run indefinitely |
An int or float used as a condition | implicitly cast to bool | no longer cast | a truthiness test that compiled in v5 now fails to compile |
array.get(-1) | runtime error | returns the last element | an off-by-one that used to shout now returns a plausible wrong value |
color.red | #FF5252 | #F23645 | plots change shade; color.teal and color.yellow moved too, and default label text went black to white |
The lazy-evaluation one is the worst of them
Every other item on that list changes a number. This one corrupts an indicator's internal state, and the damage is invisible in the code.
In v5 both sides of and were always evaluated, so an indicator inside a
condition was called on every bar and kept a continuous history. In v6
evaluation short-circuits [1], so the call is skipped on bars where the first
condition is false — and an indicator that did not run on every bar no longer
has the history it needs.
TradingView's own example [1]:
// v6: ta.rsi() is skipped whenever close <= open,
// so its internal history is no longer continuous
if close > open and ta.rsi(close, 14) > 50
signal := true
The fix is to pull the call into global scope, where it runs unconditionally:
rsiVal = ta.rsi(close, 14)
if close > open and rsiVal > 50
signal := true
This is worth checking in every converted script that calls a ta.* function
inside an if, a switch, or the right-hand side of an and. Nothing warns
you, and the backtest still produces a curve.
The margin default catches leveraged strategies
margin_long and margin_short defaulted to 0 in v5, which meant no margin
checking at all and permitted overleveraging. In v6 they default to 100,
which enforces margin and triggers margin calls [1].
A v5 strategy that was quietly overleveraged will now be stopped out by the engine. To reproduce the old behaviour deliberately, set them back explicitly:
strategy("My strategy", margin_long = 0, margin_short = 0)
That is a choice about realism, not a bug fix — v6's default is the more honest of the two, and a strategy that only worked without margin enforcement was never going to survive live.
Using the converter, and what it misses
The Pine Editor's own converter handles most of the mechanical changes automatically [1]. Use it first — hand-migrating the table above is wasted effort.
Its documented limits [1]:
- A script that does not compile cleanly as v5 cannot be auto-converted. Fix it as v5 first, then convert.
- Rare cases still produce v6 compilation errors that need a manual fix.
- Dynamic request behaviour may differ, and restoring v5 semantics can
require setting
dynamic_requests = falseexplicitly.
That last one is worth expanding, because it is a change of default rather than
a change of syntax. In v5, request.*() calls were non-dynamic by default and
dynamic_requests = true was needed to call them inside loops or conditionals.
In v6 they are dynamic by default and the compiler disables it when it is not
needed [1]. Scripts that previously needed the flag now work without it — but
nested requests can behave differently, and calling request.*() in a local
scope with dynamic_requests = false is now a compile error.
What the converter cannot do
Nothing in the converter addresses the silent changes above. It rewrites syntax;
it does not know that your ta.rsi() inside an and no longer runs every bar,
or that your strategy now enforces margin. After converting, the silent list
is still yours to check.
Where exporting avoids the question
If the strategy was built in AlfaTactix rather than hand-written, there is nothing to migrate: the strategy is stored as structured data and the generator emits current v6. Re-exporting produces a file that is already v6-correct, including the defaults above.
That only helps for strategies you build here. A v5 script you wrote or inherited has to be migrated — which is what the rest of this page is for.
What the generator emits
This is an export-oriented map, not a language manual — for the full surface, the official v6 reference is the authority [2].
The wrapper. Every export is a strategy() declaration, never
indicator(). That is what makes the Strategy Tester able to report trades at
all [3]; a script declared as an indicator plots and nothing more.
Indicator calls. Basic export draws on the ta.* namespace — ta.rsi,
ta.sma, ta.ema, ta.macd, ta.stoch, ta.atr, ta.bb, ta.cci,
ta.wpr among them. Lengths are emitted as fixed or input values, never as
mutated variables, which is also what v6 requires of a simple length
parameter.
Entries and exits. Orders are placed with strategy.entry() wrapped in an
if, and closed with strategy.exit() or strategy.close(). The if form is
not a style preference — when no longer exists in v6.
Position size and costs. strategy() carries the capital, commission and
slippage you set in the builder, so the Strategy Tester is costed rather than
frictionless. A backtest without costs is the most common way a strategy looks
profitable and is not.
What Basic export will not emit
Multi-timeframe logic, VWAP-based targets, and news filters are outside Basic. The export refuses rather than emitting something that behaves differently from what you designed — see the generator errors in Common Pine Script errors.
Tactix AI on this workflow
AlfaTactix includes Tactix AI: use Tactix Studio to describe your idea in plain language and draft timeframes, signals, filters, and risk into the same six-step Strategy Builder form — or open Tactix Guide on any step when you only need a field explained. Review every value, then export MQL5 or Pine Script from Code Generator (form-first — not untested prompt-to-code).

