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 with strategy.entry, strategy.exit, strategy.close. TradingView simulates fills and tracks PnL.
  • library(...): reusable functions imported by other scripts via import user/name/1. No plots, no strategy.*, 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_count in the indicator(...) 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.