v3 is additive
The good news first: every v2 script keeps running on v3. v3 did not break the language, it grew it. Typed structs, collections, self-managing drawings, no-repaint multi-timeframe, and multi-venue aggregation are all new capabilities layered on top of what v2 already did. If your script worked, it still works.
The version marker can stay //@version=2 or move to //@version=3: the engine treats the two identically, routing both to the modern engine, and every v3 feature runs under either line. //@version=3 is the canonical header (all of the platform's built-in scripts ship with it); there is no downgrade and nothing to migrate in the header itself. What you must NOT do is delete the marker or write //@version=1; both route the script to the deprecated legacy v1 engine. For the full tour of what v3 adds, see What's New in v3.
So migration is not a rewrite. It is a short checklist. Two changes can move your numbers: corrected TA warmup/early values, and the fix for negative timeseries subscripts.
First check: eight corrected TA builtins
Eight technical-analysis builtins were corrected to their textbook forms. Each now matches an independent reference implementation, so this is a correctness fix, not a regression. The practical effect: these functions start emitting slightly later and with different early-bar values than they did in v2. Steady-state output (well past the warmup window) is the same or more correct; the early bars are what changed.
What was corrected:
| Builtin | What changed |
|---|---|
ema | Correct seeding of the first averaged value |
rsi | Proper Wilder smoothing |
macd | Follows the corrected ema |
psar | Textbook parabolic SAR initialization |
adx | Correct directional-movement warmup |
highest | Strict window warmup |
lowest | Strict window warmup |
sma | Strict warmup (no partial-window averages) |
Why you might notice: if a script keys off an indicator's very first non-empty bars (for example a backtest that starts trading at bar 5, or a cross evaluated near the left edge of history), the first signal may shift by a bar or two. Anything reading these indicators after their warmup window is unaffected. If your logic waits for the indicator to be ready (the usual case), there is nothing to do.
When each indicator starts emitting
This table is the practical reference: with a period-5 setup, the first bar index at which each builtin produces a value. Use it to reason about where your own warmup edges land.
| Builtin | Call | First value at bar |
|---|---|---|
ema | ema(source=closeSeries, period=5) | 4 |
rsi | rsi(source=closeSeries, period=5) | 5 |
macd | macd(... fastPeriod=3, slowPeriod=6, signalPeriod=3).macd | 5 |
psar | psar(source=trade, start=0.02, increment=0.02, maxValue=0.2) | 1 |
adx | adx(source=trade, period=5).adx | 9 |
highest | highest(source=trade, period=5, priceIndex=2) | 4 |
lowest | lowest(source=trade, period=5, priceIndex=3) | 4 |
sma | sma(source=closeSeries, period=5) | 4 |
//@version=2
define(title="TA Early Values", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries closeSeries = trade.close
var emaValue = ema(source=closeSeries, period=5)
var rsiValue = rsi(source=closeSeries, period=5)
var macdValue = macd(source=closeSeries, fastPeriod=3, slowPeriod=6, signalPeriod=3)
var psarValue = psar(source=trade, start=0.02, increment=0.02, maxValue=0.2)
var adxValue = adx(source=trade, period=5)
var highestValue = highest(source=trade, period=5, priceIndex=2)
var lowestValue = lowest(source=trade, period=5, priceIndex=3)
var smaValue = sma(source=closeSeries, period=5)
plotLine(value=emaValue, colors=["#2563eb"], label=["EMA"], desc=["ema period 5"])
plotLine(value=rsiValue, colors=["#dc2626"], label=["RSI"], desc=["rsi period 5"])
plotLine(value=macdValue.macd, colors=["#7c3aed"], label=["MACD"], desc=["macd line 3 6 3"])
plotLine(value=psarValue, colors=["#059669"], label=["PSAR"], desc=["psar default style parameters"])
plotLine(value=adxValue.adx, colors=["#ea580c"], label=["ADX"], desc=["adx period 5"])
plotLine(value=highestValue, colors=["#0891b2"], label=["Highest"], desc=["highest high period 5"])
plotLine(value=lowestValue, colors=["#be123c"], label=["Lowest"], desc=["lowest low period 5"])
plotLine(value=smaValue, colors=["#374151"], label=["SMA"], desc=["sma period 5"])Compatibility aliases that still work
v3 keeps a set of v2 conveniences so older scripts do not need touching. These are normalized for you, not rejected:
- Uppercase
yAlignvalues.plotText(..., yAlign="CENTER")is accepted and normalized tomiddle;"ABOVE"becomestopand"BELOW"becomesbottom. shape="star". Accepted as aplotShapeshape (it renders as a diamond marker).- Bare
NaN. WritingNaNas a value is fine and behaves as a gap, the same asna.
The following script exercises all of these and runs clean.
//@version=2
define(title="Accepted Compatibility Aliases", position="overlay", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries closeSeries = trade.close
var maybeClose = barIndex > 10 ? closeSeries : NaN
var shapeValue = closeSeries > trade.open ? trade.high : trade.low
plotText(text="center", color="#111827", price=trade.high, yAlign="CENTER")
plotText(text="above", color="#111827", price=trade.high, yAlign="ABOVE")
plotText(text="below", color="#111827", price=trade.low, yAlign="BELOW")
plotShape(value=shapeValue, shape="star", colors=["#f97316"], width=3, fill=true, label=["Star"], desc=["star shape compatibility alias"])
plotLine(value=maybeClose, colors=["#2563eb"], width=2, label=["NaN"], desc=["bare NaN compatibility value"])What got stricter
v3 tightened a few inputs that v2 let slide. These surface as compile errors with a line and column, and each error message lists the valid options, so the fix is mechanical.
plotText.yAlign must be a known value. Valid values are top, middle, bottom (the uppercase aliases above still map onto these). An unknown one like "SIDEWAYS" fails with:
Invalid plotText.yAlign 'SIDEWAYS'. Expected one of: 'top', 'middle', 'bottom'
plotShape.shape must be a known shape. The accepted set is large (circle, cross, triangle, diamond, arrowUp, arrowDown, flag, square, label, char, star, and more), but an invalid one like "hexagon" is rejected, and the error enumerates every shape the engine accepts so you can pick the right one.
define(..., position=...) must be a string literal. It has to be a literal "onchart" or "offchart", not a variable or expression. A computed value fails with:
define(title, position, axis): 'position' must be a string literal ('onchart' | 'offchart')
If you hit any error not covered here, the Common Errors page maps real engine messages to their cause and fix.
Negative timeseries subscripts no longer look ahead
Previously, close[-1] could read a future bar. That was a lookahead bug, not a supported feature. Negative timeseries subscripts now return na. Array negative indexes still wrap from the tail, so arr[-1] remains the last element.
The script below exercises both: negative indexing on the series returns na, while negative indexing on the array reads from the tail.
//@version=2
define(title="Negative Index Series Array", position="offchart", axis=true)
timeseries trade = ohlcv(symbol=currentSymbol, exchange=currentExchange)
timeseries closeSeries = trade.close
var values = [trade.open, trade.high, trade.low, trade.close]
var seriesCheck = isna(closeSeries[-1]) ? closeSeries : na
var arrayCheck = values[-1] == trade.close ? trade.close : na
plotLine(value=seriesCheck, colors=["#2563eb"], width=2, label=["Series negative"], desc=["negative timeseries history indexes return na"])
plotLine(value=arrayCheck, colors=["#16a34a"], width=2, label=["Array negative"], desc=["negative array indexes wrap from the tail"])Migration checklist
- Keep a version marker on line 1:
//@version=2and//@version=3are interchangeable (=3is canonical). Never remove it; a missing marker or//@version=1routes to the deprecated legacy engine. - Re-run scripts that lean on the eight corrected TA builtins near the start of history; confirm any edge-case signals still fire where you expect.
- Replace any intentional
close[-1]style lookahead; it now returnsna. Keeparr[-1]only for array tail access. - If you wrote
define(position=someVar), make it a literal"onchart"or"offchart". - Everything else, including uppercase
yAlign,shape="star", and bareNaN, keeps working untouched. - When you are ready to use the new power, start with What's New in v3.