[go: up one dir, main page]

0% found this document useful (0 votes)
336 views124 pages

Pinescript Manual

Pine Script™ v5 is TradingView's programming language designed for creating custom trading tools, indicators, and strategies that can be backtested. The user manual provides a step-by-step guide for beginners to explore existing scripts, learn to read and write Pine Script™, and utilize the Pine Script™ Editor for coding. It emphasizes the accessibility of Pine Script™ while outlining the limitations imposed to ensure fair resource sharing among users.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
336 views124 pages

Pinescript Manual

Pine Script™ v5 is TradingView's programming language designed for creating custom trading tools, indicators, and strategies that can be backtested. The user manual provides a step-by-step guide for beginners to explore existing scripts, learn to read and write Pine Script™, and utilize the Pine Script™ Editor for coding. It emphasizes the accessibility of Pine Script™ while outlining the limitations imposed to ensure fair resource sharing among users.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 124

User Manual • Welcome to Pine Script™ v5

Welcome to Pine Script™ v5


Pine Script™ is TradingView’s programming language. It allows traders to create
their own trading tools and run them on our servers. We designed Pine Script™ as a
lightweight, yet powerful, language for developing indicators and strategies that
you can then backtest. Most of TradingView’s built-in indicators are written in Pine
Script™, and our thriving community of Pine Script™ programmers has published
more than 100,000 Community Scripts.

It’s our explicit goal to keep Pine Script™ accessible and easy to understand for
the broadest possible audience. Pine Script™ is cloud-based and therefore
different from client-side programming languages. While we likely won’t develop
Pine Script™ into a full-fledged language, we do constantly improve it and are
always happy to consider requests for new features.

Because each script uses computational resources in the cloud, we must impose limits in order to share these
resources fairly among our users. We strive to set as few limits as possible, but will of course have to implement as
many as needed for the platform to run smoothly. Limitations apply to the amount of data requested from additional
symbols, execution time, memory usage and script size.

Pine Script™ v5 User Manual Pine Script™ primer

© Copyright 2023, TradingView.


Options v: v5

User Manual • Pine Script™ primer

Pine Script™ primer


First steps
Introduction
Using scripts
Loading scripts from the chart
Browsing Community Scripts
Changing script settings
Reading scripts
Writing scripts
First indicator
The Pine Script™ Editor
First version
Second version
Next
Next steps
“indicators” vs “strategies”
How scripts are executed
Time series
Publishing scripts
Getting around the Pine Script™ documentation
Where to go from here?

Welcome to Pine Script™ v5 First steps

© Copyright 2023, TradingView.


Options v: v5

User Manual • Pine Script™ primer • First steps

First steps

Introduction
Using scripts
Loading scripts from the chart
Browsing Community Scripts
Changing script settings
Reading scripts
Writing scripts

Introduction
Welcome to the Pine Script™ v5 User Manual, which will accompany you in your journey to learn to program your own
trading tools in Pine Script™. Welcome also to the very active community of Pine Script™ programmers on
TradingView.

In this page, we present a step-by-step approach that you can follow to gradually become more familiar with
indicators and strategies (also called scripts) written in the Pine Script™ programming language on TradingView. We
will get you started on your journey to:

1. Use some of the tens of thousands of existing scripts on the platform.


2. Read the Pine Script™ code of existing scripts.
3. Write Pine Script™ scripts.

If you are already familiar with the use of Pine scripts on TradingView and are now ready to learn how to write your
own, then jump to the Writing scripts section of this page.

If you are new to our platform, then please read on!

Using scripts
If you are interested in using technical indicators or strategies on TradingView, you can first start exploring the
thousands of indicators already available on our platform. You can access existing indicators on the platform in two
different ways:

By using the chart’s “Indicators & Strategies” button, or


By browsing TradingView’s Community Scripts, the largest repository of trading scripts in the world, with more
than 100,000 scripts, most of which are free and open-source, which means you can see their Pine Script™ code.

If you can find the tools you need already written for you, it can be a good way to get started and gradually become
proficient as a script user, until you are ready to start your programming journey in Pine Script™.
Loading scripts from the chart
To explore and load scripts from you chart, use the “Indicators & Strategies” button:

The dialog box presents different categories of scripts in its left pane:

Favorites lists the scripts you have “favorited” by clicking on the star that appears to the left of its name when
you mouse over it.
My scripts displays the scipts you have written and saved in the Pine Script™ Editor. They are saved in
TradingView’s cloud.
Built-ins groups all TradingVIew built-ins organized in four categories: indicators, strategies, candlestick
patterns and volume profiles. Most are written in Pine Script™ and available for free.
Community Scripts is where you can search from the 100,000+ published scripts written by TradingView users.
Invite-only scripts contains the list of the invite-only scripts you have been granted access to by their authors.

Here, the section containing the TradingView built-ins is selected:

When you click on one of the indicators or strategies (the ones with the green and red arrows following their name), it
loads on your chart.

Browsing Community Scripts


From TradingView’s homepage, you can bring up the Community Scripts stream from the “Community” menu. Here,
we are pointing to the “Editors’ Picks” section, but there are many other categories you can choose from:
You can also search for scripts using the homepage’s “Search” field, and filter scripts using different criteria. The
Help Center has a page explaining the different types of scripts that are available.

The scripts stream shows script widgets, i.e., placeholders showing a miniature view of each publication’s chart and
description, and its author. By clicking on it you will open the script’s page, where you can see the script on a chart,
read the author’s description, like the script, leave comments or read the script’s source code if it was published
open-source.

Once you find an interesting script in the Community Scripts, follow the instructions in the Help Center to load it on
your chart.

Changing script settings


Once a script is loaded on the chart, you can double-click on its name (#1) to bring up its “Settings/Inputs” tab (#2):

The “Inputs” tab allows you to change the settings which the script’s author has decided to make editable. You can
configure some of the script’s visuals using the “Style” tab of the same dialog box, and which timeframes the script
should appear on using the “Visibility” tab.

Other settings are available to all scripts from the buttons that appear to the right of its name when you mouse over
it, and from the “More” menu (the three dots):

Reading scripts
Reading code written by good programmers is the best way to develop your understanding of the language. This is as
true for Pine Script™ as it is for all other programming languages. Finding good open-source Pine Script™ code is
relatively easy. These are reliable sources of code written by good programmers on TradingView:

The TradingView built-in indicators


Scripts selected as Editors’ Picks
Scripts by the authors the PineCoders account follows
Many scripts by authors with high reputation and open-source publications.

Reading code from Community Scripts is easy; if you don’t see a grey or red “lock” icon in the upper-right corner of
the script’s widget, this indicates the script is open-source. By opening its script page, you will be able to see its
source.

To see the code of TradingView built-ins, load the indicator on your chart, then hover over its name and select the
“Source code” curly braces icon (if you don’t see it, it’s because the indicator’s source is unavailable). When you click
on the icon, the Pine Script™ Editor will open and from there, you can see the script’s code. If you want to play with
it, you will need to use the Editor’s “More” menu button at the top-right of the Editor’s pane, and select “Make a
copy…”. You will then be able to modify and save the code. Because you will have created a different version of the
script, you will need to use the Editor’s “Add to Chart” button to add that new copy to the chart.

This shows the Pine Script™ Editor having just opened after we selected the “View source” button from the indicator
on our chart. We are about to make a copy of its source because it is read-only for now (indicated by the “lock” icon
near its filename in the Editor):
You can also open TradingView built-in indicators from the Pine Script™ Editor (accessible from the “Pine Script™
Editor” tab at the bottom of the chart) by using the “Open/New default built-in script…” menu selection.

Writing scripts
We have built Pine Script™ to empower both budding and seasoned traders to create their own trading tools. We have
designed it so it is relatively easy to learn for first-time programmers — although learning a first programming
language, like trading, is rarely very easy for anyone — yet powerful enough for knowledgeable programmers to build
tools of moderate complexity.

Pine Script™ allows you to write three types of scripts:

Indicators like RSI, MACD, etc.


Strategies which include logic to issue trading orders and can be backtested and forward-tested.
Libraries which are used by more advanced programmers to package oft-used functions that can be reused by
other scripts.

The next step we recommend is to write your first indicator.

Pine Script™ primer First indicator

© Copyright 2023, TradingView.


Options v: v5

User Manual • Pine Script™ primer • First indicator

First indicator

The Pine Script™ Editor


First version
Second version
Next

The Pine Script™ Editor


The Pine Script™ Editor is where you will be working on your scripts. While you can use any text editor you want to
write your Pine scripts, using our Editor has many advantages:

It highlights your code following Pine Script™ syntax.


It pops up syntax reminders for built-in and library functions when you hover over them.
It provides quick access to the Pine Script™ v5 Reference Manual popup when you ctrl + click / cmd +
click on Pine Script™ keywords.
It provides an auto-complete feature that you can activate with ctrl + space / cmd + space .
It makes the write/compile/run cycle fast because saving a new version of a script loaded on the chart also
executes it immediately.
While not as feature-rich as the top editors out there, it provides key functionality such as search and replace,
multi-cursor and versioning.

To open the Editor, click on the “Pine Script™ Editor” tab at the bottom of your TradingView chart. This will open up
the Editor’s pane.

First version
We will now create our first working Pine script, an implementation of the MACD indicator in Pine Script™:
1 //@version=5
2 indicator("MACD #1")
3 fast = 12
4 slow = 26
5 fastMA = ta.ema(close, fast)
6 slowMA = ta.ema(close, slow)
7 macd = fastMA - slowMA
8 signal = ta.ema(macd, 9)
9 plot(macd, color = color.blue)
10 plot(signal, color = color.orange)

Start by bringing up the “Open” dropdown menu at the top right of the Editor and choose “New blank
indicator”.
Then copy the example script above, taking care not to include the line numbers in your selection.
Select all the code already in the editor and replace it with the example script.
Click “Save” and choose a name for your script. Your script is now saved in TradingView’s cloud, but under your
account’s name. Nobody but you can use it.
Click “Add to Chart” in the Editor’s menu bar. The MACD indicator appears in a separate Pane under your chart.

Your first Pine script is running on your chart, which should look like this:

Let’s look at our script’s code, line by line:

Line 1: //@version=5
This is a compiler annotation telling the compiler the script will use version 5 of Pine Script™.

Line 2: indicator("MACD #1")


Defines the name of the script that will appear on the chart as “MACD”.
Line 3: fast = 12
Defines a fast integer variable which will be the length of the fast EMA.

Line 4: slow = 26
Defines a slow integer variable which will be the length of the slow EMA.

Line 5: fastMA = ta.ema(close, fast)


Defines the variable fastMA , containing the result of the EMA calculation (Exponential Moving Average) with a
length equal to fast (12), on the close series, i.e., the closing price of bars.

Line 6: slowMA = ta.ema(close, slow)


Defines the variable slowMA , containing the result of the EMA calculation with a length equal to slow (26),
from close .

Line 7: macd = fastMA - slowMA


Defines the variable macd as the difference between the two EMAs.

Line 8: signal = ta.ema(macd, 9)


Defines the variable signal as a smoothed value of macd using the EMA algorithm (Exponential Moving
Average) with a length of 9.
Line 9: plot(macd, color = color.blue)
Calls the plot function to output the variable macd using a blue line.

Line 10: plot(signal, color = color.orange)


Calls the plot function to output the variable signal using an orange line.

Second version
The first version of our script calculated MACD “manually”, but because Pine Script™ is designed to write indicators
and strategies, built-in Pine Script™ functions exist for many common indicators, including one for… MACD: ta.macd().

This is the second version of our script:

1 //@version=5
2 indicator("MACD #2")
3 fastInput = input(12, "Fast length")
4 slowInput = input(26, "Slow length")
5 [macdLine, signalLine, histLine] = ta.macd(close, fastInput, slowInput, 9)
6 plot(macdLine, color = color.blue)
7 plot(signalLine, color = color.orange)

Note that we have:

Added inputs so we can change the lengths for the MAs


We now use the ta.macd() Pine Script™ built-in to calculate our MACD, which saves us three line and makes our
code easier to read.

Let’s repeat the same process as before to copy that code in a new indicator:

Start by bringing up the “Open” dropdown menu at the top right of the Editor and choose “New blank
indicator”.
Then copy the example script above, again taking care not to include the line numbers in your selection.
Select all the code already in the editor and replace it with the second version of our script.
Click “Save” and choose a name for your script different than the previous one.
Click “Add to Chart” in the Editor’s menu bar. The “MACD #2” indicator appears in a separate Pane under the
“MACD #1” indicator.

Your second Pine script is running on your chart. If you double-click on the indicator’s name on your chart, you will
bring up the script’s “Settings/Inputs” tab, where you can now change the slow and fast lengths:
Let’s look at the lines that have changed in the second version of our script:

Line 2: indicator("MACD #2")


We have changed #1 to #2 so the second version of our indicator displays a different name on the chart.

Line 3: fastInput = input(12, "Fast length")


Instead of assigning a constant value to a variable, we have used the input() function so we can change the value
in our script’s “Settings/Inputs” tab. 12 will be the default value and the field’s label will be "Fast length" .
If the value is changed in the “Inputs” tab, the fastInput variable’s content will contain the new value and the
script will re-execute on the chart with that new value. Note that, as our Pine Script™ Style Guide recommends,
we add Input to the end of the variable’s name to remind us, later in the script, that its value comes from a
user input.
Line 4: slowInput = input(26, "Slow length")
We do the same for the slow length, taking care to use a different variable name, default value and text string
for the field’s label.
Line 5: [macdLine, signalLine, histLine] = ta.macd(close, fastInput, slowInput, 9)
This is where we call the ta.macd() built-in to perform all the first version’s calculations in one line only. The
function requires four parameters (the values after the function name, enclosed in parentheses). It returns three
values into the three variables instead of only one, like the functions we used until now, which is why we need to
enclose the list of three variables receiving the function’s result in square brackets, to the left of the = sign.
Note that two of the values we pass to the function are the “input” variables containing the fast and slow lengths:
fastInput and slowInput .

Line 6 and 7:
The variable names we are plotting there have changed, but the lines are doing the same thing as in our first
version.

Our second version performs the same calculations as our first, but we can change the two lengths used to calculate
it. Our code is also simpler and shorter by three lines. We have improved our script.

Next
We now recommend you go to our Next Steps page.
First steps Next steps

© Copyright 2023, TradingView.


Options v: v5

User Manual • Pine Script™ primer • Next steps

Next steps

“indicators” vs “strategies”
How scripts are executed
Time series
Publishing scripts
Getting around the Pine Script™ documentation
Where to go from here?

After your first steps and your first indicator, let us explore a bit more of the Pine Script™ landscape by sharing some
pointers to guide you in your journey to learn Pine Script™.

“indicators” vs “strategies”
Pine Script™ strategies are used to backtest on historical data and forward test on open markets. In addition to
indicator calculations, they contain strategy.*() calls to send trade orders to Pine Script™’s broker emulator,
which can then simulate their execution. Strategies display backtest results in the “Strategy Tester” tab at the
bottom of the chart, next to the “Pine Script™ Editor” tab.

Pine Script™ indicators also contain calculations, but cannot be used in backtesting. Because they do not require the
broker emulator, they use less resources and will run faster. It is thus advantageous to use indicators whenever you
can.

Both indicators and strategies can run in either overlay mode (over the chart’s bars) or pane mode (in a separate
section below or above the chart). Both can also plot information in their respective space, and both can generate
alert events.

How scripts are executed


A Pine script is not like programs in many programming languages that execute once and then stop. In the Pine
Script™ runtime environment, a script runs in the equivalent of an invisible loop where it is executed once on each
bar of whatever chart you are on, from left to right. Chart bars that have already closed when the script executes on
them are called historical bars. When execution reaches the chart’s last bar and the market is open, it is on the
realtime bar. The script then executes once every time a price or volume change is detected, and one last time for
that realtime bar when it closes. That realtime bar then becomes an elapsed realtime bar. Note that when the script
executes in realtime, it does not recalculate on all the chart’s historical bars on every price/volume update. It has
already calculated once on those bars, so it does not need to recalculate them on every chart tick. See the Execution
model page for more information.

When a script executes on a historical bar, the close built-in variable holds the value of that bar’s close. When a script
executes on the realtime bar, close returns the current price of the symbol until the bar closes.
Contrary to indicators, Pine Script™ strategies normally execute only once on realtime bars, when they close. They
can also be configured to execute on each price/volume update if that is what you need. See the page on Strategies
for more information, and to understand how strategies calculate differently than indicators.

Time series
The main data structure used in Pine Script™ is called a time series. Time series contain one value for each bar the
script executes on, so they continuously expand as the script executes on more bars. Past values of the time series
can be referenced using Pine Script™’s history-referencing operator: []. close[1] , for example, refers to the value
of close on the bar preceding the one where the script is executing.

While this indexing mechanism may remind many programmers of arrays, a time series is different and thinking in
terms of arrays will be detrimental to understanding this key Pine Script™ concept. A good comprehension of both the
execution model and time series is essential in understanding how Pine scripts work. If you have never worked with
data organized in time series before, you will need practice to put them to work for you. Once you familiarize
yourself with these key concepts, you will discover that by combining the use of time series with our built-in functions
specifically designed to handle them efficiently, much can be accomplished in very few lines of Pine Script™ code.

Publishing scripts
TradingView is home to a large community of Pine Script™ programmers and millions of traders from all around the
world. Once you become proficient enough in Pine Script™, you can choose to share your scripts with other traders.
Before doing so, please take the time to learn Pine Script™ well-enough to supply traders with an original and reliable
tool. All publicly published scripts are analyzed by our team of moderators and must comply with our Script Publishing
Rules, which require them to be original and well-documented.

If want to use Pine scripts for your own use, simply write them in the Pine Script™ Editor and add them to your chart
from there; you don’t have to publish them to use them. If you want to share your scripts with just a few friends, you
can publish them privately and send your friends the browser’s link to your private publication. See the page on
Publishing for more information.

Getting around the Pine Script™ documentation


While reading code from published scripts is no doubt useful, spending time in our documentation will be necessary to
attain any degree of proficiency in Pine Script™. Our two main sources of documentation on Pine Script™ are:

This Pine Script™ v5 User Manual


Our Pine Script™ v5 Reference Manual

The Pine Script™ v5 User Manual is in HTML format and in English only.

The Pine Script™ v5 Reference Manual documents what each variable, function or Pine Script™ keyword does. It is an
essential tool for all Pine Script™ programmers; your life will be miserable if you try to write scripts of any reasonable
complexity without consulting it. It exists in two formats: the HTML format we just linked to, and the popup version,
which can be accessed from the Pine Script™ Editor, by either ctrl + clicking on a keyword, or by using the
Editor’s “More/Pine Script™ reference (pop-up)” menu. The Reference Manual is translated in other languages.

There are five different versions of Pine Script™. Ensure the documentation you use corresponds to the Pine Script™
version you are coding with.

Where to go from here?


This Pine Script™ v5 User Manual contains numerous examples of code used to illustrate the concepts we discuss. By
going through it, you will be able to both learn the foundations of Pine Script™ and study the example scripts. Reading
about key concepts and trying them out right away with real code is a productive way to learn any programming
language. As you hopefully have already done in the First indicator page, copy this documentation’s examples in the
Editor and play with them. Explore! You won’t break anything.

This is how the Pine Script™ v5 User Manual you are reading is organized:

The Language section explains the main components of the Pine Script™ language and how scripts execute.
The Concepts section is more task-oriented. It explains how to do things in Pine Script™.
The Writing section explores tools and tricks that will help you write and publish scripts.
The FAQ section answers common questions from Pine Script™ programmers.
The Error messages page documents causes and fixes for the most common runtime and compiler errors.
The Release Notes page is where you can follow the frequent updates to the Pine Script™.
The Migration guides section explains how to port between different versions of Pine Script™.
The Where can I get more information page lists other useful Pine Script™-related content, including where to
ask questions when you are stuck on code.

We wish you a successful journey with Pine Script™… and trading!

First indicator Language

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language

Language
Execution model
Calculation based on historical bars
Calculation based on realtime bars
Events triggering the execution of a script
More information
Execution of Pine Script™ functions and historical context inside function blocks
Why this behavior?
Exceptions
Time series
Script structure
Version
Declaration statement
Code
Comments
Line wrapping
Compiler annotations
Identifiers
Operators
Introduction
Arithmetic operators
Comparison operators
Logical operators
`?:` ternary operator
`[ ]` history-referencing operator
Operator precedence
`=` assignement operator
`:=` reassignement operator
Variable declarations
Introduction
Initialization with `na`
Tuple declarations
Variable reassignment
Declaration modes
On each bar
`var`
`varip`
Conditional structures
Introduction
`if` structure
`if` used for its side effects
`if` used to return a value
`switch` structure
`switch` with an expression
`switch` without an expression
Matching local block type requirement
Loops
Introduction
When loops are not needed
When loops are necessary
`for`
`while`
Type system
Introduction
Forms
Types
Using forms and types
Forms
Types
`na` value
Type templates
Type casting
Tuples
Built-ins
Introduction
Built-in variables
Built-in functions
User-defined functions
Introduction
Single-line functions
Multi-line functions
Scopes in the script
Functions that return multiple results
Limitations
Arrays
Introduction
Declaring arrays
Using the `var` keyword
Reading and writing array values
Looping through array elements
Scope
History referencing
Inserting and removing array elements
Inserting
Removing
Using an array as a stack
Using an array as a queue
Calculations on arrays
Manipulating arrays
Concatenation
Copying
Joining
Sorting
Reversing
Slicing
Searching arrays
Error handling
Index xx is out of bounds. Array size is yy
Cannot call array methods when ID of array is ‘na’
Array is too large. Maximum size is 100000
Cannot create an array with a negative size
Cannot use shift() if array is empty.
Cannot use pop() if array is empty.
Index ‘from’ should be less than index ‘to’
Slice is out of bounds of the parent array
Objects
Introduction
Creating objects
Changing field values
Collecting objects
Copying objects
Shadowing
Methods
Introduction
Built-in methods
User-defined methods
Method overloading
Advanced example

Next steps Execution model

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Execution model

Execution model

Calculation based on historical bars


Calculation based on realtime bars
Events triggering the execution of a script
More information
Execution of Pine Script™ functions and historical context inside function blocks
Why this behavior?
Exceptions

The execution model of the Pine Script™ runtime is intimately linked to Pine Script™’s time series and type system.
Understanding all three is key to making the most of the power of Pine Script™.

The execution model determines how your script is executed on charts, and thus how the code you write in scripts
works. Your Pine Script™ code would do nothing were it not for Pine Script™’s runtime, which kicks in after your code
has compiled and it is executed on your chart because one of the events triggering the execution of a script has
occurred.

When a Pine Script™ is loaded on a chart it executes once on each historical bar using the available OHLCV (open,
high, low, close, volume) values for each bar. Once the script’s execution reaches the rightmost bar in the dataset, if
trading is currently active on the chart’s symbol, then Pine Script™ indicators will execute once every time an update
occurs, i.e., price or volume changes. Pine Script™ strategies will by default only execute when the rightmost bar
closes, but they can also be configured to execute on every update, like indicators do.

All symbol/timeframe pairs have a dataset comprising a limited number of bars. When you scroll a chart to the left to
see the dataset’s earlier bars, the corresponding bars are loaded on the chart. The loading process stops when there
are no more bars for that particular symbol/timeframe pair or the maximum number of bars your account type
permits has been loaded [1]. You can scroll the chart to the left until the very first bar of the dataset, which has an
index value of 0 (see bar_index).

When the script first runs on a chart, all bars in a dataset are historical bars, except the rightmost one if a trading
session is active. When trading is active on the rightmost bar, it is called the realtime bar. The realtime bar updates
when a price or volume change is detected. When the realtime bar closes, it becomes an elapsed realtime bar and a
new realtime bar opens.

Calculation based on historical bars


Let’s take a simple script and follow its execution on historical bars:
//@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)
plotshape(c, color = color.red)

On historical bars, a script executes at the equivalent of the bar’s close, when the OHLCV values are all known for
that bar. Prior to execution of the script on a bar, the built-in variables such as open , high , low , close ,
volume and time are set to values corresponding to those from that bar. A script executes once per historical bar.

Our example script is first executed on the very first bar of the dataset at index 0. Each statement is executed using
the values for the current bar. Accordingly, on the first bar of the dataset, the following statement:

src = close

initializes the variable src with the close value for that first bar, and each of the next lines is executed in turn.
Because the script only executes once for each historical bar, the script will always calculate using the same close
value for a specific historical bar.

The execution of each line in the script produces calculations which in turn generate the indicator’s output values,
which can then be plotted on the chart. Our example uses the plot and plotshape calls at the end of the script
to output some values. In the case of a strategy, the outcome of the calculations can be used to plot values or dictate
the orders to be placed.

After execution and plotting on the first bar, the script is executed on the dataset’s second bar, which has an index of
1. The process then repeats until all historical bars in the dataset are processed and the script reaches the rightmost
bar on the chart.

Calculation based on realtime bars


The behavior of a Pine Script™ on the realtime bar is very different than on historical bars. Recall that the realtime
bar is the rightmost bar on the chart when trading is active on the chart’s symbol. Also, recall that strategies can
behave in two different ways in the realtime bar. By default, they only execute when the realtime bar closes, but the
calc_on_every_tick parameter of the strategy declaration statement can be set to true to modify the
strategy’s behavior so that it executes each time the realtime bar updates, as indicators do. The behavior described
here for indicators will thus only apply to strategies using calc_on_every_tick=true .

The most important difference between execution of scripts on historical and realtime bars is that while they execute
only once on historical bars, scripts execute every time an update occurs during a realtime bar. This entails that built-
in variables such as high , low and close which never change on a historical bar, can change at each of a script’s
iteration in the realtime bar. Changes in the built-in variables used in the script’s calculations will, in turn, induce
changes in the results of those calculations. This is required for the script to follow the realtime price action. As a
result, the same script may produce different results every time it executes during the realtime bar.

Note: In the realtime bar, the close variable always represents the current price. Similarly, the high and low
built-in variables represent the highest high and lowest low reached since the realtime bar’s beginning. The Pine
Script™ built-in variables will only represent the realtime bar’s final values on the bar’s last update.

Let’s follow our script example in the realtime bar.

When the script arrives on the realtime bar it executes a first time. It uses the current values of the built-in variables
to produce a set of results and plots them if required. Before the script executes another time when the next update
happens, its user-defined variables are reset to a known state corresponding to that of the last commit at the close of
the previous bar. If no commit was made on the variables because they are initialized every bar, then they are
reinitialized. In both cases their last calculated state is lost. The state of plotted labels and lines is also reset. This
resetting of the script’s user-defined variables and drawings prior to each new iteration of the script in the realtime
bar is called rollback. Its effect is to reset the script to the same known state it was in when the realtime bar opened,
so calculations in the realtime bar are always performed from a clean state.

The constant recalculation of a script’s values as price or volume changes in the realtime bar can lead to a situation
where variable c in our example becomes true because a cross has occurred, and so the red marker plotted by the
script’s last line would appear on the chart. If on the next price update the price has moved in such a way that the
close value no longer produces calculations making c true because there is no longer a cross, then the marker
previously plotted will disappear.

When the realtime bar closes, the script executes a last time. As usual, variables are rolled back prior to execution.
However, since this iteration is the last one on the realtime bar, variables are committed to their final values for the
bar when calculations are completed.

To summarize the realtime bar process:

A script executes at the open of the realtime bar and then once per update.
Variables are rolled back before every realtime update.
Variables are committed once at the closing bar update.

Events triggering the execution of a script


A script is executed on the complete set of bars on the chart when one of the following events occurs:

A new symbol or timeframe is loaded on a chart.


A script is saved or added to the chart, from the Pine Script™ Editor or the chart’s “Indicators & strategies”
dialog box.
A value is modified in the script’s “Settings/Inputs” dialog box.
A value is modified in a strategy’s “Settings/Properties” dialog box.
A browser refresh event is detected.
A script is executed on the realtime bar when trading is active and:

One of the above conditions occurs, causing the script to execute on the open of the realtime bar, or
The realtime bar updates because a price or volume change was detected.

Note that when a chart is left untouched when the market is active, a succession of realtime bars which have been
opened and then closed will trail the current realtime bar. While these elapsed realtime bars will have been
confirmed because their variables have all been committed, the script will not yet have executed on them in their
historical state, since they did not exist when the script was last run on the chart’s dataset.

When an event triggers the execution of the script on the chart and causes it to run on those bars which have now
become historical bars, the script’s calculation can sometimes vary from what they were when calculated on the last
closing update of the same bars when they were realtime bars. This can be caused by slight variations between the
OHLCV values saved at the close of realtime bars and those fetched from data feeds when the same bars have
become historical bars. This behavior is one of the possible causes of repainting.

More information
The Pine Script™ built-in barstate.* variables that provide information on the type of bar or the event where
the script is executing. The page where they are documented also contains a script that allows you to visualize
the difference between elapsed realtime and historical bars, for example.
The Strategies page explains the details of strategy calculations, which are not identical to those of indicators.

Execution of Pine Script™ functions and historical context inside


function blocks
The history of series variables used inside Pine Script™ functions is created through each successive call to the
function. If the function is not called on each bar the script runs on, this will result in disparities between the historic
values of series inside vs outside the function’s local block. Hence, series referenced inside and outside the function
using the same index value will not refer to the same point in history if the function is not called on each bar.

Let’s look at this example script where the f() and f2() functions are called every second bar:

//@version=5
indicator("My Script", overlay = true)

// Returns the value of "a" the last time the function was called 2 bars ago.
f(a) => a[1]
// Returns the value of last bar's "close", as expected.
f2() => close[1]

oneBarInTwo = bar_index % 2 == 0
plot(oneBarInTwo ? f(close) : na, color = color.maroon, linewidth = 6, style = plot.style_cros
plot(oneBarInTwo ? f2() : na, color = color.lime, linewidth = 6, style = plot.style_circles
plot(close[2], color = color.maroon)
plot(close[1], color = color.lime)
As can be seen with the resulting plots, a[1] returns the previous value of a in the function’s context, so the last
time f() was called two bars ago — not the close of the previous bar, as close[1] does in f2() . This results in
a[1] in the function block referring to a different past value than close[1] even though they use the same index
of 1.

Why this behavior?


This behavior is required because forcing execution of functions on each bar would lead to unexpected results, as
would be the case for a label.new() function call inside an if branch, which must not execute unless the if condition
requires it.

On the other hand, this behavior leads to unexpected results with certain built-in functions which require being
executed each bar to correctly calculate their results. Such functions will not return expected results if they are
placed in contexts where they are not executed every bar, such as if branches.

The solution in these cases is to take those function calls outside their context so they can be executed on every bar.

In this script, ta.barssince() is not called on every bar because it is inside a ternary operator’s conditional branch:

//@version=5
indicator("Barssince", overlay = false)
res = close > close[1] ? ta.barssince(close < close[1]) : -1
plot(res, style = plot.style_histogram, color=res >= 0 ? color.red : color.blue)

This leads to incorrect results because ta.barssince() is not executed on every bar:
The solution is to take the ta.barssince() call outside the conditional branch to force its execution on every bar:

//@version=5
indicator("Barssince", overlay = false)
b = ta.barssince(close < close[1])
res = close > close[1] ? b : -1
plot(res, style = plot.style_histogram, color = res >= 0 ? color.red : color.blue)

Using this technique we get the expected output:

Exceptions
Not all built-in functions need to be executed every bar. These are the functions which do not require it, and so do
not need special treatment:
dayofmonth, dayofweek, hour, linebreak, math.abs, math.acos, math.asin, math.atan, math.ceil
math.cos, math.exp, math.floor, math.log, math.log10, math.max, math.min, math.pow, math.round
math.sign, math.sin, math.sqrt, math.tan, minute, month, na, nz, second, str.tostring,
ticker.heikinashi, ticker.kagi, ticker.new, ticker.renko, time, timestamp, weekofyear, year

Note
Functions called from within a for loop use the same context in each of the loop’s iterations. In the example
below, each ta.lowest() call on the same bar uses the value that was passed to it, i.e., bar_index, so function
calls used in loops do not require special treatment.

//@version=5
indicator("My Script")
va = 0.0
for i = 1 to 2 by 1
if (i + bar_index) % 2 == 0
va := ta.lowest(bar_index, 10) // same context on each call
plot(va)

Footnotes
[1]
The upper limit for the total number of historical bars is about 10000 for Pro/Pro+ users and about 20000 for
Premium users. Free users are able to see about 5000 bars.

Language Time series

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Time series

Time series
Much of the power of Pine Script™ stems from the fact that it is designed to process time series efficiently. Time
series are not a form or a type; they are the fundamental structure Pine Script™ uses to store the successive values of
a variable over time, where each value is tethered to a point in time. Since charts are composed of bars, each
representing a particular point in time, time series are the ideal data structure to work with values that may change
with time.

The notion of time series is intimately linked to Pine Script™’s execution model and type system concepts.
Understanding all three is key to making the most of the power of Pine Script™.

Take the built-in open variable, which contains the “open” price of each bar in the dataset, the dataset being all the
bars on any given chart. If your script is running on a 5min chart, then each value in the open time series is the
“open” price of the consecutive 5min chart bars. When your script refers to open, it is referring to the “open” price
of the bar the script is executing on. To refer to past values in a time series, we use the [] history-referencing
operator. When a script is executing on a given bar, open[1] refers to the value of the open time series on the
previous bar.

While time series may remind programmers of arrays, they are totally different. Pine Script™ does use an array data
structure, but it is a completely different concept than a time series.

Time series in Pine Script™, combined with its special type of runtime engine and built-in functions, are what makes it
easy to compute the cumulative total of close values without using a for loop, with only ta.cum(close) . This is
possible because although ta.cum(close) appears rather static in a script, it is in fact executed on each bar, so its
value becomes increasingly larger as the close value of each new bar is added to it. When the script reaches the
rightmost bar of the chart, ta.cum(close) returns the sum of the close value from all bars on the chart.

Similarly, the mean of the difference between the last 14 high and low values can be expressed as
ta.sma(high - low, 14) , or the distance in bars since the last time the chart made five consecutive higher
highs as barssince(rising(high, 5)) .

Even the result of function calls on successive bars leaves a trace of values in a time series that can be referenced
using the [] history-referencing operator. This can be useful, for example, when testing the close of the current bar
for a breach of the highest high in the last 10 bars, but excluding the current bar, which we could write as
breach = close > highest(close, 10)[1] . The same statement could also be written as
breach = close > highest(close[1], 10) .

The same looping logic on all bars is applied to function calls such as plot(open) which will repeat on each bar,
successively plotting on the chart the value of open for each bar.

Do not confuse “time series” with the “series” form. The time series concept explains how consecutive values of
variables are stored in Pine Script™; the “series” form denotes variables whose values can change bar to bar.
Consider, for example, the timeframe.period built-in variable which is of form “simple” and type “string”, so “simple
string”. The “simple” form entails that the variable’s value is known on bar zero (the first bar where the script
executes) and will not change during the script’s execution on all the chart’s bars. The variable’s value is the chart’s
timeframe in string format, so "D" for a 1D chart, for example. Even though its value cannot change during the
script, it would be syntactically correct in Pine Script™ (though not very useful) to refer to its value 10 bars ago using
timeframe.period[10] . This is possible because the successive values of timeframe.period for each bar are
stored in a time series, even though all the values in that particular time series are similar. Note, however, that when
the [] operator is used to access past values of a variable, it yields a result of “series” form, even though the variable
without an offset is of another form, such as “simple” in the case of timeframe.period.

When you grasp how time series can be efficiently handled using Pine Script™’s syntax and its execution model, you
can define complex calculations using little code.

Execution model Script structure

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Script structure

Script structure

Version
Declaration statement
Code
Comments
Line wrapping
Compiler annotations

A Pine script follows this general structure:

<version>
<declaration_statement>
<code>

Version
A compiler annotation in the following form tells the compiler which of the versions of Pine Script™ the script is
written in:

//@version=5

The version number can be 1 to 5.


The compiler annotation is not mandatory. When omitted, version 1 is assumed. It is strongly recommended to
always use the latest version of the language.
While it is synctactically correct to place the version compiler annotation anywhere in the script, it is much
more useful to readers when it appears at the top of the script.

Notable changes to the current version of Pine Script™ are documented in the Release notes.

Declaration statement
All Pine scripts must contain one declaration statement, which is a call to one of these functions:

indicator()
strategy()
library()

The declaration statement:

Identifies the type of the script, which in turn dictates which content is allowed in it, and how it can be used
and executed.
Sets key properties of the script such as its name, where it will appear when it is added to a chart, the precision
and format of the values it displays, and certain values that govern its runtime behavior, such as the maximum
number of drawing objects it will display on the chart. With strategies, the properties include parameters that
control backtesting, such as initial capital, commission, slippage, etc.

Each type of script has distinct requirements:

Indicators must contain at least one function call which produces output on the chart (e.g., plot(), plotshape(),
barcolor(), line.new(), etc.).
Strategies must contain at least one strategy.*() call, e.g., strategy.entry().
Libraries must contain at least one exported function or user-defined type.

Code
Lines in a script that are not comments or compiler annotations are statements, which implement the script’s
algorithm. A statement can be one of these:

variable declaration
variable reassignement
function declaration
built-in function call, user-defined function call or a library function call
if, for, while, switch or type structure.

Statements can be arranged in multiple ways:

Some statements can be expressed in one line, like most variable declarations, lines containing only a function
call or single-line function declarations. Lines can also be wrapped (continued on multiple lines). Multiple one-
line statements can be concatenated on a single line by using the comma as a separator.
Others statements such as structures or multi-line function declarations always require multiple lines because
they require a local block. A local block must be indented by a tab or four spaces. Each local block defines a
distinct local scope.
Statements in the global scope of the script (i.e., which are not part of local blocks) cannot begin with white
space (a space or a tab). Their first character must also be the line’s first character. Lines beginning in a line’s
first position become by definition part of the script’s global scope.

A simple valid Pine Script™ v5 indicator can be generated in the Pine Script™ Editor by using the “Open” button and
choosing “New blank indicator”:

//@version=5
indicator("My Script")
plot(close)

This indicator includes three local blocks, one in the f() function declaration, and two in the variable declaration
using an if structure:
//@version=5

indicator("", "", true) // Declaration statement (global scope)

barIsUp() => // Function declaration (global scope)


close > open // Local block (local scope)

plotColor = if barIsUp() // Variable declaration (global scope)


color.green // Local block (local scope)
else
color.red // Local block (local scope)

bgcolor(color.new(plotColor, 70)) // Call to a built-in function (global scope)

You can bring up a simple Pine Script™ v5 strategy by selecting “New blank strategy” instead:

//@version=5
strategy("My Strategy", overlay=true, margin_long=100, margin_short=100)

longCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))


if (longCondition)
strategy.entry("My Long Entry Id", strategy.long)

shortCondition = ta.crossunder(ta.sma(close, 14), ta.sma(close, 28))


if (shortCondition)
strategy.entry("My Short Entry Id", strategy.short)

Comments
Double slashes ( // ) define comments in Pine Script™. Comments can begin anywhere on the line. They can also
follow Pine Script™ code on the same line:

//@version=5
indicator("")
// This line is a comment
a = close // This is also a comment
plot(a)

The Pine Script™ Editor has a keyboard shortcut to comment/uncomment lines: ctrl + / . You can use it on multiple
lines by highlighting them first.

Line wrapping
Long lines can be split on multiple lines, or “wrapped”. Wrapped lines must be indented with any number of spaces,
provided it’s not a multiple of four (those boundaries are used to indent local blocks):

a = open + high + low + close

may be wrapped as:


a = open +
high +
low +
close

A long plot() call may be wrapped as:

plot(ta.correlation(src, ovr, length),


color = color.new(color.purple, 40),
style = plot.style_area,
trackprice = true)

Statements inside user-defined function declarations can also be wrapped. However, since a local block must
syntactically begin with an indentation (4 spaces or 1 tab), when splitting it onto the following line, the continuation
of the statement must start with more than one indentation (not equal to a multiple of four spaces). For example:

updown(s) =>
isEqual = s == s[1]
isGrowing = s > s[1]
ud = isEqual ?
0 :
isGrowing ?
(nz(ud[1]) <= 0 ?
1 :
nz(ud[1])+1) :
(nz(ud[1]) >= 0 ?
-1 :
nz(ud[1])-1)

You can use comments in wrapped lines:

//@version=5
indicator("")
c = open > close ? color.red :
high > high[1] ? color.lime : // A comment
low < low[1] ? color.blue : color.black
bgcolor(c)

Compiler annotations
Compiler annotations accomplish different purposes:

// @version= is used to specify the version of Pine Script™ used in a script.


// @description is used to provide a description for a library.
// @function , // @param and // @returns are used to document function definitions.
// @type and // @field are used to document user-defined type (UDT) definitions.
// @variable is used to document variable declarations.
// #region and // #endregion can be used in scripts to create collapsible blocks in the Editor.

This script draws a rectangle using three interactively selected points on the chart. It illustrates how compiler
annotations can be used:
//@version=5
indicator("Triangle", "", true)

int TIME_DEFAULT = 0
float PRICE_DEFAULT = 0.0

x1Input = input.time(TIME_DEFAULT, "Point 1", inline = "1", confirm = true)


y1Input = input.price(PRICE_DEFAULT, "", inline = "1", tooltip = "Pick point 1", confir
x2Input = input.time(TIME_DEFAULT, "Point 2", inline = "2", confirm = true)
y2Input = input.price(PRICE_DEFAULT, "", inline = "2", tooltip = "Pick point 2", confir
x3Input = input.time(TIME_DEFAULT, "Point 3", inline = "3", confirm = true)
y3Input = input.price(PRICE_DEFAULT, "", inline = "3", tooltip = "Pick point 3", confir

// @type Used to represent the coordinates and color to draw a triangle.


// @field time1 Time of first point.
// @field time2 Time of second point.
// @field time3 Time of third point.
// @field price1 Price of first point.
// @field price2 Price of second point.
// @field price3 Price of third point.
// @field lineColor Color to be used to draw the triangle lines.
type Triangle
int time1
int time2
int time3
float price1
float price2
float price3
color lineColor

//@function Draws a triangle using the coordinates of the `t` object.


//@param t (Triangle) Object representing the triangle to be drawn.
//@returns The ID of the last line drawn.
drawTriangle(Triangle t) =>
line.new(t.time1, t.price1, t.time2, t.price2, xloc = xloc.bar_time, color = t.lineColor
line.new(t.time2, t.price2, t.time3, t.price3, xloc = xloc.bar_time, color = t.lineColor
line.new(t.time1, t.price1, t.time3, t.price3, xloc = xloc.bar_time, color = t.lineColor

// Draw the triangle only once on the last historical bar.


if barstate.islastconfirmedhistory
//@variable Used to hold the Triangle object to be drawn.
Triangle triangle = Triangle.new()

triangle.time1 := x1Input
triangle.time2 := x2Input
triangle.time3 := x3Input
triangle.price1 := y1Input
triangle.price2 := y2Input
triangle.price3 := y3Input
triangle.lineColor := color.purple

drawTriangle(triangle)
Time series Identifiers

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Identifiers

Identifiers
Identifiers are names used for user-defined variables and functions:

They must begin with an uppercase ( A-Z ) or lowercase ( a-z ) letter, or an underscore ( _ ).
The next characters can be letters, underscores or digits ( 0-9 ).
They are case-sensitive.

Here are some examples:

myVar
_myVar
my123Var
functionName
MAX_LEN
max_len
maxLen
3barsDown // NOT VALID!

The Pine Script™ Style Guide recommends using uppercase SNAKE_CASE for constants, and camelCase for other
identifiers:

GREEN_COLOR = #4CAF50
MAX_LOOKBACK = 100
int fastLength = 7
// Returns 1 if the argument is `true`, 0 if it is `false` or `na`.
zeroOne(boolValue) => boolValue ? 1 : 0

Script structure Operators

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Operators

Operators

Introduction
Arithmetic operators
Comparison operators
Logical operators
`?:` ternary operator
`[ ]` history-referencing operator
Operator precedence
`=` assignement operator
`:=` reassignement operator

Introduction
Some operators are used to build expressions returning a result:

Arithmetic operators
Comparison operators
Logical operators
The ?: ternary operator
The [] history-referencing operator

Other operators are used to assign values to variables:

= is used to assign a value to a variable, but only when you declare the variable (the first time you use it)
:= is used to assign a value to a previously declared variable. The following operators can also be used in such
a way: += , -= , *= , /= , %=

As is explained in the Type system page, forms and types play a critical role in determining the type of results that
expressions yield. This, in turn, has an impact on how and with what functions you will be allowed to use those
results. Expressions always return a form of the strongest one used in the expression, e.g., if you multiply an “input
int” with a “series int”, the expression will produce a “series int” result, which you will not be able to use as the
argument to length in ta.ema().

This script will produce a compilation error:


//@version=5
indicator("")
lenInput = input.int(14, "Length")
factor = year > 2020 ? 3 : 1
adjustedLength = lenInput * factor
ma = ta.ema(close, adjustedLength) // Compilation error!
plot(ma)

The compiler will complain: Cannot call ‘ta.ema’ with argument ‘length’=’adjustedLength’. An argument of ‘series
int’ type was used but a ‘simple int’ is expected;. This is happening because lenInput is an “input int” but
factor is a “series int” (it can only be determined by looking at the value of year on each bar). The
adjustedLength variable is thus assigned a “series int” value. Our problem is that the Reference Manual entry for
ta.ema() tells us that its length parameter requires values of “simple” form, which is a weaker form that “series”,
so a “series int” value is not allowed.

The solution to our conundrum requires:

Using another moving average function that supports a “series int” length, such as ta.sma(), or
Not using a calculation producing a “series int” value for our length.

Arithmetic operators
There are five arithmetic operators in Pine Script™:

+ Addition and string concatenation

- Subtraction

* Multiplication

/ Division

% Modulo (remainder after division)

The arithmetic operators above are all binary (means they need two operands — or values — to work on, like in
1 + 2 ). The + and - also serve as unary operators (means they work on one operand, like -1 or +1 ).

If both operands are numbers but at least one of these is of float type, the result will also be a float. If both operands
are of int type, the result will also be an int. If at least one operand is na, the result is also na.

The + operator also serves as the concatenation operator for strings. "EUR"+"USD" yields the "EURUSD" string.

The % operator calculates the modulo by rounding down the quotient to the lowest possible value. Here is an easy
example that helps illustrate how the modulo is calculated behind the scenes:

//@version=5
indicator("Modulo function")
modulo(series int a, series int b) =>
a - b * math.floor(nz(a/b))
plot(modulo(-1, 100))

Comparison operators
There are six comparison operators in Pine Script™:
< Less Than

<= Less Than or Equal To

!= Not Equal

== Equal

> Greater Than

>= Greater Than or Equal To

Comparison operations are binary. If both operands have a numerical value, the result will be of type bool, i.e.,
true , false or na.

Examples

1 > 2 // false
1 != 1 // false
close >= open // Depends on values of `close` and `open`

Logical operators
There are three logical operators in Pine Script™:

not Negation

and Logical Conjunction

or Logical Disjunction

The operator not is unary. When applied to a true , operand the result will be false , and vice versa.

and operator truth table:

a b a and b

true true true

true false false

false true false

false false false

or operator truth table:


a b a or b

true true true

true false true

false true true

false false false

`?:` ternary operator


The ?: ternary operator is used to create expressions of the form:

condition ? valueWhenConditionIsTrue : valueWhenConditionIsFalse

The ternary operator returns a result that depends on the value of condition . If it is true , then
valueWhenConditionIsTrue is returned. If condition is false or na, then
valueWhenConditionIsFalse is returned.

A combination of ternary expressions can be used to achieve the same effect as a switch structure, e.g.:

timeframe.isintraday ? color.red : timeframe.isdaily ? color.green : timeframe.ismonthly

The example is calculated from left to right:

If timeframe.isintraday is true , then color.red is returned. If it is false , then timeframe.isdaily is


evaluated.
If timeframe.isdaily is true , then color.green is returned. If it is false , then timeframe.ismonthly is
evaluated.
If timeframe.ismonthly is true , then color.blue is returned, otherwise na is returned.

Note that the return values on each side of the : are expressions — not local blocks, so they will not affect the limit
of 500 local blocks per scope.

`[ ]` history-referencing operator
It is possible to refer to past values of time series using the [] history-referencing operator. Past values are values a
variable had on bars preceding the bar where the script is currently executing — the current bar. See the Execution
model page for more information about the way scripts are executed on bars.

The [] operator is used after a variable, expression or function call. The value used inside the square brackets of the
operator is the offset in the past we want to refer to. To refer to the value of the volume built-in variable two bars
away from the current bar, one would use volume[2] .

Because series grow dynamically, as the script moves on sucessive bars, the offset used with the operator will refer to
different bars. Let’s see how the value returned by the same offset is dynamic, and why series are very different from
arrays. In Pine Script™, the close variable, or close[0] which is equivalent, holds the value of the current bar’s
“close”. If your code is now executing on the third bar of the dataset (the set of all bars on your chart), close will
contain the price at the close of that bar, close[1] will contain the price at the close of the preceding bar (the
dataset’s second bar), and close[2] , the first bar. close[3] will return na because no bar exists in that position,
and thus its value is not available.
When the same code is executed on the next bar, the fourth in the dataset, close will now contain the closing
price of that bar, and the same close[1] used in your code will now refer to the “close” of the third bar in the
dataset. The close of the first bar in the dataset will now be close[3] , and this time close[4] will return na.

In the Pine Script™ runtime environment, as your code is executed once for each historical bar in the dataset, starting
from the left of the chart, Pine Script™ is adding a new element in the series at index 0 and pushing the pre-existing
elements in the series one index further away. Arrays, in comparison, can have constant or variable sizes, and their
content or indexing structure is not modified by the runtime environment. Pine Script™ series are thus very different
from arrays and only share familiarity with them through their indexing syntax.

When the market for the chart’s symbol is open and the script is executing on the chart’s last bar, the realtime bar,
close returns the value of the current price. It will only contain the actual closing price of the realtime bar the last
time the script is executed on that bar, when it closes.

Pine Script™ has a variable that contains the number of the bar the script is executing on: bar_index. On the first bar,
bar_index is equal to 0 and it increases by 1 on each successive bar the script executes on. On the last bar, bar_index
is equal to the number of bars in the dataset minus one.

There is another important consideration to keep in mind when using the [] operator in Pine Script™. We have seen
cases when a history reference may return the na value. na represents a value which is not a number and using it in
any expression will produce a result that is also na (similar to NaN). Such cases often happen during the script’s
calculations in the early bars of the dataset, but can also occur in later bars under certain conditions. If your Pine
Script™ code does not explicitly provide for handling these special cases, they can introduce invalid results in your
script’s calculations which can ripple through all the way to the realtime bar. The na and nz functions are designed to
allow for handling such cases.

These are all valid uses of the [] operator:

high[10]
ta.sma(close, 10)[1]
ta.highest(high, 10)[20]
close > nz(close[1], open)

Note that the [] operator can only be used once on the same value. This is not allowed:

close[1][2] // Error: incorrect use of [] operator

Operator precedence
The order of calculations is determined by the operators’ precedence. Operators with greater precedence are
calculated first. Below is a list of operators sorted by decreasing precedence:
Precedence Operator

9 []

8 unary + , unary - , not

7 *, /, %

6 +, -

5 > , < , >= , <=

4 == , !=

3 and

2 or

1 ?:

If in one expression there are several operators with the same precedence, then they are calculated left to right.

If the expression must be calculated in a different order than precedence would dictate, then parts of the expression
can be grouped together with parentheses.

`=` assignement operator


The = operator is used to assign a variable when it is initialized — or declared —, i.e., the first time you use it. It
says this is a new variable that I will be using, and I want it to start on each bar with this value .

These are all valid variable declarations:

i = 1
MS_IN_ONE_MINUTE = 1000 * 60
showPlotInput = input.bool(true, "Show plots")
pHi = pivothigh(5, 5)
plotColor = color.green

See the Variable declarations page for more information on how to declare variables.

`:=` reassignement operator


The := is used to reassign a value to an existing variable. It says use this variable that was declared earlier in my
script, and give it a new value.

Variables which have been first declared, then reassigned using := , are called mutable variables. All the following
examples are valid variable reassignments. You will find more information on how var works in the section on the
`var` declaration mode:
//@version=5
indicator("", "", true)
// Declare `pHi` and initilize it on the first bar only.
var float pHi = na
// Reassign a value to `pHi`
pHi := nz(ta.pivothigh(5, 5), pHi)
plot(pHi)

Note that:

We declare pHi with this code: var float pHi = na . The var keyword tells Pine Script™ that we only want
that variable initialized with na on the dataset’s first bar. The float keyword tells the compiler we are
declaring a variable of type “float”. This is necessary because, contrary to most cases, the compiler cannot
automatically determine the type of the value on the right side of the = sign.
While the variable declaration will only be executed on the first bar because it uses var, the
pHi := nz(ta.pivothigh(5, 5), pHi) line will be executed on all the chart’s bars. On each bar, it
evaluates if the pivothigh() call returns na because that is what the function does when it hasn’t found a new
pivot. The nz() function is the one doing the “checking for na” part. When its first argument
( ta.pivothigh(5, 5) ) is na, it returns the second argument ( pHi ) instead of the first. When pivothigh()
returns the price point of a newly found pivot, that value is assigned to pHi . When it returns na because no
new pivot was found, we assign the previous value of pHi to itself, in effect preserving its previous value.

The output of our script looks like this:

Note that:

The line preserves its previous value until a new pivot is found.
Pivots are detected five bars after the pivot actually occurs because our ta.pivothigh(5, 5) call says that
we require five lower highs on both sides of a high point for it to be detected as a pivot.

See the Variable reassignment section for more information on how to reassign values to variables.

Identifiers Variable declarations


© Copyright 2023, TradingView.
Options v: v5

User Manual • Language • Variable declarations

Variable declarations

Introduction
Initialization with `na`
Tuple declarations
Variable reassignment
Declaration modes
On each bar
`var`
`varip`

Introduction
Variables are identifiers that hold values. They must be declared in your code before you use them. The syntax of
variable declarations is:

[<declaration_mode>] [<type>] <identifier> = <expression> | <structure>

or

<tuple_declaration> = <function_call> | <structure>

where:

| means “or”, and parts enclosed in square brackets ( [] ) can appear zero or one time.
<declaration_mode> is the variable’s declaration mode. It can be var or varip, or nothing.
<type> is optional, as in almost all Pine Script™ variable declarations (see types).
<identifier> is the variable’s name.
<expression> can be a literal, a variable, an expression or a function call.
<structure> can be an if, for, while or switch structure.
<tuple_declaration> is a comma-separated list of variable names enclosed in square brackets ( [] ), e.g.,
[ma, upperBand, lowerBand] .

These are all valid variable declarations. The last one requires four lines:
BULL_COLOR = color.lime
i = 1
len = input(20, "Length")
float f = 10.5
closeRoundedToTick = math.round_to_mintick(close)
st = ta.supertrend(4, 14)
var barRange = float(na)
var firstBarOpen = open
varip float lastClose = na
[macdLine, signalLine, histLine] = ta.macd(close, 12, 26, 9)
plotColor = if close > open
color.green
else
color.red

Note
The above statements all contain the = assignment operator because they are variable declarations. When you
see similar lines using the := reassignment operator, the code is reassigning a value to a variable that was
already declared. Those are variable reassignments. Be sure you understand the distinction as this is a
common stumbling block for newcomers to Pine Script™. See the next Variable reassignment section for details.

The formal syntax of a variable declaration is:

<variable_declaration>
[<declaration_mode>] [<type>] <identifier> = <expression> | <structure>
|
<tuple_declaration> = <function_call> | <structure>

<declaration_mode>
var | varip

<type>
int | float | bool | color | string | line | linefill | label | box | table | array<type> |

Initialization with `na`


In most cases, an explicit type declaration is redundant because type is automatically inferred from the value on the
right of the = at compile time, so the decision to use them is often a matter of preference. For example:

baseLine0 = na // compile time error!


float baseLine1 = na // OK
baseLine2 = float(na) // OK

In the first line of the example, the compiler cannot determine the type of the baseLine0 variable because na is a
generic value of no particular type. The declaration of the baseLine1 variable is correct because its float type is
declared explicitly. The declaration of the baseLine2 variable is also correct because its type can be derived from
the expression float(na) , which is an explicit cast of the na value to the float type. The declarations of
baseLine1 and baseLine2 are equivalent.

Tuple declarations
Function calls or structures are allowed to return multiple values. When we call them and want to store the values
they return, a tuple declaration must be used, which is a comma-separated set of one or more values enclosed in
brackets. This allows us to declare multiple variables simultaneously. As an example, the ta.bb() built-in function for
Bollinger bands returns three values:

[bbMiddle, bbUpper, bbLower] = ta.bb(close, 5, 4)

Variable reassignment
A variable reassignment is done using the := reassignment operator. It can only be done after a variable has been first
declared and given an initial value. Reassigning a new value to a variable is often necessary in calculations, and it is
always necessary when a variable from the global scope must be assigned a new value from within a structure’s local
block, e.g.:

//@version=5
indicator("", "", true)
sensitivityInput = input.int(2, "Sensitivity", minval = 1, tooltip = "Higher values make color
ma = ta.sma(close, 20)
maUp = ta.rising(ma, sensitivityInput)
maDn = ta.falling(ma, sensitivityInput)

// On first bar only, initialize color to gray


var maColor = color.gray
if maUp
// MA has risen for two bars in a row; make it lime.
maColor := color.lime
else if maDn
// MA has fallen for two bars in a row; make it fuchsia.
maColor := color.fuchsia

plot(ma, "MA", maColor, 2)

Note that:

We initialize maColor on the first bar only, so it preserves its value across bars.
On every bar, the if statement checks if the MA has been rising or falling for the user-specified number of bars
(the default is 2). When that happens, the value of maColor must be reassigned a new value from within the if
local blocks. To do this, we use the := reassignment operator.
If we did not use the := reassignment operator, the effect would be to initialize a new maColor local variable
which would have the same name as that of the global scope, but actually be a very confusing independent
entity that would persist only for the length of the local block, and then disappear without a trace.

All user-defined variables in Pine Script™ are mutable, which means their value can be changed using the :=
reassignment operator. Assigning a new value to a variable may change its form (see the page on Pine Script™’s type
system for more information). A variable can be assigned a new value as many times as needed during the script’s
execution on one bar, so a script can contain any number of reassignments of one variable. A variable’s declaration
mode determines how new values assigned to a variable will be saved.

Declaration modes
Understanding the impact that declaration modes have on the behavior of variables requires prior knowledge of Pine
Script™’s execution model.

When you declare a variable, if a declaration mode is specified, it must come first. Three modes can be used:

“On each bar”, when none is specified


var
varip

On each bar
When no explicit declaration mode is specified, i.e. no var or varip keyword is used, the variable is declared and
initialized on each bar, e.g., the following declarations from our first set of examples in this page’s introduction:

BULL_COLOR = color.lime
i = 1
len = input(20, "Length")
float f = 10.5
closeRoundedToTick = math.round_to_mintick(close)
st = ta.supertrend(4, 14)
[macdLine, signalLine, histLine] = ta.macd(close, 12, 26, 9)
plotColor = if close > open
color.green
else
color.red

`var`
When the var keyword is used, the variable is only initilized once, on the first bar if the declaration is in the global
scope, or the first time the local block is executed if the declaration is inside a local block. After that, it will preserve
its last value on successive bars, until we reassign a new value to it. This behavior is very useful in many cases where a
variable’s value must persist through the iterations of a script across successive bars. For example, suppose we’d like
to count the number of green bars on the chart:

//@version=5
indicator("Green Bars Count")
var count = 0
isGreen = close >= open
if isGreen
count := count + 1
plot(count)
Without the var modifier, variable count would be reset to zero (thus losing its value) every time a new bar
update triggered a script recalculation.

Declaring variables on the first bar only is often useful to manage drawings more efficiently. Suppoose we want to
extend the last bar’s close line to the right of the right chart. We could write:

//@version=5
indicator("Inefficient version", "", true)
closeLine = line.new(bar_index - 1, close, bar_index, close, extend = extend.right, width
line.delete(closeLine[1])

but this is inefficient because we are creating and deleting the line on each historical bar and on each update in the
realtime bar. It is more efficient to use:

//@version=5
indicator("Efficient version", "", true)
var closeLine = line.new(bar_index - 1, close, bar_index, close, extend = extend.right, width
if barstate.islast
line.set_xy1(closeLine, bar_index - 1, close)
line.set_xy2(closeLine, bar_index, close)

Note that:

We initialize closeLine on the first bar only, using the var declaration mode
We restrict the execution of the rest of our code to the chart’s last bar by enclosing our code that updates the
line in an if barstate.islast structure.

There is a very slight penalty performance for using the var declaration mode. For that reason, when declaring
constants, it is preferable not to use var if performance is a concern, unless the initialization involves calculations
that take longer than the maintenance penalty, e.g., functions with complex code or string manipulations.

`varip`
Understanding the behavior of variables using the varip declaration mode requires prior knowledge of Pine Script™’s
execution model and bar states.

The varip keyword can be used to declare variables that escape the rollback process, which is explained in the page
on Pine Script™’s execution model.

Whereas scripts only execute once at the close of historical bars, when a script is running in realtime, it executes
every time the chart’s feed detects a price or volume update. At every realtime update, Pine Script™’s runtime
normally resets the values of a script’s variables to their last committed value, i.e., the value they held when the
previous bar closed. This is generally handy, as each realtime script execution starts from a known state, which
simplifies script logic.

Sometimes, however, script logic requires code to be able to save variable values between different executions in
the realtime bar. Declaring variables with varip makes that possible. The “ip” in varip stands for intrabar persist.

Let’s look at the following code, which does not use varip:
//@version=5
indicator("")
int updateNo = na
if barstate.isnew
updateNo := 1
else
updateNo := updateNo + 1

plot(updateNo, style = plot.style_circles)

On historical bars, barstate.isnew is always true, so the plot shows a value of “1” because the else part of the if
structure is never executed. On realtime bars, barstate.isnew is only true when the script first executes on the bar’s
“open”. The plot will then briefly display “1” until subsequent executions occur. On the next executions during the
realtime bar, the second branch of the if statement is executed because barstate.isnew is no longer true. Since
updateNo is initialized to na at each execution, the updateNo + 1 expression yields na, so nothing is plotted on
further realtime executions of the script.

If we now use varip to declare the updateNo variable, the script behaves very differently:

//@version=5
indicator("")
varip int updateNo = na
if barstate.isnew
updateNo := 1
else
updateNo := updateNo + 1

plot(updateNo, style = plot.style_circles)

The difference now is that updateNo tracks the number of realtime updates that occur on each realtime bar. This
can happen because the varip declaration allows the value of updateNo to be preserved between realtime updates;
it is no longer rolled back at each realtime execution of the script. The test on barstate.isnew allows us to reset the
update count when a new realtime bar comes in.

Because varip only affects the behavior of your code in the realtime bar, it follows that backtest results on strategies
designed using logic based on varip variables will not be able to reproduce that behavior on historical bars, which will
invalidate test results on them. This also entails that plots on historical bars will not be able to reproduce the script’s
behavior in realtime.

Operators Conditional structures

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Conditional structures

Conditional structures

Introduction
`if` structure
`if` used for its side effects
`if` used to return a value
`switch` structure
`switch` with an expression
`switch` without an expression
Matching local block type requirement

Introduction
The conditional structures in Pine Script™ are if and switch. They can be used:

For their side effects, i.e., when they don’t return a value but do things, like reassign values to variables or call
functions.
To return a value or a tuple which can then be assigned to one (or more, in the case of tuples) variable.

Conditional structures, like the for and while structures, can be embedded; you can use an if or switch inside another
structure.

Some Pine Script™ built-in functions cannot be called from within the local blocks of conditional structures. They are:
alertcondition(), barcolor(), fill(), hline(), indicator(), library(), plot(), plotbar(), plotcandle(), plotchar(),
plotshape(), strategy(). This does not entail their functionality cannot be controlled by conditions evaluated by your
script — only that it cannot be done by including them in conditional structures. Note that while input*.()
function calls are allowed in local blocks, their functionality is the same as if they were in the script’s global scope.

The local blocks in conditional structures must be indented by four spaces or a tab.

`if` structure

`if` used for its side effects


An if structure used for its side effects has the following syntax:
if <expression>
<local_block>
{else if <expression>
<local_block>}
[else
<local_block>]

where:

Parts enclosed in square brackets ( [] ) can appear zero or one time, and those enclosed in curly braces ( {} )
can appear zero or more times.
<expression> must be of “bool” type or be auto-castable to that type, which is only possible for “int” or “float”
values (see the Type system page).
<local_block> consists of zero or more statements followed by a return value, which can be a tuple of values. It
must be indented by four spaces or a tab.
There can be zero or more else if clauses.
There can be zero or one else clause.

When the <expression> following the if evaluates to true, the first local block is executed, the if structure’s execution
ends, and the value(s) evaluated at the end of the local block are returned.

When the <expression> following the if evaluates to false, the successive else if clauses are evaluated, if there
are any. When the <expression> of one evaluates to true, its local block is executed, the if structure’s execution
ends, and the value(s) evaluated at the end of the local block are returned.

When no <expression> has evaluated to true and an else clause exists, its local block is executed, the if structure’s
execution ends, and the value(s) evaluated at the end of the local block are returned.

When no <expression> has evaluated to true and no else clause exists, na is returned.

Using if structures for their side effects can be useful to manage the order flow in strategies, for example. While the
same functionality can often be achieved using the when parameter in strategy.*() calls, code using if
structures is easier to read:

if (ta.crossover(source, lower))
strategy.entry("BBandLE", strategy.long, stop=lower,
oca_name="BollingerBands",
oca_type=strategy.oca.cancel, comment="BBandLE")
else
strategy.cancel(id="BBandLE")

Restricting the execution of your code to specific bars ican be done using if structures, as we do here to restrict
updates to our label to the chart’s last bar:

//@version=5
indicator("", "", true)
var ourLabel = label.new(bar_index, na, na, color = color(na), textcolor = color.orange)
if barstate.islast
label.set_xy(ourLabel, bar_index + 2, hl2[1])
label.set_text(ourLabel, str.tostring(bar_index + 1, "# bars in chart"))

Note that:

We initialize the ourLabel variable on the script’s first bar only, as we use the var declaration mode. The
value used to initialize the variable is provided by the label.new() function call, which returns a label ID pointing
to the label it creates. We use that call to set the label’s properties because once set, they will persist until we
change them.
What happens next is that on each successive bar the Pine Script™ runtime will skip the initialization of
ourLabel , and the if structure’s condition (barstate.islast) is evaluated. It returns false on all bars until
the last one, so the script does nothing on most historical bars after bar zero.
On the last bar, barstate.islast becomes true and the structure’s local block executes, modifying on each chart
update the properties of our label, which displays the number of bars in the dataset.
We want to display the label’s text without a background, so we make the label’s background na in the
label.new() function call, and we use hl2[1] for the label’s y position because we don’t want it to move all
the time. By using the average of the previous bar’s high and low values, the label doesn’t move until the
moment when the next realtime bar opens.
We use bar_index + 2 in our label.set_xy() call to offset the label to the right by two bars.

`if` used to return a value


An if structure used to return one or more values has the following syntax:

[<declaration_mode>] [<type>] <identifier> = if <expression>


<local_block>
{else if <expression>
<local_block>}
[else
<local_block>]

where:

Parts enclosed in square brackets ( [] ) can appear zero or one time, and those enclosed in curly braces ( {} )
can appear zero or more times.
<declaration_mode> is the variable’s declaration mode
<type> is optional, as in almost all Pine Script™ variable declarations (see types)
<identifier> is the variable’s name
<expression> can be a literal, a variable, an expression or a function call.
<local_block> consists of zero or more statements followed by a return value, which can be a tuple of values. It
must be indented by four spaces or a tab.
The value assigned to the variable is the return value of the <local_block>, or na if no local block is executed.

This is an example:

//@version=5
indicator("", "", true)
string barState = if barstate.islastconfirmedhistory
"islastconfirmedhistory"
else if barstate.isnew
"isnew"
else if barstate.isrealtime
"isrealtime"
else
"other"

f_print(_text) =>
var table _t = table.new(position.middle_right, 1, 1)
table.cell(_t, 0, 0, _text, bgcolor = color.yellow)
f_print(barState)

It is possible to omit the else block. In this case, if the condition is false, an empty value ( na , false , or "" )
will be assigned to the var_declarationX variable.

This is an example showing how na is returned when no local block is executed. If close > open is false in here,
na is returned:
x = if close > open
close

`switch` structure
The switch structure exists in two forms. One switches on the different values of a key expression:

[[<declaration_mode>] [<type>] <identifier> = ]switch <expression>


{<expression> => <local_block>}
=> <local_block>

The other form does not use an expression as a key; it switches on the evaluation of different expressions:

[[<declaration_mode>] [<type>] <identifier> = ]switch


{<expression> => <local_block>}
=> <local_block>

where:

Parts enclosed in square brackets ( [] ) can appear zero or one time, and those enclosed in curly braces ( {} )
can appear zero or more times.
<declaration_mode> is the variable’s declaration mode
<type> is optional, as in almost all Pine Script™ variable declarations (see types)
<identifier> is the variable’s name
<expression> can be a literal, a variable, an expression or a function call.
<local_block> consists of zero or more statements followed by a return value, which can be a tuple of values. It
must be indented by four spaces or a tab.
The value assigned to the variable is the return value of the <local_block>, or na if no local block is executed.
The => <local_block> at the end allows you to specify a return value which acts as a default to be used
when no other case in the structure is executed.

Only one local block of a switch structure is executed. It is thus a structured switch that doesn’t fall through cases.
Consequently, break statements are unnecessary.

Both forms are allowed as the value used to initialize a variable.

As with the if structure, if no local block is exectuted, na is returned.

`switch` with an expression


Let’s look at an example of a switch using an expression:
//@version=5
indicator("Switch using an expression", "", true)

string maType = input.string("EMA", "MA type", options = ["EMA", "SMA", "RMA", "WMA"])
int maLength = input.int(10, "MA length", minval = 2)

float ma = switch maType


"EMA" => ta.ema(close, maLength)
"SMA" => ta.sma(close, maLength)
"RMA" => ta.rma(close, maLength)
"WMA" => ta.wma(close, maLength)
=>
runtime.error("No matching MA type found.")
float(na)

plot(ma)

Note that:

The expression we are switching on is the variable maType , which is of “input int” type (see here for an
explanation of what the “input” form is). Since it cannot change during the execution of the script, this
guarantees that whichever MA type the user selects will be executing on each bar, which is a requirement for
functions like ta.ema() which require a “simple int” argument for their length parameter.
If no matching value is found for maType , the switch executes the last local block introduced by => , which
acts as a catch-all. We generate a runtime error in that block. We also end it with float(na) so the local
block returns a value whose type is compatible with that of the other local blocks in the structure, to avoid a
compilation error.

`switch` without an expression


This is an example of a switch structure wich does not use an exppression:

//@version=5
strategy("Switch without an expression", "", true)

bool longCondition = ta.crossover( ta.sma(close, 14), ta.sma(close, 28))


bool shortCondition = ta.crossunder(ta.sma(close, 14), ta.sma(close, 28))

switch
longCondition => strategy.entry("Long ID", strategy.long)
shortCondition => strategy.entry("Short ID", strategy.short)

Note that:

We are using the switch to select the appropriate strategy order to emit, depending on whether the
longCondition or shortCondition “bool” variables are true .

The building conditions of longCondition and shortCondition are exclusive. While they can both be
false simultaneously, they cannot be true at the same time. The fact that only one local block of the
switch structure is ever executed is thus not an issue for us.

We evaluate the calls to ta.crossover() and ta.crossunder() prior to entry in the switch structure. Not doing so,
as in the following example, would prevent the functions to be executed on each bar, which would result in a
compiler warning and erratic behavior:
//@version=5
strategy("Switch without an expression", "", true)

switch
// Compiler warning! Will not calculate correctly!
ta.crossover( ta.sma(close, 14), ta.sma(close, 28)) => strategy.entry("Long ID",
ta.crossunder(ta.sma(close, 14), ta.sma(close, 28)) => strategy.entry("Short ID",

Matching local block type requirement


When multiple local blocks are used in structures, the type of the return value of all its local blocks must match. This
applies only if the structure is used to assign a value to a variable in a declaration, because a variable can only have
one type, and if the statement returns two incompatible types in its branches, the variable type cannot be properly
determined. If the structure is not assigned anywhere, its branches can return different values.

This code compiles fine because close and open are both of the float type:

x = if close > open


close
else
open

This code does not compile because the first local block returns a float value, while the second one returns a
string`, and the result of the if -statement is assigned to the x variable:

// Compilation error!
x = if close > open
close
else
"open"

Variable declarations Loops

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Loops

Loops

Introduction
When loops are not needed
When loops are necessary
`for`
`while`

Introduction

When loops are not needed


Pine Script™’s runtime and its built-in functions make loops unnecessary in many situations. Budding Pine Script™
programmers not yet familiar with the Pine Script™ runtime and built-ins who want to calculate the average of the
last 10 close values will often write code such as:

//@version=5
indicator("Inefficient MA", "", true)
MA_LENGTH = 10
sumOfCloses = 0.0
for offset = 0 to MA_LENGTH - 1
sumOfCloses := sumOfCloses + close[offset]
inefficientMA = sumOfCloses / MA_LENGTH
plot(inefficientMA)

A for loop is unnecessary and inefficient to accomplish tasks like this in Pine Script™. This is how it should be done.
This code is shorter and will run much faster because it does not use a loop and uses the ta.sma() built-in function to
accomplish the task:

//@version=5
indicator("Efficient MA", "", true)
thePineMA = ta.sma(close, 10)
plot(thePineMA)

Counting the occurrences of a condition in the last bars is also a task which beginning Pine Script™ programmers often
think must be done with a loop. To count the number of up bars in the last 10 bars, they will use:
//@version=5
indicator("Inefficient sum")
MA_LENGTH = 10
upBars = 0.0
for offset = 0 to MA_LENGTH - 1
if close[offset] > open[offset]
upBars := upBars + 1
plot(upBars)

The efficient way to write this in Pine Script™ (for the programmer because it saves time, to achieve the fastest-
loading charts, and to share our common resources most equitably), is to use the math.sum() built-in function to
accomplish the task:

//@version=5
indicator("Efficient sum")
upBars = math.sum(close > open ? 1 : 0, 10)
plot(upBars)

What’s happening in there is:

We use the ?: ternary operator to build an expression that yields 1 on up bars and 0 on other bars.
We use the math.sum() built-in function to keep a running sum of that value for the last 10 bars.

When loops are necessary


Loops exist for good reason because even in Pine Script™, they are necessary in some cases. These cases typically
include:

The manipulation of arrays.


Looking back in history to analyze bars using a reference value that can only be known on the current bar, e.g.,
to find how many past highs are higher than the high of the current bar. Since the current bar’s high is only
known on the bar the script is running on, a loop is necessary to go back in time and analyze past bars.
Performing calculations on past bars that cannot be accomplished using Pine Script™’s built-in functions, like the
Pearson correlation coefficient.

`for`
The for structure allows the repetitive execution of statements using a counter. Its syntax is:

[[<declaration_mode>] [<type>] <identifier> = ]for <identifier> = <expression> to <expression>[


<local_block_loop>

where:

Parts enclosed in square brackets ( [] ) can appear zero or one time, and those enclosed in curly braces ( {} )
can appear zero or more times.
<declaration_mode> is the variable’s declaration mode
<type> is optional, as in almost all Pine Script™ variable declarations (see types)
<identifier> is a variable’s name
<expression> can be a literal, a variable, an expression or a function call.
<local_block_loop> consists of zero or more statements followed by a return value, which can be a tuple of
values. It must be indented by four spaces or a tab. It can contain the break statement to exit the loop, or the
continue statement to exit the current iteration and continue on with the next.
The value assigned to the variable is the return value of the <local_block_loop>, i.e., the last value calculated
on the loop’s last iteration, or na if the loop is not executed.
The identifier in for <identifier> is the loop’s counter initial value.
The expression in = <expression> is the start value of the counter.
The expression in to <expression> is the end value of the counter. It is only evaluated upon entry in the
loop.
The expression in by <expression> is optional. It is the step by which the loop counter is increased or
decreased on each iteration of the loop. Its default value is 1 when start value < end value . It is -1
when start value > end value . The step (+1 or -1) used as the default is determined by the start and
end values.

This example uses a for statement to look back a user-defined amount of bars to determine how many bars have a
high that is higher or lower than the high of the last bar on the chart. A for loop is necessary here, since the script
only has access to the reference value on the chart’s last bar. Pine Script™’s runtime cannot, here, be used to
calculate on the fly, as the script is executing bar to bar:

//@version=5
indicator("`for` loop")
lookbackInput = input.int(50, "Lookback in bars", minval = 1, maxval = 4999)
higherBars = 0
lowerBars = 0
if barstate.islast
var label lbl = label.new(na, na, "", style = label.style_label_left)
for i = 1 to lookbackInput
if high[i] > high
higherBars += 1
else if high[i] < high
lowerBars += 1
label.set_xy(lbl, bar_index, high)
label.set_text(lbl, str.tostring(higherBars, "# higher bars\n") + str.tostring(lowerBars

This example uses a loop in its checkLinesForBreaches() function to go through an array of pivot lines and
delete them when price crosses them. A loop is necessary here because all the lines in each of the hiPivotLines
and loPivotLines arrays must be checked on each bar, and there is no Pine Script™ built-in that can do this for us:
//@version=5
MAX_LINES_COUNT = 100
indicator("Pivot line breaches", "", true, max_lines_count = MAX_LINES_COUNT)

color hiPivotColorInput = input(color.new(color.lime, 0), "High pivots")


color loPivotColorInput = input(color.new(color.fuchsia, 0), "Low pivots")
int pivotLegsInput = input.int(5, "Pivot legs")
int qtyOfPivotsInput = input.int(50, "Quantity of last pivots to remember", minval =
int maxLineLengthInput = input.int(400, "Maximum line length in bars", minval = 2)

// ————— Queues a new element in an array and de-queues its first element.
qDq(array, qtyOfElements, arrayElement) =>
array.push(array, arrayElement)
if array.size(array) > qtyOfElements
// Only deqeue if array has reached capacity.
array.shift(array)

// —————— Loop through an array of lines, extending those that price has not crossed and deleti
checkLinesForBreaches(arrayOfLines) =>
int qtyOfLines = array.size(arrayOfLines)
// Don't loop in case there are no lines to check because "to" value will be `na` then`.
for lineNo = 0 to (qtyOfLines > 0 ? qtyOfLines - 1 : na)
// Need to check that array size still warrants a loop because we may have deleted arra
if lineNo < array.size(arrayOfLines)
line currentLine = array.get(arrayOfLines, lineNo)
float lineLevel = line.get_price(currentLine, bar_index)
bool lineWasCrossed = math.sign(close[1] - lineLevel) != math.sign(close - lineLe
bool lineIsTooLong = bar_index - line.get_x1(currentLine) > maxLineLengthInput
if lineWasCrossed or lineIsTooLong
// Line stays on the chart but will no longer be extend on further bars.
array.remove(arrayOfLines, lineNo)
// Force type of both local blocks to same type.
int(na)
else
line.set_x2(currentLine, bar_index)
int(na)

// Arrays of lines containing non-crossed pivot lines.


var line[] hiPivotLines = array.new_line(qtyOfPivotsInput)
var line[] loPivotLines = array.new_line(qtyOfPivotsInput)

// Detect new pivots.


float hiPivot = ta.pivothigh(pivotLegsInput, pivotLegsInput)
float loPivot = ta.pivotlow(pivotLegsInput, pivotLegsInput)

// Create new lines on new pivots.


if not na(hiPivot)
line newLine = line.new(bar_index[pivotLegsInput], hiPivot, bar_index, hiPivot, color
line.delete(qDq(hiPivotLines, qtyOfPivotsInput, newLine))
else if not na(loPivot)
line newLine = line.new(bar_index[pivotLegsInput], loPivot, bar_index, loPivot, color
line.delete(qDq(loPivotLines, qtyOfPivotsInput, newLine))

// Extend lines if they haven't been crossed by price.


checkLinesForBreaches(hiPivotLines)
checkLinesForBreaches(loPivotLines)

`while`
The while structure allows the repetitive execution of statements until a condition is false. Its syntax is:
[[<declaration_mode>] [<type>] <identifier> = ]while <expression>
<local_block_loop>

where:

Parts enclosed in square brackets ( [] ) can appear zero or one time.


<declaration_mode> is the variable’s declaration mode
<type> is optional, as in almost all Pine Script™ variable declarations (see types)
<identifier> is a variable’s name
<expression> can be a literal, a variable, an expression or a function call. It is evaluated at each iteration of the
loop. When it evaluates to true , the loop executes. When it evaluates to false the loop stops. Note that
evaluation of the expression is done before each iteration only. Changes to the expression’s value inside the
loop will only have an impact on the next iteration.
<local_block_loop> consists of zero or more statements followed by a return value, which can be a tuple of
values. It must be indented by four spaces or a tab. It can contain the break statement to exit the loop, or the
continue statement to exit the current iteration and continue on with the next.
The value assigned to the <identifier> variable is the return value of the <local_block_loop>, i.e., the last value
calculated on the loop’s last iteration, or na if the loop is not executed.

This is the first code example of the for section written using a while structure instead of a for one:

//@version=5
indicator("`for` loop")
lookbackInput = input.int(50, "Lookback in bars", minval = 1, maxval = 4999)
higherBars = 0
lowerBars = 0
if barstate.islast
var label lbl = label.new(na, na, "", style = label.style_label_left)
// Initialize the loop counter to its start value.
i = 1
// Loop until the `i` counter's value is <= the `lookbackInput` value.
while i <= lookbackInput
if high[i] > high
higherBars += 1
else if high[i] < high
lowerBars += 1
// Counter must be managed "manually".
i += 1
label.set_xy(lbl, bar_index, high)
label.set_text(lbl, str.tostring(higherBars, "# higher bars\n") + str.tostring(lowerBars

Note that:

The i counter must be incremented by one explicitly inside the while’s local block.
We use the += operator to add one to the counter. lowerBars += 1 is equivalent to
lowerBars := lowerBars + 1 .

Let’s calculate the factorial function using a while structure:


//@version=5
indicator("")
int n = input.int(10, "Factorial of", minval=0)

factorial(int val = na) =>


int counter = val
int fact = 1
result = while counter > 0
fact := fact * counter
counter := counter - 1
fact

// Only evaluate the function on the first bar.


var answer = factorial(n)
plot(answer)

Note that:

We use input.int() for our input because we need to specify a minval value to protect our code. While input()
also supports the input of “int” type values, it does not support the minval parameter.

We have packaged our script’s functionality in a factorial() function which accepts as an argument the
value whose factorial it must calculate. We have used int val = na to declare our function’s parameter,
which says that if the function is called without an argument, as in factorial() , then the val parameter
will initialize to na, which will prevent the execution of the while loop because its counter > 0 expression
will return na. The while structure will thus initialize the result variable to na. In turn, because the
initialization of result is the return value of the our function’s local block, the function will return na.

Note the last line of the while’s local block: fact . It is the local block’s return value, so the value it had on
the while structure’s last iteration.

Our initialization of result is not required; we do it for readability. We could just as well have used:

while counter > 0


fact := fact * counter
counter := counter - 1
fact

Conditional structures Type system

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Type system

Type system

Introduction
Forms
Types
Using forms and types
Forms
const
input
simple
series
Types
int
float
bool
color
string
plot and hline
line, linefill, label, box and table
Arrays and matrices
User-defined types
void
`na` value
Type templates
Type casting
Tuples

Introduction
Pine Script™’s type system is important because it determines what sort of values can be used when calling Pine
Script™ functions, which is a requirement to do pretty much anything in Pine Script™. While it is possible to write very
simple scripts without knowing anything about the type system, a reasonable understanding of it is necessary to
achieve any degree of profiency with the language, and in-depth knowledge of its subtleties will allow you to exploit
the full potential of Pine Script™.

The type system uses the form type pair to qualify the type of all values, be they literals, a variable, the result of an
expression, the value returned by functions or the arguments supplied when calling a function.

The form expresses when a value is known.

The type denotes the nature of a value.


Note
We will often use “type” to refer to the “form type” pair.

The type system is intimately linked to Pine Script™’s execution model and time series concepts. Understanding all
three is key to making the most of the power of Pine Script™.

Forms
Pine Script™ forms identify when a variable’s value is known. They are:

“const” for values known at compile time (when adding an indicator to a chart or saving it in the Pine Script™
Editor)
“input” for values known at input time (when values are changed in a script’s “Settings/Inputs” tab)
“simple” for values known at bar zero (when the script begins execution on the chart’s first historical bar)
“series” for values known on each bar (any time during the execution of a script on any bar)

Forms are organized in the following hierarchy: const < input < simple < series, where “const” is considered a
weaker form than “input”, for example, and “series” stronger than “simple”. The form hierarchy translates into the
rule that, whenever a given form is required, a weaker form is also allowed.

An expression’s result is always of the strongest form used in the expression’s calculation. Furthermore, once a
variable acquires a stronger form, that state is irreversible; it can never be converted back to a weaker form. A
variable of “series” form can thus never be converted back to a “simple” form, for use with a function that requires
arguments of that form.

Note that of all these forms, only the “series” form allows values to change dynamically, bar to bar, during the
script’s execution over each bar of the chart’s history. Such values include close or hlc3 or any variable calculated
using values of “series” form. Variables of “const”, “input” or “simple” forms cannot change values once execution of
the script has begun.

Types
Pine Script™ types identify the nature of a value. They are:

The fundamental types: “int”, “float”, “bool”, “color” and “string”


The special types: “plot”, “hline”, “line”, “linefill”, “label”, “box”, “table”, “array”, “matrix”
User-defined types (UDTs)
“void”

Each fundamental type refers to the nature of the value contained in a variable: 1 is of type “int”, 1.0 is of type
“float”, "AAPL" is of type “string”, etc. Variables of special types contain an ID referring to an object of the type’s
name. A variable of type “label” contains an ID (or pointer) referring to a label, and so on. The “void” type means no
value is returned.

The Pine Script™ compiler can automatically convert some types into others when a value is not of the required type.
The auto-casting rules are: int �� float �� bool. See the Type casting section of this page for more information
on type casting.

Except for parameter definitions appearing in function signatures, Pine Script™ forms are implicit in code; they are
never declared because they are always determined by the compiler. Types, however, can be specified when
declaring variables, e.g.:
//@version=5
indicator("", "", true)
int periodInput = input.int(100, "Period", minval = 2)
float ma = ta.sma(close, periodInput)
bool xUp = ta.crossover(close, ma)
color maColor = close > ma ? color.lime : color.fuchsia
plot(ma, "MA", maColor)
plotchar(xUp, "Cross Up", "▲", location.top, size = size.tiny)

Using forms and types

Forms

const
Values of “const” form must be known at compile time, before your script has access to any information related to
the symbol/timeframe information it is running on. Compilation occurs when you save a script in the Pine Script™
Editor, which doesn’t even require it to already be running on your chart. “const” variables cannot change during the
execution of a script.

Variables of “const” form can be initialized using a literal value, or calculated from expressions using only literal
values or other variables of “const” form. Our Style guide recommends using upper case SNAKE_CASE to name
variables of “const” form. While it is not a requirement, “const” variables can be declared using the var keyword so
they are only initialized on the first bar of the dataset. See the section on `var` for more information.

These are examples of literal values:

literal int: 1 , -1 , 42
literal float: 1. , 1.0 , 3.14 , 6.02E-23 , 3e8
literal bool: true , false
literal string: "A text literal" , "Embedded single quotes 'text'" ,
'Embedded double quotes "text"'
literal color: #FF55C6 , #FF55C6ff

Note
In Pine Script™, the built-in variables open , high , low , close , volume , time , hl2 , hlc3 , ohlc4 ,
etc., are of “series” form because their values can change bar to bar.

The “const” form is a requirement for the arguments to the title and shorttitle parameters in indicator(), for
example. All these are valid variables that can be used as arguments for those parameters when calling the function:

//@version=5
NAME1 = "My indicator"
var NAME2 = "My Indicator"
var NAME3 = "My" + "Indicator"
var NAME4 = NAME2 + " No. 2"
indicator(NAME4, "", true)
plot(close)

This will trigger a compilation error:


//@version=5
var NAME = "My indicator for " + syminfo.type
indicator(NAME, "", true)
plot(close)

The reason for the error is that the NAME variable’s calculation depends on the value of syminfo.type which is a
“simple string” (syminfo.type returns a string corresponding to the sector the chart’s symbol belongs to, eg.,
"crypto" , "forex" , etc.).

Note that using the := operator to assign a new value to a previously declared “const” variable will transform it into
a “simple” variable, e.g., here with name1 , for which we do not use an uppercase name because it is not of “const”
form:

var name1 = "My Indicator "


var NAME2 = "No. 2"
name1 := name1 + NAME2

input
Values of “input” form are known when the values initialized through input.*() functions are determined. These
functions determine the values that can be modified by script users in the script’s “Settings/Inputs” tab. When these
values are changed, this always triggers a re-execution of the script from the beginning of the chart’s history (bar
zero), so variables of “input” form are always known when the script begins execution, and they do not change during
the script’s execution.

Note
The input.source() function yields a value of “series” type — not “input”. This is because built-in variables such
as open, high, low, close, hl2, hlc3, ohlc4, etc., are of “series” form.

The script plots the moving average of a user-defined source and period from a symbol and timeframe also
determined through inputs:

//@version=5
indicator("", "", true)
symbolInput = input.symbol("AAPL", "Symbol")
timeframeInput = input.timeframe("D", "Timeframe")
sourceInput = input.source(close, "Source")
periodInput = input(10, "Period")
v = request.security(symbolInput, timeframeInput, ta.sma(sourceInput, periodInput))
plot(v)

Note that:

The symbolInput , timeframeInput and periodInput variables are of “input” form.


The sourceInput variable is of “series” form because it is determined from a call to input.source().
Our request.security() call is valid because its symbol and timeframe parameters require a “simple”
argument and the “input” form we use is weaker than “simple”. The function’s expression parameter
requires a “series” form argument, and that is what form our sourceInput variable is. Note that because a
“series” form is required there, we could have used “const”, “input” or “simple” forms as well.
As per our style guide’s recommendations, we use the “Input” suffix with our input variables to help readers of
our code remember the origin of these variables.

Wherever an “input” form is required, a “const” form can also be used.


simple
Values of “simple” form are known only when a script begins execution on the first bar of a chart’s history, and they
never change during the execution of the script. Built-in variables of the syminfo.* , timeframe.* and
ticker.* families, for example, all return results of “simple” form because their value depends on the chart’s
symbol, which can only be detected when the script executes on it.

A “simple” form argument is also required for the length argument of functions such as ta.ema() or ta.rma() which
cannot work with dynamic lengths that could change during the script’s execution.

Wherever a “simple” form is required, a “const” or “input” form can also be used.

series
Values of “series” form (also sometimes called dynamic) provide the most flexibility because they can change on any
bar, or even multiples times during the same bar, in loops for example. Built-in variables such as open, close, high,
time or volume are of “series” form, as would be the result of expressions calculated using them. Functions such as
barssince() or crossover() yield a result of “series” form because it varies bar to bar, as does that of the [] history-
referencing operator used to access past values of a time series. While the “series” form is the most common form
used in Pine Script™, it is not always allowed as arguments to Pine Script™ built-in functions.

Suppose you want to display the value of pivots on your chart. This will require converting values into strings, so the
string values your code will be using will be of “series string” type. Pine Script™’s label.new() function can be used to
place such “series string” text on the chart because its text parameter accepts arguments of “series” form:

//@version=5
indicator("", "", true)
pivotBarsInput = input(3)
hiP = ta.pivothigh(high, pivotBarsInput, pivotBarsInput)
if not na(hiP)
label.new(bar_index[pivotBarsInput], hiP, str.tostring(hiP, format.mintick),
style = label.style_label_down,
color = na,
textcolor = color.silver)
plotchar(hiP, "hiP", "•", location.top, size = size.tiny)

Note that:

The str.tostring(hiP, format.mintick) call we use to convert the pivot’s value to a string yields a
“series string” result, which will work with label.new().
While prices appear at the pivot, the pivots actually require pivotBarsInput bars to have elapsed before
they can be detected. Pivot prices only appear on the pivot because we plot them in the past after the pivot’s
detection, using bar_index[pivotBarsInput] (the bar_index’s value, offset pivotBarsInput bars
back). In real time, these prices would only appear pivotBarsInput bars after the actual pivot.
We print a blue dot using plotchar() when a pivot is detected in our code.
Pine Script™’s plotshape() can also be used to position text on the chart, but because its text parameter
requires a “const string” argument, we could not have used it in place of label.new() in our script.

Wherever a “series” form is required, a “const”, “input” or “simple” form can also be used.

Types

int
Integer literals must be written in decimal notation, e.g.:
1
-1
750

Built-in variables such as bar_index, time, timenow, time_close, or dayofmonth all return values of type “int”.

float
Floating-point literals contain a delimiter (the symbol . ) and may also contain the symbol e or E (which means
“multiply by 10 to the power of X”, where X is the number after the symbol e ), e.g.:

3.14159 // Rounded value of Pi (π)


- 3.0
6.02e23 // 6.02 * 10^23 (a very large value)
1.6e-19 // 1.6 * 10^-19 (a very small value)

The internal precision of floats in Pine Script™ is 1e-10.

bool
There are only two literals representing bool values:

true // true value


false // false value

When an expression of type “bool” returns na and it is used to test a conditional statement or operator, the “false”
branch is executed.

color
Color literals have the following format: #RRGGBB or #RRGGBBAA . The letter pairs represent 00 to FF
hexadecimal values (0 to 255 in decimal) where:

RR , GG and BB pairs are the values for the color’s red, green and blue components
AA is an optional value for the color’s transparency (or alpha component) where 00 is invisible and FF
opaque. When no AA pair is supplied, FF is used.
The hexadecimal letters can be upper or lower case

Examples:

#000000 // black color


#FF0000 // red color
#00FF00 // green color
#0000FF // blue color
#FFFFFF // white color
#808080 // gray color
#3ff7a0 // some custom color
#FF000080 // 50% transparent red color
#FF0000ff // same as #FF0000, fully opaque red color
#FF000000 // completely transparent color

Pine Script™ also has built-in color constants such as color.green, color.red, color.orange, color.blue (the default
color used in plot() and other plotting functions), etc.

When using color built-ins, is possible to add transparency information to them with color.new.

Note that when specifying red, green or blue components in color.*() functions, a 0-255 decimal value must be
used. When specifying transparency in such functions, it is in the form of a 0-100 value (which can be of “float” type
to access the underlying 255 potential valoues) where the scale 00-FF scale for color literals is inverted: 100 is thus
invisible and 0 is opaque.

Here is an example:

//@version=5
indicator("Shading the chart's background", "", true)
BASE_COLOR = color.navy
bgColor = dayofweek == dayofweek.monday ? color.new(BASE_COLOR, 50) :
dayofweek == dayofweek.tuesday ? color.new(BASE_COLOR, 60) :
dayofweek == dayofweek.wednesday ? color.new(BASE_COLOR, 70) :
dayofweek == dayofweek.thursday ? color.new(BASE_COLOR, 80) :
dayofweek == dayofweek.friday ? color.new(BASE_COLOR, 90) :
color.new(color.blue, 80)
bgcolor(bgColor)

See the page on colors for more information on using colors in Pine Script™.

string
String literals may be enclosed in single or double quotation marks, e.g.:

"This is a double quoted string literal"


'This is a single quoted string literal'

Single and double quotation marks are functionally equivalent. A string enclosed within double quotation marks may
contain any number of single quotation marks, and vice versa:

"It's an example"
'The "Star" indicator'

You can escape the string’s delimiter in the string by using a backslash. For example:

'It\'s an example'
"The \"Star\" indicator"

You can concatenate strings using the + operator.

plot and hline


Pine Script™’s fill() function fills the space between two lines with a color. Both lines must have been plotted with
either plot() or hline() function calls. Each plotted line is referred to in the fill() function using IDs which are of “plot”
or “hline” type, e.g.:

//@version=5
indicator("", "", true)
plotID1 = plot(high)
plotID2 = plot(math.max(close, open))
fill(plotID1, plotID2, color.yellow)

Note that there is no plot or hline keyword to explicitly declare the type of plot() or hline() IDs.

line, linefill, label, box and table


Drawings appeared in Pine Script™ starting with v4. Each drawing has its own type: line, linefill, label, box, table.

Each type is also used as a namespace containing all the built-in functions used to operate on each type of drawing.
One of these is a new() constructor used to create an object of that type: line.new(), linefill.new(), label.new(),
box.new() and table.new().

These functions all return an ID which is a reference that uniquely identifies a drawing object. IDs are always of
“series” form, thus their form and type is “series line”, “series label”, etc. Drawing IDs act like a pointer in that they
are used to reference a specific instance of a drawing in all the functions of that drawing’s namespace. For example,
the line ID returned by a line.new() call will then be used to refer to it when comes time to delete the line using
line.delete().

Arrays and matrices


Arrays and matrices in Pine Script™ are identified by an ID, much like drawings such as lines. The type of the ID
defines the type of elements contained in the array or matrix. Array and matrix types are specified by appending a
type template to the array or matrix keywords:

array<int> defines an array containing “int” elements.


array<label> defines an array containing “label” IDs.
array<UDF> defines an array containing objects of a user-defined type (UDT).
matrix<float> defines a matrix containing “float” elements.
matrix<UDF> defines a matrix containing objects of a user-defined type (UDT).

An array containing elements of type “int” initalized with one element of value 10 can be declared in the following,
equivalent ways:

a1 = array.new<int>(1, 10)
array<int> a2 = array.new<int>(1, 10)
a3 = array.from(10)
array<int> a4 = array.from(10)

Note that the int[] syntax can also be used to declare an array of “int” elements, but that use is discouraged. No
equivalent exists to specify the type of matrices in that way. Also note that type-specific built-ins such as
array.new_int() also exist, but the more generic array.new<type> form is preferred, which would be
array.new<int>() to create an array of “int” elements.

User-defined types
The type keyword allows the creation of user-defined types (UDTs) from which objects can be created. UDTs are
composite types; they contain an arbitrary number of fields that can be of any type. The syntax to define a user-
defined type is:

where:

export is used to export the UDT from a library. See the Libraries page for more information.
<UDT_identifier> is the name of the user-defined type.
<field_type> is the type of the field.
<field_name> is the name of the field.
<value> is an optional default value for the field, which will be assigned to it when new objects of that UDT
are created. The field’s default value will be na if none is specified. The same rules as those governing the
default values of parameters in function signatures apply to the default values of fields. For example, the []
history-referencing operator cannot be used with them, and expressions are not allowed.

In this example, we create a UDT containing two fields to hold pivot information, the time of the pivot’s bar and its
price level:
type pivotPoint
int openTime
float level

User-defined types can be embedded, so a field can be of the same type as the UDT it belongs to. Here, we add a
field to our previous pivotPoint type that will hold the pivot information for another pivot point:

type pivotPoint
int openTime
float level
pivotPoint nextPivot

Two built-in methods can be used with a UDT: new() and copy() . Read about them in the Objects page.

void
There is a “void” type in Pine Script™. Functions having only side-effects and returning no usable result return the
“void” type. An example of such a function is alert(); it does something (triggers an alert event), but it returns no
useful value.

A “void” result cannot be used in an expression or assigned to a variable. No void keyword exists in Pine Script™, as
variables cannot be declared using the “void” type.

`na` value
In Pine Script™ there is a special value called na, which is an acronym for not available, meaning the value of an
expression or variable is undefined. It is similar to the null value in Java, or None in Python.

na values can be automatically cast to almost any type. In some cases, however, the Pine Script™ compiler cannot
automatically infer a type for an na value because more than one automatic type-casting rule can be applied. For
example:

// Compilation error!
myVar = na

Here, the compiler cannot determine if myVar will be used to plot something, as in plot(myVar) where its type
would be “float”, or to set some text as in label.set_text(lb, text = myVar) where its type would be
“string”, or for some other purpose. Such cases must be explicitly resolved in one of two ways:

float myVar = na

or

myVar = float(na)

To test if some value is na, a special function must be used: na(). For example:

myClose = na(myVar) ? 0 : close

Do not use the == operator to test for na values, as this method is unreliable.

Designing your calculations so they are na-resistant is often useful. In this example, we define a condition that is
true when the bar’s close is higher than the previous one. For this calculation to work correctly on the dataset’s
first bar where no previous close exists and close[1] will return na, we use the nz() function to replace it with the
current bar’s open for that special case:

bool risingClose = close > nz(close[1], open)

Protecting against na values can also be useful to prevent an initial na value from propagating in a calculation’s result
on all bars. This happens here because the initial value of ath is na, and math.max() returns na if one of its
arguments is na:

// Declare `ath` and initialize it with `na` on the first bar.


var float ath = na
// On all bars, calculate the maximum between the `high` and the previous value of `ath`.
ath := math.max(ath, high)

To protect against this, we could instead use:

var float ath = na


ath := math.max(nz(ath), high)

where we are replacing any na values of ath with zero. Even better would be:

var float ath = high


ath := math.max(ath, high)

Type templates
Type templates are used to build array and matrix types. They are a type name enclosed in angle brackets, for
example: <int> , <label> , <pivotPoint> (where pivotPoint is a user-defined type (UDT)). Type templates
can be constructed from:

Fundamental types: “int”, “float”, “bool”, “color” and “string”


The following special types: “line”, “linefill”, “label”, “box”, “table”
User-defined types (UDTs)

They can be used to declare the type of a variable and in the array.new<type> or matrix.new<type> function calls
used to create a new array or matrix:

// Declare an empty array variable.


var array<int> a = na
// Create an array of 10 "int" elements.
var a = array.new<int>(10)

Type casting
There is an automatic type-casting mechanism in Pine Script™ which can cast (or convert) certain types to another.
The auto-casting rules are: “int” �� “float” �� “bool”, which means that when a “float” is required, an “int”
can be used in its place, and when a “bool” value is required, an “int” or “float” value can be used in its place.

See auto-casting in action in this code:


//@version=5
indicator("")
plotshape(close)

Note that:

plotshape() requires a “series bool” argument for its first parameter named series . The true / false value
of that “bool” argument determines if the function plots a shape or not.
We are here calling plotshape() with close as its first argument. This would not be allowed without Pine’s auto-
casting rules, which allow a “float” to be cast to a “bool”. When a “float” is cast to a “bool”, any non-zero
values are converted to true , and zero values are converted to false . As a result of this, our code will plot
an “X” on all bars, as long as close is not equal to zero.

It may sometimes be necessary to cast one type into another because auto-casting rules will not suffice. For these
cases, explicit type-casting functions exist. They are: int(), float(), bool(), color(), string(), line(), linefill(), label(),
box(), and table().

This is code that will not compile because we fail to convert the type of the argument used for length when calling
ta.sma():

//@version=5
indicator("")
len = 10.0
s = ta.sma(close, len) // Compilation error!
plot(s)

The code fails to compile with the following error: Cannot call ‘ta.sma` with argument ‘length’=’len’. An argument
of ‘const float’ type was used but a ‘series int’ is expected. The compiler is telling us that we supplied a “float”
value where an “int” is required. There is no auto-casting rule that can automatically cast a “float” to an “int”, so we
will need to do the job ourselves. For this, we will use the int() function to force the type conversion of the value we
supply as a length to ta.sma() from “float” to “int”:

//@version=5
indicator("")
len = 10.0
s = ta.sma(close, int(len))
plot(s)

Explicit type-casting can also be useful when declaring variables and initializing them to na which can be done in two
ways:

// Cast `na` to the "label" type.


lbl = label(na)
// Explicitly declare the type of the new variable.
label lbl = na

Tuples
A tuple is a comma-separated set of expressions enclosed in brackets that can be used when a function or a local
block must return more than one variable as a result. For example:

calcSumAndMult(a, b) =>
sum = a + b
mult = a * b
[sum, mult]
[sum, mult]

In this example there is a two-element tuple on the last statement of the function’s code block, which is the result
returned by the function. Tuple elements can be of any type. There is also a special syntax for calling functions that
return tuples, which uses a tuple declaration on the left side of the equal sign in what is a multi-variable declaration.
The result of a function such as calcSumAndMult() that returns a tuple must be assigned to a tuple declaration,
i.e., a set of comma-separated list of new variables that will receive the values returned by the function. Here, the
value of the sum and mult variables calculated by the function will be assigned to the s and m variables:

[s, m] = calcSumAndMul(high, low)

Note that the type of s and m cannot be explicitly defined; it is always inferred by the type of the function return
results.

Tuples can be useful to request multiple values in one request.security() call:

roundedOHLC() =>
[math.round_to_mintick(open), math.round_to_mintick(high), math.round_to_mintick(low),
[op, hi, lo, cl] = request.security(syminfo.tickerid, "D", roundedOHLC())

or:

[op, hi, lo, cl] = request.security(syminfo.tickerid, "D", [math.round_to_mintick(open),

or this form if no rounding is required

[op, hi, lo, cl] = request.security(syminfo.tickerid, "D", [open, high, low, close])

Tuples can also be used as return results of local blocks, in an if statement for example:

[v1, v2] = if close > open


[high, close]
else
[close, low]

They cannot be used in ternaries, however, because the return values of a ternary statement are not considered as
local blocks. This is not allowed:

// Not allowed.
[v1, v2] = close > open ? [high, close] : [close, low]

Please note that the items within a tuple returned from a function may be of simple or series form, depending on its
contents. If a tuple contains a series value, all other elements within the tuple will also be of the series form. For
example:
//@version=5
indicator("tuple_typeforms")

makeTicker(simple string prefix, simple string ticker) =>


tId = prefix + ":" + ticker // simple string
source = close // series float
[tId, source]

// Both variables are series now.


[tId, source] = makeTicker("BATS", "AAPL")

// Error cannot call 'request.security' with 'series string' tId.


r = request.security(tId, "", source)

plot(r)

Loops Built-ins

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Built-ins

Built-ins

Introduction
Built-in variables
Built-in functions

Introduction
Pine Script™ has hundreds of built-in variables and functions. They provide your scripts with valuable information and
make calculations for you, dispensing you from coding them. The better you know the Pine Script™ built-ins, the more
you will be able to do with your Pine scripts.

In this page we present an overview of some of Pine Script™’s built-in variables and functions. They will be covered in
more detail in the pages of this manual covering specific themes.

All Pine Script™ built-in variables and functions are defined in the Pine Script™ v5 Reference Manual. It is called a
“Reference Manual” because it is the definitive reference on the Pine Script™ language. It is an essential tool that
will accompany you anytime you code in Pine Script™, whether you are a beginner or an expert. If you are learning
your first programming language, make the Reference Manual your friend. Ignoring it will make your programming
experience with Pine Script™ difficult and frustrating — as it would with any other programming language.

Variables and functions in the same family share the same namespace, which is a prefix to the function’s name. The
ta.sma() function, for example, is in the ta namespace, which stands for “technical analysis”. A namespace can
contain both variables and functions.

Some variables have function versions as well, e.g.:

The ta.tr variable returns the “True Range” of the current bar. The ta.tr(true) function call also returns the
“True Range”, but when the previous close value which is normally needed to calculate it is na, it calculates
using high - low instead.
The time variable gives the time at the open of the current bar. The time(timeframe) function returns the time
of the bar’s open from the timeframe specified, even if the chart’s timeframe is different. The
time(timeframe, session) function returns the time of the bar’s open from the timeframe specified, but only
if it is within the session time. The time(timeframe, session, timezone) function returns the time of the bar’s
open from the timeframe specified, but only if it is within the session time in the specified timezone .

Built-in variables
Built-in variables exist for different purposes. These are a few examples:

Price- and volume-related variables: open, high, low, close, hl2, hlc3, ohlc4, and volume.
Symbol-related information in the syminfo namespace: syminfo.basecurrency, syminfo.currency,
syminfo.description, syminfo.mintick, syminfo.pointvalue, syminfo.prefix, syminfo.root, syminfo.session,
syminfo.ticker, syminfo.tickerid, syminfo.timezone, and syminfo.type.
Timeframe (a.k.a. “interval” or “resolution”, e.g., 15sec, 30min, 60min, 1D, 3M) variables in the timeframe
namespace: timeframe.isseconds, timeframe.isminutes, timeframe.isintraday, timeframe.isdaily,
timeframe.isweekly, timeframe.ismonthly, timeframe.isdwm, timeframe.multiplier, and timeframe.period.
Bar states in the barstate namespace (see the Bar states page): barstate.isconfirmed, barstate.isfirst,
barstate.ishistory, barstate.islast, barstate.islastconfirmedhistory, barstate.isnew, and barstate.isrealtime.
Strategy-related information in the strategy namespace: strategy.equity, strategy.initial_capital,
strategy.grossloss, strategy.grossprofit, strategy.wintrades, strategy.losstrades, strategy.position_size,
strategy.position_avg_price, strategy.wintrades, etc.

Built-in functions
Many functions are used for the result(s) they return. These are a few examples:

Math-related functions in the math namespace: math.abs(), math.log(), math.max(), math.random(),


math.round_to_mintick(), etc.
Technical indicators in the ta namespace: ta.sma(), ta.ema(), ta.macd(), ta.rsi(), ta.supertrend(), etc.
Support functions often used to calculate technical indicators in the ta namespace: ta.barssince(),
ta.crossover(), ta.highest(), etc.
Functions to request data from other symbols or timeframes in the request namespace: request.dividends(),
request.earnings(), request.financial(), request.quandl(), request.security(), request.splits().
Functions to manipulate strings in the str namespace: str.format(), str.length(), str.tonumber(), str.tostring(),
etc.
Functions used to define the input values that script users can modify in the script’s “Settings/Inputs” tab, in
the input namespace: input(), input.color(), input.int(), input.session(), input.symbol(), etc.
Functions used to manipulate colors in the color namespace: color.from_gradient(), color.new(), color.rgb(),
etc.

Some functions do not return a result but are used for their side effects, which means they do something, even if they
don’t return a result:

Functions used as a declaration statement defining one of three types of Pine scripts, and its properties. Each
script must begin with a call to one of these functions: indicator(), strategy() or library().
Plotting or coloring functions: bgcolor(), plotbar(), plotcandle(), plotchar(), plotshape(), fill().
Strategy functions placing orders, in the strategy namespace: strategy.cancel(), strategy.close(),
strategy.entry(), strategy.exit(), strategy.order(), etc.
Strategy functions returning information on indivdual past trades, in the strategy namespace:
strategy.closedtrades.entry_bar_index(), strategy.closedtrades.entry_price(),
strategy.closedtrades.entry_time(), strategy.closedtrades.exit_bar_index(),
strategy.closedtrades.max_drawdown(), strategy.closedtrades.max_runup(), strategy.closedtrades.profit(), etc.
Functions to generate alert events: alert() and alertcondition().

Other functions return a result, but we don’t always use it, e.g.: hline(), plot(), array.pop(), label.new(), etc.

All Pine Script™ built-in functions are defined in the Pine Script™ v5 Reference Manual. You can click on any of the
function names listed here to go to its entry in the Reference Manual, which documents the function’s signature, i.e.,
the list of parameters it accepts and the form-type of the value(s) it returns (a function can return more than one
result). The Reference Manual entry will also list, for each parameter:

Its name.
The form-type of the value it requires (we use argument to name the values passed to a function when calling
it).
If the parameter is required or not.

All built-in functions have one or more parameters defined in their signature. Not all parameters are required for
every function.
Let’s look at the ta.vwma() function, which returns the volume-weighted moving average of a source value. This is its
entry in the Reference Manual:

The entry gives us the information we need to use it:

What the function does.

Its signature (or definition):

ta.vwma(source, length) → series float

The parameters it includes: source and length

The form and type of the result it returns: “series float”.

An example showing it in use: plot(ta.vwma(close, 15)) .

An example showing what it does, but in long form, so you can better understand its calculations. Note that this
is meant to explain — not as usable code, because it is more complicated and takes longer to execute. There are
only disadvantages to using the long form.

The “RETURNS” section explains exacty what value the function returns.

The “ARGUMENTS” section lists each parameter and gives the critical information concerning what form-type is
required for arguments used when calling the function.

The “SEE ALSO” section refers you to related Reference Manual entries.

This is a call to the function in a line of code that declares a myVwma variable and assigns the result of
ta.vwma(close, 20) to it:

myVwma = ta.vwma(close, 20)

Note that:

We use the built-in variable close as the argument for the source parameter.
We use 20 as the argument for the length parameter.
If placed in the global scope (i.e., starting in a line’s first position), it will be executed by the Pine Script™
runtime on each bar of the chart.

We can also use the parameter names when calling the function. Parameter names are called keyword arguments
when used in a function call:

myVwma = ta.vwma(source = close, length = 20)

You can change the position of arguments when using keyword arguments, but only if you use them for all your
arguments. When calling functions with many parameters such as indicator(), you can also forego keyword arguments
for the first arguments, as long as you don’t skip any. If you skip some, you must then use keyword arguments so the
Pine Script™ compiler can figure out which parameter they correspond to, e.g.:

indicator("Example", "Ex", true, max_bars_back = 100)

Mixing things up this way is not allowed:

indicator(precision = 3, "Example") // Compilation error!

When calling Pine Script™ built-ins, it is critical to ensure that the arguments you use are of the form and type
required, which will vary for each parameter.

To learn how to do this, one needs to understand Pine Script™’s type system. The Reference Manual entry for each
built-in function includes an “ARGUMENTS” section which lists the form-type required for the argument supplied to
each of the function’s parameters.

Type system User-defined functions

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • User-defined functions

User-defined functions

Introduction
Single-line functions
Multi-line functions
Scopes in the script
Functions that return multiple results
Limitations

Introduction
User-defined functions are functions that you write, as opposed to the built-in functions in Pine Script™. They are
useful to define calculations that you must do repetitevely, or that you want to isolate from your script’s main section
of calculations. Think of user-defined functions as a way to extend the capabilities of Pine Script™, when no built-in
function will do what you need.

You can write your functions in two ways:

In a single line, when they are simple, or


On multiple lines

Functions can be located in two places:

If a function is only used in one script, you can include it in the script where it is used. See our Style guide for
recommendations on where to place functions in your script.
You can create a Pine Script™ library to include your functions, which makes them reusable in other scripts
without having to copy their code. Distinct requirements exist for library functions. They are explained in the
page on libraries.

Whether they use one line or multiple lines, user-defined functions have the following characteristics:

They cannot be embedded. All functions are defined in the script’s global scope.
They do not support recursion. It is not allowed for a function to call itself from within its own code.
The type of the value returned by a function is determined automatically and depends on the type of arguments
used in each particular function call.
Each function call

Single-line functions
Simple functions can often be written in one line. This is the formal definition of single-line functions:
<function_declaration>
<identifier>(<parameter_list>) => <return_value>

<parameter_list>
{<parameter_definition>{, <parameter_definition>}}

<parameter_definition>
[<identifier> = <default_value>]

<return_value>
<statement> | <expression> | <tuple>

Here is an example:

f(x, y) => x + y

After the function f() has been declared, it’s possible to call it using different types of arguments:

a = f(open, close)
b = f(2, 2)
c = f(open, 2)

In the example above, the type of variable a is series because the arguments are both series. The type of variable
b is integer because arguments are both literal integers. The type of variable c is series because the addition of a
series and literal integer produces a series result.

Multi-line functions
Pine Script™ also supports multi-line functions with the following syntax:

<identifier>(<parameter_list>) =>
<local_block>

<identifier>(<list of parameters>) =>


<variable declaration>
...
<variable declaration or expression>

where:

<parameter_list>
{<parameter_definition>{, <parameter_definition>}}

<parameter_definition>
[<identifier> = <default_value>]

The body of a multi-line function consists of several statements. Each statement is placed on a separate line and must
be preceded by 1 indentation (4 spaces or 1 tab). The indentation before the statement indicates that it is a part of
the body of the function and not part of the script’s global scope. After the function’s code, the first statement
without an indent indicates the body of the function has ended.

Either an expression or a declared variable should be the last statement of the function’s body. The result of this
expression (or variable) will be the result of the function’s call. For example:
geom_average(x, y) =>
a = x*x
b = y*y
math.sqrt(a + b)

The function geom_average has two arguments and creates two variables in the body: a and b . The last
statement calls the function math.sqrt (an extraction of the square root). The geom_average call will return the
value of the last expression: (math.sqrt(a + b)) .

Scopes in the script


Variables declared outside the body of a function or of other local blocks belong to the global scope. User-declared
and built-in functions, as well as built-in variables also belong to the global scope.

Each function has its own local scope. All the variables declared within the function, as well as the function’s
arguments, belong to the scope of that function, meaning that it is impossible to reference them from outside — e.g.,
from the global scope or the local scope of another function.

On the other hand, since it is possible to refer to any variable or function declared in the global scope from the scope
of a function (except for self-referencing recursive calls), one can say that the local scope is embedded into the
global scope.

In Pine Script™, nested functions are not allowed, i.e., one cannot declare a function inside another one. All user
functions are declared in the global scope. Local scopes cannot intersect with each other.

Functions that return multiple results


In most cases a function returns only one result, but it is possible to return a list of results (a tuple-like result):

fun(x, y) =>
a = x+y
b = x-y
[a, b]

Special syntax is required for calling such functions:

[res0, res1] = fun(open, close)


plot(res0)
plot(res1)

Limitations
User-defined functions can use any of the Pine Script™ built-ins, except: barcolor(), fill(), hline(), indicator(),
library(), plot(), plotbar(), plotcandle(), plotchar(), plotshape() and strategy().
Built-ins Arrays

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Arrays

Arrays

Introduction
Declaring arrays
Using the `var` keyword
Reading and writing array values
Looping through array elements
Scope
History referencing
Inserting and removing array elements
Inserting
Removing
Using an array as a stack
Using an array as a queue
Calculations on arrays
Manipulating arrays
Concatenation
Copying
Joining
Sorting
Reversing
Slicing
Searching arrays
Error handling
Index xx is out of bounds. Array size is yy
Cannot call array methods when ID of array is ‘na’
Array is too large. Maximum size is 100000
Cannot create an array with a negative size
Cannot use shift() if array is empty.
Cannot use pop() if array is empty.
Index ‘from’ should be less than index ‘to’
Slice is out of bounds of the parent array

Introduction
Arrays can be used to store multiple values in one data structure. Think of them as a better way to handle cases
where you would otherwise need a set of variables named price00 , price01 and price02 . Arrays are an
advanced feature used for scripts requiring intricate data-handling. If you are a beginning Pine Script™ programmer,
we recommend you become familiar with other, more accessible Pine Script™ features before you tackle arrays.
Pine Script™ arrays are one-dimensional. All elements of an array are of the same type, which can be “int”, “float”,
“bool”, “color”, “string”, “line”, “label”, “box” or “table”, always of “series” form. Arrays are referenced using an
array ID similar to line or label IDs. Pine Script™ does not use an indexing operator to reference individual array
elements; instead, functions like array.get() and array.set() are used to read and write values of array elements.
Array values can be used in all Pine Script™ expressions and functions where a value of “series” form is allowed.

Elements within an array are referred to using an index, which starts at 0 and extends to the number or elements in
the array, minus one. Arrays in Pine Script™ can be sized dynamically, so the number of elements in the array can be
modified within one iteration of the script on a bar, and vary across bars. Multiple arrays can be used in the same
script. The size of arrays is limited to 100,000.

Note
We will use beginning of an array to designate index 0, and end of an array to designate the array’s element
with the highest index value. We will also extend the meaning of array to include array IDs, for the sake of
brevity.

Declaring arrays
The following syntax can be used to declare arrays:

<type>[] <identifier> = <expression>


var <type>[] <identifier> = <expression>
array<type> <identifier> = <expression>
var array<type> <identifier> = <expression>

The [] modifier (not to be confused with the [] history-referencing operator) is appended to the type name when
declaring arrays. However, since type-specific functions are always used to create arrays, the <type>[] part of the
declaration is redundant, except if you initialize an array variable to na . Explicitly declaring the array type helps
state our intention to readers more clearly.

In the following example, we declare an array variable named prices and initialize it with na . Consequently, its
type must be specified. The variable will be used to designate an array containing “float” values, but no array has
been created by this declaration yet. For the moment, the array variable contains no valid array ID, its value being
na :

float[] prices = na

We could also write the above example in this alternate form:

array<float> prices = na

When declaring an array and the <expression> is not na , one of the following functions must be used:
array.new_<type>(size, initial_value) , array.from(), or array.copy(). For the
array.new_<type>(size, initial_value) functions, the arguments of the size and initial_value
parameters can be “series” to allow dynamic sizing and initialization of array elements. The following example
creates an array containing zero “float” elements, and this time, the array ID returned by the array.new_float()
function call is assigned to prices :

prices = array.new_float(0)
Similar array creation functions exist for the other types of array elements: array.new_int(), array.new_bool(),
array.new_color(), array.new_string(), array.new_line(), array.new_linefill(), array.new_label(), array.new_box() and
array.new_table().

When declaring an array using one of the array creation functions, you can initialize all elements using the
initial_value parameter. When no argument is supplied for initial_value , the array elements are initialized
to na . The following declaration creates an array ID named prices . The array is created with two elements, each
initialized with the value of the close built-in variable on that bar:

prices = array.new_float(2, close)

You can also use array.from() to create an array and initialize it with different values at the same time. array.from()
infers the array’s size and the type of its elements, which must be consistent, from the arguments supplied to the
function when calling it. Similarly to ``array.new_*()` functions, it accepts “series” arguments.

All three of the lines of code below will create identical “bool” arrays with the same two elements:

statesArray = array.from(close > open, high != close)


bool[] statesArray = array.from(close > open, high != close)
array<bool> statesArray = array.from(close > open, high != close)

Using the `var` keyword


The var keyword can be used when declaring arrays. It works just as it does for other variables; it causes the
declaration to only be executed on the first iteration of the script on the dataset’s bar at bar_index zero. Because the
array is never re-initialized on subsequent bars, its value will persist across bars, as the script iterates on them.

When an array declaration is done using var and a new value is pushed at the end of the array on each bar, the array
will grow by one on each bar and be of size bar_index + 1 plus one (bar_index starts at zero) by the time the
script executes on the last bar, as this code will do:

//@version=5
indicator("Using `var`")
var a = array.new_float(0)
array.push(a, close)
if barstate.islast
label.new(bar_index, 0, "Array size: " + str.tostring(array.size(a)) + "\nbar_index: "

The same code without the var keyword would re-declare the array on each bar. After execution of the array.push()
call, the array would thus be of size one on all the dataset’s bars.

This initializes an array of constant lengths which will not change during the script’s execution, so we only declare it
on the first bar:

var int[] lengths = array.from(2, 12, 20, 50, 100, 200)

Reading and writing array values


Values can be written to existing individual array elements using array.set(id, index, value), and read using
array.get(id, index). As is the case whenever an array index is used in your code, it is imperative that the index never
be greater than the array’s size, minus one (because array indices start at zero). You can obtain the size of an array
by using the array.size(id) function.
The following example uses array.set() to initialize an array of colors to instances of one base color using different
transparency levels. It then fetches the proper array element to use it in a bgcolor() call:

//@version=5
indicator("Distance from high", "", true)
lookbackInput = input.int(100)
FILL_COLOR = color.green
// Declare array and set its values on the first bar only.
var fillColors = array.new_color(5)
if barstate.isfirst
// Initialize the array elements with progressively lighter shades of the fill color.
array.set(fillColors, 0, color.new(FILL_COLOR, 70))
array.set(fillColors, 1, color.new(FILL_COLOR, 75))
array.set(fillColors, 2, color.new(FILL_COLOR, 80))
array.set(fillColors, 3, color.new(FILL_COLOR, 85))
array.set(fillColors, 4, color.new(FILL_COLOR, 90))

// Find the offset to highest high. Change its sign because the function returns a negative val
lastHiBar = - ta.highestbars(high, lookbackInput)
// Convert the offset to an array index, capping it to 4 to avoid a runtime error.
// The index used by `array.get()` will be the equivalent of `floor(fillNo)`.
fillNo = math.min(lastHiBar / (lookbackInput / 5), 4)
// Set background to a progressively lighter fill with increasing distance from location of hig
bgcolor(array.get(fillColors, fillNo))
// Plot key values to the Data Window for debugging.
plotchar(lastHiBar, "lastHiBar", "", location.top, size = size.tiny)
plotchar(fillNo, "fillNo", "", location.top, size = size.tiny)

Another technique that can be used to initialize the elements in an array is to declare the array with size zero, and
then populate it using array.push() to append new elements to the end of the array, increasing the size of the array
by one at each call. The following code is functionally identical to the initialization section from the preceding script:

// Declare array and set its values on the first bar only.
var fillColors = array.new_color(0)
if barstate.isfirst
// Initialize the array elements with progressively lighter shades of the fill color.
array.push(fillColors, color.new(FILL_COLOR, 70))
array.push(fillColors, color.new(FILL_COLOR, 75))
array.push(fillColors, color.new(FILL_COLOR, 80))
array.push(fillColors, color.new(FILL_COLOR, 85))
array.push(fillColors, color.new(FILL_COLOR, 90))
Finally, we could use array.from():

//@version=5
indicator("Using `var`")
FILL_COLOR = color.green
var color[] fillColors =
array.from(
color.new(FILL_COLOR, 70),
color.new(FILL_COLOR, 75),
color.new(FILL_COLOR, 80),
color.new(FILL_COLOR, 85),
color.new(FILL_COLOR, 90))
// Cycle background through the array's colors.
bgcolor(array.get(fillColors, bar_index % array.size(fillColors)))

The array.fill(id, value, index_from, index_to) function can be used to fill contiguous sets of array elements with a
value. Used without the last two optional parameters, the function fills the whole array, so:

a = array.new_float(10, close)

and:

a = array.new_float(10)
array.fill(a, close)

are equivalent, but:

a = array.new_float(10)
array.fill(a, close, 1, 3)

only fills the second and third elements (at index 1 and 2) of the array with close . Note how array.fill()’s last
parameter, index_to , needs to be one greater than the last index to be filled. The remaining elements will hold
the na value, as no intialization value was provided when the array was declared.

Looping through array elements


When looping through array elements when the array’s size is unknown, you can use:

//@version=5
indicator("Protected `for` loop", overlay = true)
array<float> a = request.security_lower_tf(syminfo.tickerid, "1", close)

string labelText = ""


for i = 0 to (array.size(a) == 0 ? na : array.size(a) - 1)
labelText += str.tostring(array.get(a, i)) + "\n"

label.new(bar_index, high, text = labelText)

Note that:
We use the request.security_lower_tf() function which returns an array of close prices at the 1 minute
timeframe.
This code example will throw an error if you use it on a chart timeframe smaller than 1 minute .
for loops do not execute if the to expression is na. Note that the to value is only evaluated once upon
entry.

A much more recommended method to loop through array elements when the array’s size is unknown is to use a for…
in loop. This method is a variation of the traditional for loop that dynamically adjusts the number of iterations based
on the array’s size. Here is an example of how you can write the code example from above using this method:

//@version=5
indicator("`for...in` loop", overlay = true)
array<float> a = request.security_lower_tf(syminfo.tickerid, "1", close)

string labelText = ""


for price in a
labelText += str.tostring(price) + "\n"

label.new(bar_index, high, text = labelText)

A while loop statement can also be used:

//@version=5
indicator("`while` loop", overlay = true)
array<float> a = request.security_lower_tf(syminfo.tickerid, "1", close)

string labelText = ""


int i = 0
while i < array.size(a)
labelText += str.tostring(array.get(a, i)) + "\n"
i += 1

label.new(bar_index, high, text = labelText)

Scope
Arrays can be declared in a script’s global scope, as well as in the local scope of a function or an if branch. One
major distinction between Pine Script™ arrays and variables declared in the global scope, is that global arrays can be
modified from within the local scope of a function. This new capability can be used to implement global variables that
can be both read and set from within any function in the script. We use it here to calculate progressively lower or
higher levels:
//@version=5
indicator("Bands", "", true)
factorInput = 1 + (input.float(-2., "Step %") / 100)
// Use the lowest average OHLC in last 50 bars from 10 bars back as the our base level.
level = array.new_float(1, ta.lowest(ohlc4, 50)[10])

nextLevel(val) =>
newLevel = array.get(level, 0) * val
// Write new level to the global array so it can be used as the base in the next call to th
array.set(level, 0, newLevel)
newLevel

plot(nextLevel(1))
plot(nextLevel(factorInput))
plot(nextLevel(factorInput))
plot(nextLevel(factorInput))

History referencing
Past instances of array IDs or elements cannot be referenced directly using Pine Script™’s [ ] history-referencing
operator. One cannot write: array.get(a[1], 0) to fetch the value of the array’s first element on the previous
bar.

In Pine Script™, however, each call to a function leaves behind a series trail of function results on previous bars. This
series can in turn be used when working with arrays. One can thus write: ma = ta.sma(array.get(a, 0), 20)
to calculate the simple moving average of the value returned by the array.get(a, 0) call on the last 20 bars.

To illustrate this, let’s first see how we can fetch the previous bar’s close value in two, equivalent ways. For
previousClose1 we use the result of the array.get(a, 0) function call on the previous bar. Since on the
previous bar the array’s only element was initialized to that bar’s close (as it is on every bar), referring to
array.get(a, 0)[1] returns that bar’s close , i.e., the value of the array.get(a, 0) call on the previous
bar.

For previousClose2 we use the history-referencing operator to fetch the previous bar’s close in normal Pine
Script™ fashion:

//@version=5
indicator("History referencing")
// Re-declare the array on each bar.
a = array.new_float(1)
// Set the value of its only element to `close`.
array.set(a, 0, close)

previousClose1 = array.get(a, 0)[1]


previousClose2 = close[1]
plot(previousClose1, "previousClose1", color.gray, 6)
plot(previousClose2, "previousClose2", color.white, 2)

In the following example we add two, equivalent calculations of a moving average to our previous code example. For
ma1 we use ta.sma() on the series of values returned by the array.get(a, 0) function call on each bar. Since at
this point in the script the call returns the current bar’s close , that is the value used for the average’s calculation.
We evaluate ma2 using the usual way we would calculate a simple average in Pine Script™:
//@version=5
indicator("History referencing")
a = array.new_float(1)
array.set(a, 0, close)
previousClose1 = array.get(a, 0)[1]
previousClose2 = close[1]
plot(previousClose1, "previousClose1", color.gray, 6)
plot(previousClose2, "previousClose2", color.white, 2)

ma1 = ta.sma(array.get(a, 0), 20)


ma2 = ta.sma(close, 20)
plot(ma1, "MA 1", color.aqua, 6)
plot(ma2, "MA 2", color.white, 2)

// Last set having no impact.


array.set(a, 0, 10.0)

Notice the last line of this script. It illustrates how even if we set the value of the array’s element to 10.0 at the
end of the script, resulting in the final value for the element being committed as 10.0 on the bar’s last execution of
the script, the earlier call to array.get(a, 0) nonetheless returned the close value because that was the value
of the array element at that point in the script. The series value of the function call will thus be each bar’s close
value.

Inserting and removing array elements

Inserting
Three functions can be used to insert new elements in an array.

array.unshift() inserts a new element at the beginning of an array, at index zero, and shifts any existing elements
right by one.

array.insert() can insert a new element at any position in the array. Its index parameter is the index where the new
element will be added. The element existing at the index used in the function call and any others to its right are
shifted one place to the right:
//@version=5
indicator("`array.insert()`")
a = array.new_float(5, 0)
for i = 0 to 4
array.set(a, i, i + 1)
if barstate.islast
label.new(bar_index, 0, "BEFORE\na: " + str.tostring(a), size = size.large)
array.insert(a, 2, 999)
label.new(bar_index, 0, "AFTER\na: " + str.tostring(a), style = label.style_label_up,

array.push() will add a new element at the end of an array.

Removing
Four functions can be used to remove elements from an array. The first three will return the value of the removed
element.

array.remove() removes the element at the index value used, and returns that element’s value.

array.shift() removes the first element from an array and returns its value.

array.pop() removes the last element of an array and returns its value.

array.clear() will remove all elements from an array. Note that clearing an array won’t delete the underlying data.
See the example below which illustrates how this works:

//@version=5
indicator("`array.clear()` example", overlay = true)

// We create a label array and add a label to the array on each new bar
var a = array.new_label()
label lbl = label.new(bar_index, high, "Text", color = color.red)
array.push(a, lbl)

var table t = table.new(position.top_right, 1, 1)


// We clear the array on the last bar which won't delete the individual labels
if barstate.islast
array.clear(a)
table.cell(t, 0, 0, "Array elements count: " + str.tostring(array.size(a)), bgcolor =

Using an array as a stack


Stacks are LIFO (last in, first out) constructions. They behave somewhat like a vertical pile of books to which books
can only be added or removed one at a time, always from the top. Pine Script™ arrays can be used as a stack, in
which case you will use the array.push() and array.pop() functions to add and remove elements at the end of the
array.

array.push(prices, close) will add a new element to the end of the prices array, increasing the array’s
size by one.

array.pop(prices) will remove the end element from the prices array, return its value and decrease the
array’s size by one.

See how the functions are used here to remember successive lows in rallies:
//@version=5
indicator("Lows from new highs", "", true)
var lows = array.new_float(0)
flushLows = false

// Remove last element from the stack when `_cond` is true.


array_pop(id, cond) => cond and array.size(id) > 0 ? array.pop(id) : float(na)

if ta.rising(high, 1)
// Rising highs; push a new low on the stack.
array.push(lows, low)
// Force the return type of this `if` block to be the same as that of the next block.
bool(na)
else if array.size(lows) >= 4 or low < array.min(lows)
// We have at least 4 lows or price has breached the lowest low;
// sort lows and set flag indicating we will plot and flush the levels.
array.sort(lows, order.ascending)
flushLows := true

// If needed, plot and flush lows.


lowLevel = array_pop(lows, flushLows)
plot(lowLevel, "Low 1", low > lowLevel ? color.silver : color.purple, 2, plot.style_linebr
lowLevel := array_pop(lows, flushLows)
plot(lowLevel, "Low 2", low > lowLevel ? color.silver : color.purple, 3, plot.style_linebr
lowLevel := array_pop(lows, flushLows)
plot(lowLevel, "Low 3", low > lowLevel ? color.silver : color.purple, 4, plot.style_linebr
lowLevel := array_pop(lows, flushLows)
plot(lowLevel, "Low 4", low > lowLevel ? color.silver : color.purple, 5, plot.style_linebr

if flushLows
// Clear remaining levels after the last 4 have been plotted.
array.clear(lows)

Using an array as a queue


Queues are FIFO (first in, first out) constructions. They behave somewhat like cars arriving at a red light. New cars are
queued at the end of the line, and the first car to leave will be the first one that arrived to the red light.

In the following code example, we let users decide through the script’s inputs how many labels they want to have on
their chart. We use that quantity to determine the size of the array of labels we then create, initializing the array’s
elements to na .

When a new pivot is detected, we create a label for it, saving the label’s ID in the pLabel variable. We then queue
the ID of that label by using array.push() to append the new label’s ID to the end of the array, making our array size
one greater than the maximum number of labels to keep on the chart.

Lastly, we de-queue the oldest label by removing the array’s first element using array.shift() and deleting the label
referenced by that array element’s value. As we have now de-queued an element from our queue, the array contains
pivotCountInput elements once again. Note that on the dataset’s first bars we will be deleting na label IDs until
the maximum number of labels has been created, but this does not cause runtime errors. Let’s look at our code:

//@version=5
MAX_LABELS = 100
indicator("Show Last n High Pivots", "", true, max_labels_count = MAX_LABELS)

pivotCountInput = input.int(5, "How many pivots to show", minval = 0, maxval = MAX_LABELS


pivotLegsInput = input.int(3, "Pivot legs", minval = 1, maxval = 5)

// Create an array containing the user-selected max count of label IDs.


var labelIds = array.new_label(pivotCountInput)

pHi = ta.pivothigh(pivotLegsInput, pivotLegsInput)


if not na(pHi)
// New pivot found; plot its label `i_pivotLegs` bars back.
pLabel = label.new(bar_index[pivotLegsInput], pHi, str.tostring(pHi, format.mintick),
// Queue the new label's ID by appending it to the end of the array.
array.push(labelIds, pLabel)
// De-queue the oldest label ID from the queue and delete the corresponding label.
label.delete(array.shift(labelIds))

Calculations on arrays
While series variables can be viewed as a horizontal set of values stretching back in time, Pine Script™’s one-
dimensional arrays can be viewed as vertical structures residing on each bar. As an array’s set of elements is not a
time series, Pine Script™’s usual mathematical functions are not allowed on them. Special-purpose functions must be
used to operate on all of an array’s values. The available functions are: array.abs(), array.avg(), array.covariance(),
array.min(), array.max(), array.median(), array.mode(), array.percentile_linear_interpolation(),
array.percentile_nearest_rank(), array.percentrank(), array.range(), array.standardize(), array.stdev(), array.sum(),
array.variance().

Note that contrary to the usual mathematical functions in Pine Script™, those used on arrays do not return na when
some of the values they calculate on have na values. There are a few exceptions to this rule:

When all array elements have na value or the array contains no elements, na is returned.
array.standardize() however, will return an empty array.
array.mode() will return na when no mode is found.

Manipulating arrays

Concatenation
Two arrays can be merged—or concatenated—using array.concat(). When arrays are concatenated, the second array is
appended to the end of the first, so the first array is modified while the second one remains intact. The function
returns the array ID of the first array:

//@version=5
indicator("`array.concat()`")
a = array.new_float(0)
b = array.new_float(0)
array.push(a, 0)
array.push(a, 1)
array.push(b, 2)
array.push(b, 3)
if barstate.islast
label.new(bar_index, 0, "BEFORE\na: " + str.tostring(a) + "\nb: " + str.tostring(b),
c = array.concat(a, b)
array.push(c, 4)
label.new(bar_index, 0, "AFTER\na: " + str.tostring(a) + "\nb: " + str.tostring(b) +

Copying
You can copy an array using array.copy(). Here we copy the array a to a new array named _b :
//@version=5
indicator("`array.copy()`")
a = array.new_float(0)
array.push(a, 0)
array.push(a, 1)
if barstate.islast
b = array.copy(a)
array.push(b, 2)
label.new(bar_index, 0, "a: " + str.tostring(a) + "\nb: " + str.tostring(b), size = size.l

Note that simply using _b = a in the previous example would not have copied the array, but only its ID. From
thereon, both variables would point to the same array, so using either one would affect the same array.

Joining
Use array.join() to concatenate all of the elements in the array into a string and separate these elements with the
specified separator:

//@version=5
indicator("")
v1 = array.new_string(10, "test")
v2 = array.new_string(10, "test")
array.push(v2, "test1")
v3 = array.new_float(5, 5)
v4 = array.new_int(5, 5)
l1 = label.new(bar_index, close, array.join(v1))
l2 = label.new(bar_index, close, array.join(v2, ","))
l3 = label.new(bar_index, close, array.join(v3, ","))
l4 = label.new(bar_index, close, array.join(v4, ","))

Sorting
Arrays containing “int” or “float” elements can be sorted in either ascending or descending order using array.sort().
The order parameter is optional and defaults to order.ascending. As all array.*() function arguments, it is of
form “series”, so can be determined at runtime, as is done here. Note that in the example, which array is sorted is
also determined at runtime:
//@version=5
indicator("`array.sort()`")
a = array.new_float(0)
b = array.new_float(0)
array.push(a, 2)
array.push(a, 0)
array.push(a, 1)
array.push(b, 4)
array.push(b, 3)
array.push(b, 5)
if barstate.islast
barUp = close > open
array.sort(barUp ? a : b, barUp ? order.ascending : order.descending)
label.new(bar_index, 0,
"a " + (barUp ? "is sorted ▲: " : "is not sorted: ") + str.tostring(a) + "\n\n" +
"b " + (barUp ? "is not sorted: " : "is sorted ▼: ") + str.tostring(b), size = size.la

Another useful option for sorting arrays is to use the array.sort_indices() function, which takes a reference to the
original array and returns an array containing the indices from the original array. Please note that this function won’t
modify the original array. The order parameter is optional and defaults to order.ascending.

Reversing
Use array.reverse() to reverse an array:

//@version=5
indicator("`array.reverse()`")
a = array.new_float(0)
array.push(a, 0)
array.push(a, 1)
array.push(a, 2)
if barstate.islast
array.reverse(a)
label.new(bar_index, 0, "a: " + str.tostring(a))
Slicing
Slicing an array using array.slice() creates a shallow copy of a subset of the parent array. You determine the size of
the subset to slice using the index_from and index_to parameters. The index_to argument must be one
greater than the end of the subset you want to slice.

The shallow copy created by the slice acts like a window on the parent array’s content. The indices used for the slice
define the window’s position and size over the parent array. If, as in the example below, a slice is created from the
first three elements of an array (indices 0 to 2), then regardless of changes made to the parent array, and as long as
it contains at least three elements, the shallow copy will always contain the parent array’s first three elements.

Additionally, once the shallow copy is created, operations on the copy are mirrored on the parent array. Adding an
element to the end of the shallow copy, as is done in the following example, will widen the window by one element
and also insert that element in the parent array at index 3. In this example, to slice the subset from index 0 to index
2 of array a , we must use _sliceOfA = array.slice(a, 0, 3) :

//@version=5
indicator("`array.slice()`")
a = array.new_float(0)
array.push(a, 0)
array.push(a, 1)
array.push(a, 2)
array.push(a, 3)
if barstate.islast
// Create a shadow of elements at index 1 and 2 from array `a`.
sliceOfA = array.slice(a, 0, 3)
label.new(bar_index, 0, "BEFORE\na: " + str.tostring(a) + "\nsliceOfA: " + str.tostring
// Remove first element of parent array `a`.
array.remove(a, 0)
// Add a new element at the end of the shallow copy, thus also affecting the original array
array.push(sliceOfA, 4)
label.new(bar_index, 0, "AFTER\na: " + str.tostring(a) + "\nsliceOfA: " + str.tostring

Searching arrays
We can test if a value is part of an array with the array.includes() function, which returns true if the element is
found. We can find the first occurrence of a value in an array by using the array.indexof() function. The first
occurence is the one with the lowest index. We can also find the last occurrence of a value with array.lastindexof():
//@version=5
indicator("Searching in arrays")
valueInput = input.int(1)
a = array.new_float(0)
array.push(a, 0)
array.push(a, 1)
array.push(a, 2)
array.push(a, 1)
if barstate.islast
valueFound = array.includes(a, valueInput)
firstIndexFound = array.indexof(a, valueInput)
lastIndexFound = array.lastindexof(a, valueInput)
label.new(bar_index, 0, "a: " + str.tostring(a) +
"\nFirst " + str.tostring(valueInput) + (firstIndexFound != -1 ? " value was found at in
"\nLast " + str.tostring(valueInput) + (lastIndexFound != -1 ? " value was found at in

We can also perform a binary search on an array but note that performing a binary search on an array means that the
array will first need to be sorted in ascending order only. The array.binary_search() function will return the value’s
index if it was found or -1 if it wasn’t. If we want to always return an existing index from the array even if our chosen
value wasn’t found, then we can use one of the other binary search functions available. The
array.binary_search_leftmost() function, which returns an index if the value was found or the first index to the left
where the value would be found. The array.binary_search_rightmost() function is almost identical and returns an
index if the value was found or the first index to the right where the value would be found.

Error handling
Malformed array.*() call syntax in Pine scripts will cause the usual compiler error messages to appear in Pine
Script™ Editor’s console, at the bottom of the window, when you save a script. Refer to the Pine Script™ v5 Reference
Manual when in doubt regarding the exact syntax of function calls.

Scripts using arrays can also throw runtime errors, which appear in place of the indicator’s name on charts. We
discuss those runtime errors in this section.

Index xx is out of bounds. Array size is yy


This will most probably be the most frequent error you encounter. It will happen when you reference an nonexistent
array index. The “xx” value will be the value of the faulty index you tried to use, and “yy” will be the size of the
array. Recall that array indices start at zero—not one—and end at the array’s size, minus one. An array of size 3’s last
valid index is thus 2 .

To avoid this error, you must make provisions in your code logic to prevent using an index lying outside of the array’s
index boundaries. This code will generate the error because the last index we use in the loop is outside the valid
index range for the array:

//@version=5
indicator("Out of bounds index")
a = array.new_float(3)
for i = 1 to 3
array.set(a, i, i)
plot(array.pop(a))

The correct for statement is:

for i = 0 to 2
To loop on all array elements in an array of unknown size, use:

//@version=5
indicator("Protected `for` loop")
sizeInput = input.int(0, "Array size", minval = 0, maxval = 100000)
a = array.new_float(sizeInput)
for i = 0 to (array.size(a) == 0 ? na : array.size(a) - 1)
array.set(a, i, i)
plot(array.pop(a))

When you size arrays dynamically using a field in your script’s Settings/Inputs tab, protect the boundaries of that
value using input.int()’s minval and maxval parameters:

//@version=5
indicator("Protected array size")
sizeInput = input.int(10, "Array size", minval = 1, maxval = 100000)
a = array.new_float(sizeInput)
for i = 0 to sizeInput - 1
array.set(a, i, i)
plot(array.size(a))

See the Looping section of this page for more information.

Cannot call array methods when ID of array is ‘na’


When an array ID is initialized to na , operations on it are not allowed, since no array exists. All that exists at that
point is an array variable containing the na value rather that a valid array ID pointing to an existing array. Note that
an array created with no elements in it, as you do when you use a = array.new_int(0) , has a valid ID
nonetheless. This code will throw the error we are discussing:

//@version=5
indicator("Out of bounds index")
int[] a = na
array.push(a, 111)
label.new(bar_index, 0, "a: " + str.tostring(a))

To avoid it, create an array with size zero using:

int[] a = array.new_int(0)

or:

a = array.new_int(0)

Array is too large. Maximum size is 100000


This error will appear if your code attempts to declare an array with a size greater than 100,000. It will also occur if,
while dynamically appending elements to an array, a new element would increase the array’s size past the maximum.

Cannot create an array with a negative size


We haven’t found any use for arrays of negative size yet, but if you ever do, we may allow them :)
Cannot use shift() if array is empty.
This error will occur if array.shift() is called to remove the first element of an empty array.

Cannot use pop() if array is empty.


This error will occur if array.pop() is called to remove the last element of an empty array.

Index ‘from’ should be less than index ‘to’


When two indices are used in functions such as array.slice(), the first index must always be smaller than the second
one.

Slice is out of bounds of the parent array


This message occurs whenever the parent array’s size is modified in such a way that it makes the shallow copy
created by a slice point outside the boundaries of the parent array. This code will reproduce it because after creating
a slice from index 3 to 4 (the last two elements of our five-element parent array), we remove the parent’s first
element, making its size four and its last index 3. From that moment on, the shallow copy which is still poiting to the
“window” at the parent array’s indices 3 to 4, is pointing out of the parent array’s boundaries:

//@version=5
indicator("Slice out of bounds")
a = array.new_float(5, 0)
b = array.slice(a, 3, 5)
array.remove(a, 0)
c = array.indexof(b, 2)
plot(c)

User-defined functions Objects

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Objects

Objects

Introduction
Creating objects
Changing field values
Collecting objects
Copying objects
Shadowing

Note
This page contains advanced material. If you are a beginning Pine Script™ programmer, we recommend that you
become familiar with other, more accessible Pine Script™ features before you venture here.

Introduction
Pine Script™ objects are instances of user-defined types (UDTs). They are the equivalent of variables containing parts
called fields, each able to hold independent values that can be of various types.

Experienced programmers can think of UDTs as methodless classes. They allow users to create custom types that
organize different values under one logical entity.

Creating objects
Before an object can be created, its type must be defined. The User-defined types section of the Type system page
explains how to do so.

Let’s define a pivotPoint type to hold pivot information:

type pivotPoint
int x
float y
string xloc = xloc.bar_time

Note that:

We use the type keyword to declare the creation of a UDT.


We name our new UDT pivotPoint .
After the first line, we create a local block containing the type and name of each field.
The x field will hold the x-coordinate of the pivot. It is declared as an “int” because it will hold either a
timestamp or a bar index of “int” type.
y is a “float” because it will hold the pivot’s price.
xloc is a field that will specify the units of x : xloc.bar_index or xloc.bar_time. We set its default value to
xloc.bar_time by using the = operator. When an object is created from that UDT, its xloc field will thus be
set to that value.

Now that our pivotPoint UDT is defined, we can proceed to create objects from it. We create objects using the
UDT’s new() built-in method. To create a new foundPoint object from our pivotPoint UDT, we use:

foundPoint = pivotPoint.new()

We can also specify field values for the created object using the following:

foundPoint = pivotPoint.new(time, high)

Or the equivalent:

foundPoint = pivotPoint.new(x = time, y = high)

At this point, the foundPoint object’s x field will contain the value of the time built-in when it is created, y will
contain the value of high and the xloc field will contain its default value of xloc.bar_time because no value was
defined for it when creating the object.

Object placeholders can also be created by declaring na object names using the following:

pivotPoint foundPoint = na

This example displays a label where high pivots are detected. The pivots are detected legsInput bars after they
occur, so we must plot the label in the past for it to appear on the pivot:

//@version=5
indicator("Pivot labels", overlay = true)
int legsInput = input(10)

// Define the `pivotPoint` UDT.


type pivotPoint
int x
float y
string xloc = xloc.bar_time

// Detect high pivots.


pivotHighPrice = ta.pivothigh(legsInput, legsInput)
if not na(pivotHighPrice)
// A new high pivot was found; display a label where it occurred `legsInput` bars back.
foundPoint = pivotPoint.new(time[legsInput], pivotHighPrice)
label.new(
foundPoint.x,
foundPoint.y,
str.tostring(foundPoint.y, format.mintick),
foundPoint.xloc,
textcolor = color.white)

Take note of this line from the above example:


foundPoint = pivotPoint.new(time[legsInput], pivotHighPrice)

This could also be written using the following:

pivotPoint foundPoint = na
foundPoint := pivotPoint.new(time[legsInput], pivotHighPrice)

When an object is created using var or varip, those keywords apply to all of the object’s fields:

//@version=5
indicator("")
type barInfo
int i = bar_index
int t = time
float c = close

// Created on bar zero.


var firstBar = barInfo.new()
// Created on every bar.
currentBar = barInfo.new()

plot(firstBar.i)
plot(currentBar.i)

Changing field values


The value of an object’s fields can be changed using the := reassignment operator.

This line of our previous example:

foundPoint = pivotPoint.new(time[legsInput], pivotHighPrice)

Could be written using the following:

foundPoint = pivotPoint.new()
foundPoint.x := time[legsInput]
foundPoint.y := pivotHighPrice

Collecting objects
Arrays and matrices can contain objects, allowing users to add virtual dimensions to their data structures. To declare
object arrays and matrices, use UDT names in type templates, which are constructed using angle brackets.

This example declares an empty array that will hold objects of the pivotPoint UDT and initializes the
pivotHighArray variable with its ID:

pivotHighArray = array.new<pivotPoint>()

To explicitly declare the type of a variable as an array or a matrix of a user-defined type, you can use the array<> and
matrix<> keywords, e.g.:
var array<pivotPoint> pivotHighArray = na
pivotHighArray := array.new<pivotPoint>()

Let’s use what we have learned to create a script that detects high pivot points. The script first collects historical
pivot information in an array. It then loops through the array on the last historical bar, creating a label for each pivot
and connecting the pivots with lines:
//@version=5
indicator("Pivot Points High", overlay = true)

int legsInput = input(10)

// Define the `pivotPoint` UDT containing the time and price of pivots.
type pivotPoint
int openTime
float level

// Create an empty `pivotPoint` array.


var pivotHighArray = array.new<pivotPoint>()

// Detect new pivots (`na` is returned when no pivot is found).


pivotHighPrice = ta.pivothigh(legsInput, legsInput)

// Add a new `pivotPoint` object to the end of the array for each detected pivot.
if not na(pivotHighPrice)
// A new pivot is found; create a new object of `pivotPoint` type, setting its `openTime` a
newPivot = pivotPoint.new(time[legsInput], pivotHighPrice)
// Add the new pivot object to the array.
array.push(pivotHighArray, newPivot)

// On the last historical bar, draw pivot labels and connecting lines.
if barstate.islastconfirmedhistory
var pivotPoint previousPoint = na
for eachPivot in pivotHighArray
// Display a label at the pivot point.
label.new(eachPivot.openTime, eachPivot.level, str.tostring(eachPivot.level, format.mi
// Create a line between pivots.
if not na(previousPoint)
// Only create a line starting at the loop's second iteration because lines connect
line.new(previousPoint.openTime, previousPoint.level, eachPivot.openTime, eachPivo
// Save the pivot for use in the next iteration.
previousPoint := eachPivot

Copying objects
Pine Script™ objects are assigned by reference. When an existing object is assigned to a new variable, both point to
the same object.

In the example below, we create a pivot1 object and set its x field to 1000. Then, we declare a pivot2 variable
containing the reference to the pivot1 object, so both point to the same instance. Changing pivot2.x will thus
also change pivot1.x , as both refer to the x field of the same object:

//@version=5
indicator("")
type pivotPoint
int x
float y
pivot1 = pivotPoint.new()
pivot1.x := 1000
pivot2 = pivot1
pivot2.x := 2000
// Both plot the value 2000.
plot(pivot1.x)
plot(pivot2.x)

To create a copy of an object that is independent of the original, we can use the built-in copy() method in this
case.

In this example, we declare the pivot2 variable referring to a copied instance of the pivot1 object. Now,
changing pivot2.x will not change pivot1.x , as it refers to the x field of a separate object:

//@version=5
indicator("")
type pivotPoint
int x
float y
pivot1 = pivotPoint.new()
pivot1.x := 1000
pivot2 = pivotPoint.copy(pivot1)
pivot2.x := 2000
// Plots 1000 and 2000.
plot(pivot1.x)
plot(pivot2.x)

It’s important to note that the built-in copy() method produces a shallow copy of an object. If an object has fields
with special types (array, matrix, line, linefill, label, box, or table), those fields in a shallow copy of the object will
point to the same instances as the original.

In the following example, we have defined an InfoLabel type with a label as one of its fields. The script
instantiates a shallow copy of the parent object, then calls a user-defined set() method to update the info
and lbl fields of each object. Since the lbl field of both objects points to the same label instance, changes to this
field in either object affect the other:

//@version=5
indicator("Shallow Copy")

type InfoLabel
string info
label lbl

method set(InfoLabel this, int x = na, int y = na, string info = na) =>
if not na(x)
this.lbl.set_x(x)
if not na(y)
this.lbl.set_y(y)
if not na(info)
this.info := info
this.lbl.set_text(this.info)

var parent = InfoLabel.new("", label.new(0, 0))


var shallow = parent.copy()

parent.set(bar_index, 0, "Parent")
shallow.set(bar_index, 1, "Shallow Copy")

To produce a deep copy of an object with all of its special type fields pointing to independent instances, we must
explicitly copy those fields as well.

In this example, we have defined a deepCopy() method that instantiates a new InfoLabel object with its lbl
field pointing to a copy of the original’s field. Changes to the deep copy’s lbl field will not affect the parent
object, as it points to a separate instance:
//@version=5
indicator("Deep Copy")

type InfoLabel
string info
label lbl

method set(InfoLabel this, int x = na, int y = na, string info = na) =>
if not na(x)
this.lbl.set_x(x)
if not na(y)
this.lbl.set_y(y)
if not na(info)
this.info := info
this.lbl.set_text(this.info)

method deepCopy(InfoLabel this) =>


InfoLabel.new(this.info, this.lbl.copy())

var parent = InfoLabel.new("", label.new(0, 0))


var deep = parent.deepCopy()

parent.set(bar_index, 0, "Parent")
deep.set(bar_index, 1, "Deep Copy")

Shadowing
To avoid potential conflicts in the eventuality where namespaces added to Pine Script™ in the future would collide
with UDTs or object names in existing scripts; as a rule, UDTs and object names shadow the language’s namespaces.
For example, a UDT or object can use the name of built-in types, such as line or table.

Only the language’s five primitive types cannot be used to name UDTs or objects: int, float, string, bool, and color.

Arrays Methods

© Copyright 2023, TradingView.


Options v: v5

User Manual • Language • Methods

Methods

Introduction
Built-in methods
User-defined methods
Method overloading
Advanced example

Note
This page contains advanced material. If you are a beginning Pine Script™ programmer, we recommend that you
become familiar with other, more accessible Pine Script™ features before you venture here.

Introduction
Pine Script™ methods are specialized functions associated with specific instances of built-in or user-defined types.
They are essentially the same as regular functions in most regards but offer a shorter, more convenient syntax. Users
can access methods using dot notation on variables directly, just like accessing the fields of a Pine Script™ object.

Built-in methods
Pine Script™ includes built-in methods for array, matrix, line, linefill, label, box, and table types. These methods
provide users with a more concise way to call specialized routines for these types within their scripts.

When using these special types, the expressions

<namespace>.<functionName>([paramName =] <objectName>, …)

and

<objectName>.<functionName>(…)

are equivalent. For example, rather than using

array.get(id, index)
to get the value from an array id at the specified index , we can simply use

id.get(index)

to achieve the same effect. This notation eliminates the need for users to reference the function’s namespace, as
get() is a method of id in this context.

Written below is a practical example to demonstrate the usage of built-in methods in place of functions.

The following script computes Bollinger Bands over a specified number of prices sampled once every n bars. It calls
array.push() and array.shift() to queue sourceInput values through the sourceArray , then array.avg() and
array.stdev() to compute the sampleMean and sampleDev . The script then uses these values to calculate the
highBand and lowBand , which it plots on the chart along with the sampleMean :
//@version=5
indicator("Custom Sample BB", overlay = true)

float sourceInput = input.source(close, "Source")


int samplesInput = input.int(20, "Samples")
int n = input.int(10, "Bars")
float multiplier = input.float(2.0, "StdDev")

var array<float> sourceArray = array.new<float>(samplesInput)


var float sampleMean = na
var float sampleDev = na

// Identify if `n` bars have passed.


if bar_index % n == 0
// Update the queue.
array.push(sourceArray, sourceInput)
array.shift(sourceArray)
// Update the mean and standard deviaiton values.
sampleMean := array.avg(sourceArray)
sampleDev := array.stdev(sourceArray) * multiplier

// Calculate bands.
float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev

plot(sampleMean, "Basis", color.orange)


plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)

Let’s rewrite this code to utilize methods rather than built-in functions. In this version, we have replaced all built-in
array.* functions in the script with equivalent methods:

//@version=5
indicator("Custom Sample BB", overlay = true)

float sourceInput = input.source(close, "Source")


int samplesInput = input.int(20, "Samples")
int n = input.int(10, "Bars")
float multiplier = input.float(2.0, "StdDev")

var array<float> sourceArray = array.new<float>(samplesInput)


var float sampleMean = na
var float sampleDev = na

// Identify if `n` bars have passed.


if bar_index % n == 0
// Update the queue.
sourceArray.push(sourceInput)
sourceArray.shift()
// Update the mean and standard deviaiton values.
sampleMean := sourceArray.avg()
sampleDev := sourceArray.stdev() * multiplier

// Calculate band values.


float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev

plot(sampleMean, "Basis", color.orange)


plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)

Note that:
We call the array methods using sourceArray.* rather than referencing the array namespace.
We do not include sourceArray as a parameter when we call the methods since they already reference
the object.

User-defined methods
Pine Script™ allows users to define custom methods for use with objects of any built-in or user-defined type. Defining
a method is essentially the same as defining a function, but with two key differences:

The method keyword must be included before the function name.


The type of the first parameter in the signature must be explicitly declared, as it represents the type of object
that the method will be associated with.

[export] method <functionName>(<paramType> <paramName> [= <defaultValue>], …) =>


<functionBlock>

Let’s apply user-defined methods to our previous Bollinger Bands example to encapsulate operations from the global
scope, which will simplify the code and promote reusability. See this portion from the example:

// Identify if `n` bars have passed.


if bar_index % n == 0
// Update the queue.
sourceArray.push(sourceInput)
sourceArray.shift()
// Update the mean and standard deviaiton values.
sampleMean := sourceArray.avg()
sampleDev := sourceArray.stdev() * multiplier

// Calculate band values.


float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev

We will start by defining a simple method to queue values through an array in a single call.

This maintainQueue() method invokes the push() and shift() methods on a srcArray when takeSample is true
and returns the object:

// @function Maintains a queue of the size of `srcArray`.


// It appends a `value` to the array and removes its oldest element at positi
// @param srcArray (array<float>) The array where the queue is maintained.
// @param value (float) The new value to be added to the queue.
// The queue's oldest value is also removed, so its size is constant.
// @param takeSample (bool) A new `value` is only pushed into the queue if this is true.
// @returns (array<float>) `srcArray` object.
method maintainQueue(array<float> srcArray, float value, bool takeSample = true) =>
if takeSample
srcArray.push(value)
srcArray.shift()
srcArray

Note that:
Just as with user-defined functions, we use the @function compiler annotation to document method
descriptions.

Now we can replace sourceArray.push() and sourceArray.shift() with


sourceArray.maintainQueue() in our example:
// Identify if `n` bars have passed.
if bar_index % n == 0
// Update the queue.
sourceArray.maintainQueue(sourceInput)
// Update the mean and standard deviaiton values.
sampleMean := sourceArray.avg()
sampleDev := sourceArray.stdev() * multiplier

// Calculate band values.


float highBand = sampleMean + sampleDev
float lowBand = sampleMean - sampleDev

From here, we will further simplify our code by defining a method that handles all Bollinger Band calculations within
its scope.

This calcBB() method invokes the avg() and stdev() methods on a srcArray to update mean and dev values
when calculate is true. The method uses these values to return a tuple containing the basis, upper band, and
lower band values respectively:

// @function Computes Bollinger Band values from an array of data.


// @param srcArray (array<float>) The array where the queue is maintained.
// @param multiplier (float) Standard deviaiton multiplier.
// @param calcuate (bool) The method will only calculate new values when this is true.
// @returns A tuple containing the basis, upper band, and lower band respectively.
method calcBB(array<float> srcArray, float mult, bool calculate = true) =>
var float mean = na
var float dev = na
if calculate
// Compute the mean and standard deviation of the array.
mean := srcArray.avg()
dev := srcArray.stdev() * mult
[mean, mean + dev, mean - dev]

With this method, we can now remove Bollinger Band calculations from the global scope and improve code readability:

// Identify if `n` bars have passed.


bool newSample = bar_index % n == 0

// Update the queue and compute new BB values on each new sample.
[sampleMean, highBand, lowBand] = sourceArray.maintainQueue(sourceInput, newSample).calcBB

Note that:
Rather than using an if block in the global scope, we have defined a newSample variable that is only
true once every n bars. The maintainQueue() and calcBB() methods use this value for their
respective takeSample and calculate parameters.
Since the maintainQueue() method returns the object that it references, we’re able to call calcBB()
from the same line of code, as both methods apply to array<float> instances.

Here is how the full script example looks now that we’ve applied our user-defined methods:
//@version=5
indicator("Custom Sample BB", overlay = true)

float sourceInput = input.source(close, "Source")


int samplesInput = input.int(20, "Samples")
int n = input.int(10, "Bars")
float multiplier = input.float(2.0, "StdDev")

var array<float> sourceArray = array.new<float>(samplesInput)

// @function Maintains a queue of the size of `srcArray`.


// It appends a `value` to the array and removes its oldest element at positi
// @param srcArray (array<float>) The array where the queue is maintained.
// @param value (float) The new value to be added to the queue.
// The queue's oldest value is also removed, so its size is constant.
// @param takeSample (bool) A new `value` is only pushed into the queue if this is true.
// @returns (array<float>) `srcArray` object.
method maintainQueue(array<float> srcArray, float value, bool takeSample = true) =>
if takeSample
srcArray.push(value)
srcArray.shift()
srcArray

// @function Computes Bollinger Band values from an array of data.


// @param srcArray (array<float>) The array where the queue is maintained.
// @param multiplier (float) Standard deviaiton multiplier.
// @param calcuate (bool) The method will only calculate new values when this is true.
// @returns A tuple containing the basis, upper band, and lower band respectively.
method calcBB(array<float> srcArray, float mult, bool calculate = true) =>
var float mean = na
var float dev = na
if calculate
// Compute the mean and standard deviation of the array.
mean := srcArray.avg()
dev := srcArray.stdev() * mult
[mean, mean + dev, mean - dev]

// Identify if `n` bars have passed.


bool newSample = bar_index % n == 0

// Update the queue and compute new BB values on each new sample.
[sampleMean, highBand, lowBand] = sourceArray.maintainQueue(sourceInput, newSample).calcBB

plot(sampleMean, "Basis", color.orange)


plot(highBand, "Upper", color.lime)
plot(lowBand, "Lower", color.red)

Method overloading
User-defined methods can override and overload existing built-in and user-defined methods with the same identifier.
This capability allows users to define multiple routines associated with different parameter signatures under the same
method name.

As a simple example, suppose we want to define a method to identify a variable’s type. Since we must explicitly
specify the type of object associated with a user-defined method, we will need to define overloads for each type that
we want it to recognize.

Below, we have defined a getType() method that returns a string representation of a variable’s type with overloads
for the five primitive types:
// @function Identifies an object's type.
// @param this Object to inspect.
// @returns (string) A string representation of the type.
method getType(int this) =>
na(this) ? "int(na)" : "int"

method getType(float this) =>


na(this) ? "float(na)" : "float"

method getType(bool this) =>


na(this) ? "bool(na)" : "bool"

method getType(color this) =>


na(this) ? "color(na)" : "color"

method getType(string this) =>


na(this) ? "string(na)" : "string"

Now we can use these overloads to inspect some variables. This script uses str.format() to format the results from
calling the getType() method on five different variables into a single results string, then displays the string in
the lbl label using the built-in set_text() method:
//@version=5
indicator("Type Inspection")

// @function Identifies an object's type.


// @param this Object to inspect.
// @returns (string) A string representation of the type.
method getType(int this) =>
na(this) ? "int(na)" : "int"

method getType(float this) =>


na(this) ? "float(na)" : "float"

method getType(bool this) =>


na(this) ? "bool(na)" : "bool"

method getType(color this) =>


na(this) ? "color(na)" : "color"

method getType(string this) =>


na(this) ? "string(na)" : "string"

a = 1
b = 1.0
c = true
d = color.white
e = "1"

// Inspect variables and format results.


results = str.format(
"a: {0}\nb: {1}\nc: {2}\nd: {3}\ne: {4}",
a.getType(), b.getType(), c.getType(), d.getType(), e.getType()
)

var label lbl = label.new(0, 0)


lbl.set_x(bar_index)
lbl.set_text(results)

Note that:
The underlying type of each variable determines which overload of getType() the compiler will use.
The method will append “(na)” to the output string when a variable is na to demarcate that it is empty.

Advanced example
Let’s apply what we’ve learned to construct a script that estimates the cumulative distribution of elements in an
array, meaning the fraction of elements in the array that are less than or equal to any given value.

There are many ways in which we could choose to tackle this objective. For this example, we will start by defining a
method to replace elements of an array, which will help us count the occurrences of elements within a range of
values.

Written below is an overload of the built-in fill() method for array<float> instances. This overload replaces
elements in a srcArray within the range between the lowerBound and upperBound with an innerValue ,
and replaces all elements outside the range with an outerValue :
// @function Replaces elements in a `srcArray` between `lowerBound` and `upperBound` w
// and replaces elements outside the range with an `outerValue`.
// @param srcArray (array<float>) Array to modify.
// @param innerValue (float) Value to replace elements within the range with.
// @param outerValue (float) Value to replace elements outside the range with.
// @param lowerBound (float) Lowest value to replace with `innerValue`.
// @param upperBound (float) Highest value to replace with `innerValue`.
// @returns (array<float>) `srcArray` object.
method fill(array<float> srcArray, float innerValue, float outerValue, float lowerBound,
for [i, element] in srcArray
if (element >= lowerBound or na(lowerBound)) and (element <= upperBound or na(upperBou
srcArray.set(i, innerValue)
else
srcArray.set(i, outerValue)
srcArray

With this method, we can filter an array by value ranges to produce an array of occurrences. For example, the
expression

srcArray.copy().fill(1.0, 0.0, min, val)

copies the srcArray object, replaces all elements between min and val with 1.0, then replaces all elements
above val with 0.0. From here, it’s easy to estimate the output of the cumulative distribution function at the val ,
as it’s simply the average of the resulting array:

srcArray.copy().fill(1.0, 0.0, min, val).avg()

Note that:
The compiler will only use this fill() overload instead of the built-in when the user provides
innerValue , outerValue , lowerBound , and upperBound arguments in the call.
If either lowerBound or upperBound is na , its value is ignored while filtering the fill range.
We are able to call copy() , fill() , and avg() successively on the same line of code because the first
two methods return an array<float> instance.

We can now use this to define a method that will calculate our empirical distribution values. The following eCDF()
method estimates a number of evenly spaced ascending steps from the cumulative distribution function of a
srcArray and pushes the results into a cdfArray :

// @function Estimates the empirical CDF of a `srcArray`.


// @param srcArray (array<float>) Array to calculate on.
// @param steps (int) Number of steps in the estimation.
// @returns (array<float>) Array of estimated CDF ratios.
method eCDF(array<float> srcArray, int steps) =>
float min = srcArray.min()
float rng = srcArray.range() / steps
array<float> cdfArray = array.new<float>()
// Add averages of `srcArray` filtered by value region to the `cdfArray`.
float val = min
for i = 1 to steps
val += rng
cdfArray.push(srcArray.copy().fill(1.0, 0.0, min, val).avg())
cdfArray

Lastly, to ensure that our eCDF() method functions properly for arrays containing small and large values, we will
define a method to normalize our arrays.
This featureScale() method uses array min() and range() methods to produce a rescaled copy of a srcArray .
We will use this to normalize our arrays prior to invoking the eCDF() method:

// @function Rescales the elements within a `srcArray` to the interval [0, 1].
// @param srcArray (array<float>) Array to normalize.
// @returns (array<float>) Normalized copy of the `srcArray`.
method featureScale(array<float> srcArray) =>
float min = srcArray.min()
float rng = srcArray.range()
array<float> scaledArray = array.new<float>()
// Push normalized `element` values into the `scaledArray`.
for element in srcArray
scaledArray.push((element - min) / rng)
scaledArray

Note that:
This method does not include special handling for divide by zero conditions. If rng is 0, the value of the
array element will be na .

The full example below queues a sourceArray of size length with sourceInput values using our previous
maintainQueue() method, normalizes the array’s elements using the featureScale() method, then calls the
eCDF() method to get an array of estimates for n evenly spaced steps on the distribution. The script then calls a
user-defined makeLabel() function to display the estimates and prices in a label on the right side of the chart:

//@version=5
indicator("Empirical Distribution", overlay = true)

float sourceInput = input.source(close, "Source")


int length = input.int(20, "Length")
int n = input.int(20, "Steps")

// @function Maintains a queue of the size of `srcArray`.


// It appends a `value` to the array and removes its oldest element at positi
// @param srcArray (array<float>) The array where the queue is maintained.
// @param value (float) The new value to be added to the queue.
// The queue's oldest value is also removed, so its size is constant.
// @param takeSample (bool) A new `value` is only pushed into the queue if this is true.
// @returns (array<float>) `srcArray` object.
method maintainQueue(array<float> srcArray, float value, bool takeSample = true) =>
if takeSample
if takeSample
srcArray.push(value)
srcArray.shift()
srcArray

// @function Replaces elements in a `srcArray` between `lowerBound` and `upperBound` w


// and replaces elements outside the range with an `outerValue`.
// @param srcArray (array<float>) Array to modify.
// @param innerValue (float) Value to replace elements within the range with.
// @param outerValue (float) Value to replace elements outside the range with.
// @param lowerBound (float) Lowest value to replace with `innerValue`.
// @param upperBound (float) Highest value to replace with `innerValue`.
// @returns (array<float>) `srcArray` object.
method fill(array<float> srcArray, float innerValue, float outerValue, float lowerBound,
for [i, element] in srcArray
if (element >= lowerBound or na(lowerBound)) and (element <= upperBound or na(upperBou
srcArray.set(i, innerValue)
else
srcArray.set(i, outerValue)
srcArray

// @function Estimates the empirical CDF of a `srcArray`.


// @param srcArray (array<float>) Array to calculate on.
// @param steps (int) Number of steps in the estimation.
// @returns (array<float>) Array of estimated CDF ratios.
method eCDF(array<float> srcArray, int steps) =>
float min = srcArray.min()
float rng = srcArray.range() / steps
array<float> cdfArray = array.new<float>()
// Add averages of `srcArray` filtered by value region to the `cdfArray`.
float val = min
for i = 1 to steps
val += rng
cdfArray.push(srcArray.copy().fill(1.0, 0.0, min, val).avg())
cdfArray

// @function Rescales the elements within a `srcArray` to the interval [0, 1].
// @param srcArray (array<float>) Array to normalize.
// @returns (array<float>) Normalized copy of the `srcArray`.
method featureScale(array<float> srcArray) =>
float min = srcArray.min()
float rng = srcArray.range()
array<float> scaledArray = array.new<float>()
// Push normalized `element` values into the `scaledArray`.
for element in srcArray
scaledArray.push((element - min) / rng)
scaledArray

// @function Draws a label containing eCDF estimates in the format "{price}: {percent}%"
// @param srcArray (array<float>) Array of source values.
// @param cdfArray (array<float>) Array of CDF estimates.
// @returns (void)
makeLabel(array<float> srcArray, array<float> cdfArray) =>
float max = srcArray.max()
float rng = srcArray.range() / cdfArray.size()
string results = ""
var label lbl = label.new(0, 0, "", style = label.style_label_left, text_font_family
// Add percentage strings to `results` starting from the `max`.
cdfArray.reverse()
for [i, element] in cdfArray
results += str.format("{0}: {1}%\n", max - i * rng, element * 100)
// Update `lbl` attributes.
lbl.set_xy(bar_index + 1, srcArray.avg())
lbl.set_text(results)

var array<float> sourceArray = array.new<float>(length)


var array<float> sourceArray = array.new<float>(length)

// Add background color for the last `length` bars.


bgcolor(bar_index > last_bar_index - length ? color.new(color.orange, 80) : na)

// Queue `sourceArray`, feature scale, then estimate the distribution over `n` steps.
array<float> distArray = sourceArray.maintainQueue(sourceInput).featureScale().eCDF(n)
// Draw label.
makeLabel(sourceArray, distArray)

Objects Concepts

© Copyright 2023, TradingView.


Options v: v5

User Manual • Concepts

Concepts
Alerts
Introduction
Background
Which type of alert is best?
Script alerts
`alert()` function events
Order fill events
`alertcondition()` events
Using one condition
Using compound conditions
Placeholders
Avoiding repainting with alerts
Backgrounds
Bar coloring
Bar plotting
Introduction
Plotting candles with `plotcandle()`
Plotting bars with `plotbar()`
Bar states
Introduction
Bar state built-in variables
`barstate.isfirst`
`barstate.islast`
`barstate.ishistory`
`barstate.isrealtime`
`barstate.isnew`
`barstate.isconfirmed`
`barstate.islastconfirmedhistory`
Example
Chart information
Introduction
Prices and volume
Symbol information
Chart timeframe
Session information
Colors
Introduction
Transparency
Z-index
Constant colors
Conditional coloring
Calculated colors
color.new()
color.rgb()
color.from_gradient()
Mixing transparencies
Tips
Designing usable colors schemes
Plot crisp lines
Customize gradients
Color selection through script settings
Fills
Introduction
`plot()` and `hline()` fills
Line fills
Inputs
Introduction
Input functions
Input function parameters
Input types
Simple input
Integer input
Float input
Boolean input
Color input
Timeframe input
Symbol input
Session input
Source input
Time input
Other features affecting Inputs
Tips
Levels
`hline()` levels
Fills between levels
Libraries
Introduction
Creating a library
Library functions
Argument form control
User-defined types and objects
Publishing a library
House Rules
Using a library
Lines and boxes
Introduction
Lines
Creating lines
Modifying lines
Line styles
Getting line properties
Cloning lines
Deleting lines
Boxes
Creating boxes
Modifying boxes
Box styles
Getting box properties
Cloning boxes
Deleting boxes
Realtime behavior
Limitations
Total number of objects
Future references with `xloc.bar_index`
Additional securities
Historical buffer and `max_bars_back`
Examples
Pivot Points Standard
Pivot Points High/Low
Linear Regression
Zig Zag
Non-standard charts data
Introduction
`ticker.heikinashi()`
`ticker.renko()`
`ticker.linebreak()`
`ticker.kagi()`
`ticker.pointfigure()`
Plots
Introduction
`plot()` parameters
Plotting conditionally
Value control
Color control
Levels
Offsets
Plot count limit
Scale
Merging two indicators
Repainting
Introduction
For script users
For Pine Script™ programmers
Historical vs realtime calculations
Fluid data values
Repainting `request.security()` calls
Using `request.security()` at lower timeframes
Future leak with `request.security()`
`varip`
Bar state built-ins
`timenow`
Strategies
Plotting in the past
Dataset variations
Starting points
Revision of historical data
Sessions
Introduction
Session strings
Session string specifications
Using session strings
Session states
Using sessions with `request.security()`
Strategies
A simple strategy example
Applying a strategy to a chart
Backtesting and forwardtesting
Broker emulator
Order placement commands
Closing market position
OCA groups
Risk management
Currency
Margin
Tables
Introduction
Creating tables
Placing a single value in a fixed position
Coloring the chart’s background
Creating a display panel
Displaying a heatmap
Tips
Text and shapes
Introduction
`plotchar()`
`plotshape()`
`plotarrow()`
Labels
Creating and modifying labels
Positioning labels
Reading label properties
Cloning labels
Deleting labels
Realtime behavior
Time
Introduction
Four references
Time built-ins
Time zones
Time variables
`time` and `time_close`
`time_tradingday`
`timenow`
Calendar dates and times
`syminfo.timezone()`
Time functions
`time()` and `time_close()`
Calendar dates and times
`timestamp()`
Formatting dates and time
Timeframes
Introduction
Timeframe string specifications
Comparing timeframes

Methods Alerts
© Copyright 2023, TradingView.
Options v: v5

You might also like