Overview
Pine Script v6 (shipped November 2024, current as of 2026) runs inside TradingView and recompiles on every bar. The runtime is unusual: series-typed values, a global bar_index, and a security-call model that repaints if used carelessly. v6 tightened the type system: booleans can no longer be na, and/or evaluate lazily, and technical-analysis calls must use the ta.* namespace (ta.ema, not bare ema). Convert v5 scripts with the Pine Editor’s “Convert code to v6” command, then fix what the converter flags. This page covers the conventions that produce stable, non-repainting scripts. Read general-principles first.
Pick the right script kind
Pine has three script kinds. Choose by what the script needs to do, not by familiarity.
indicator(...): visual output on the chart. Plots, fills, alerts. No trading. Cheap to compute.strategy(...): backtestable trading logic withstrategy.entry,strategy.exit,strategy.close. TradingView simulates fills and tracks PnL.library(...): reusable functions imported by other scripts viaimport user/name/1. No plots, nostrategy.*, no UI inputs.
A script that calculates a signal and a script that trades the signal should be separate. Keep the indicator clean and call it from a strategy script via request.security on the same symbol, or copy the function into a library.
Use var for state that survives across bars
A plain assignment re-runs on every bar. var initializes once and persists.
//@version=6
indicator("Streak", overlay=false)
var int upStreak = 0
upStreak := close > close[1] ? upStreak + 1 : 0
plot(upStreak)Use varip only when you need state that updates intrabar on every tick and survives bar close; the rare correct use is a live order-book accumulator. Most strategies should never see varip.
Avoid lookahead in request.security
The default request.security call repaints. To get a stable historical value that matches what live execution sees, request the previous bar of the higher timeframe with lookahead_off (the default) and a [1] offset.
htfClose = request.security(syminfo.tickerid, "60", close[1], lookahead=barmerge.lookahead_off)Never set lookahead=barmerge.lookahead_on in a strategy that simulates fills, or backtests will look magic and live trading will not. The only legitimate lookahead_on use is in an indicator that explicitly studies future-bar relationships for research, and you label the plot accordingly.
Plot performance: budget your draws
Each script has a hard limit on plot, line, label, and box objects. Hit the limit and TradingView silently drops the oldest objects, which looks like a bug.
- Cap
max_lines_count,max_labels_count,max_boxes_countin theindicator(...)declaration based on what you actually need. - Delete objects you no longer need with
line.delete,label.delete,box.delete. - Avoid creating a new object on every bar when an
xloc.bar_index-anchored update is enough.
A script that draws a 200-bar history of pivots should keep a fixed array of line handles and recycle them, not allocate one per bar.
Strategy entries and exits
Use the dedicated strategy calls; do not reinvent fills with if blocks and global counters.
//@version=6
strategy("Donchian", overlay=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
length = input.int(20)
upper = ta.highest(high, length)
lower = ta.lowest(low, length)
if close > upper[1]
strategy.entry("long", strategy.long)
if close < lower[1]
strategy.close("long")Use strategy.entry for signaled entries (it reverses an opposite position automatically), strategy.order for unconditional orders, and strategy.exit for bracket exits with stop and target. Reference [1] when comparing against the prior bar, so the signal is computed on a closed bar.
Alerts with alertcondition and alert
alertcondition declares an alert the user wires in the TradingView UI; it compiles into the script’s available alert list. alert fires at runtime when called and supports dynamic messages.
crossUp = ta.crossover(close, ta.sma(close, 50))
alertcondition(crossUp, title="Cross up", message="Close crossed above SMA(50)")
if crossUp
alert("Cross up at " + str.tostring(close), alert.freq_once_per_bar_close)Use alert.freq_once_per_bar_close for signal alerts; intrabar firing repaints alerts the same way it repaints plots. Mention the timeframe and symbol in the message body so a phone-delivered alert is self-describing.
Inputs are the contract
Every magic number gets an input.int, input.float, input.bool, or input.source with a clear title and a sensible default. The Settings dialog is the user’s only configuration surface; treat it like an API.
For shared math (RSI variants, volatility filters, position sizers), publish a library and import it from the indicators and strategies that use it. Versioned libraries avoid the copy-paste drift that the same dependency rules in general-principles warn against, and the typed-input discipline mirrors the validation patterns in typescript.