Skip to content

Performance ​

Why is your script slow, and what makes it faster? Measure it first with the profiler, which shows each line's share of the run. Most of the time a slow script does on every bar what only the last bar needs, such as filling a table or redrawing its drawings, and moving that work behind barstate.islast is the biggest gain you can get.

pine
//@version=6
indicator("Last 10 closes", overlay = true)
var table t = table.new(position.top_right, 1, 10)
// Filled once, on the last bar. Filled on every bar, the table
// looks the same and the run takes over ten times as long.
if barstate.islast
    for r = 0 to 9
        table.cell(t, 0, r, str.tostring(close[r], format.mintick))

Measure first ​

Turn on Profiler mode in the Script Editor's More menu. The script runs again with every line measured, and the gutter shows each line's share of the run, with the three costliest lines marked. Its hints point at the common causes on this page, such as a block that runs on every bar while only the last bar's drawings stay. The profiler explains how to read it.

Do the last bar's work on the last bar ​

A table, a label that shows the latest values or a line fitted to recent bars only needs to be right on the last bar. Done on every bar, the same work runs thousands of times and all but the last result is thrown away. Gate it with if barstate.islast, as the example does. A drawing that should also update as each live bar closes, such as a line refitted on closed bars, takes the gate in Drawing on the last bar only.

Keep the gate for what's redrawn whole. Anything the script builds up bar by bar, such as a label at each pivot or a value a later bar reads, has to stay where it is.

Let the built-ins keep their windows ​

A ta.* call keeps its window as the bars go by, so ta.sma(close, 200) adds one value and drops one on each bar. A loop that reads close[i] for 200 values does all 200 again on every bar. Timed on 6,144 daily bars, the loop took about twice the built-in's time on the compiler and about eight times on the interpreter, and it gives you nothing the built-in doesn't.

pine
//@version=6
indicator("Mean of the last 200 closes")
plot(ta.sma(close, 200), "Built-in window")
// The same mean, worked out again from scratch on every bar
float sum = 0.0
for i = 0 to 199
    sum += close[i]
plot(bar_index >= 199 ? sum / 200 : na, "Loop over history")

A ta.* call inside a loop is slower and also wrong: it keeps one state for all its passes, and the run warns you. See State in functions and branches.

The compiler ​

Your script runs compiled by default. In your browser the whole script compiles to JavaScript once, and on the server the Rust engine compiles it once to machine code. A change of settings runs the same compiled code with new values, so moving an input doesn't compile again.

When the browser's compiler can't take a script, the run goes on in the interpreter, the engine every other one is checked against, which is much slower. The run then gives a warning that starts Running on the interpreter, which is much slower: and says what stopped the compiler. Few scripts meet it. If yours does, the reason names the construct to rewrite.

The Script Editor's More menu can turn the compiler off, and the Rust engine on the chart. Use them only to check that an answer doesn't depend on the engine: on a script TradingView compiles, every engine is built to give the same values, bit for bit, and a difference is a bug to report. See The engines.

Changing a setting ​

When you change an input, the compiled script reruns only the statements that input reaches. A statement that doesn't read it replays the values it worked out last time, which is what makes a slider or an Optimizer sweep quick.

That replay is for plain statements: ta.* and math.* calls, arithmetic and inputs with a title and a literal default. A statement reruns every time when it declares a var, works with an array, a map or an object of your own, draws, calls a function the engine can't prove pure, or reads an input with no title. You don't have to write for it, but giving each input a title, as you would anyway, keeps it working.

Requests ​

Each request.security call runs its expression on another symbol's or timeframe's bars, which is a run of its own over those bars. Ask for what you need from one place in one call, with a tuple, rather than one call a value: the profiler flags two requests on one symbol and timeframe for this.

pine
//@version=6
indicator("Daily open and close")
[o, c] = request.security(syminfo.tickerid, "1D", [open, close]) // one call
plot(c - o, "Day's change so far")

Where a script runs ​

A study on a chart runs on the terminal's server, on the compiled Rust engine. One that declares strategy(), uses varip, reads timenow or imports a library other than the technical-analysis one runs compiled in your browser. On a script TradingView compiles, every engine gives the same values, so where it runs changes only how long it takes. Text TradingView refuses has no such promise: see scope. Which engine runs where has the details.

Traps ​

  • Fast here, stopped there. A chart run here has no time limit, and TradingView stops a script after 40 seconds. A heavy script you mean to publish there needs the same care. See Limits.
  • A loop over 100,000 passes ends early. A long loop isn't only slow: it stops at 100,000 passes with no error, and the script goes on with a short result. See Limits.
  • The profiled run is slower. The profiler's own cost is taken out of each line's figure, but the profiled run takes longer than a plain one, so read its shares, not its total. See How far to trust the figures.

See also ​

AmenoScript is compatible with Pine Script v6. Pine Script is a trademark of TradingView, Inc., which has no part in AmenoScript.