Skip to content

VP Script

First steps

VP Script is the language indicators are written in. If you have written TypeScript you can already read it, and the differences from Pine are mostly things it does not make you think about.

Writing this with an AI assistant?

Copy the language, its full reference and its rules as one Markdown document, then paste it into a new chat before you ask for an indicator. Without it you will get Pine, which does not run here.

Open ChatGPTOpen Claude

It is also a plain URL, if your tool can fetch one: /docs/script/llms

Where you write it

Open any chart, then the Scripts panel beside it. Press NEW, type, and press RUN. The indicator appears on the chart and in the Indicators dialog under My scripts, where you can add and remove it like any built-in.

Scripts are saved in your browser. They follow your account only once you publish one, and nothing is uploaded until you do.

The smallest indicator

indicator('My first indicator')
plot(close)

Two lines and it is a real study: named, on the price pane, drawing the close. Every script starts by declaring itself, because the name is what the legend, the settings dialog and the saved chart all refer to it by.

Something useful

indicator('Two averages', { shortTitle: 'MAs' })
const fastLen = input.int('Fast', 10, { min: 2, max: 200 })
const slowLen = input.int('Slow', 30, { min: 2, max: 400 })
const src = input.source('Source', 'close')
const fast = ta.sma(src, fastLen)
const slow = ta.sma(src, slowLen)
plot(fast, { title: 'Fast', color: '#22c55e', width: 2 })
plot(slow, { title: 'Slow', color: '#ef4444', width: 2 })
marker(ta.crossover(fast, slow), { shape: 'triangleUp', color: '#22c55e', location: 'below' })
marker(ta.crossunder(fast, slow), { shape: 'triangleDown', color: '#ef4444', location: 'above' })

Three things worth noticing. The lengths are input.int, so a reader changes them in the settings dialog rather than editing your code. ta.sma takes a whole series and returns a whole series. And marker takes a condition series, drawing a shape on every bar where it is true, rather than being called once per bar.

Seeing what it is doing

log prints under the editor. A series prints as its length and its first few values rather than ten thousand numbers, so logging one is useful instead of overwhelming.

indicator('Debugging')
const rsi = ta.rsi(close, 14)
log('bars', barCount)
log('rsi', rsi)
log('last', rsi[rsi.length - 1])
plot(rsi)

It shows the last run only. A chart recomputes whenever an input changes, and a growing transcript would show lines from three edits ago as if they had just happened. If a run throws, whatever it printed first stays on screen, which is usually the thing that explains the error.

log is a development tool and does nothing once an indicator is published: capture is refused for anything running sandboxed. You never have to strip your log calls before publishing, and a reader’s chart pays nothing for them.

What happens when you press Run

One pass over the whole bar array, producing a description of what to draw.

If you are coming from Pine

The big difference: Pine runs your whole script once per bar, so cross-bar state needs var and every counter is one. VP Script runs once over the whole bar array. ta.* are pure transforms from series to series, loops are ordinary loops, and state is ordinary local variables.

What that removes is evaluation order. There is no bar index advancing under you, so there is nothing to repaint by accident and no lookahead to get wrong. A value that is not knowable yet simply is not in the series.

null means “no value here” and propagates rather than becoming zero. The first length - 1 bars of any average are null, and they draw as a gap instead of as a line at zero. This is the single most common surprise, and the language page covers it.

Sharing it

Anything you write stays yours and stays local until you press PUBLISH. Published indicators are open source by design: readers can read exactly what they are about to run, which is the only honest basis for running a stranger’s code.