Pine Script™ Execution Model

The Pine Script™ execution model is key to understanding how your scripts are run on charts. It functions hand-in-hand with Pine Script™'s time series and type system, providing a comprehensive overview of the script's capabilities.

When a Pine Script™ is deployed on a chart, it processes each historical bar using the available OHLCV (open, high, low, close, volume) values. After completing its run on the historical bars, the script shifts its focus to the rightmost bar in the dataset, provided that trading is currently active on the chart's symbol.

Calculating with Historical Bars

Consider a simple script as an example. On historical bars, it runs at the equivalent of the bar's close, as this is when all the OHLCV values are known for the bar. This execution process repeats until all historical bars in the dataset are processed, and the script reaches the rightmost bar on the chart.

//@version=5
indicator("My Script", overlay = true)
src = close
a = ta.sma(src, 5)
b = ta.sma(src, 50)
c = ta.cross(a, b)
plot(a, color = color.blue)
plot(b, color = color.black)
indicator.signal(c)

Maintaining Historical Values

Every function call in Pine Script leaves a trail of historical values that can be accessed by the script on subsequent bars. The consistency of these historical values is crucial for accurate calculations and results. Here's an example:

pinescriptCopy code//@version=5
indicator("My script")

calcBarIndex() =>
    int index = na
    index := nz(index[1], replacement = -1) + 1

condition = bar_index % 2 == 0
globalScopeBarIndex = calcBarIndex()
int customIndex = na

if condition
    customIndex := globalScopeBarIndex

plot(bar_index,   "Bar index",    color = color.green)
plot(customIndex, "Custom index", color = color.red, style = plot.style_cross)

Understanding Exceptions

In Pine Script, the execution of every function leaves behind a series of historical values. This sequence can be accessed in subsequent bars using the [] operator. A consistent series of these historical values depends on the function being called on each bar. However, if your script doesn't call functions consistently on each bar, this can lead to inconsistent historical data. This inconsistency may, in turn, affect calculations and results, particularly when they rely on the continuity of these historical series. Be aware that the compiler will warn you in such situations, indicating that the values from a function (either built-in or user-defined) may not be accurate.

Consider the following example script, this inconsistency can be rectified by calling the function at the script’s global scope, ensuring the function executes on every bar. By doing this, the function's entire history is recorded and referenced instead of just the results from every other bar.

The implication of this behavior is not limited to user-defined functions. It can significantly impact built-in functions that reference history internally, like the ta.sma() function. To avoid inconsistencies, assign the ta.sma() function to a variable in the global scope and reference that variable's history as needed.

This behavior is crucial because forcing the execution of functions on each bar can lead to unforeseen results, especially in functions that produce side effects. A function with side effects does more than just return a value; for example, the label.new() function creates a label on the chart. Forcing it to be called on every bar, even when it is inside of an if structure, would create labels where they shouldn't logically appear.

However, not all built-in functions require execution on every bar. Functions that don't interact with their historical data, like math.max(), do not require special treatment. If you don't see a compiler warning when using a function within a conditional block, it's safe to use it without impacting calculations. If a warning does appear, consider moving the function call to the global scope. Always ensure the output is correct to avoid unexpected results.

Last updated