0% found this document useful (0 votes)
16K views160 pages

Pine Script v5 User Manual (350-509)

The document is the Pine Script v5 User Manual. It discusses: 1) How Pine Script handles exiting market positions by default using FIFO rules, exiting the oldest open position first unless a specific entry is specified. 2) An example that demonstrates exiting specific positions by specifying the "from_entry" parameter in the strategy.exit() call. 3) How One-Cancels-All (OCA) groups allow orders to be fully or partially cancelled depending on the oca_type, allowing strategies to cancel or reduce orders upon execution of new orders.

Uploaded by

tavo2014
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)
16K views160 pages

Pine Script v5 User Manual (350-509)

The document is the Pine Script v5 User Manual. It discusses: 1) How Pine Script handles exiting market positions by default using FIFO rules, exiting the oldest open position first unless a specific entry is specified. 2) An example that demonstrates exiting specific positions by specifying the "from_entry" parameter in the strategy.exit() call. 3) How One-Cancels-All (OCA) groups allow orders to be fully or partially cancelled depending on the oca_type, allowing strategies to cancel or reduce orders upon execution of new orders.

Uploaded by

tavo2014
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/ 160

Pine Script™ v5 User Manual

1 //@version=5
2 strategy("Buy low, sell high", overlay = true, default_qty_type = strategy.cash,␣
,→default_qty_value = 5000)

4 int length = input.int(20, "Length")


5

6 float highest = ta.highest(length)


7 float lowest = ta.lowest(length)
8

9 switch
10 low == lowest => strategy.entry("Buy", strategy.long)
11 high == highest => strategy.entry("Sell", strategy.short)

4.17.8 Closing a market position

Although it is possible to simulate an exit from a specific entry order shown in the List of Trades tab of the Strategy Tester
module, all orders are linked according to FIFO (first in, first out) rules. If the user does not specify the from_entry
parameter of a strategy.exit() call, the strategy will exit the open market position starting from the first entry order that
opened it.
The following example simulates two orders sequentially: “Buy1” at the market price for the last 100 bars and “Buy2”
once the position size matches the size of “Buy1”. The strategy only places an exit order when the positionSize is
15 units. The script does not supply a from_entry argument to the strategy.exit() command, so the strategy places
exit orders for all open positions each time it calls the function, starting with the first. It plots the positionSize in a
separate pane for visual reference:

340 Chapter 4. Concepts


Pine Script™ v5 User Manual

1 //@version=5
2 strategy("Exit Demo", pyramiding = 2)
3

4 float positionSize = strategy.position_size


5

6 if positionSize == 0 and last_bar_index - bar_index <= 100


7 strategy.entry("Buy1", strategy.long, 5)
8 else if positionSize == 5
9 strategy.entry("Buy2", strategy.long, 10)
10 else if positionSize == 15
11 strategy.exit("bracket", loss = 10, profit = 10)
12

13 plot(positionSize == 0 ? na : positionSize, "Position Size", color.lime, 4, plot.


,→style_histogram)

Note that:
• We included pyramiding = 2 in our script’s declaration statement to allow it to simulate two consecutive
orders in the same direction.
Suppose we wanted to exit “Buy2” before “Buy1”. Let’s see what happens if we instruct the strategy to close “Buy2”
before “Buy1” when it fills both orders:

4.17. Strategies 341


Pine Script™ v5 User Manual

1 //@version=5
2 strategy("Exit Demo", pyramiding = 2)
3

4 float positionSize = strategy.position_size


5

6 if positionSize == 0 and last_bar_index - bar_index <= 100


7 strategy.entry("Buy1", strategy.long, 5)
8 else if positionSize == 5
9 strategy.entry("Buy2", strategy.long, 10)
10 else if positionSize == 15
11 strategy.close("Buy2")
12 strategy.exit("bracket", "Buy1", loss = 10, profit = 10)
13

14 plot(positionSize == 0 ? na : positionSize, "Position Size", color.lime, 4, plot.


,→style_histogram)

As we can see in the Strategy Tester’s “List of Trades” tab, rather than closing the “Buy2” position with strat-
egy.close(), it closes the quantity of “Buy1” first, which is half the quantity of the close order, then closes half of the
“Buy2” position, as the broker emulator follows FIFO rules by default. Users can change this behavior by specifying
close_entries_rule = "ANY" in the strategy() function.

4.17.9 OCA groups

One-Cancels-All (OCA) groups allow a strategy to fully or partially cancel other orders upon the execution of order
placement commands, including strategy.entry() and strategy.order(), with the same oca_name, depending on the
oca_type that the user provides in the function call.

`strategy.oca.cancel`

The strategy.oca.cancel OCA type cancels all orders with the same oca_name upon the fill or partial fill of an order
from the group.
For example, the following strategy executes orders upon ma1 crossing ma2. When the strategy.position_size is 0, it
places long and short stop orders on the high and low of the bar. Otherwise, it calls strategy.close_all() to close all
open positions with a market order. Depending on the price action, the strategy may fill both orders before issuing a
close order. Additionally, if the broker emulator’s intrabar assumption supports it, both orders may fill on the same bar.
The strategy.close_all() command does nothing in such cases, as the script cannot invoke the action until after already
executing both orders:

342 Chapter 4. Concepts


Pine Script™ v5 User Manual

1 //@version=5
2 strategy("OCA Cancel Demo", overlay=true)
3

4 float ma1 = ta.sma(close, 5)


5 float ma2 = ta.sma(close, 9)
6

7 if ta.cross(ma1, ma2)
8 if strategy.position_size == 0
9 strategy.order("Long", strategy.long, stop = high)
10 strategy.order("Short", strategy.short, stop = low)
11 else
12 strategy.close_all()
13

14 plot(ma1, "Fast MA", color.aqua)


15 plot(ma2, "Slow MA", color.orange)

To eliminate scenarios where the strategy fills long and short orders before a close order, we can instruct it to cancel one
order after it executes the other. In this example, we’ve set the oca_name for both strategy.order() commands to “Entry”
and their oca_type to strategy.oca.cancel:

1 //@version=5
2 strategy("OCA Cancel Demo", overlay=true)
3

4 float ma1 = ta.sma(close, 5)


5 float ma2 = ta.sma(close, 9)
6

7 if ta.cross(ma1, ma2)
8 if strategy.position_size == 0
9 strategy.order("Long", strategy.long, stop = high, oca_name = "Entry", oca_
,→type = strategy.oca.cancel)

(continues on next page)

4.17. Strategies 343


Pine Script™ v5 User Manual

(continued from previous page)


10 strategy.order("Short", strategy.short, stop = low, oca_name = "Entry", oca_
,→type = strategy.oca.cancel)

11 else
12 strategy.close_all()
13

14 plot(ma1, "Fast MA", color.aqua)


15 plot(ma2, "Slow MA", color.orange)

`strategy.oca.reduce`

The strategy.oca.reduce OCA type does not cancel orders. Instead, it reduces the size of orders with the same oca_name
upon each new fill by the number of closed contracts/shares/lots/units, which is particularly useful for exit strategies.
The following example demonstrates an attempt at a long-only exit strategy that generates a stop-loss order and two take-
profit orders for each new entry. Upon the crossover of two moving averages, it simulates a “Long” entry order using
strategy.entry() with a qty of 6 units, then simulates stop/limit orders for 6, 3, and 3 units using strategy.order() at the
stop, limit1, and limit2 prices respectively.
After adding the strategy to our chart, we see it doesn’t work as intended. The issue with this script is that strategy.order()
doesn’t belong to an OCA group by default, unlike strategy.exit(). Since we have not explicitly assigned the orders to an
OCA group, the strategy does not cancel or reduce them when it fills one, meaning it’s possible to trade a greater quantity
than the open position and reverse the direction:

1 //@version=5
2 strategy("Multiple TP Demo", overlay = true)
3

4 var float stop = na


5 var float limit1 = na
6 var float limit2 = na
7

8 bool longCondition = ta.crossover(ta.sma(close, 5), ta.sma(close, 9))


9 if longCondition and strategy.position_size == 0
10 stop := close * 0.99
11 limit1 := close * 1.01
12 limit2 := close * 1.02
(continues on next page)

344 Chapter 4. Concepts


Pine Script™ v5 User Manual

(continued from previous page)


13 strategy.entry("Long", strategy.long, 6)
14 strategy.order("Stop", strategy.short, stop = stop, qty = 6)
15 strategy.order("Limit 1", strategy.short, limit = limit1, qty = 3)
16 strategy.order("Limit 2", strategy.short, limit = limit2, qty = 3)
17

18 bool showPlot = strategy.position_size != 0


19 plot(showPlot ? stop : na, "Stop", color.red, style = plot.style_linebr)
20 plot(showPlot ? limit1 : na, "Limit 1", color.green, style = plot.style_linebr)
21 plot(showPlot ? limit2 : na, "Limit 2", color.green, style = plot.style_linebr)

For our strategy to work as intended, we must instruct it to reduce the number of units for the other stop-loss/take-profit
orders so that they do not exceed the size of the remaining open position.
In the example below, we’ve set the oca_name for each order in our exit strategy to “Bracket” and the oca_type to
strategy.oca.reduce. These settings tell the strategy to reduce the qty values of orders in the “Bracket” group by the qty
filled when it executes one of them, preventing it from trading an excessive number of units and causing a reversal:

1 //@version=5
2 strategy("Multiple TP Demo", overlay = true)
3

4 var float stop = na


5 var float limit1 = na
6 var float limit2 = na
7

8 bool longCondition = ta.crossover(ta.sma(close, 5), ta.sma(close, 9))


9 if longCondition and strategy.position_size == 0
10 stop := close * 0.99
11 limit1 := close * 1.01
12 limit2 := close * 1.02
13 strategy.entry("Long", strategy.long, 6)
14 strategy.order("Stop", strategy.short, stop = stop, qty = 6, oca_name = "Bracket
,→", oca_type = strategy.oca.reduce)

15 strategy.order("Limit 1", strategy.short, limit = limit1, qty = 3, oca_name =


,→"Bracket", oca_type = strategy.oca.reduce)

16 strategy.order("Limit 2", strategy.short, limit = limit2, qty = 6, oca_name =


,→"Bracket", oca_type = strategy.oca.reduce)

17

18 bool showPlot = strategy.position_size != 0


19 plot(showPlot ? stop : na, "Stop", color.red, style = plot.style_linebr)
20 plot(showPlot ? limit1 : na, "Limit 1", color.green, style = plot.style_linebr)
21 plot(showPlot ? limit2 : na, "Limit 2", color.green, style = plot.style_linebr)

4.17. Strategies 345


Pine Script™ v5 User Manual

Note that:
• We changed the qty of the “Limit 2” order to 6 instead of 3 because the strategy will reduce its value by 3
when it fills the “Limit 1” order. Keeping the qty value of 3 would cause it to drop to 0 and never fill after
filling the first limit order.

`strategy.oca.none`

The strategy.oca.none OCA type specifies that an order executes independently of any OCA group. This value is the
default oca_type for strategy.order() and strategy.entry() order placement commands.

Note: If two order placement commands have the same oca_name but different oca_type values, the strategy
considers them to be from two distinct groups. i.e., OCA groups cannot combine strategy.oca.cancel, strategy.oca.reduce,
and strategy.oca.none OCA types.

4.17.10 Currency

Pine Script™ strategies can use different base currencies than the instruments they calculate on. Users can specify the
simulated account’s base currency by including a currency.* variable as the currency argument in the strategy()
function, which will change the script’s strategy.account_currency value. The default currency value for strategies is
currency.NONE, meaning that the script uses the base currency of the instrument on the chart.
When a strategy script uses a specified base currency, it multiplies the simulated profits by the FX_IDC conversion rate
from the previous trading day. For example, the strategy below places an entry order for a standard lot (100,000 units)
with a profit target and stop-loss of 1 point on each of the last 500 chart bars, then plots the net profit alongside the inverted
daily close of the symbol in a separate pane. We have set the base currency to currency.EUR. When we add this script
to FX_IDC:EURUSD, the two plots align, confirming the strategy uses the previous day’s rate from this symbol for its
calculations:

1 //@version=5
2 strategy("Currency Test", currency = currency.EUR)
3

4 if last_bar_index - bar_index < 500


5 strategy.entry("LE", strategy.long, 100000)
6 strategy.exit("LX", "LE", profit = 1, loss = 1)
7 plot(math.abs(ta.change(strategy.netprofit)), "1 Point profit", color = color.fuchsia,
(continues on next page)

346 Chapter 4. Concepts


Pine Script™ v5 User Manual

(continued from previous page)


,→ linewidth = 4)
8 plot(request.security(syminfo.tickerid, "D", 1 / close)[1], "Previous day's inverted␣
,→price", color = color.lime)

Note that:
• When trading on timeframes higher than daily, the strategy will use the closing price from one trading day
before the bar closes for cross-rate calculation on historical bars. For example, on a weekly timeframe, it will
base the cross-rate on the previous Thursday’s closing value, though the strategy will still use the daily closing
rate for real-time bars.

4.17.11 Altering calculation behavior

Strategies execute on all historical bars available from a chart, then automatically continue their calculations in real-time
as new data is available. By default, strategy scripts only calculate once per confirmed bar. We can alter this behavior by
changing the parameters of the strategy() function or clicking the checkboxes in the “Recalculate” section of the script’s
“Properties” tab.

`calc_on_every_tick`

calc_on_every_tick is an optional setting that controls the calculation behavior on real-time data. When this
parameter is enabled, the script will recalculate its values on each new price tick. By default, its value is false, meaning
the script only executes calculations after a bar is confirmed.
Enabling this calculation behavior may be particularly useful when forward testing since it facilitates granular, real-time
strategy simulation. However, it’s important to note that this behavior introduces a data difference between real-time and
historical simulations, as historical bars do not contain tick information. Users should exercise caution with this setting,
as the data difference may cause a strategy to repaint its history.
The following script will simulate a new order each time that close reaches the highest or lowest value over
the input length. Since calc_on_every_tick is enabled in the strategy declaration, the script will simulate new
orders on each new real-time price tick after compilation:

1 //@version=5
2 strategy("Donchian Channel Break", overlay = true, calc_on_every_tick = true,␣
,→pyramiding = 20)

4 int length = input.int(15, "Length")


5

6 float highest = ta.highest(close, length)


7 float lowest = ta.lowest(close, length)
8

9 if close == highest
10 strategy.entry("Buy", strategy.long)
11 if close == lowest
12 strategy.entry("Sell", strategy.short)
13

14 //@variable The starting time for real-time bars.


15 var realTimeStart = timenow
16

17 // Color the background of real-time bars.


18 bgcolor(time_close >= realTimeStart ? color.new(color.orange, 80) : na)
19

(continues on next page)

4.17. Strategies 347


Pine Script™ v5 User Manual

(continued from previous page)


20 plot(highest, "Highest", color = color.lime)
21 plot(lowest, "Lowest", color = color.red)

Note that:
• The script uses a pyramiding value of 20 in its declaration, which allows the strategy to simulate a maxi-
mum of 20 trades in the same direction.
• To visually demarcate what bars are processed as real-time bars by the strategy, the script colors the back-
ground for all bars since the timenow when it was last compiled.
After applying the script to the chart and letting it calculate on some real-time bars, we may see an output like the
following:

The script placed “Buy” orders on each new real-time tick the condition was valid on, resulting in multiple orders per
bar. However, it may surprise users unfamiliar with this behavior to see the strategy’s outputs change after recompiling
the script, as the bars that it previously executed real-time calculations on are now historical bars, which do not hold tick
information:

348 Chapter 4. Concepts


Pine Script™ v5 User Manual

`calc_on_order_fills`

The optional calc_on_order_fills setting enables the recalculation of a strategy immediately after simulating an
order fill, which allows the script to use more granular prices and place additional orders without waiting for a bar to be
confirmed.
Enabling this setting can provide the script with additional data that would otherwise not be available until after a bar
closes, such as the current average price of a simulated position on an unconfirmed bar.
The example below shows a simple strategy declared with calc_on_order_fills enabled that simulates a “Buy”
order when the strategy.position_size is 0. The script uses the strategy.position_avg_price to calculate a stopLoss
and takeProfit and simulates “Exit” orders when the price crosses them, regardless of whether the bar is confirmed.
As a result, as soon as an exit is triggered, the strategy recalculates and places a new entry order because the strat-
egy.position_size is once again equal to 0. The strategy places the order once the exit happens and executes it on the next
tick after the exit, which will be one of the bar’s OHLC values, depending on the emulated intrabar movement:

4.17. Strategies 349


Pine Script™ v5 User Manual

1 //@version=5
2 strategy("Intrabar exit", overlay = true, calc_on_order_fills = true)
3

4 float stopSize = input.float(5.0, "SL %", minval = 0.0) / 100.0


5 float profitSize = input.float(5.0, "TP %", minval = 0.0) / 100.0
6

7 if strategy.position_size == 0.0
8 strategy.entry("Buy", strategy.long)
9

10 float stopLoss = strategy.position_avg_price * (1.0 - stopSize)


11 float takeProfit = strategy.position_avg_price * (1.0 + profitSize)
12

13 strategy.exit("Exit", stop = stopLoss, limit = takeProfit)

Note that:
• With calc_on_order_fills turned off, the same strategy will only ever enter one bar after it triggers
an exit order. First, the mid-bar exit will happen, but no entry order. Then, the strategy will simulate an entry
order once the bar closes, which it will fill on the next tick after that, i.e., the open of the next bar.
It’s important to note that enabling calc_on_order_fills may produce unrealistic strategy results, as the broker
emulator may assume order prices that are not possible when trading in real-time. Users must exercise caution with this
setting and carefully consider the logic in their scripts.
The following example simulates a “Buy” order after each new order fill and bar confirmation over a 25-bar window from
the last_bar_index when the script loaded on the chart. With the setting enabled, the strategy simulates four entries per
bar since the emulator considers each bar to have four ticks (open, high, low, close), which is unrealistic behavior, as it’s
not typically possible for an order to fill at the exact high or low of a bar:

1 //@version=5
2 strategy("buy on every fill", overlay = true, calc_on_order_fills = true, pyramiding␣
,→= 100)

4 if last_bar_index - bar_index <= 25


5 strategy.entry("Buy", strategy.long)

350 Chapter 4. Concepts


Pine Script™ v5 User Manual

`process_orders_on_close`

The default strategy behavior simulates orders at the close of each bar, meaning that the earliest opportunity to fill the
orders and execute strategy calculations and alerts is upon the opening of the following bar. Traders can change this
behavior to process a strategy using the closing value of each bar by enabling the process_orders_on_close
setting.
This behavior is most useful when backtesting manual strategies in which traders exit positions before a bar closes or
in scenarios where algorithmic traders in non-24x7 markets set up after-hours trading capability so that alerts sent after
close still have hope of filling before the following day.
Note that:
• It’s crucial to be aware that using strategies with process_orders_on_close in a live trading environ-
ment may lead to a repainting strategy, as alerts on the close of a bar still occur when the market closes, and
orders may not fill until the next market open.

4.17.12 Simulating trading costs

For a strategy performance report to contain relevant, meaningful data, traders should strive to account for potential real-
world costs in their strategy results. Neglecting to do so may give traders an unrealistic view of strategy performance and
undermine the credibility of test results. Without modeling the potential costs associated with their trades, traders may
overestimate a strategy’s historical profitability, potentially leading to suboptimal decisions in live trading. Pine Script™
strategies include inputs and parameters for simulating trading costs in performance results.

Commission

Commission refers to the fee a broker/exchange charges when executing trades. Depending on the broker/exchange,
some may charge a flat fee per trade or contract/share/lot/unit, and others may charge a percentage of the total transaction
value. Users can set the commission properties of their strategies by including commission_type and commis-
sion_value arguments in the strategy() function or by setting the “Commission” inputs in the “Properties” tab of the
strategy settings.
The following script is a simple strategy that simulates a “Long” position of 2% of equity when close equals the
highest value over the length, and closes the trade when it equals the lowest value:

4.17. Strategies 351


Pine Script™ v5 User Manual

1 //@version=5
2 strategy("Commission Demo", overlay=true, default_qty_value = 2, default_qty_type =␣
,→strategy.percent_of_equity)

4 length = input.int(10, "Length")


5

6 float highest = ta.highest(close, length)


7 float lowest = ta.lowest(close, length)
8

9 switch close
10 highest => strategy.entry("Long", strategy.long)
11 lowest => strategy.close("Long")
12

13 plot(highest, color = color.new(color.lime, 50))


14 plot(lowest, color = color.new(color.red, 50))

Upon inspecting the results in the Strategy Tester, we see that the strategy had a positive equity growth of 17.61% over
the testing range. However, the backtest results do not account for fees the broker/exchange may charge. Let’s see what
happens to these results when we include a small commission on every trade in the strategy simulation. In this example,
we’ve included commission_type = strategy.commission.percent and commission_value = 1
in the strategy() declaration, meaning it will simulate a commission of 1% on all executed orders:

352 Chapter 4. Concepts


Pine Script™ v5 User Manual

1 //@version=5
2 strategy(
3 "Commission Demo", overlay=true, default_qty_value = 2, default_qty_type =␣
,→strategy.percent_of_equity,

4 commission_type = strategy.commission.percent, commission_value = 1


5 )
6

7 length = input.int(10, "Length")


8

9 float highest = ta.highest(close, length)


10 float lowest = ta.lowest(close, length)
11

12 switch close
13 highest => strategy.entry("Long", strategy.long)
14 lowest => strategy.close("Long")
15

16 plot(highest, color = color.new(color.lime, 50))


17 plot(lowest, color = color.new(color.red, 50))

As we can see in the example above, after applying a 1% commission to the backtest, the strategy simulated a significantly
reduced net profit of only 1.42% and a more volatile equity curve with an elevated max drawdown, highlighting the impact
commission simulation can have on a strategy’s test results.

4.17. Strategies 353


Pine Script™ v5 User Manual

Slippage and unfilled limits

In real-life trading, a broker/exchange may fill orders at slightly different prices than a trader intended due to volatility,
liquidity, order size, and other market factors, which can profoundly impact a strategy’s performance. The disparity
between expected prices and the actual prices at which the broker/exchange executes trades is what we refer to as slippage.
Slippage is dynamic and unpredictable, making it impossible to simulate precisely. However, factoring in a small amount
of slippage on each trade during a backtest or forward test may help the results better align with reality. Users can model
slippage in their strategy results, sized as a fixed number of ticks, by including a slippage argument in the strategy()
declaration or by setting the “Slippage” input in the “Properties” tab of the strategy settings.
The following example demonstrates how slippage simulation affects the fill prices of market orders in a strategy test.
The script below places a “Buy” market order of 2% equity when the market price is above an EMA while the EMA
is rising and closes the position when the price dips below the EMA while it’s falling. We’ve included slippage =
20 in the strategy() function, which declares that the price of each simulated order will slip 20 ticks in the direction
of the trade. The script uses strategy.opentrades.entry_bar_index() and strategy.closedtrades.exit_bar_index() to get the
entryIndex and exitIndex, which it utilizes to obtain the fillPrice of the order. When the bar index is at the
entryIndex, the fillPrice is the first strategy.opentrades.entry_price() value. At the exitIndex, fillPrice
is the strategy.closedtrades.exit_price() value from the last closed trade. The script plots the expected fill price along with
the simulated fill price after slippage to visually compare the difference:

1 //@version=5
2 strategy(
3 "Slippage Demo", overlay = true, slippage = 20,
4 default_qty_value = 2, default_qty_type = strategy.percent_of_equity
5 )
6

7 int length = input.int(5, "Length")


8

9 //@variable Exponential moving average with an input `length`.


10 float ma = ta.ema(close, length)
11

12 //@variable Returns `true` when `ma` has increased and `close` is greater than it,␣
,→`false` otherwise.

13 bool longCondition = close > ma and ma > ma[1]


(continues on next page)

354 Chapter 4. Concepts


Pine Script™ v5 User Manual

(continued from previous page)


14 //@variable Returns `true` when `ma` has decreased and `close` is less than it,␣
,→`false` otherwise.

15 bool shortCondition = close < ma and ma < ma[1]


16

17 // Enter a long market position on `longCondition`, close the position on␣


,→`shortCondition`.

18 if longCondition
19 strategy.entry("Buy", strategy.long)
20 if shortCondition
21 strategy.close("Buy")
22

23 //@variable The `bar_index` of the position's entry order fill.


24 int entryIndex = strategy.opentrades.entry_bar_index(0)
25 //@variable The `bar_index` of the position's close order fill.
26 int exitIndex = strategy.closedtrades.exit_bar_index(strategy.closedtrades - 1)
27

28 //@variable The fill price simulated by the strategy.


29 float fillPrice = switch bar_index
30 entryIndex => strategy.opentrades.entry_price(0)
31 exitIndex => strategy.closedtrades.exit_price(strategy.closedtrades - 1)
32

33 //@variable The expected fill price of the open market position.


34 float expectedPrice = fillPrice ? open : na
35

36 color expectedColor = na
37 color filledColor = na
38

39 if bar_index == entryIndex
40 expectedColor := color.green
41 filledColor := color.blue
42 else if bar_index == exitIndex
43 expectedColor := color.red
44 filledColor := color.fuchsia
45

46 plot(ma, color = color.new(color.orange, 50))


47

48 plotchar(fillPrice ? open : na, "Expected fill price", "—", location.absolute,␣


,→expectedColor)

49 plotchar(fillPrice, "Fill price after slippage", "—", location.absolute, filledColor)

Note that:
• Since the strategy applies constant slippage to all order fills, some orders can fill outside the candle range
in the simulation. Thus users should exercise caution with this setting, as excessive simulated slippage can
produce unrealistically worse testing results.
Some traders may assume that they can avoid the adverse effects of slippage by using limit orders, as unlike market orders,
they cannot execute at a worse price than the specified value. However, depending on the state of the real-life market,
even if the market price reaches an order price, there’s a chance that a limit order may not fill, as limit orders can only fill
if a security has sufficient liquidity and price action around the value. To account for the possibility of unfilled orders in a
backtest, users can specify the backtest_fill_limits_assumption value in the declaration statement or use
the “Verify price for limit orders” input in the “Properties” tab to instruct the strategy to fill limit orders only after prices
move a defined number of ticks past order prices.
The following example places a limit order of 2% equity at a bar’s hlcc4 when the high is the highest value over the
past length bars and there are no pending entries. The strategy closes the market position and cancels all orders when
the low is the lowest value. Each time the strategy triggers an order, it draws a horizontal line at the limitPrice,

4.17. Strategies 355


Pine Script™ v5 User Manual

which it updates on each bar until closing the position or canceling the order:

1 //@version=5
2 strategy(
3 "Verify price for limits example", overlay = true,
4 default_qty_type = strategy.percent_of_equity, default_qty_value = 2
5 )
6

7 int length = input.int(25, title = "Length")


8

9 //@variable Draws a line at the limit price of the most recent entry order.
10 var line limitLine = na
11

12 // Highest high and lowest low


13 highest = ta.highest(length)
14 lowest = ta.lowest(length)
15

16 // Place an entry order and draw a new line when the the `high` equals the `highest`␣
,→value and `limitLine` is `na`.

17 if high == highest and na(limitLine)


18 float limitPrice = hlcc4
19 strategy.entry("Long", strategy.long, limit = limitPrice)
20 limitLine := line.new(bar_index, limitPrice, bar_index + 1, limitPrice)
21

22 // Close the open market position, cancel orders, and set `limitLine` to `na` when␣
,→the `low` equals the `lowest` value.

23 if low == lowest
24 strategy.cancel_all()
25 limitLine := na
26 strategy.close_all()
27

28 // Update the `x2` value of `limitLine` if it isn't `na`.


29 if not na(limitLine)
30 limitLine.set_x2(bar_index + 1)
31

32 plot(highest, "Highest High", color = color.new(color.green, 50))


(continues on next page)

356 Chapter 4. Concepts


Pine Script™ v5 User Manual

(continued from previous page)


33 plot(lowest, "Lowest Low", color = color.new(color.red, 50))

By default, the script assumes that all limit orders are guaranteed to fill. However, this is often not the case in real-life
trading. Let’s add price verification to our limit orders to account for potentially unfilled ones. In this example, we’ve
included backtest_fill_limits_assumption = 3 in the strategy() function call. As we can see, using limit
verification omits some simulated order fills and changes the times of others since the entry orders can now only fill after
the price penetrates the limit price by three ticks:

Note: It’s important to notice that although the limit verification changed the times of some order fills, the strategy
simulated them at the same prices. This “time-warping” effect is a compromise that preserves the prices of verified limit
orders, but it can cause the strategy to simulate their fills at times that wouldn’t necessarily be possible in the real world.
Users should exercise caution with this setting and understand its limitations when analyzing strategy results.

4.17.13 Risk management

Designing a strategy that performs well, let alone one that does so in a broad class of markets, is a challenging task.
Most are designed for specific market patterns/conditions and may produce uncontrollable losses when applied to other
data. Therefore, a strategy’s risk management qualities can be critical to its performance. Users can set risk management
criteria in their strategy scripts using the special commands with the strategy.risk prefix.
Strategies can incorporate any number of risk management criteria in any combination. All risk management commands
execute on every tick and order execution event, regardless of any changes to the strategy’s calculation behavior. There is
no way to disable any of these commands at a script’s runtime. Irrespective of the risk rule’s location, it will always apply
to the strategy unless the user removes the call from the code.
strategy.risk.allow_entry_in()
This command overrides the market direction allowed for strategy.entry() commands. When a user specifies the
trade direction with this function (e.g., strategy.direction.long), the strategy will only enter trades in that direction.
However, it’s important to note that if a script calls an entry command in the opposite direction while there’s an
open market position, the strategy will simulate a market order to exit the position.

4.17. Strategies 357


Pine Script™ v5 User Manual

strategy.risk.max_cons_loss_days()
This command cancels all pending orders, closes the open market position, and stops all additional trade actions
after the strategy simulates a defined number of trading days with consecutive losses.
strategy.risk.max_drawdown()
This command cancels all pending orders, closes the open market position, and stops all additional trade actions
after the strategy’s drawdown reaches the amount specified in the function call.
strategy.risk.max_intraday_filled_orders()
This command specifies the maximum number of filled orders per trading day (or per chart bar if the timeframe is
higher than daily). Once the strategy executes the maximum number of orders for the day, it cancels all pending
orders, closes the open market position, and halts trading activity until the end of the current session.
strategy.risk.max_intraday_loss()
This command controls the maximum loss the strategy will tolerate per trading day (or per chart bar if the timeframe
is higher than daily). When the strategy’s losses reach this threshold, it will cancel all pending orders, close the
open market position, and stop all trading activity until the end of the current session.
strategy.risk.max_position_size()
This command specifies the maximum possible position size when using strategy.entry() commands. If the quantity
of an entry command results in a market position that exceeds this threshold, the strategy will reduce the order
quantity so that the resulting position does not exceed the limitation.

4.17.14 Margin

Margin is the minimum percentage of a market position a trader must hold in their account as collateral to receive and
sustain a loan from their broker to achieve their desired leverage. The margin_long and margin_short parameters
of the strategy() declaration and the “Margin for long/short positions” inputs in the “Properties” tab of the script settings
allow strategies to specify margin percentages for long and short positions. For example, if a trader sets the margin for
long positions to 25%, they must have enough funds to cover 25% of an open long position. This margin percentage also
means the trader can potentially spend up to 400% of their equity on their trades.
If a strategy’s simulated funds cannot cover the losses from a margin trade, the broker emulator triggers a margin call,
which forcibly liquidates all or part of the position. The exact number of contracts/shares/lots/units that the emulator
liquidates is four times what is required to cover a loss to prevent constant margin calls on subsequent bars. The emulator
calculates the amount using the following algorithm:
1. Calculate the amount of capital spent on the position: Money Spent = Quantity * Entry Price
2. Calculate the Market Value of Security (MVS): MVS = Position Size * Current Price
3. Calculate the Open Profit as the difference between MVS and Money Spent. If the position is short, we multiply
this by -1.
4. Calculate the strategy’s equity value: Equity = Initial Capital + Net Profit + Open Profit
5. Calculate the margin ratio: Margin Ratio = Margin Percent / 100
6. Calculate the margin value, which is the cash required to cover the trader’s portion of the position: Margin =
MVS * Margin Ratio
7. Calculate the available funds: Available Funds = Equity - Margin
8. Calculate the total amount of money the trader has lost: Loss = Available Funds / Margin Ratio
9. Calculate how many contracts/shares/lots/units the trader would need to liquidate to cover the loss. We truncate
this value to the same decimal precision as the minimum position size for the current symbol: Cover Amount
= TRUNCATE(Loss / Current Price).
10. Calculate how many units the broker will liquidate to cover the loss: Margin Call = Cover Amount * 4

358 Chapter 4. Concepts


Pine Script™ v5 User Manual

To examine this calculation in detail, let’s add the built-in Supertrend Strategy to the NASDAQ:TSLA chart on the 1D
timeframe and set the “Order size” to 300% of equity and the “Margin for long positions” to 25% in the “Properties” tab
of the strategy settings:

The first entry happened at the bar’s opening price on 16 Sep 2010. The strategy bought 682,438 shares (Position size)
at 4.43 USD (Entry price). Then, on 23 Sep 2010, when the price dipped to 3.9 (Current price), the emulator forcibly
liquidated 111,052 shares via margin call.

Money spent: 682438 * 4.43 = 3023200.34


MVS: 682438 * 3.9 = 2661508.2
Open Profit: −361692.14
Equity: 1000000 + 0 − 361692.14 = 638307.86
Margin Ratio: 25 / 100 = 0.25
Margin: 2661508.2 * 0.25 = 665377.05
Available Funds: 638307.86 - 665377.05 = -27069.19
Money Lost: -27069.19 / 0.25 = -108276.76
Cover Amount: TRUNCATE(-108276.76 / 3.9) = TRUNCATE(-27763.27) = -27763
Margin Call Size: -27763 * 4 = - 111052

4.17.15 Strategy Alerts

Regular Pine Script™ indicators have two different mechanisms to set up custom alert conditions: the alertcondition()
function, which tracks one specific condition per function call, and the alert() function, which tracks all its calls simulta-
neously, but provides greater flexibility in the number of calls, alert messages, etc.
Pine Script™ strategies do not work with alertcondition() calls, but they do support the generation of custom alerts via
the alert() function. Along with this, each function that creates orders also comes with its own built-in alert functionality
that does not require any additional code to implement. As such, any strategy that uses an order placement command can
issue alerts upon order execution. The precise mechanics of such built-in strategy alerts are described in the Order Fill
events section of the Alerts page in our User Manual.
When a strategy uses functions that create orders and the alert() function together, the alert creation dialogue provides
a choice between the conditions that it will trigger upon: it can trigger on alert() events, order fill events, or both.

4.17. Strategies 359


Pine Script™ v5 User Manual

For many trading strategies, the latency between a triggered condition and a live trade can be a critical performance factor.
By default, strategy scripts can only execute alert() function calls on the close of real-time bars, considering them to use
alert.freq_once_per_bar_close, regardless of the freq argument in the call. Users can change the alert frequency by also
including calc_on_every_tick = true in the strategy() call or selecting the “Recalculate on every tick” option
in the “Properties” tab of the strategy settings before creating the alert. However, depending on the script, this may also
adversely impact a strategy’s behavior, so exercise caution and be aware of the limitations when using this approach.
When sending alerts to a third party for strategy automation, we recommend using order fill alerts rather than the alert()
function since they don’t suffer the same limitations; alerts from order fill events execute immediately, unaffected by
a script’s calc_on_every_tick setting. Users can set the default message for order fill alerts via the @strat-
egy_alert_message compiler annotation. The text provided with this annotation will populate the “Message” field
for order fills in the alert creation dialogue.
The following script shows a simple example of a default order fill alert message. Above the strategy() declaration state-
ment, it uses @strategy_alert_message with placeholders for the trade action, position size, ticker, and fill price
values in the message text:

1 //@version=5
2 //@strategy_alert_message {{strategy.order.action}} {{strategy.position_size}} {
,→{ticker}} @ {{strategy.order.price}}

3 strategy("Alert Message Demo", overlay = true)


4 float fastMa = ta.sma(close, 5)
5 float slowMa = ta.sma(close, 10)
6

7 if ta.crossover(fastMa, slowMa)
8 strategy.entry("buy", strategy.long)
9

10 if ta.crossunder(fastMa, slowMa)
11 strategy.entry("sell", strategy.short)
12

13 plot(fastMa, "Fast MA", color.aqua)


14 plot(slowMa, "Slow MA", color.orange)

This script will populate the alert creation dialogue with its default message when the user selects its name from the
“Condition” dropdown tab:

360 Chapter 4. Concepts


Pine Script™ v5 User Manual

Upon the alert trigger, the strategy will populate the placeholders in the alert message with their corresponding values.
For example:

4.17. Strategies 361


Pine Script™ v5 User Manual

4.17.16 Notes on testing strategies

It’s common for traders to test and tune their strategies in historical and real-time market conditions because many believe
that analyzing the results may provide valuable insight into a strategy’s characteristics, potential weaknesses, and possibly
its future potential. However, traders should always be aware of the biases and limitations of simulated strategy results,
especially when using the results to support live trading decisions. This section outlines some caveats associated with
strategy validation and tuning and possible solutions to mitigate their effects.

Note: While testing strategies on existing data may give traders helpful information about a strategy’s qualities, it’s
important to note that neither the past nor the present guarantees the future. Financial markets can change rapidly and
unpredictably, which may cause a strategy to sustain uncontrollable losses. Additionally, simulated results may not fully
account for other real-world factors that can impact trading performance. Therefore, we recommend that traders thor-
oughly understand the limitations and risks when evaluating backtests and forward tests and consider them “parts of the
whole” in their validation processes rather than basing decisions solely on the results.

Backtesting and forward testing

Backtesting is a technique that traders use to evaluate the historical performance of a trading strategy or model by simu-
lating and analyzing its past results on historical market data; this technique assumes that analysis of a strategy’s results on
past data may provide insight into its strengths and weaknesses. When backtesting, many traders tweak the parameters
of a strategy in an attempt to optimize its results. Analysis and optimization of historical results may help traders to gain
a deeper understanding of a strategy. However, traders should always understand the risks and limitations when basing
their decisions on optimized backtest results.
Parallel to backtesting, prudent trading system development often also involves incorporating real-time analysis as a tool
for evaluating a trading system on a forward-looking basis. Forward testing aims to gauge the performance of a strategy
in real-time, real-world market conditions, where factors such as trading costs, slippage, and liquidity can meaningfully
affect its performance. Forward testing has the distinct advantage of not being affected by certain types of biases (e.g.,
lookahead bias or “future data leakage”) but carries the disadvantage of being limited in the quantity of data to test.
Therefore, it’s not typically a standalone solution for strategy validation, but it can provide helpful insights into a strategy’s
performance in current market conditions.
Backtesting and forward testing are two sides of the same coin, as both approaches aim to validate the effectiveness of
a strategy and identify its strengths and weaknesses. By combining backtesting and forward testing, traders may be able
to compensate for some limitations and gain a clearer perspective on their strategy’s performance. However, it’s up to
traders to sanitize their strategies and evaluation processes to ensure that insights align with reality as closely as possible.

Lookahead bias

One typical issue in backtesting some strategies, namely ones that request alternate timeframe data, use repainting variables
such as timenow, or alter calculation behavior for intrabar order fills, is the leakage of future data into the past during
evaluation, which is known as lookahead bias. Not only is this bias a common cause of unrealistic strategy results since
the future is never actually knowable beforehand, but it is also one of the typical causes of strategy repainting. Traders
can often confirm this bias by forward testing their systems, as lookahead bias does not apply to real-time data where no
known data exists beyond the current bar. Users can eliminate this bias in their strategies by ensuring that they don’t use
repainting variables that leak the future into the past, request.*() functions don’t include barmerge.lookahead_on
without offsetting the data series as described on this section of our page on repainting, and they use realistic calculation
behavior.

362 Chapter 4. Concepts


Pine Script™ v5 User Manual

Selection bias

Selection bias is a common issue that many traders experience when testing their strategies. It occurs when a trader only
analyzes results on specific instruments or timeframes while ignoring others. This bias can result in a distorted perspective
of the strategy’s robustness, which may impact trading decisions and performance optimizations. Traders can reduce the
effects of selection bias by evaluating their strategies on multiple, ideally diverse, symbols and timeframes, making it a
point not to ignore poor performance results in their analysis or cherry-pick testing ranges.

Overfitting

A common pitfall when optimizing a backtest is the potential for overfitting (“curve fitting”), which occurs when the
strategy is tailored for specific data and fails to generalize well on new, unseen data. One widely-used approach to help
reduce the potential for overfitting and promote better generalization is to split an instrument’s data into two or more parts
to test the strategy outside the sample used for optimization, otherwise known as “in-sample” (IS) and “out-of-sample”
(OOS) backtesting. In this approach, traders use the IS data for strategy optimization, while the OOS portion is used
for testing and evaluating IS-optimized performance on new data without further optimization. While this and other,
more robust approaches may provide a glimpse into how a strategy might fare after optimization, traders should exercise
caution, as the future is inherently unknowable. No trading strategy can guarantee future performance, regardless of the
data used for testing and optimization.

4.18 Tables

• Introduction
• Creating tables
• Tips

4.18.1 Introduction

Tables are objects that can be used to position information in specific and fixed locations in a script’s visual space. Contrary
to all other plots or objects drawn in Pine Script™, tables are not anchored to specific bars; they float in a script’s space,
whether in overlay or pane mode, in studies or strategies, independently of the chart bars being viewed or the zoom factor
used.
Tables contain cells arranged in columns and rows, much like a spreadsheet. They are created and populated in two
distincts steps:
1. A table’s structure and key attributes are defined using table.new(), which returns a table ID that acts like a pointer
to the table, just like label, line, or array IDs do. The table.new() call will create the table object but does not display
it.

4.18. Tables 363


Pine Script™ v5 User Manual

2. Once created, and for it to display, the table must be populated using one table.cell() call for each cell. Table cells
can contain text, or not. This second step is when the width and height of cells are defined.
Most attributes of a previously created table can be changed using table.set_*() setter functions. Attributes of
previously populated cells can be modified using table.cell_set_*() functions.
A table is positioned in an indicator’s space by anchoring it to one of nine references: the four corners or midpoints,
including the center. Tables are positioned by expanding the table from its anchor, so a table anchored to the posi-
tion.middle_right reference will be drawn by expanding up, down and left from that anchor.
Two modes are available to determine the width/height of table cells:
• A default automatic mode calculates the width/height of cells in a column/row using the widest/highest text in them.
• An explicit mode allows programmers to define the width/height of cells using a percentage of the indicator’s
available x/y space.
Displayed table contents always represent the last state of the table, as it was drawn on the script’s last execution, on the
dataset’s last bar. Contrary to values displayed in the Data Window or in indicator values, variable contents displayed in
tables will thus not change as a script user moves his cursor over specific chart bars. For this reason, it is strongly recom-
mended to always restrict execution of all table.*() calls to either the first or last bars of the dataset. Accordingly:
• Use the var keyword to declare tables.
• Enclose all other calls inside an if barstate.islast block.
Multiple tables can be used in one script, as long as they are each anchored to a different position. Each table
object is identified by its own ID. Limits on the quantity of cells in all tables are determined by the total number
of cells used in one script.

4.18.2 Creating tables

When creating a table using table.new(), three parameters are mandatory: the table’s position and its number of columns
and rows. Five other parameters are optional: the table’s background color, the color and width of the table’s outer
frame, and the color and width of the borders around all cells, excluding the outer frame. All table attributes except
its number of columns and rows can be modified using setter functions: table.set_position(), table.set_bgcolor(), ta-
ble.set_frame_color(), table.set_frame_width(), table.set_border_color() and table.set_border_width().
Tables can be deleted using table.delete(), and their content can be selectively removed using table.clear().
When populating cells using table.cell(), you must supply an argument for four mandatory parameters: the table id
the cell belongs to, its column and row index using indices that start at zero, and the text string the cell contains,
which can be null. Seven other parameters are optional: the width and height of the cell, the text’s attributes (color,
horizontal and vertical alignment, size), and the cell’s background color. All cell attributes can be modified us-
ing setter functions: table.cell_set_text(), table.cell_set_width(), table.cell_set_height(), table.cell_set_text_color(), ta-
ble.cell_set_text_halign(), table.cell_set_text_valign(), table.cell_set_text_size() and table.cell_set_bgcolor().
Keep in mind that each successive call to table.cell() redefines all the cell’s properties, deleting any properties set by
previous table.cell() calls on the same cell.

364 Chapter 4. Concepts


Pine Script™ v5 User Manual

Placing a single value in a fixed position

Let’s create our first table, which will place the value of ATR in the upper-right corner of the chart. We first create a
one-cell table, then populate that cell:

1 //@version=5
2 indicator("ATR", "", true)
3 // We use `var` to only initialize the table on the first bar.
4 var table atrDisplay = table.new(position.top_right, 1, 1)
5 // We call `ta.atr()` outside the `if` block so it executes on each bar.
6 myAtr = ta.atr(14)
7 if barstate.islast
8 // We only populate the table on the last bar.
9 table.cell(atrDisplay, 0, 0, str.tostring(myAtr))

Note that:
• We use the var keyword when creating the table with table.new().
• We populate the cell inside an if barstate.islast block using table.cell().
• When populating the cell, we do not specify the width or height. The width and height of our cell will thus
adjust automatically to the text it contains.
• We call ta.atr(14) prior to entry in our if block so that it evaluates on each bar. Had we used str.
tostring(ta.atr(14)) inside the if block, the function would not have evaluated correctly because it would
be called on the dataset’s last bar without having calculated the necessary values from the previous bars.
Let’s improve the usability and aesthethics of our script:

1 //@version=5
2 indicator("ATR", "", true)
3 atrPeriodInput = input.int(14, "ATR period", minval = 1, tooltip = "Using a period␣
,→of 1 yields True Range.")

5 var table atrDisplay = table.new(position.top_right, 1, 1, bgcolor = color.gray,␣


,→frame_width = 2, frame_color = color.black)

6 myAtr = ta.atr(atrPeriodInput)
7 if barstate.islast
8 table.cell(atrDisplay, 0, 0, str.tostring(myAtr, format.mintick), text_color =␣
,→color.white)

4.18. Tables 365


Pine Script™ v5 User Manual

Note that:
• We used table.new() to define a background color, a frame color and its width.
• When populating the cell with table.cell(), we set the text to display in white.
• We pass format.mintick as a second argument to the str.tostring() function to restrict the precision of ATR to the
chart’s tick precision.
• We now use an input to allow the script user to specify the period of ATR. The input also includes a tooltip, which
the user can see when he hovers over the “i” icon in the script’s “Settings/Inputs” tab.

Coloring the chart’s background

This example uses a one-cell table to color the chart’s background on the bull/bear state of RSI:

1 //@version=5
2 indicator("Chart background", "", true)
3 bullColorInput = input.color(color.new(color.green, 95), "Bull", inline = "1")
4 bearColorInput = input.color(color.new(color.red, 95), "Bear", inline = "1")
5 // ————— Function colors chart bg on RSI bull/bear state.
6 colorChartBg(bullColor, bearColor) =>
7 var table bgTable = table.new(position.middle_center, 1, 1)
8 float r = ta.rsi(close, 20)
9 color bgColor = r > 50 ? bullColor : r < 50 ? bearColor : na
10 if barstate.islast
11 table.cell(bgTable, 0, 0, width = 100, height = 100, bgcolor = bgColor)
12

13 colorChartBg(bullColorInput, bearColorInput)

Note that:
• We provide users with inputs allowing them to specify the bull/bear colors to use for the background, and send
those input colors as arguments to our colorChartBg() function.
• We create a new table only once, using the var keyword to declare the table.
• We use table.cell() on the last bar only, to specify the cell’s properties. We make the cell the width and height of
the indicator’s space, so it covers the whole chart.

366 Chapter 4. Concepts


Pine Script™ v5 User Manual

Creating a display panel

Tables are ideal to create sophisticated display panels. Not only do they make it possible for display panels to always be
visible in a constant position, they provide more flexible formatting because each cell’s properties are controlled separately:
background, text color, size and alignment, etc.
Here, we create a basic display panel showing a user-selected quantity of MAs values. We display their period in the
first column, then their value with a green/red/gray background that varies with price’s position with regards to each MA.
When price is above/below the MA, the cell’s background is colored with the bull/bear color. When the MA falls between
the current bar’s open and close, the cell’s background is of the neutral color.

1 //@version=5
2 indicator("Price vs MA", "", true)
3

4 var string GP1 = "Moving averages"


5 int masQtyInput = input.int(20, "Quantity", minval = 1, maxval = 40, group =␣
,→GP1, tooltip = "1-40")

6 int masStartInput = input.int(20, "Periods begin at", minval = 2, maxval = 200,␣


,→group = GP1, tooltip = "2-200")

7 int masStepInput = input.int(20, "Periods increase by", minval = 1, maxval =␣


,→100, group = GP1, tooltip = "1-100")

9 var string GP2 = "Display"


10 string tableYposInput = input.string("top", "Panel position", inline = "11", options␣
,→= ["top", "middle", "bottom"], group = GP2)

11 string tableXposInput = input.string("right", "", inline = "11", options = ["left",


,→"center", "right"], group = GP2)

12 color bullColorInput = input.color(color.new(color.green, 30), "Bull", inline = "12


,→", group = GP2)

13 color bearColorInput = input.color(color.new(color.red, 30), "Bear", inline = "12",␣


(continues on next page)

4.18. Tables 367


Pine Script™ v5 User Manual

(continued from previous page)


,→ group = GP2)
14 color neutColorInput = input.color(color.new(color.gray, 30), "Neutral", inline =
,→"12", group = GP2)

15

16 var table panel = table.new(tableYposInput + "_" + tableXposInput, 2, masQtyInput + 1)


17 if barstate.islast
18 // Table header.
19 table.cell(panel, 0, 0, "MA", bgcolor = neutColorInput)
20 table.cell(panel, 1, 0, "Value", bgcolor = neutColorInput)
21

22 int period = masStartInput


23 for i = 1 to masQtyInput
24 // ————— Call MAs on each bar.
25 float ma = ta.sma(close, period)
26 // ————— Only execute table code on last bar.
27 if barstate.islast
28 // Period in left column.
29 table.cell(panel, 0, i, str.tostring(period), bgcolor = neutColorInput)
30 // If MA is between the open and close, use neutral color. If close is lower/
,→higher than MA, use bull/bear color.

31 bgColor = close > ma ? open < ma ? neutColorInput : bullColorInput : open >␣


,→ma ? neutColorInput : bearColorInput

32 // MA value in right column.


33 table.cell(panel, 1, i, str.tostring(ma, format.mintick), text_color = color.
,→black, bgcolor = bgColor)

34 period += masStepInput

Note that:
• Users can select the table’s position from the inputs, as well as the bull/bear/neutral colors to be used for the
background of the right column’s cells.
• The table’s quantity of rows is determined using the number of MAs the user chooses to display. We add one row
for the column headers.
• Even though we populate the table cells on the last bar only, we need to execute the calls to ta.sma() on every bar
so they produce the correct results. The compiler warning that appears when you compile the code can be safely
ignored.
• We separate our inputs in two sections using group, and join the relevant ones on the same line using inline.
We supply tooltips to document the limits of certain fields using tooltip.

368 Chapter 4. Concepts


Pine Script™ v5 User Manual

Displaying a heatmap

Our next project is a heatmap, which will indicate the bull/bear relationship of the current price relative to its past values.
To do so, we will use a table positioned at the bottom of the chart. We will display colors only, so our table will contain
no text; we will simply color the background of its cells to produce our heatmap. The heatmap uses a user-selectable
lookback period. It loops across that period to determine if price is above/below each bar in that past, and displays a
progressively lighter intensity of the bull/bear color as we go further in the past:

1 //@version=5
2 indicator("Price vs Past", "", true)
3

4 var int MAX_LOOKBACK = 300


5

6 int lookBackInput = input.int(150, minval = 1, maxval = MAX_LOOKBACK, step = 10)


7 color bullColorInput = input.color(#00FF00ff, "Bull", inline = "11")
8 color bearColorInput = input.color(#FF0080ff, "Bear", inline = "11")
9

10 // ————— Function draws a heatmap showing the position of the current `_src` relative␣
,→to its past `_lookBack` values.

11 drawHeatmap(src, lookBack) =>


12 // float src : evaluated price series.
13 // int lookBack: number of past bars evaluated.
14 // Dependency: MAX_LOOKBACK
15

16 // Force historical buffer to a sufficient size.


17 max_bars_back(src, MAX_LOOKBACK)
18 // Only run table code on last bar.
19 if barstate.islast
20 var heatmap = table.new(position.bottom_center, lookBack, 1)
21 for i = 1 to lookBackInput
22 float transp = 100. * i / lookBack
23 if src > src[i]
24 table.cell(heatmap, lookBack - i, 0, bgcolor = color.
,→new(bullColorInput, transp))

25 else
26 table.cell(heatmap, lookBack - i, 0, bgcolor = color.
,→new(bearColorInput, transp))

27

28 drawHeatmap(high, lookBackInput)

Note that:
• We define a maximum lookback period as a MAX_LOOKBACK constant. This is an important value and we use it
for two purposes: to specify the number of columns we will create in our one-row table, and to specify the lookback

4.18. Tables 369


Pine Script™ v5 User Manual

period required for the _src argument in our function, so that we force Pine Script™ to create a historical buffer
size that will allow us to refer to the required quantity of past values of _src in our for loop.
• We offer users the possibility of configuring the bull/bear colors in the inputs and we use inline to place the
color selections on the same line.
• Inside our function, we enclose our table-creation code in an if barstate.islast construct so that it only runs on the
last bar of the chart.
• The initialization of the table is done inside the if statement. Because of that, and the fact that it uses the var
keyword, initialization only occurs the first time the script executes on a last bar. Note that this behavior is different
from the usual var declarations in the script’s global scope, where initialization occurs on the first bar of the dataset,
at bar_index zero.
• We do not specify an argument to the text parameter in our table.cell() calls, so an empty string is used.
• We calculate our transparency in such a way that the intensity of the colors decreases as we go further in history.
• We use dynamic color generation to create different transparencies of our base colors as needed.
• Contrary to other objects displayed in Pine scripts, this heatmap’s cells are not linked to chart bars. The configured
lookback period determines how many table cells the heatmap contains, and the heatmap will not change as the
chart is panned horizontally, or scaled.
• The maximum number of cells that can be displayed in the scritp’s visual space will depend on your viewing device’s
resolution and the portion of the display used by your chart. Higher resolution screens and wider windows will allow
more table cells to be displayed.

4.18.3 Tips

• When creating tables in strategy scripts, keep in mind that unless the strategy uses calc_on_every_tick =
true, table code enclosed in if barstate.islast blocks will not execute on each realtime update, so the table will not
display as you expect.
• Keep in mind that successive calls to table.cell() overwrite the cell’s properties specified by previous table.cell()
calls. Use the setter functions to modify a cell’s properties.
• Remember to control the execution of your table code wisely by restricting it to the necessary bars only. This saves
server resources and your charts will display faster, so everybody wins.

370 Chapter 4. Concepts


Pine Script™ v5 User Manual

4.19 Text and shapes

• Introduction
• `plotchar()`
• `plotshape()`
• `plotarrow()`
• Labels

4.19.1 Introduction

You may display text or shapes using five different ways with Pine Script™:
• plotchar()
• plotshape()
• plotarrow()
• Labels created with label.new()
• Tables created with table.new() (see Tables)
Which one to use depends on your needs:
• Tables can display text in various relative positions on charts that will not move as users scroll of zoom the chart
horizontally. Their content is not tethered to bars. In contrast, text displayed with plotchar(), plotshape() or la-
bel.new() is always tethered to a specific bar, so it will move with the bar’s position on the chart. See the page on
Tables for more information on them.
• Three function include are able to display pre-defined shapes: plotshape(), plotarrow() and Labels created with
label.new().
• plotarrow() cannot display text, only up or down arrows.
• plotchar() and plotshape() can display non-dynamic text on any bar or all bars of the chart.
• plotchar() can only display one character while plotshape() can display strings, including line breaks.
• label.new() can display a maximum of 500 labels on the chart. Its text can contain dynamic text, or “series strings”.
Line breaks are also supported in label text.
• While plotchar() and plotshape() can display text at a fixed offset in the past or the future, which cannot change
during the script’s execution, each label.new() call can use a “series” offset that can be calculated on the fly.
These are a few things to keep in mind concerning Pine Script™ strings:
• Since the text parameter in both plotchar() and plotshape() require a “const string” argument, it cannot contain
values such as prices that can only be known on the bar (“series string”).
• To include “series” values in text displayed using label.new(), they will first need to be converted to strings using
str.tostring().
• The concatenation operator for strings in Pine is +. It is used to join string components into one string, e.g., msg
= "Chart symbol: " + syminfo.tickerid (where syminfo.tickerid is a built-in variable that returns
the chart’s exchange and symbol information in string format).
• Characters displayed by all these functions can be Unicode characters, which may include Unicode symbols. See
this Exploring Unicode script to get an idea of what can be done with Unicode characters.

4.19. Text and shapes 371


Pine Script™ v5 User Manual

• The color or size of text can sometimes be controlled using function parameters, but no inline formatting (bold,
italics, monospace, etc.) is possible.
• Text from Pine scripts always displays on the chart in the Trebuchet MS font, which is used in many TradingView
texts, including this one.
This script displays text using the four methods available in Pine Script™:

1 //@version=5
2 indicator("Four displays of text", overlay = true)
3 plotchar(ta.rising(close, 5), "`plotchar()`", " ", location.belowbar, color.lime,␣
,→size = size.small)

4 plotshape(ta.falling(close, 5), "`plotchar()`", location = location.abovebar, color =␣


,→na, text = "•`plotshape()•`\n ", textcolor = color.fuchsia, size = size.huge)

6 if bar_index % 25 == 0
7 label.new(bar_index, na, "•LABEL•\nHigh = " + str.tostring(high, format.mintick)␣
,→+ "\n ", yloc = yloc.abovebar, style = label.style_none, textcolor = color.black,␣

,→size = size.normal)

9 printTable(txt) => var table t = table.new(position.middle_right, 1, 1), table.cell(t,


,→ 0, 0, txt, bgcolor = color.yellow)

10 printTable("•TABLE•\n" + str.tostring(bar_index + 1) + " bars\nin the dataset")

Note that:
• The method used to display each text string is shown with the text, except for the lime up arrows displayed using
plotchar(), as it can only display one character.
• Label and table calls can be inserted in conditional structures to control when their are executed, whereas plotchar()
and plotshape() cannot. Their conditional plotting must be controlled using their first argument, which is a “series
bool” whose true or false value determines when the text is displayed.
• Numeric values displayed in the table and labels is first converted to a string using str.tostring().
• We use the + operator to concatenate string components.
• plotshape() is designed to display a shape with accompanying text. Its size parameter controls the size of the
shape, not of the text. We use na for its color argument so that the shape is not visible.
• Contrary to other texts, the table text will not move as you scroll or scale the chart.
• Some text strings contain the 2/7 Unicode arrow (U+1F807).

372 Chapter 4. Concepts


Pine Script™ v5 User Manual

• Some text strings contain the \n sequence that represents a new line.

4.19.2 `plotchar()`

This function is useful to display a single character on bars. It has the following syntax:

plotchar(series, title, char, location, color, offset, text, textcolor, editable,␣


,→size, show_last, display) → void

See the Reference Manual entry for plotchar() for details on its parameters.
As explained in the When the script’s scale must be preserved section of our page on Debugging, the function can be used
to display and inspect values in the Data Window or in the indicator values displayed to the right of the script’s name on
the chart:

1 //@version=5
2 indicator("", "", true)
3 plotchar(bar_index, "Bar index", "", location.top)

Note that:
• The cursor is on the chart’s last bar.
• The value of bar_index on that bar is displayed in indicator values (1) and in the Data Window (2).
• We use location.top because the default location.abovebar will put the price into play in the script’s scale, which
will often interfere with other plots.
plotchar() also works well to identify specific points on the chart or to validate that conditions are true when we expect
them to be. This example displays an up arrow under bars where close, high and volume have all been rising for two bars:

1 //@version=5
2 indicator("", "", true)
3 bool longSignal = ta.rising(close, 2) and ta.rising(high, 2) and (na(volume) or ta.
,→rising(volume, 2))

4 plotchar(longSignal, "Long", "▲", location.belowbar, color = na(volume) ? color.gray␣


,→: color.blue, size = size.tiny)

4.19. Text and shapes 373


Pine Script™ v5 User Manual

Note that:
• We use (na(volume) or ta.rising(volume, 2)) so our script will work on symbols without volume
data. If we did not make provisions for when there is no volume data, which is what na(volume) does by
being true when there is no volume, the longSignal variable’s value would never be true because ta.
rising(volume, 2) yields false in those cases.
• We display the arrow in gray when there is no volume, to remind us that all three base conditions are not being met.
• Because plotchar() is now displaying a character on the chart, we use size = size.tiny to control its size.
• We have adapted the location argument to display the character under bars.
If you don’t mind plotting only circles, you could also use plot() to achieve a similar effect:

1 //@version=5
2 indicator("", "", true)
3 longSignal = ta.rising(close, 2) and ta.rising(high, 2) and (na(volume) or ta.
,→rising(volume, 2))

4 plot(longSignal ? low - ta.tr : na, "Long", color.blue, 2, plot.style_circles)

This method has the inconvenience that, since there is no relative positioning mechanism with plot() one must shift the
circles down using something like ta.tr (the bar’s “True Range”):

374 Chapter 4. Concepts


Pine Script™ v5 User Manual

4.19.3 `plotshape()`

This function is useful to display pre-defined shapes and/or text on bars. It has the following syntax:

plotshape(series, title, style, location, color, offset, text, textcolor, editable,␣


,→size, show_last, display) → void

See the Reference Manual entry for plotshape() for details on its parameters.
Let’s use the function to achieve more or less the same result as with our second example of the previous section:

1 //@version=5
2 indicator("", "", true)
3 longSignal = ta.rising(close, 2) and ta.rising(high, 2) and (na(volume) or ta.
,→rising(volume, 2))

4 plotshape(longSignal, "Long", shape.arrowup, location.belowbar)

Note that here, rather than using an arrow character, we are using the shape.arrowup argument for the style
parameter.

It is possible to use different plotshape() calls to superimpose text on bars. You will need to use \n followed by a special
non-printing character that doesn’t get stripped out to preserve the newline’s functionality. Here we’re using a Unicode
Zero-width space (U+200E). While you don’t see it in the following code’s strings, it is there and can be copy/pasted. The
special Unicode character needs to be the last one in the string for text going up, and the first one when you are plotting
under the bar and text is going down:

1 //@version=5
2 indicator("Lift text", "", true)
3 plotshape(true, "", shape.arrowup, location.abovebar, color.green, text = "A")
4 plotshape(true, "", shape.arrowup, location.abovebar, color.lime, text = "B\n")
5 plotshape(true, "", shape.arrowdown, location.belowbar, color.red, text = "C")
6 plotshape(true, "", shape.arrowdown, location.belowbar, color.maroon, text = "\nD")

4.19. Text and shapes 375


Pine Script™ v5 User Manual

The available shapes you can use with the style parameter are:

376 Chapter 4. Concepts


Pine Script™ v5 User Manual

Argument Shape With Text Argument Shape With Text

shape.xcross shape.arrowup

shape.cross shape.arrowdown

shape.circle shape.square

shape.triangleup shape.diamond

shape.triangledown shape.labelup

shape.flag shape.labeldown

4.19.4 `plotarrow()`

The plotarrow function displays up or down arrows of variable length, based on the relative value of the series used in the
function’s first argument. It has the following syntax:

plotarrow(series, title, colorup, colordown, offset, minheight, maxheight, editable,␣


,→show_last, display) → void

See the Reference Manual entry for plotarrow() for details on its parameters.
The series parameter in plotarrow() is not a “series bool” as in plotchar() and plotshape(); it is a “series int/float”
and there’s more to it than a simple true or false value determining when the arrows are plotted. This is the logic
governing how the argument supplied to series affects the behavior of plotarrow():
• series > 0: An up arrow is displayed, the length of which will be proportional to the relative value of the series
on that bar in relation to other series values.

4.19. Text and shapes 377


Pine Script™ v5 User Manual

• series < 0: A down arrow is displayed, proportionally-sized using the same rules.
• series == 0 or na(series): No arrow is displayed.
The maximum and minimum possible sizes for the arrows (in pixels) can be controlled using the minheight and
maxheight parameters.
Here is a simple script illustrating how plotarrow() works:

1 //@version=5
2 indicator("", "", true)
3 body = close - open
4 plotarrow(body, colorup = color.teal, colordown = color.orange)

Note how the heigth of arrows is proportional to the relative size of the bar bodies.
You can use any series to plot the arrows. Here we use the value of the “Chaikin Oscillator” to control the location and
size of the arrows:

1 //@version=5
2 indicator("Chaikin Oscillator Arrows", overlay = true)
3 fastLengthInput = input.int(3, minval = 1)
4 slowLengthInput = input.int(10, minval = 1)
5 osc = ta.ema(ta.accdist, fastLengthInput) - ta.ema(ta.accdist, slowLengthInput)
6 plotarrow(osc)

378 Chapter 4. Concepts


Pine Script™ v5 User Manual

Note that we display the actual “Chaikin Oscillator” in a pane below the chart, so you can see what values are used to
determine the position and size of the arrows.

4.19.5 Labels

Labels are only available in v4 and higher versions of Pine Script™. They work very differently than plotchar() and
plotshape().
Labels are objects, like lines and boxes, or tables. Like them, they are referred to using an ID, which acts like a pointer.
Label IDs are of “label” type. As with other objects, labels IDs are “time series” and all the functions used to manage
them accept “series” arguments, which makes them very flexible.

Note: On TradingView charts, a complete set of Drawing Tools allows users to create and modify drawings using mouse
actions. While they may sometimes look similar to drawing objects created with Pine Script™ code, they are unrelated
entities. Drawing objects created using Pine code cannot be modified with mouse actions, and hand-drawn drawings from
the chart user interface are not visible from Pine scripts.

Labels are advantageous because:


• They allow “series” values to be converted to text and placed on charts. This means they are ideal to display values
that cannot be known before time, such as price values, support and resistance levels, of any other values that your
script calculates.
• Their positioning options are more flexible that those of the plot*() functions.
• They offer more display modes.
• Contrary to plot*() functions, label-handling functions can be inserted in conditional or loop structures, making
it easier to control their behavior.
• You can add tooltips to labels.
One drawback to using labels versus plotchar() and plotshape() is that you can only draw a limited quantity of them
on the chart. The default is ~50, but you can use the max_labels_count parameter in your indicator() or strategy()
declaration statement to specify up to 500. Labels, like lines and boxes, are managed using a garbage collection mechanism
which deletes the oldest ones on the chart, such that only the most recently drawn labels are visible.

4.19. Text and shapes 379


Pine Script™ v5 User Manual

Your toolbox of built-ins to manage labels are all in the label namespace. They include:
• label.new() to create labels.
• label.set_*() functions to modify the properties of an existing label.
• label.get_*() functions to read the properties of an existing label.
• label.delete() to delete labels
• The label.all array which always contains the IDs of all the visible labels on the chart. The array’s size will depend
on the maximum label count for your script and how many of those you have drawn. aray.size(label.all)
will return the array’s size.

Creating and modifying labels

The label.new() function creates a new label. It has the following signature:

label.new(x, y, text, xloc, yloc, color, style, textcolor, size, textalign, tooltip)␣
,→→ series label

The setter functions allowing you to change a label’s properties are:


• label.set_x()
• label.set_y()
• label.set_xy()
• label.set_text()
• label.set_xloc()
• label.set_yloc()
• label.set_color()
• label.set_style()
• label.set_textcolor()
• label.set_size()
• label.set_textalign()
• label.set_tooltip()
They all have a similar signature. The one for label.set_color() is:

label.set_color(id, color) → void

where:
• id is the ID of the label whose property is to be modified.
• The next parameter is the property of the label to modify. It depends on the setter function used. label.set_xy()
changes two properties, so it has two such parameters.
This is how you can create labels in their simplest form:

1 //@version=5
2 indicator("", "", true)
3 label.new(bar_index, high)

380 Chapter 4. Concepts


Pine Script™ v5 User Manual

Note that:
• The label is created with the parameters x = bar_index (the index of the current bar, bar_index) and y =
high (the bar’s high value).
• We do not supply an argument for the function’s text parameter. Its default value being an empty string, no text
is displayed.
• No logic controls our label.new() call, so labels are created on every bar.
• Only the last 54 labels are displayed because our indicator() call does not use the max_labels_count parameter
to specify a value other than the ~50 default.
• Labels persist on bars until your script deletes them using label.delete(), or garbage collection removes them.
In the next example we display a label on the bar with the highest high value in the last 50 bars:

1 //@version=5
2 indicator("", "", true)
3

4 // Find the highest `high` in last 50 bars and its offset. Change it's sign so it is␣
,→positive.

5 LOOKBACK = 50
6 hi = ta.highest(LOOKBACK)
7 highestBarOffset = - ta.highestbars(LOOKBACK)
8

9 // Create label on bar zero only.


10 var lbl = label.new(na, na, "", color = color.orange, style = label.style_label_lower_
,→left)

11 // When a new high is found, move the label there and update its text and tooltip.
12 if ta.change(hi)
13 // Build label and tooltip strings.
14 labelText = "High: " + str.tostring(hi, format.mintick)
15 tooltipText = "Offest in bars: " + str.tostring(highestBarOffset) + "\nLow: " +␣
,→str.tostring(low[highestBarOffset], format.mintick)

16 // Update the label's position, text and tooltip.


17 label.set_xy(lbl, bar_index[highestBarOffset], hi)
18 label.set_text(lbl, labelText)
19 label.set_tooltip(lbl, tooltipText)

4.19. Text and shapes 381


Pine Script™ v5 User Manual

Note that:
• We create the label on the first bar only by using the var keyword to declare the lbl variable that contains the
label’s ID. The x, y and text arguments in that label.new() call are irrelevant, as the label will be updated on
further bars. We do, however, take care to use the color and style we want for the labels, so they don’t need
updating later.
• On every bar, we detect if a new high was found by testing for changes in the value of hi
• When a change in the high value occurs, we update our label with new information. To do this, we use three
label.set*() calls to change the label’s relevant information. We refer to our label using the lbl variable,
which contains our label’s ID. The script is thus maintaining the same label throughout all bars, but moving it and
updating its information when a new high is detected.
Here we create a label on each bar, but we set its properties conditionally, depending on the bar’s polarity:

1 //@version=5
2 indicator("", "", true)
3 lbl = label.new(bar_index, na)
4 if close >= open
5 label.set_text( lbl, "green")
6 label.set_color(lbl, color.green)
7 label.set_yloc( lbl, yloc.belowbar)
8 label.set_style(lbl, label.style_label_up)
9 else
10 label.set_text( lbl, "red")
11 label.set_color(lbl, color.red)
12 label.set_yloc( lbl, yloc.abovebar)
13 label.set_style(lbl, label.style_label_down)

382 Chapter 4. Concepts


Pine Script™ v5 User Manual

Positioning labels

Labels are positioned on the chart according to x (bars) and y (price) coordinates. Five parameters affect this behavior:
x, y, xloc, yloc and style:
x
Is either a bar index or a time value. When a bar index is used, the value can be offset in the past or in the future
(maximum of 500 bars in the future). Past or future offsets can also be calculated when using time values. The x
value of an existing label can be modified using label.set_x() or label.set_xy().
xloc
Is either xloc.bar_index (the default) or xloc.bar_time. It determines which type of argument must be used with x.
With xloc.bar_index, x must be an absolute bar index. With xloc.bar_time, x must be a UNIX time in milliseconds
corresponding to the time value of a bar’s open. The xloc value of an existing label can be modified using
label.set_xloc().
y
Is the price level where the label is positioned. It is only taken into account with the default yloc value of yloc.
price. If yloc is yloc.abovebar or yloc.belowbar then the y argument is ignored. The y value of an existing
label can be modified using label.set_y() or label.set_xy().
yloc
Can be yloc.price (the default), yloc.abovebar or yloc.belowbar. The argument used for y is only taken into account
with yloc.price. The yloc value of an existing label can be modified using label.set_yloc().
style
The argument used has an impact on the visual appearance of the label and on its position relative to the reference
point determined by either the y value or the top/bottom of the bar when yloc.abovebar or yloc.belowbar are used.
The style of an existing label can be modified using label.set_style().
These are the available style arguments:

4.19. Text and shapes 383


Pine Script™ v5 User Manual

Argument Label Label with Argument La- Label with


text bel text

label.style_xcross label.style_label_up

label.style_cross label.
style_label_down

label.style_flag label.
style_label_left

label.style_circle label.
style_label_right

label.style_square label.
style_label_lower_left

label. label.
style_diamond style_label_lower_right

label. label.
style_triangleup style_label_upper_left

label. label.
style_triangledown style_label_upper_right

384
label. label. Chapter 4. Concepts
style_arrowup style_label_center
Pine Script™ v5 User Manual

When using xloc.bar_time, the x value must be a UNIX timestamp in milliseconds. See the page on Time for more
information. The start time of the current bar can be obtained from the time built-in variable. The bar time of previous
bars is time[1], time[2] and so on. Time can also be set to an absolute value with the timestamp function. You may
add or subtract periods of time to achieve relative time offset.
Let’s position a label one day ago from the date on the last bar:

1 //@version=5
2 indicator("")
3 daysAgoInput = input.int(1, tooltip = "Use negative values to offset in the future")
4 if barstate.islast
5 MS_IN_ONE_DAY = 24 * 60 * 60 * 1000
6 oneDayAgo = time - (daysAgoInput * MS_IN_ONE_DAY)
7 label.new(oneDayAgo, high, xloc = xloc.bar_time, style = label.style_label_right)

Note that because of varying time gaps and missing bars when markets are closed, the positioning of the label may not
always be exact. Time offsets of the sort tend to be more reliable on 24x7 markets.
You can also offset using a bar index for the x value, e.g.:

label.new(bar_index + 10, high)


label.new(bar_index - 10, high[10])
label.new(bar_index[10], high[10])

Reading label properties

The following getter functions are available for labels:


• label.get_x()
• label.get_y()
• label.get_text()
They all have a similar signature. The one for label.get_text() is:

label.get_text(id) → series string

where id is the label whose text is to be retrieved.

Cloning labels

The label.copy() function is used to clone labels. Its syntax is:

label.copy(id) → void

Deleting labels

The label.delete() function is used to delete labels. Its syntax is:

label.delete(id) → void

To keep only a user-defined quantity of labels on the chart, one could use code like this:

4.19. Text and shapes 385


Pine Script™ v5 User Manual

1 //@version=5
2 MAX_LABELS = 500
3 indicator("", max_labels_count = MAX_LABELS)
4 qtyLabelsInput = input.int(5, "Labels to keep", minval = 0, maxval = MAX_LABELS)
5 myRSI = ta.rsi(close, 20)
6 if myRSI > ta.highest(myRSI, 20)[1]
7 label.new(bar_index, myRSI, str.tostring(myRSI, "#.00"), style = label.style_none)
8 if array.size(label.all) > qtyLabelsInput
9 label.delete(array.get(label.all, 0))
10 plot(myRSI)

Note that:
• We define a MAX_LABELS constant to hold the maximum quantity of labels a script can accommodate. We use
that value to set the max_labels_count parameter’s value in our indicator() call, and also as the maxval
value in our input.int() call to cap the user value.
• We create a new label when our RSI breaches its highest value of the last 20 bars. Note the offset of [1] we use
in if myRSI > ta.highest(myRSI, 20)[1]. This is necessary. Without it, the value returned by
ta.highest() would always include the current value of myRSI, so myRSI would never be higher than the function’s
return value.
• After that, we delete the oldest label in the label.all array that is automatically maintained by the Pine Script™
runtime and contains the ID of all the visible labels drawn by our script. We use the array.get() function to retrieve
the array element at index zero (the oldest visible label ID). We then use label.delete() to delete the label linked
with that ID.
Note that if one wants to position a label on the last bar only, it is unnecessary and inefficent to create and delete the label
as the script executes on all bars, so that only the last label remains:
// INEFFICENT!
//@version=5
indicator("", "", true)
lbl = label.new(bar_index, high, str.tostring(high, format.mintick))
label.delete(lbl[1])

This is the efficient way to realize the same task:


1 //@version=5
2 indicator("", "", true)
3 if barstate.islast
4 // Create the label once, the first time the block executes on the last bar.
(continues on next page)

386 Chapter 4. Concepts


Pine Script™ v5 User Manual

(continued from previous page)


5 var lbl = label.new(na, na)
6 // On all iterations of the script on the last bar, update the label's␣
,→information.

7 label.set_xy(lbl, bar_index, high)


8 label.set_text(lbl, str.tostring(high, format.mintick))

Realtime behavior

Labels are subject to both commit and rollback actions, which affect the behavior of a script when it executes in the
realtime bar. See the page on Pine Script™’s Execution model.
This script demonstrates the effect of rollback when running in the realtime bar:

1 //@version=5
2 indicator("", "", true)
3 label.new(bar_index, high)

On realtime bars, label.new() creates a new label on every script update, but because of the rollback process, the label
created on the previous update on the same bar is deleted. Only the last label created before the realtime bar’s close will
be committed, and thus persist.

4.20 Time

• Introduction
• Time variables
• Time functions
• Formatting dates and time

4.20. Time 387


Pine Script™ v5 User Manual

4.20.1 Introduction

Four references

Four different references come into play when using date and time values in Pine Script™:
1. UTC time zone: The native format for time values in Pine Script™ is the Unix time in milliseconds. Unix time
is the time elapsed since the Unix Epoch on January 1st, 1970. See here for the current Unix time in seconds
and here for more information on Unix Time. A value for the Unix time is called a timestamp. Unix timestamps
are always expressed in the UTC (or “GMT”, or “GMT+0”) time zone. They are measured from a fixed reference,
i.e., the Unix Epoch, and do not vary with time zones. Some built-ins use the UTC time zone as a reference.
2. Exchange time zone: A second time-related key reference for traders is the time zone of the exchange where an
instrument is traded. Some built-ins like hour return values in the exchange’s time zone by default.
3. timezone parameter: Some functions that normally return values in the exchange’s time zone, such as hour()
include a timezone parameter that allows you to adapt the function’s result to another time zone. Other functions
like time() include both session and timezone parameters. In those cases, the timezone argument applies
to how the session argument is interpreted — not to the time value returned by the function.
4. Chart’s time zone: This is the time zone chosen by the user from the chart using the “Chart Settings/Symbol/Time
Zone” field. This setting only affects the display of dates and times on the chart. It does not affect the behavior of
Pine scripts, and they have no visibility over this setting.
When discussing variables or functions, we will note if they return dates or times in UTC or exchange time zone. Scripts
do not have visibility on the user’s time zone setting on his chart.

Time built-ins

Pine Script™ has built-in variables to:


• Get timestamp information from the current bar (UTC time zone): time and time_close
• Get timestamp information for the beginning of the current trading day (UTC time zone): time_tradingday
• Get the current time in one-second increments (UTC time zone): timenow
• Retrieve calendar and time values from the bar (exchange time zone): year, month, weekofyear, dayofmonth,
dayofweek, hour, minute and second
• Return the time zone of the exchange of the chart’s symbol with syminfo.timezone
There are also built-in functions that can:
• Return timestamps of bars from other timeframes with time() and time_close(), without the need for a re-
quest.security() call
• Retrieve calendar and time values from any timestamp, which can be offset with a time zone: year(), month(),
weekofyear(), dayofmonth(), dayofweek(), hour(), minute() and second()
• Create a timestamp using timestamp()
• Convert a timestamp to a formatted date/time string for display, using str.format()
• Input data and time values. See the section on Inputs.
• Work with session information.

388 Chapter 4. Concepts


Pine Script™ v5 User Manual

Time zones

TradingViewers can change the time zone used to display bar times on their charts. Pine scripts have no visibility over this
setting. While there is a syminfo.timezone variable to return the time zone of the exchange where the chart’s instrument
is traded, there is no chart.timezone equivalent.
When displaying times on the chart, this shows one way of providing users a way of adjusting your script’s time values to
those of their chart. This way, your displayed times can match the time zone used by traders on their chart:

1 //@version=5
2 indicator("Time zone control")
3 MS_IN_1H = 1000 * 60 * 60
4 TOOLTIP01 = "Enter your time zone's offset (+ or −), including a decimal fraction if␣
,→needed."

5 hoursOffsetInput = input.float(0.0, "Timezone offset (in hours)", minval = -12.0,␣


,→maxval = 14.0, step = 0.5, tooltip = TOOLTIP01)

7 printTable(txt) =>
8 var table t = table.new(position.middle_right, 1, 1)
9 table.cell(t, 0, 0, txt, text_halign = text.align_right, bgcolor = color.yellow)
10

11 msOffsetInput = hoursOffsetInput * MS_IN_1H


12 printTable(
13 str.format("Last bar''s open time UTC: {0,date,HH:mm:ss yyyy.MM.dd}", time) +
14 str.format("\nLast bar''s close time UTC: {0,date,HH:mm:ss yyyy.MM.dd}", time_
,→close) +

15 str.format("\n\nLast bar''s open time EXCHANGE: {0,date,HH:mm:ss yyyy.MM.dd}",␣


,→time(timeframe.period, syminfo.session, syminfo.timezone)) +

16 str.format("\nLast bar''s close time EXCHANGE: {0,date,HH:mm:ss yyyy.MM.dd}", time_


,→close(timeframe.period, syminfo.session, syminfo.timezone)) +

17 str.format("\n\nLast bar''s open time OFFSET ({0}): {1,date,HH:mm:ss yyyy.MM.dd}",␣


,→hoursOffsetInput, time + msOffsetInput) +

18 str.format("\nLast bar''s close time OFFSET ({0}): {1,date,HH:mm:ss yyyy.MM.dd}",␣


,→hoursOffsetInput, time_close + msOffsetInput) +

19 str.format("\n\nCurrent time OFFSET ({0}): {1,date,HH:mm:ss yyyy.MM.dd}",␣


,→hoursOffsetInput, timenow + msOffsetInput))

Note that:

4.20. Time 389


Pine Script™ v5 User Manual

• We convert the user offset expressed in hours to milliseconds with msOffsetInput. We then add that offset
to a timestamp in UTC format before converting it to display format, e.g., time + msOffsetInput and
timenow + msOffsetInput.
• We use a tooltip to provide instructions to users.
• We provide minval and maxval values to protect the input field, and a step value of 0.5 so that when they use
the field’s up/down arrows, they can intuitively figure out that fractions can be used.
• The str.format() function formats our time values, namely the last bar’s time and the current time.
Some functions that normally return values in the exchange’s time zone provide means to adapt their result to another
time zone through the timezone parameter. This script illustrates how to do this with hour():

1 //@version=5
2 indicator('`hour(time, "GMT+0")` in orange')
3 color BLUE_LIGHT = #0000FF30
4 plot(hour, "", BLUE_LIGHT, 8)
5 plot(hour(time, syminfo.timezone))
6 plot(hour(time, "GMT+0"),"UTC", color.orange)

Note that:
• The hour variable and the hour() function normally returns a value in the exchange’s time zone. Accordingly,
plots in blue for both hour and hour(time, syminfo.timezone) overlap. Using the function form with
syminfo.timezone is thus redundant if the exchange’s hour is required.
• The orange line plotting hour(time, "GMT+0"), however, returns the bar’s hour at UTC, or “GMT+0” time,
which in this case is four hours less than the exchange’s time, since MSFT trades on the NASDAQ whose time
zone is UTC-4.

Time zone strings

The argument used for the timezone parameter in functions such as time(), timestamp(), hour(), etc., can be in different
formats, which you can find in the IANA time zone database name reference page. Contents from the “TZ database name”,
“UTC offset ±hh:mm” and “UTC DST offset ±hh:mm” columns of that page’s table can be used.
To express an offset of +5.5 hours from UTC, these strings found in the reference page are all equivalent:
• "GMT+05:30"
• "Asia/Calcutta"
• "Asia/Colombo"

390 Chapter 4. Concepts


Pine Script™ v5 User Manual

• "Asia/Kolkata"
Non-fractional offsets can be expressed in the "GMT+5" form. "GMT+5.5" is not allowed.

4.20.2 Time variables

`time` and `time_close`

Let’s start by plotting time and time_close, the Unix timestamp in milliseconds of the bar’s opening and closing time:

1 //@version=5
2 indicator("`time` and `time_close` values on bars")
3 plot(time, "`time`")
4 plot(time_close, "`time_close`")

Note that:
• The time and time_close variables returns a timestamp in UNIX time, which is independent of the timezone selected
by the user on his chart. In this case, the chart’s time zone setting is the exchange time zone, so whatever symbol
is on the chart, its exchange time zone will be used to display the date and time values on the chart’s cursor. The
NASDAQ’s time zone is UTC-4, but this only affects the chart’s display of date/time values; it does not impact the
values plotted by the script.
• The last time value for the plot shown in the scale is the number of milliseconds elapsed from 00:00:00 UTC, 1
January, 1970, until the bar’s opening time. It corresponds to 17:30 on the 27th of September 2021. However,
because the chart uses the UTC-4 time zone (the NASDAQ’s time zone), it displays the 13:30 time, four hours
earlier than UTC time.
• The difference between the two values on the last bar is the number of milliseconds in one hour (1000 * 60 * 60 =
3,600,000) because we are on a 1H chart.

4.20. Time 391


Pine Script™ v5 User Manual

`time_tradingday`

time_tradingday is useful when a symbol trades on overnight sessions that start and close on different calendar days. For
example, this happens in forex markets where a session can open Sunday at 17:00 and close Monday at 17:00.
The variable returns the time of the beginning of the trading day in UNIX time when used at timeframes of 1D and less.
When used on timeframes higher than 1D, it returns the starting time of the last trading day in the bar (e.g., at 1W, it will
return the starting time of the last trading day of the week).

`timenow`

timenow returns the current time in UNIX time. It works in realtime, but also when a script executes on historical bars.
In realtime, your scripts will only perceive changes when they execute on feed updates. When no updates occur, the script
is idle, so it cannot update its display. See the page on Pine Script™’s execution model for more information.
This script uses the values of timenow and time_close to calculate a realtime countdown for intraday bars. Contrary to
the countdown on the chart, this one will only update when a feed update causes the script to execute another iteration:

1 //@version=5
2 indicator("", "", true)
3

4 printTable(txt) =>
5 var table t = table.new(position.middle_right, 1, 1)
6 table.cell(t, 0, 0, txt, text_halign = text.align_right, bgcolor = color.yellow)
7

8 printTable(str.format("{0,time,HH:mm:ss.SSS}", time_close - timenow))

Calendar dates and times

Calendar date and time variables such as year, month, weekofyear, dayofmonth, dayofweek, hour, minute and second can
be useful to test for specific dates or times, and as arguments to timestamp().
When testing for specific dates or times, ones needs to account for the possibility that the script will be executing on
timeframes where the tested condition cannot be detected, or for cases where a bar with the specific requirement will not
exist. Suppose, for example, we wanted to detect the first trading day of the month. This script shows how using only
dayofmonth will not work when a weekly chart is used or when no trading occurs on the 1st of the month:

392 Chapter 4. Concepts


Pine Script™ v5 User Manual

1 //@version=5
2 indicator("", "", true)
3 firstDayIncorrect = dayofmonth == 1
4 firstDay = ta.change(time("M"))
5 plotchar(firstDayIncorrect, "firstDayIncorrect", "•", location.top, size = size.small)
6 bgcolor(firstDay ? color.silver : na)

Note that:
• Using ta.change(time("M")) is more robust as it works on all months (#1 and #2), displayed as the silver
background, whereas the blue dot detected using dayofmonth == 1 does not work (#1) when the first trading
day of September occurs on the 2nd.
• The dayofmonth == 1 condition will be true on all intrabars of the first day of the month, but ta.
change(time("M")) will only be true on the first.
If you wanted your script to only display for years 2020 and later, you could use:

1 //@version=5
2 indicator("", "", true)
3 plot(year >= 2020 ? close : na, linewidth = 3)

`syminfo.timezone()`

syminfo.timezone returns the time zone of the chart symbol’s exchange. It can be helpful when a timezone parameter
is available in a function, and you want to mention that you are using the exchange’s timezone explicitly. It is usually
redundant because when no argument is supplied to timezone, the exchange’s time zone is assumed.

4.20.3 Time functions

`time()` and `time_close()`

The time() and time_close() functions have the following signature:

time(timeframe, session, timezone) → series int


time_close(timeframe, session, timezone) → series int

They accept three arguments:


timeframe
A string in timeframe.period format.
session
An optional string in session specification format: "hhmm-hhmm[:days]", where the [:days] part is op-
tional. See the page on sessions for more information.
timezone
An optional value that qualifies the argument for session when one is used.
See the time() and time_close() entries in the Reference Manual for more information.
The time() function is most often used to:
1. Test if a bar is in a specific time period, which will require using the session parameter. In those cases,
timeframe.period, i.e., the chart’s timeframe, will often be used for the first parameter. When using the
function this way, we rely on the fact that it will return na when the bar is not part of the period specified in the
session argument.

4.20. Time 393


Pine Script™ v5 User Manual

2. Detecting changes in higher timeframes than the chart’s by using the higher timeframe for the timeframe ar-
gument. When using the function for this purpose, we are looking for changes in the returned value, which
means the higher timeframe bar has changed. This will usually require using ta.change() to test, e.g., ta.
change(time("D")) will return the change in time when a new higher timeframe bar comes in, so the ex-
pression’s result will cast to a “bool” value when used in a conditional expression. The “bool” result will be true
when there is a change and false when there is no change.

Testing for sessions

Let’s look at an example of the first case where we want to determine if a bar’s starting time is part of a period between
11:00 and 13:00:

1 //@version=5
2 indicator("Session bars", "", true)
3 inSession = not na(time(timeframe.period, "1100-1300"))
4 bgcolor(inSession ? color.silver : na)

Note that:
• We use time(timeframe.period, "1100-1300"), which says: “Check the chart’s timeframe if the
current bar’s opening time is between 11:00 and 13:00 inclusively”. The function returns its opening time if the
bar is in the session. If it is not, the function returns na.
• We are interested in identifying the instances when time() does not return na because that means the bar is in the
session, so we test for not na(...). We do not use the actual return value of time() when it is not na; we are
only interested in whether it returns na or not.

Testing for changes in higher timeframes

It is often helpful to detect changes in a higher timeframe. For example, you may want to detect trading day changes while
on intraday charts. For these cases, you can use the fact that time("D") returns the opening time of the 1D bar, even
if the chart is at an intraday timeframe such as 1H:

394 Chapter 4. Concepts


Pine Script™ v5 User Manual

1 //@version=5
2 indicator("", "", true)
3 bool newDay = ta.change(time("D"))
4 bgcolor(newDay ? color.silver : na)
5

6 newExchangeDay = ta.change(dayofmonth)
7 plotchar(newExchangeDay, "newExchangeDay", " ", location.top, size = size.small)

Note that:
• The newDay variable detects changes in the opening time of 1D bars, so it follows the conventions for the chart’s
symbol, which uses overnight sessions of 17:00 to 17:00. It changes values when a new session comes in.
• Because newExchangeDay detects change in dayofmonth in the calendar day, it changes when the day changes
on the chart.
• The two change detection methods only coincide on the chart when there are days without trading. On Sundays
here, for example, both detection methods will detect a change because the calendar day changes from the last
trading day (Friday) to the first calendar day of the new week, Sunday, which is when Monday’s overnight session
begins at 17:00.

Calendar dates and times

Calendar date and time functions such as year(), month(), weekofyear(), dayofmonth(), dayofweek(), hour(), minute()
and second() can be useful to test for specific dates or times. They all have signatures similar to the ones shown here for
dayofmonth():

dayofmonth(time) → series int


dayofmonth(time, timezone) → series int

This will plot the day of the opening of the bar where the January 1st, 2021 at 00:00 time falls between its time and
time_close values:

1 //@version=5
2 indicator("")
3 exchangeDay = dayofmonth(timestamp("2021-01-01"))
4 plot(exchangeDay)

The value will be the 31st or the 1st, depending on the calendar day of when the session opens on the chart’s symbol. The
date for symbols traded 24x7 at exchanges using the UTC time zone will be the 1st. For symbols trading on exchanges at
UTC-4, the date will be the 31st.

4.20. Time 395


Pine Script™ v5 User Manual

`timestamp()`

The timestamp() function has a few different signatures:

timestamp(year, month, day, hour, minute, second) → simple/series int


timestamp(timezone, year, month, day, hour, minute, second) → simple/series int
timestamp(dateString) → const int

The only difference between the first two is the timezone parameter. Its default value is syminfo.timezone. See the
Time zone strings section of this page for valid values.
The third form is used as a defval value in input.time(). See the timestamp() entry in the Reference Manual for more
information.
timestamp() is useful to generate a timestamp for a specific date. To generate a timestamp for Jan 1, 2021, use either one
of these methods:

1 //@version=5
2 indicator("")
3 yearBeginning1 = timestamp("2021-01-01")
4 yearBeginning2 = timestamp(2021, 1, 1, 0, 0)
5 printTable(txt) => var table t = table.new(position.middle_right, 1, 1), table.cell(t,
,→ 0, 0, txt, bgcolor = color.yellow)

6 printTable(str.format("yearBeginning1: {0,date,yyyy.MM.dd hh:mm}\nyearBeginning2: {1,


,→date,yyyy.MM.dd hh:mm}", yearBeginning1, yearBeginning1))

You can use offsets in timestamp() arguments. Here, we subtract 2 from the value supplied for its day parameter to get
the date/time from the chart’s last bar two days ago. Note that because of different bar alignments on various instruments,
the bar identified on the chart may not always be exactly 48 hours away, although the function’s return value is correct:

1 //@version=5
2 indicator("")
3 twoDaysAgo = timestamp(year, month, dayofmonth - 2, hour, minute)
4 printTable(txt) => var table t = table.new(position.middle_right, 1, 1), table.cell(t,
,→ 0, 0, txt, bgcolor = color.yellow)

5 printTable(str.format("{0,date,yyyy.MM.dd hh:mm}", twoDaysAgo))

4.20.4 Formatting dates and time

Timestamps can be formatted using str.format(). These are examples of various formats:

396 Chapter 4. Concepts


Pine Script™ v5 User Manual

1 //@version=5
2 indicator("", "", true)
3

4 print(txt, styl) =>


5 var alignment = styl == label.style_label_right ? text.align_right : text.align_
,→left

6 var lbl = label.new(na, na, "", xloc.bar_index, yloc.price, color(na), styl,␣


,→color.black, size.large, alignment)

7 if barstate.islast
8 label.set_xy(lbl, bar_index, hl2[1])
9 label.set_text(lbl, txt)
10

11 var string format =


12 "{0,date,yyyy.MM.dd hh:mm:ss}\n" +
13 "{1,date,short}\n" +
14 "{2,date,medium}\n" +
15 "{3,date,long}\n" +
16 "{4,date,full}\n" +
17 "{5,date,h a z (zzzz)}\n" +
18 "{6,time,short}\n" +
19 "{7,time,medium}\n" +
20 "{8,date,'Month 'MM, 'Week' ww, 'Day 'DD}\n" +
21 "{9,time,full}\n" +
22 "{10,time,hh:mm:ss}\n" +
23 "{11,time,HH:mm:ss}\n" +
24 "{12,time,HH:mm:ss} Left in bar\n"
25

26 print(format, label.style_label_right)
27 print(str.format(format,
28 time, time, time, time, time, time, time,
29 timenow, timenow, timenow, timenow,
30 timenow - time, time_close - timenow), label.style_label_left)

4.20. Time 397


Pine Script™ v5 User Manual

4.21 Timeframes

• Introduction
• Timeframe string specifications
• Comparing timeframes

4.21.1 Introduction

The timeframe of a chart is sometimes also referred to as its interval or resolution. It is the unit of time represented by
one bar on the chart. All standard chart types use a timeframe: “Bars”, “Candles”, “Hollow Candles”, “Line”, “Area” and
“Baseline”. One non-standard chart type also uses timeframes: “Heikin Ashi”.
Programmers interested in accessing data from multiple timeframes will need to become familiar with how timeframes
are expressed in Pine Script™, and how to use them.
Timeframe strings come into play in different contexts:
• They must be used in request.security() when requesting data from another symbol and/or timeframe. See the page
on Other timeframes and data to explore the use of request.security().
• They can be used as an argument to time() and time_close() functions, to return the time of a higher timeframe
bar. This, in turn, can be used to detect changes in higher timeframes from the chart’s timeframe without using
request.security(). See the Testing for changes in higher timeframes section to see how to do this.
• The input.timeframe() function provides a way to allow script users to define a timeframe through a script’s “Inputs”
tab (see the Timeframe input section for more information).
• The indicator() declaration statement has an optional timeframe parameter that can be used to provide multi-
timeframe capabilities to simple scripts without using request.security().
• Many built-in variables provide information on the timeframe used by the chart the script is running on. See the
Chart timeframe section for more information on them, including timeframe.period which returns a string in Pine
Script™’s timeframe specification format.

398 Chapter 4. Concepts


Pine Script™ v5 User Manual

4.21.2 Timeframe string specifications

Timeframe strings follow these rules:


• They are composed of the multiplier and the timeframe unit, e.g., “1S”, “30” (30 minutes), “1D” (one day), “3M”
(three months).
• The unit is represented by a single letter, with no letter used for minutes: “S” for seconds, “D” for days, “W” for
weeks and “M” for months.
• When no multiplier is used, 1 is assumed: “S” is equivalent to “1S”, “D” to “1D, etc. If only “1” is used, it is
interpreted as “1min”, since no unit letter identifier is used for minutes.
• There is no “hour” unit; “1H” is not valid. The correct format for one hour is “60” (remember no unit letter is
specified for minutes).
• The valid multipliers vary for each timeframe unit:
– For seconds, only the discrete 1, 5, 10, 15 and 30 multipliers are valid.
– For minutes, 1 to 1440.
– For days, 1 to 365.
– For weeks, 1 to 52.
– For months, 1 to 12.

4.21.3 Comparing timeframes

It can be useful to compare different timeframe strings to determine, for example, if the timeframe used on the chart is
lower than the higher timeframes used in the script, as using timeframes lower than the chart is usually not a good idea.
See the Requesting data of a lower timeframe section for more information on the subject.
Converting timeframe strings to a representation in fractional minutes provides a way to compare them using a universal
unit. This script uses the timeframe.in_seconds() function to convert a timeframe into float seconds and then converts the
result into minutes:
Note that:
• We use the built-in timeframe.in_seconds() function to convert the chart and the input.timeframe() function into
seconds, then divide by 60 to convert into minutes.
• We use two calls to the timeframe.in_seconds() function in the initialization of the chartTFInMinutes and
inputTFInMinutes variables. In the first instance, we do not supply an argument for its timeframe param-
eter, so the function returns the chart’s timeframe in seconds. In the second call, we supply the timeframe selected
by the script’s user through the call to input.timeframe().
• Next, we validate the timeframes to ensure that the input timeframe is equal to or higher than the chart’s timeframe.
If it is not, we generate a runtime error.
• We finally print the two timeframe values converted to minutes.

4.21. Timeframes 399


Pine Script™ v5 User Manual

400 Chapter 4. Concepts


CHAPTER

FIVE

WRITING SCRIPTS

5.1 Style guide

• Introduction
• Naming Conventions
• Script organization
• Spacing
• Line wrapping
• Vertical alignment
• Explicit typing

5.1.1 Introduction

This style guide provides recommendations on how to name variables and organize your Pine scripts in a standard way
that works well. Scripts that follow our best practices will be easier to read, understand and maintain.
You can see scripts using these guidelines published from the TradingView and PineCoders accounts on the platform.

401
Pine Script™ v5 User Manual

5.1.2 Naming Conventions

We recommend the use of:


• camelCase for all identifiers, i.e., variable or function names: ma, maFast, maLengthInput, maColor,
roundedOHLC(), pivotHi().
• All caps SNAKE_CASE for constants: BULL_COLOR, BEAR_COLOR, MAX_LOOKBACK.
• The use of qualifying suffixes when it provides valuable clues about the type or provenance of a variable: maShow-
Input, bearColor, bearColorInput, volumesArray, maPlotID, resultsTable, levels-
ColorArray.

5.1.3 Script organization

The Pine Script™ compiler is quite forgiving of the positioning of specific statements or the version compiler annotation
in the script. While other arrangements are syntactically correct, this is how we recommend organizing scripts:

<license>
<version>
<declaration_statement>
<import_statements>
<constant_declarations>
<inputs>
<function_declarations>
<calculations>
<strategy_calls>
<visuals>
<alerts>

<license>

If you publish your open-source scripts publicly on TradingView (scripts can also be published privately), your open-
source code is by default protected by the Mozilla license. You may choose any other license you prefer.
The reuse of code from those scripts is governed by our House Rules on Script Publishing which preempt the author’s
license.
The standard license comments appearing at the beginning of scripts are:

// This source code is subject to the terms of the Mozilla Public License 2.0 at␣
,→https://mozilla.org/MPL/2.0/

// © username

<version>

This is the compiler annotation defining the version of Pine Script™ the script will use. If none is present, v1 is used.
For v5, use:

1 //@version=5

402 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

<declaration_statement>

This is the mandatory declaration statement which defines the type of your script. It must be a call to either indicator(),
strategy(), or library().

<import_statements>

If your script uses one or more Pine Script™ libraries, your import statements belong here.

<constant_declarations>

Scripts can declare variables qualified as “const”, i.e., ones referencing a constant value.
We refer to variables as “constants” when they meet these criteria:
• Their declaration uses the optional const keyword (see our User Manual’s section on type qualifiers for more
information).
• They are initialized using a literal (e.g., 100 or "AAPL") or a built-in qualified as “const” (e.g., color.green).
• Their value does not change during the script’s execution.
We use SNAKE_CASE to name these variables and group their declaration near the top of the script. For example:

// ————— Constants
int MS_IN_MIN = 60 * 1000
int MS_IN_HOUR = MS_IN_MIN * 60
int MS_IN_DAY = MS_IN_HOUR * 24

color GRAY = #808080ff


color LIME = #00FF00ff
color MAROON = #800000ff
color ORANGE = #FF8000ff
color PINK = #FF0080ff
color TEAL = #008080ff
color BG_DIV = color.new(ORANGE, 90)
color BG_RESETS = color.new(GRAY, 90)

string RST1 = "No reset; cumulate since the beginning of the chart"
string RST2 = "On a stepped higher timeframe (HTF)"
string RST3 = "On a fixed HTF"
string RST4 = "At a fixed time"
string RST5 = "At the beginning of the regular session"
string RST6 = "At the first visible chart bar"
string RST7 = "Fixed rolling period"

string LTF1 = "Least precise, covering many chart bars"


string LTF2 = "Less precise, covering some chart bars"
string LTF3 = "More precise, covering less chart bars"
string LTF4 = "Most precise, 1min intrabars"

string TT_TOTVOL = "The 'Bodies' value is the transparency of the total volume␣
,→candle bodies. Zero is opaque, 100 is transparent."

string TT_RST_HTF = "This value is used when '" + RST3 +"' is selected."
string TT_RST_TIME = "These values are used when '" + RST4 +"' is selected.
A reset will occur when the time is greater or equal to the bar's open time, and␣
(continues on next page)

5.1. Style guide 403


Pine Script™ v5 User Manual

(continued from previous page)


,→less than its close time.\nHour: 0-23\nMinute: 0-59"
string TT_RST_PERIOD = "This value is used when '" + RST7 +"' is selected."

In this example:
• The RST* and LTF* constants will be used as tuple elements in the options argument of input.*() calls.
• The TT_* constants will be used as tooltip arguments in input.*() calls. Note how we use a line continu-
ation for long string literals.
• We do not use var to initialize constants. The Pine Script™ runtime is optimized to handle declarations on each bar,
but using var to initialize a variable only the first time it is declared incurs a minor penalty on script performance
because of the maintenance that var variables require on further bars.
Note that:
• Literals used in more than one place in a script should always be declared as a constant. Using the constant rather
than the literal makes it more readable if it is given a meaningful name, and the practice makes code easier to
maintain. Even though the quantity of milliseconds in a day is unlikely to change in the future, MS_IN_DAY is
more meaningful than 1000 * 60 * 60 * 24.
• Constants only used in the local block of a function or if, while, etc., statement for example, can be declared in that
local block.

<inputs>

It is much easier to read scripts when all their inputs are in the same code section. Placing that section at the beginning
of the script also reflects how they are processed at runtime, i.e., before the rest of the script is executed.
Suffixing input variable names with input makes them more readily identifiable when they are used later in the script:
maLengthInput, bearColorInput, showAvgInput, etc.

// ————— Inputs
string resetInput = input.string(RST2, "CVD Resets", ␣
,→ inline = "00", options = [RST1, RST2, RST3, RST4, RST5, RST6, RST7])
string fixedTfInput = input.timeframe("D", " Fixed HTF: ", ␣
,→ tooltip = TT_RST_HTF)
int hourInput = input.int(9, " Fixed time hour: ", ␣
,→ inline = "01", minval = 0, maxval = 23)
int minuteInput = input.int(30, "minute", ␣
,→ inline = "01", minval = 0, maxval = 59, tooltip = TT_RST_TIME)
int fixedPeriodInput = input.int(20, " Fixed period: ", ␣
,→ inline = "02", minval = 1, tooltip = TT_RST_PERIOD)
string ltfModeInput = input.string(LTF3, "Intrabar precision", ␣
,→ inline = "03", options = [LTF1, LTF2, LTF3, LTF4])

404 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

<function_declarations>

All user-defined functions must be defined in the script’s global scope; nested function definitions are not allowed in Pine
Script™.
Optimal function design should minimize the use of global variables in the function’s scope, as they undermine function
portability. When it can’t be avoided, those functions must follow the global variable declarations in the code, which
entails they can’t always be placed in the <function_declarations> section. Such dependencies on global variables should
ideally be documented in the function’s comments.
It will also help readers if you document the function’s objective, parameters and result. The same syntax used in libraries
can be used to document your functions. This can make it easier to port your functions to a library should you ever decide
to do so.

1 //@version=5
2 indicator("<function_declarations>", "", true)
3

4 string SIZE_LARGE = "Large"


5 string SIZE_NORMAL = "Normal"
6 string SIZE_SMALL = "Small"
7

8 string sizeInput = input.string(SIZE_NORMAL, "Size", options = [SIZE_LARGE, SIZE_


,→NORMAL, SIZE_SMALL])

10 // @function Used to produce an argument for the `size` parameter in built-in␣


,→functions.

11 // @param userSize (simple string) User-selected size.


12 // @returns One of the `size.*` built-in constants.
13 // Dependencies: SIZE_LARGE, SIZE_NORMAL, SIZE_SMALL
14 getSize(simple string userSize) =>
15 result =
16 switch userSize
17 SIZE_LARGE => size.large
18 SIZE_NORMAL => size.normal
19 SIZE_SMALL => size.small
20 => size.auto
21

22 if ta.rising(close, 3)
23 label.new(bar_index, na, yloc = yloc.abovebar, style = label.style_arrowup, size␣
,→= getSize(sizeInput))

<calculations>

This is where the script’s core calculations and logic should be placed. Code can be easier to read when variable decla-
rations are placed near the code segment using the variables. Some programmers prefer to place all their non-constant
variable declarations at the beginning of this section, which is not always possible for all variables, as some may require
some calculations to have been executed before their declaration.

5.1. Style guide 405


Pine Script™ v5 User Manual

<strategy_calls>

Strategies are easier to read when strategy calls are grouped in the same section of the script.

<visuals>

This section should ideally include all the statements producing the script’s visuals, whether they be plots, drawings,
background colors, candle-plotting, etc. See the Pine Script™ User Manual’s section on here for more information on
how the relative depth of visuals is determined.

<alerts>

Alert code will usually require the script’s calculations to have executed before it, so it makes sense to put it at the end of
the script.

5.1.4 Spacing

A space should be used on both sides of all operators, except unary operators (-1). A space is also recommended after
all commas and when using named function arguments, as in plot(series = close)
int a = close > open ? 1 : -1
var int newLen = 2
newLen := min(20, newlen + 1)
float a = -b
float c = d > e ? d - e : d
int index = bar_index % 2 == 0 ? 1 : 2
plot(close, color = color.red)

5.1.5 Line wrapping

Line wrapping can make long lines easier to read. Line wraps are defined by using an indentation level that is not a
multiple of four, as four spaces or a tab are used to define local blocks. Here we use two spaces:
plot(
series = close,
title = "Close",
color = color.blue,
show_last = 10
)

5.1.6 Vertical alignment

Vertical alignment using tabs or spaces can be useful in code sections containing many similar lines such as constant
declarations or inputs. They can make mass edits much easier using the Pine Editor’s multi-cursor feature (ctrl + alt
+ / ):
// Colors used as defaults in inputs.
color COLOR_AQUA = #0080FFff
color COLOR_BLACK = #000000ff
color COLOR_BLUE = #013BCAff
(continues on next page)

406 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

(continued from previous page)


color COLOR_CORAL = #FF8080ff
color COLOR_GOLD = #CCCC00ff

5.1.7 Explicit typing

Including the type of variables when declaring them is not required and is usually overkill for small scripts; we do not
systematically use it. It can be useful to make the type of a function’s result clearer, and to distinguish a variable’s
declaration (using =) from its reassignments (using :=). Using explicit typing can also make it easier for readers to find
their way in larger scripts.

5.2 Debugging

• Introduction
• The lay of the land
• Displaying numeric values
• Displaying strings
• Debugging conditions
• Debugging from inside functions
• Debugging from inside `for` loops
• Tips

5.2.1 Introduction

TradingView’s close integration between the Pine Script™ Editor and charts allows for efficient and interactive debugging
of Pine Script™ code. Once a programmer understands the most appropriate technique to use in each situation, they will
be able to debug scripts quickly and thoroughly. This page demonstrates the most useful techniques to debug Pine Script™
code.
If you are not yet familiar with Pine Script™’s execution model, it is important that you read the Execution model page
of this User Manual so you understand how your debugging code will behave in the Pine Script™ environment.

5.2. Debugging 407


Pine Script™ v5 User Manual

5.2.2 The lay of the land

Values plotted by Pine scripts can be displayed in four distinct places:


1. Next to the script’s name (controlled by the “Indicator Values” checkbox in the “Chart settings/Status Line” tab).
2. In the script’s pane, whether your script is a chart overlay or in a separate pane.
3. In the scale (only displays the last bar’s value and is controlled by the “Indicator Last Value Label” checkbox in the
“Chart settings/Scale” tab).
4. In the Data Window (which you can bring up using the fourth icon down, to the right of your chart).

Note the following in the preceding screenshot:


• The chart’s cursor is on the dataset’s first bar, where bar_index is zero. That value is reflected next to the indicator’s
name and in the Data Window. Moving your cursor on other bars would update those values so they always
represent the value of the plot on that bar. This is a good way to inspect the value of a variable as the script’s
execution progresses from bar to bar.
• The title argument of our plot() call, “Bar Index”, is used as the value’s legend in the Data Window.
• The precision of the values displayed in the Data Window is dependent on the chart symbol’s tick value. You can
modify it in two ways:
– By changing the value of the “Precision” field in the script’s “Settings/Style” tab. You can obtain up to eight
digits of precision using this method.
– By using the precision parameter in your script’s indicator() or strategy() declaration statement. This
method allows specifying up to 16 digits precision.
• The plot() call in our script plots the value of bar_index in the indicator’s pane, which shows the increasing value
of the variable.
• The scale of the script’s pane is automatically sized to accommodate the smallest and largest values plotted by all
plot() calls in the script.

408 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

5.2.3 Displaying numeric values

When the script’s scale is unimportant

The script in the preceding screenshot used the simplest way to inspect numerical values: a plot() call, which plots a line
corresponding to the variable’s value in the script’s display area. Our example script plotted the value of the bar_index
built-in variable, which contains the bar’s number, a value beginning at zero on the dataset’s first bar and increased by one
on each subsequent bar. We used a plot() call to plot the variable to inspect because our script was not plotting anything
else; we were not preoccupied with preserving the scale for other plots to continue to plot normally. This is the script we
used:
1 //@version=5
2 indicator("Plot `bar_index`")
3 plot(bar_index, "Bar Index")

When the script’s scale must be preserved

Plotting values in the script’s display area is not always possible. When we already have other plots going on and adding
debugging plots of variables whose values fall outside the script’s plotting boundaries would make the plots unreadable,
another technique must be used to inspect values if we want to preserve the scale of the other plots.
Suppose we want to continue inspecting the value of bar_index, but this time in a script where we are also plotting RSI:
1 //@version=5
2 indicator("Plot RSI and `bar_index`")
3 r = ta.rsi(close, 20)
4 plot(r, "RSI", color.black)
5 plot(bar_index, "Bar Index")

Running the script on a dataset containing a large number of bars yields the following display:

where:
1. The RSI line in black is flat because it varies between zero and 100, but the indicator’s pane is scaled to show the
maximum value of bar_index, which is 25692.0000.
2. The value of bar_index on the bar the cursor is on is displayed next to the indicator’s name, and its blue plot in the
script’s pane is flat.
3. The 25692.0000 value of bar_index shown in the scale represents its value on the last bar, so the dataset contains
25693 bars.
4. The value of bar_index on the bar the cursor is on is also displayed in the Data Window, along with that bar’s value
for RSI just above it.
In order to preserve our plot of RSI while still being able to inspect the value or bar_index, we will plot the variable using
plotchar() like this:

5.2. Debugging 409


Pine Script™ v5 User Manual

1 //@version=5
2 indicator("Plot RSI and `bar_index`")
3 r = ta.rsi(close, 20)
4 plot(r, "RSI", color.black)
5 plotchar(bar_index, "Bar index", "", location.top)

where:
• Because the value of bar_index is no longer being plotted in the script’s pane, the pane’s boundaries are now those
of RSI, which displays normally.
• The value plotted using plotchar() is displayed next to the script’s name and in the Data Window.
• We are not plotting a character with our plotchar() call, so the third argument is an empty string (""). We are
also specifying location.top as the location argument, so that we do not put the symbol’s price in play in the
calculation of the display area’s boundaries.

5.2.4 Displaying strings

Pine Script™ labels must be used to display strings. Labels only appear in the script’s display area; strings shown in labels
do not appear in the Data Window or anywhere else.

Labels on each bar

The following script demonstrates the simplest way to repetitively draw a label showing the symbol’s name:

1 //@version=5
2 indicator("Simple label", "", true)
3 label.new(bar_index, high, syminfo.ticker)

By default, only the last 50 labels will be shown on the chart. You can increase this amount up to a maximum of 500 by
using the max_labels_count parameter in your script’s indicator() or strategy() declaration statement. For example:

410 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

indicator("Simple label", "", true, max_labels_count = 500)

Labels on last bar

As strings manipulated in Pine scripts often do not change bar to bar, the method most frequently used to visualize them
is to draw a label on the dataset’s last bar. Here, we use a function to create a label that only appears on the chart’s last
bar. Our f_print() function has only one parameter, the text string to be displayed:

1 //@version=5
2 indicator("print()", "", true)
3 print(txt) =>
4 // Create label on the first bar.
5 var lbl = label.new(bar_index, na, txt, xloc.bar_index, yloc.price, color(na),␣
,→label.style_none, color.gray, size.large, text.align_left)

6 // On next bars, update the label's x and y position, and the text it displays.
7 label.set_xy(lbl, bar_index, ta.highest(10)[1])
8 label.set_text(lbl, txt)
9

10 print("Multiplier = " + str.tostring(timeframe.multiplier) + "\nPeriod = " +␣


,→timeframe.period + "\nHigh = " + str.tostring(high))

11 print("Hello world!\n\n\n\n")

Note the following in our last code example:


• We use the print() function to enclose the label-drawing code. While the function is called on each bar, the
label is only created on the dataset’s first bar because of our use of the var keyword when declaring the lbl variable
inside the function. After creating it, we only update the label’s x and y coordinates and its text on each successive
bar. If we did not update those values, the label would remain on the dataset’s first bar and would only display the
text string’s value on that bar. Lastly, note that we use ta.highest(10)[1] to position the label vertically,
By using the highest high of the previous 10 bars, we prevent the label from moving during the realtime bar. You
may need to adapt this y position in other contexts.
• We call the print() function twice to show that if you make multiple calls because it makes debugging multiple
strings easier, you can superimpose their text by using the correct amount of newlines (\n) to separate each one.
• We use the str.tostring() function to convert numeric values to a string for inclusion in the text to be displayed.

5.2. Debugging 411


Pine Script™ v5 User Manual

5.2.5 Debugging conditions

Single conditions

Many methods can be used to display occurrences where a condition is met. This code shows six ways to identify bars
where RSI is smaller than 30:

1 //@version=5
2 indicator("Single conditions")
3 r = ta.rsi(close, 20)
4 rIsLow = r < 30
5 hline(30)
6

7 // Method #1: Change the plot's color.


8 plot(r, "RSI", rIsLow ? color.fuchsia : color.black)
9 // Method #2: Plot a character in the bottom region of the display.
10 plotchar(rIsLow, "rIsLow char at bottom", "▲", location.bottom, size = size.small)
11 // Method #3: Plot a character on the RSI line.
12 plotchar(rIsLow ? r : na, "rIsLow char on line", "•", location.absolute, color.red,␣
,→size = size.small)

13 // Method #4: Plot a shape in the top region of the display.


14 plotshape(rIsLow, "rIsLow shape", shape.arrowup, location.top)
15 // Method #5: Plot an arrow.
16 plotarrow(rIsLow ? 1 : na, "rIsLow arrow")
17 // Method #6: Change the background's color.
18 bgcolor(rIsLow ? color.new(color.green, 90) : na)

Note that:
• We define our condition in the rIsLow boolean variable and it is evaluated on each bar. The r < 30 expression
used to assign a value to the variable evaluates to true or false (or na when r is na, as is the case in the first
bars of the dataset).
• Method #1 uses a change in the color of the RSI plot on the condition. Whenever a plot’s color changes, it colors
the plot starting from the preceding bar.
• Method #2 uses plotchar() to plot an up triangle in the bottom part of the indicator’s display. Using different
combinations of positions and characters allows the simultaneous identification of multiple conditions on a single
bar. This is one of our preferred methods to identify conditions on the chart.
• Method #3 also uses a plotchar() call, but this time the character is positioned on the RSI line. In order to achieve
this, we use location.absolute and Pine Script™’s ?: ternary conditional operator to define a conditional expression
where a y position is used only when our rIsLow condition is true. When it is not true, na is used, so no character
is displayed.

412 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

• Method #4 uses plotshape() to plot a blue up arrow in the top part of the indicator’s display area when our condition
is met.
• Method #5 uses plotarrow() to plot a green up arrow at the bottom of the display when our condition is met.
• Method #6 uses bgcolor() to change the color of the background when our condition is met. The ternary operator
is used once again to evaluate our condition. It will return color.green when rIsLow is true, and the na
color (which does not color the background) when rIsLow is false or na.
• Lastly, note how a boolean variable with a true value displays as 1 in the Data Window. false values are
denoted by a zero value.

Compound conditions

Programmers needing to identify situations where more than one condition is met must build compound conditions by
aggregating individual conditions using the and logical operator. Because compound conditions will only perform as
expected if their individual conditions trigger correctly, you will save yourself many headaches if you validate the behavior
of individual conditions before using a compound condition in your code.
The state of multiple individual conditions can be displayed using a technique like this one, where four individual condi-
tions are used to build our bull compound condition:

1 //@version=5
2 indicator("Compound conditions")
3 periodInput = input.int(20)
4 bullLevelInput = input.int(55)
5

6 r = ta.rsi(close, periodInput)
7

8 // Condition #1.
9 rsiBull = r > bullLevelInput
10 // Condition #2.
11 hiChannel = ta.highest(r, periodInput * 2)[1]
12 aboveHiChannel = r > hiChannel
13 // Condition #3.
14 channelIsOld = hiChannel >= hiChannel[periodInput]
15 // Condition #4.
16 historyIsBull = math.sum(rsiBull ? 1 : -1, periodInput * 3) > 0
17 // Compound condition.
18 bull = rsiBull and aboveHiChannel and channelIsOld and historyIsBull
19

20 hline(bullLevelInput)
21 plot(r, "RSI", color.black)
22 plot(hiChannel, "High Channel")
23

24 plotchar(rsiBull ? bullLevelInput : na, "rIsBull", "1", location.absolute, color.


,→green, size = size.tiny)

25 plotchar(aboveHiChannel ? r : na, "aboveHiChannel", "2", location.absolute, size =␣


,→size.tiny)

26 plotchar(channelIsOld, "channelIsOld", "3", location.bottom, size = size.tiny)


27 plotchar(historyIsBull, "historyIsBull", "4", location.top, size = size.tiny)
28 bgcolor(bull ? not bull[1] ? color.new(color.green, 50) : color.new(color.green, 90)␣
,→: na)

5.2. Debugging 413


Pine Script™ v5 User Manual

Note that:
• We use a plotchar() call to display each condition’s number, taking care to spread them over the indicator’s y space
so they don’t overlap.
• The first two plotchar() calls use absolute positioning to place the condition number so that it helps us remember
the corresponding condition. The first one which displays “1” when RSI is higher than the user-defined bull level
for example, positions the “1” on the bull level.
• We use two different shades of green to color the background: the brighter one indicates the first bar where our
compound condition becomes true, the lighter green identifies subsequent bars where our compound condition
continues to be true.
• While it is not always strictly necessary to assign individual conditions to a variable because they can be used directly
in boolean expressions, it makes for more readable code when you assign a condition to a variable name that will
remind you and your readers of what it represents. Readability considerations should always prevail in cases like
this one, where the hit on performance of assigning conditions to variable names is minimal or null.

5.2.6 Debugging from inside functions

Variables in function are local to the function, so not available for plotting from the script’s global scope. In this script we
have written the hlca() function to calculate a weighed average:

1 //@version=5
2 indicator("Debugging from inside functions", "", true)
3 hlca() =>
4 var float avg = na
5 hlca = math.avg(high, low, close, nz(avg, close))
6 avg := ta.sma(hlca, 20)
7

8 h = hlca()
9 plot(h)

We need to inspect the value of hlca in the function’s local scope as the function calculates, bar to bar. We cannot access
the hlca variable used inside the function from the script’s global scope. We thus need another mechanism to pull that
variable’s value from inside the function’s local scope, while still being able to use the function’s result. We can use Pine
Script™’s ability to have functions return a tuple to gain access to the variable:

1 //@version=5
2 indicator("Debugging from inside functions", "", true)
3 hlca() =>
4 var float avg = na
5 instantVal = math.avg(high, low, close, nz(avg, close))
6 avg := ta.sma(instantVal, 20)
(continues on next page)

414 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

(continued from previous page)


7 // Return two values instead of one.
8 [avg, instantVal]
9

10 [h, instantVal] = hlca()


11 plot(h, "h")
12 plot(instantVal, "instantVal", color.black)

Contrary to global scope variables, array elements of globally defined arrays can be modified from within functions. We
can use this feature to write a functionally equivalent script:
1 //@version=5
2 indicator("Debugging from inside functions", "", true)
3 // Create an array containing only one float element.
4 instantValGlobal = array.new_float(1)
5 hlca() =>
6 var float avg = na
7 instantVal = math.avg(high, low, close, nz(avg, close))
8 // Set the array's only element to the current value of `_instantVal`.
9 array.set(instantValGlobal, 0, instantVal)
10 avg := ta.sma(instantVal, 20)
11

12 h = hlca()
13 plot(h, "h")
14 // Retrieve the value of the array's only element which was set from inside the␣
,→function.

15 plot(array.get(instantValGlobal, 0), "instantValGlobal", color.black)

5.2.7 Debugging from inside `for` loops

Values inside for loops cannot be plotted using plot() calls in the loop. As in functions, such variables are also local to the
loop’s scope. Here, we explore three different techniques to inspect variable values originating from for loops, starting
from this code example, which calculates the balance of bars in the lookback period which have a higher/lower true range
value than the current bar:
1 //@version=5
2 indicator("Debugging from inside `for` loops")
3 lookbackInput = input.int(20, minval = 0)
4

5 float trBalance = 0
6 for i = 1 to lookbackInput
7 trBalance := trBalance + math.sign(ta.tr - ta.tr[i])
8

(continues on next page)

5.2. Debugging 415


Pine Script™ v5 User Manual

(continued from previous page)


9 hline(0)
10 plot(trBalance)

Extracting a single value

If we want to inspect the value of a variable at a single point in the loop, we can save it and plot it once the loop is exited.
Here, we save the value of tr in the val variable at the loop’s last iteration:

1 //@version=5
2 indicator("Debugging from inside `for` loops", max_lines_count = 500, max_labels_
,→count = 500)

3 lookbackInput = input.int(20, minval = 0)


4

5 float val = na
6 float trBalance = 0
7 for i = 1 to lookbackInput
8 trBalance := trBalance + math.sign(ta.tr - ta.tr[i])
9 if i == lookbackInput
10 val := ta.tr[i]
11 hline(0)
12 plot(trBalance)
13 plot(val, "val", color.black)

Using lines and labels

When we want to extract values from more than one loop iteration we can use lines and labels. Here we draw a line
corresponding to the value of ta.tr used in each loop iteration. We also use a label to display, for each line, the loop’s
index and the line’s value. This gives us a general idea of the values being used in each loop iteration:

1 //@version=5
2 indicator("Debugging from inside `for` loops", max_lines_count = 500, max_labels_
,→count = 500)

3 lookbackInput = input.int(20, minval = 0)


4

5 float trBalance = 0
6 for i = 1 to lookbackInput
7 trBalance := trBalance + math.sign(ta.tr - ta.tr[i])
8 line.new(bar_index[1], ta.tr[i], bar_index, ta.tr[i], color = color.black)
9 label.new(bar_index, ta.tr[i], str.tostring(i) + "•" + str.tostring(ta.tr[i]),␣
,→style = label.style_none, size = size.small)

(continues on next page)

416 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

(continued from previous page)


10

11 hline(0)
12 plot(trBalance)

Note that:
• To show more detail, the scale in the preceding screenshot has been manually expanded by clicking and dragging
the scale area.
• We use max_lines_count = 500, max_labels_count = 500 in our indicator() declaration statement
to display the maximum number of lines and labels.
• Each loop iteration does not necessarily produce a distinct ta.tr value, which is why we may not see 20 distinct lines
for each bar.
• If we wanted to show only one level, we could use the same technique while isolating a specific loop iteration as we
did in the preceding example.

5.2. Debugging 417


Pine Script™ v5 User Manual

Extracting multiple values

We can also extract multiple values from loop iterations by building a single string which we will display using a label
after the loop executes:

1 //@version=5
2 indicator("Debugging from inside `for` loops", max_lines_count = 500, max_labels_
,→count = 500)

3 lookbackInput = input.int(20, minval = 0)


4

5 string = ""
6 float trBalance = 0
7 for i = 1 to lookbackInput
8 trBalance := trBalance + math.sign(ta.tr - ta.tr[i])
9 string := string + str.tostring(i, "00") + "•" + str.tostring(ta.tr[i]) + "\n"
10

11 label.new(bar_index, 0, string, style = label.style_none, size = size.small,␣


,→textalign = text.align_left)

12 hline(0)
13 plot(trBalance)

Note that:
• The scale in the preceding screenshot has been manually expanded by clicking and dragging the scale area so the
content of the indicator’s display area content could be moved vertically to show only its relevant part.
• We use str.tostring(i, "00") to force the display of the loop’s index to zero-padded two digits so they
align neatly.
When loops with numerous iterations make displaying all their values impractical, you can sample a subset of the iterations.
This code uses the % (modulo) operator to include values from every second loop iteration:

for i = 1 to i_lookBack
lowerRangeBalance := lowerRangeBalance + math.sign(ta.tr - ta.tr[i])
if i % 2 == 0
string := string + str.tostring(i, "00") + "•" + str.tostring(ta.tr[i]) + "\n"

418 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

5.2.8 Tips

The two techniques we use most frequently to debug our Pine Script™ code are:

plotchar(v, "v", "", location.top, size = size.tiny)

to plot variables of type float, int or bool in the indicator’s values and the Data Window, and the one-line version of our
print() function to debug strings:

print(txt) => var _label = label.new(bar_index, na, txt, xloc.bar_index, yloc.price,␣


,→color(na), label.style_none, color.gray, size.large, text.align_left), label.set_

,→xy(_label, bar_index, ta.highest(10)[1]), label.set_text(_label, txt)

print(stringName)

As we use AutoHotkey for Windows to speed repetitive tasks, we include these lines in our AutoHotkey script (this is not
Pine Script™ code):

; ————— This is AHK code, not Pine Script™. —————


^+f:: SendInput plotchar(^v, "^v", "", location.top, size = size.tiny){Return}
^+p:: SendInput print(txt) => var lbl = label.new(bar_index, na, txt, xloc.bar_index,␣
,→yloc.price, color(na), label.style_none, color.gray, size.large, text.align_left),␣

,→label.set_xy(lbl, bar_index, highest(10)[1]), label.set_text(lbl, txt)`nprint()

,→{Left}

The second line will type a debugging plotchar() call including an expression or variable name previously copied to the
clipboard when we use ctrl + shift + f. Copying the variableName variable name or the close > open
conditional expression to the clipboard and hitting ctrl + shift + f will, respectively, yield:

plotchar(variableName, "variableName", "", location.top, size = size.tiny)


plotchar(close > open, "close > open", "", location.top, size = size.tiny)

The third line triggers on ctrl + shift + p. It types our one-line print() function in a script and on a second line,
an empty call to the function with the cursor placed so all that’s left to do is type the string we want to display:

print(txt) => var lbl = label.new(bar_index, na, txt, xloc.bar_index, yloc.price,␣


,→color(na), label.style_none, color.gray, size.large, text.align_left), label.set_

,→xy(lbl, bar_index, ta.highest(10)[1]), label.set_text(lbl, txt)

print()

Note: AutoHotkey works only on Windows systems. Keyboard Maestro or others can be substituted on Apple systems.

5.2. Debugging 419


Pine Script™ v5 User Manual

5.3 Publishing scripts

• Script visibility and access


• Preparing a publication
• Publishing a script
• Updating a publication

Programmers who wish to share their Pine scripts with other traders can publish them.

Note: If you write scripts for your personal use, there is no need to publish them; you can save them in the Pine Editor
and use the “Add to Chart” button to add your script to your chart.

5.3.1 Script visibility and access

When you publish a script, you control its visibility and access:
• Visibility is controlled by choosing to publish publicly or privately. See How do private ideas and scripts differ
from public ones? in the Help Center for more details. Publish publicly when you have written a script you think
can be useful to TradingViewers. Public scripts are subject to moderation. To avoid moderation, ensure your
publication complies with our House Rules and Script Publishing Rules. Publish privately when you don’t want
your script visible to all other users, but want to share it with a few friends.
• Access determines if users will see your source code, and how they will be able to use your script. There are three
access types: open, protected (reserved to paid accounts) or invite-only (reserved to Premium accounts). See What
are the different types of published scripts? in the Help Center for more details.

When you publish a script

• The publication’s title is determined by the argument used for the title parameter in the script’s indicator() or
strategy() declaration statement. That title is also used when TradingViewers search for script names.
• The name of your script on the chart will be the argument used for the shorttitle parameter in the script’s
indicator() or strategy() declaration statement, or the title argument in library().
• Your script must have a description explaining what your script does and how to use it.
• The chart you are using when you publish will become visible in your publication, including any other scripts or
drawings on it. Remove unrelated scripts or drawings from your chart before publishing your script.
• Your script’s code can later be updated. Each update can include release notes which will appear, dated, under your
original description.
• Scripts can be liked, shared, commented on or reported by other users.
• Your published scripts appear under the “SCRIPTS” tab of your user profile.
• A script widget and a script page are created for your script. The script widget is your script’s placeholder showing
in script feeds on the platform. It contains your script’s title, chart and the first few lines of your description. When
users click on your script widget, the script’s page opens. It contains all the information relating to your script.

420 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

Visibility

Public

When you publish a public script:


• Your script will be inluded in our Community Scripts where it becomes visible to the millions of TradingViewers
on all internationalized versions of the site.
• Your publication must comply with House Rules and Script Publishing Rules.
• If your script is an invite-only script, you must comply with our Vendor Requirements.
• It becomes accessible through the search functions for scripts.
• You will not be able to edit your original description or its title, nor change its public/private visibility, nor its access
type (open-source, protected, invite-only).
• You will not be able to delete your publication.

Private

When you publish a private script:


• It will not be visible to other users unless you share its url with them.
• It is visible to you from your user profile’s “SCRIPTS” tab.
• Private scripts are identifiable by the “X” and “lock” icons in the top-right of their widget. The “X” is used to delete
it.
• It is not moderated, unless you sell access to it or make it available publicly, as it is then no longer “private”.
• You can update its original description and title.
• You cannot link to or mentioned it from any public TradingView content (ideas, script descriptions, comments,
chats, etc.).
• It is not accessible through the search functions for scripts.

Access

Public or private scripts can be published using one of three access types: open, protected or invite-only. The access type
you can select from will vary with the type of account you hold.

Open

The Pine Script™ code of scripts published open is visible to all users. Open-source scripts on TradingView use the
Mozilla license by default, but you may choose any license you want. You can find information on licensing at GitHub.

5.3. Publishing scripts 421


Pine Script™ v5 User Manual

Protected

The code of protected scripts is hidden from view and no one but its author can access it. While the script’s code is not
accessible, protected scripts can be used freely by any user. Only Pro, Pro+ or Premium accounts may publish public
protected scripts.

Invite-only

The invite-only access type protects both the script’s code and its use. The publisher of an invite-only script must explicitly
grant access to individual users. Invite-only scripts are mostly used by script vendors providing paid access to their scripts.
Only Premium accounts can publish invite-only scripts, and they must comply with our Vendor Requirements.
TradingView does not benefit from script sales. Transactions concerning invite-only scripts are strictly between users and
vendors; they do not involve TradingView.
Public invite-only scripts are the only scripts for which vendors are allowed to ask for payment on TradingView.
On their invite-only script’s page, authors will see a “Manage Access” button. The “Manage Access” window allows
authors to control who has access to their script.

422 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

5.3.2 Preparing a publication

1. Even if you intend to publish publicly, it is always best to start with a private publication because you can use it
to validate what your final publication will look like. You can edit the title, description, code or chart of private
publications, and contrary to public scripts, you can delete private scripts when you don’t need them anymore, so
they are the perfect way to practice before sharing a script publicly. You can read more about preparing script
descriptions in the How We Write and Format Script Descriptions publication.
2. Prepare your chart. Load your script on the chart and remove other scripts or drawings that won’t help users
understand your script. Your script’s plots should be easy to identify on the chart that will be published with it.
3. Load your code in the Pine Editor if it isn’t already. In the Editor, click the “Publish Script” button:

4. A popup appears to remind you that if you publish publicly, it’s important that your publication comply with House
Rules. Once you’re through the popup, place your description in the field below the script’s title. The default title
proposed for your publication is the title field from your script’s code. It is always best to use that title; it makes
it easier for users to search for your script if it is public. Select the visibility of your publication. We want to publish
a private publication, so we check the “Private Script” checkbox at the bottom-right of the “Publish Script” window:

5.3. Publishing scripts 423


Pine Script™ v5 User Manual

5. Select the access type you want for your script: Open, Protected or Invite-only. We have selected “Open” for open-

source.
6. Select the appropriate categories for your script (at least one is mandatory) and enter optional custom tags.

424 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

7. Click the “Publish Private Script” button in the lower-right of the window. When the publication is complete, your
published script’s page will appear. You are done! You can confirm the publication by going to your User Profile
and viewing your “SCRIPTS” tab. From there, you will be able to open your script’s page and edit your private
publication by using the “Edit” button in the top-right of your script’s page. Note that you can also update private
publications, just like you can public ones. If you want to share your private publication with a friend, privately
send her the url from your script’s page. Remember you are not allowed to share links to private publications in
public TradingView content.

5.3.3 Publishing a script

Whether you intend to publish privately or publicly, first follow the steps in the previous section. If you intend to publish
privately, you will be done. If you intend to publish publicly and are satisfied with the preparatory process of validating
your private publication, follow the same steps as above but do not check the “Private Script” checkbox and click the
“Publish Public Script” button at the bottom-right of the “Publish Script” page.
When you publish a new public script, you have a 15-minute window to make changes to your description or delete the
publication. After that you will no longer be able to change your publication’s title, description, visiblity or access type.
If you make an error, send a message to the PineCoders moderator account; they moderate script publications and will
help.

5.3.4 Updating a publication

You can update both public or private script publications. When you update a script, its code must be different than the
previously published version’s code. You can add release notes with your update. They will appear after your script’s
original description in the script’s page.
By default, the chart used when you update will replace the previous chart in your script’s page. You can choose not to
update your script page’s chart, however. Note that while you can update the chart displayed in the script’s page, the chart
from the script’s widget will not update.
In the same way you can validate a public publication by first publishing a private script, you can also validate an update
on a private publication before proceeding with it on your public one. The process of updating a published script is the
same for public and private scripts.

5.3. Publishing scripts 425


Pine Script™ v5 User Manual

If you intend to update both the code and chart of your published script, prepare your chart the same way you would for
a new publication. In the following example, we will not be updating the publication’s chart:
1. As you would for a new publication, load your script in the Editor and click the “Publish Script” button.
2. Once in the “Publish Script” window, select the “Update Existing Script” but-
ton. Then select the script to update from the “Choose script” dropdown menu:

3. Enter your release notes in the text field. The differences in your code are highlighted below your release notes.
4. We do not want to update the publication’s chart, so we check the “Don’t update the chart” checkbox:

5. Click the “Publish New Version” button. You’re done.

426 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

5.4 Limitations

• Introduction
• Time
• Chart visuals
• `request.*()` calls
• Script size and memory
• Other limitations

5.4.1 Introduction

As is mentioned in our Welcome page:


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.
If you develop complex scripts using Pine Script™, sooner or later you will run into some of the limitations we impose.
This section provides you with an overview of the limitations that you may encounter. There are currently no means for
Pine Script™ programmers to get data on the resources consumed by their scripts. We hope this will change in the future.
In the meantime, when you are considering large projects, it is safest to make a proof of concept in order to assess the
probability of your script running into limitations later in your project.
Here are the limits imposed in the Pine Script™ environment.

5.4.2 Time

Script compilation

Scripts must compile before they are executed on charts. Compilation occurs when you save a script from the editor
or when you add a script to the chart. A two-minute limit is imposed on compilation time, which will depend on the
size and complexity of your script, and whether or not a cached version of a previous compilation is available. When a
compile exceeds the two-minute limit, a warning is issued. Heed that warning by shortening your script because after three
consecutives warnings a one-hour ban on compilation attempts is enforced. The first thing to consider when optimizing
code is to avoid repetitions by using functions to encapsulate oft-used segments, and call functions instead of repeating
code.

5.4. Limitations 427


Pine Script™ v5 User Manual

Script execution

Once a script is compiled it can be executed. See the Events triggering the execution of a script for a list of the events
triggering the execution of a script. The time allotted for the script to execute on all bars of a dataset varies with account
types. The limit is 20 seconds for basic accounts, 40 for others.

Loop execution

The execution time for any loop on any single bar is limited to 500 milliseconds. The outer loop of embedded loops
counts as one loop, so it will time out first. Keep in mind that even though a loop may execute under the 500 ms time
limit on a given bar, the time it takes to execute on all the dataset’s bars may nonetheless cause your script to exceed the
total execution time limit. For example, the limit on total execution time will make it impossible for you script to execute
a 400 ms loop on each bar of a 20,000-bar dataset because your script would then need 8000 seconds to execute.

5.4.3 Chart visuals

Plot limits

A maximum of 64 plot counts are allowed per script. The functions that generate plot counts are:
• plot()
• plotarrow()
• plotbar()
• plotcandle()
• plotchar()
• plotshape()
• alertcondition()
• bgcolor()
• fill(), but only if its color is of the series form.
The following functions do not generate plot counts:
• hline()
• line.new()
• label.new()
• table.new()
• box.new()
One function call can generate up to seven plot counts, depending on the function and how it is called. When your script
exceeds the maximum of 64 plot counts, the runtime error message will display the plot count generated by your script.
Once you reach that point, you can determine how many plot counts a function call generates by commenting it out in a
script. As long as your script still throws an error, you will be able to see how the actual plot count decreases after you
have commented out a line.
The following example shows different function calls and the number of plot counts each one will generate:

428 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

1 //@version=5
2 indicator("Plot count example")
3

4 bool isUp = close > open


5 color isUpColor = isUp ? color.green : color.red
6 bool isDn = not isUp
7 color isDnColor = isDn ? color.red : color.green
8

9 // Uses one plot count each.


10 p1 = plot(close, color = color.white)
11 p2 = plot(open, color = na)
12

13 // Uses two plot counts for the `close` and `color` series.
14 plot(close, color = isUpColor)
15

16 // Uses one plot count for the `close` series.


17 plotarrow(close, colorup = color.green, colordown = color.red)
18

19 // Uses two plot counts for the `close` and `colorup` series.
20 plotarrow(close, colorup = isUpColor)
21

22 // Uses three plot counts for the `close`, `colorup`, and the `colordown` series.
23 plotarrow(close - open, colorup = isUpColor, colordown = isDnColor)
24

25 // Uses four plot counts for the `open`, `high`, `low`, and `close` series.
26 plotbar(open, high, low, close, color = color.white)
27

28 // Uses five plot counts for the `open`, `high`, `low`, `close`, and `color` series.
29 plotbar(open, high, low, close, color = isUpColor)
30

31 // Uses four plot counts for the `open`, `high`, `low`, and `close` series.
32 plotcandle(open, high, low, close, color = color.white, wickcolor = color.white,␣
,→bordercolor = color.purple)

33

34 // Uses five plot counts for the `open`, `high`, `low`, `close`, and `color` series.
35 plotcandle(open, high, low, close, color = isUpColor, wickcolor = color.white,␣
,→bordercolor = color.purple)

36

37 // Uses six plot counts for the `open`, `high`, `low`, `close`, `color`, and␣
,→`wickcolor` series.

38 plotcandle(open, high, low, close, color = isUpColor, wickcolor = isUpColor ,␣


,→bordercolor = color.purple)

39

40 // Uses seven plot counts for the `open`, `high`, `low`, `close`, `color`,␣
,→`wickcolor`, and `bordercolor` series.

41 plotcandle(open, high, low, close, color = isUpColor, wickcolor = isUpColor ,␣


,→bordercolor = isUp ? color.lime : color.maroon)

42

43 // Uses one plot count for the `close` series.


44 plotchar(close, color = color.white, text = "|", textcolor = color.white)
45

46 // Uses two plot counts for the `close`` and `color` series.
47 plotchar(close, color = isUpColor, text = "—", textcolor = color.white)
48

49 // Uses three plot counts for the `close`, `color`, and `textcolor` series.
50 plotchar(close, color = isUpColor, text = "O", textcolor = isUp ? color.yellow :␣
,→color.white)

(continues on next page)

5.4. Limitations 429


Pine Script™ v5 User Manual

(continued from previous page)


51

52 // Uses one plot count for the `close` series.


53 plotshape(close, color = color.white, textcolor = color.white)
54

55 // Uses two plot counts for the `close` and `color` series.
56 plotshape(close, color = isUpColor, textcolor = color.white)
57

58 // Uses three plot counts for the `close`, `color`, and `textcolor` series.
59 plotshape(close, color = isUpColor, textcolor = isUp ? color.yellow : color.white)
60

61 // Uses one plot count.


62 alertcondition(close > open, "close > open", "Up bar alert")
63

64 // Uses one plot count.


65 bgcolor(isUp ? color.yellow : color.white)
66

67 // Uses one plot count for the `color` series.


68 fill(p1, p2, color = isUpColor)

This example generates a plot count of 56. If we were to add two more instances of the last call to plotcandle(), the script
would throw an error stating that the script now uses 70 plot counts, as each additional call to plotcandle() generates seven
plot counts, and 56 + (7 * 2) is 70.

Line, box, and label limits

Contrary to plots which can cover the entire dataset, by default, only the last 50 lines drawn by a script are visible on
charts. The same goes for boxes and labels. You can increase the quantity of drawing objects preserved on charts up to a
maximum of 500 by using the max_lines_count, max_boxes_count or max_labels_count parameters in
the indicator() or strategy() declaration statements.
In this example we set the maximum quantity of last labels shown on the chart to 100:

1 //@version=5
2 indicator("Label limits example", max_labels_count = 100, overlay = true)
3 label.new(bar_index, high, str.tostring(high, format.mintick))

It’s important to note that when you set any of the attributes of a drawing object to na, it still counts as a drawing on the
chart and thus contributes to a script’s drawing totals. To demonstrate this, the following script draws a “Buy” and “Sell”
label on each bar with x values determined by the longCondition and shortCondition variables. The “Buy”
label’s x value is na when the bar index is even, and the “Sell” label’s x value is na when the bar index is odd. Although
the max_labels_count is 10 in this example, we can see that the script displays fewer than ten labels on the chart
since the ones with na values also count toward the total:

430 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

1 //@version=5
2

3 // Approximate maximum number of label drawings


4 MAX_LABELS = 10
5

6 indicator("labels with na", overlay = false, max_labels_count = MAX_LABELS)


7

8 // Add background color for the last MAX_LABELS bars.


9 bgcolor(bar_index > last_bar_index - MAX_LABELS ? color.new(color.green, 80) : na)
10

11 longCondition = bar_index % 2 != 0
12 shortCondition = bar_index % 2 == 0
13

14 // Add "Buy" and "Sell" labels on each new bar.


15 label.new(longCondition ? bar_index : na, 0, text = "Buy", color = color.new(color.
,→green, 0), style = label.style_label_up)

16 label.new(shortCondition ? bar_index : na, 0, text = "Sell", color = color.new(color.


,→red, 0), style = label.style_label_down)

17

18 plot(longCondition ? 1 : 0)
19 plot(shortCondition ? 1 : 0)

If we want the script to display the desired number of labels, we need to eliminate the ones with na x values so that they
don’t add to the script’s label count. This example conditionally draws the “Buy” and “Sell” labels rather than always
drawing them and setting their attributes to na on alternating bars:

1 //@version=5
2

(continues on next page)

5.4. Limitations 431


Pine Script™ v5 User Manual

(continued from previous page)


3 // Approximate maximum number of label drawings
4 MAX_LABELS = 10
5

6 indicator("conditional labels", overlay = false, max_labels_count = MAX_LABELS)


7

8 // Add background color for the last MAX_LABELS bars.


9 bgcolor(bar_index > last_bar_index - MAX_LABELS ? color.new(color.green, 80) : na)
10

11 longCondition = bar_index % 2 != 0
12 shortCondition = bar_index % 2 == 0
13

14 // Add a "Buy" label when `longCondition` is true.


15 if longCondition
16 label.new(bar_index, 0, text = "Buy", color = color.new(color.green, 0), style =␣
,→label.style_label_up)

17 // Add a "Sell" label when `shortCondition` is true.


18 if shortCondition
19 label.new(bar_index, 0, text = "Sell", color = color.new(color.red, 0), style =␣
,→label.style_label_down)

20

21 plot(longCondition ? 1 : 0)
22 plot(shortCondition ? 1 : 0)

Table limits

A maximum of nine tables can be displayed by a script, one for each of the possible locations: position.bottom_center,
position.bottom_left, position.bottom_right, position.middle_center, position.middle_left, position.middle_right, posi-
tion.top_center, position.top_left, or position.top_right. If you place two tables in the same position, only the most
recently added table will be visible.

5.4.4 `request.*()` calls

Number of calls

A script cannot make more than 40 calls to request.*() functions. All instances of calls to these functions are
counted, even if they are included in code blocks or functions that are never actually used in the script’s logic. The func-
tions counting towards this limit are: request.security(), request.security_lower_tf(), request.quandl(), request.financial(),
request.dividends(), request.earnings() and request.splits().

Intrabars

When accessing lower timeframes, with request.security() or request.security_lower_tf(), a maximum of 100,000 intra-
bars can be used in calculations.
The quantity of chart bars covered with 100,000 intrabars will vary with the ratio of the chart’s timeframe to the lower
timeframe used, and with the average number of intrabars contained in each chart bar. For example, when using a 1min
lower timeframe, chart bars at the 60min timeframe of an active 24x7 market will usually contain 60 intrabars each.
Because 100,000 / 60 = 1666.67, the quantity of chart bars covered by the 100,000 intrabars will typically be 1666. On
markets where 60min chart bars do not always contain 60 1min intrabars, more chart bars will be covered.

432 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

Tuple element limit

All the request.*() functions in one script taken together cannot return more than 127 tuple values. Below we have
an example showing what can cause this error and how to work around it:

1 //@version=5
2 indicator("Tuple values error")
3

4 // CAUSES ERROR:
5 [v1, v2, v3,...] = request.security(syminfo.tickerid, "1D", [s1, s2, s3,...])
6

7 // Works fine:
8 type myType
9 int v1
10 int v2
11 int v3
12 ...
13

14 myObj = request.security(syminfo.tickerid, "1D", myType.new())

Note that:
• In this example, we have a request.security() function with at least three values in our tuple, and we could either
have more than 127 values in our tuple above or more than 127 values between multiple request.security() functions
to throw this error.
• We get around the error by simply creating a User-defined object that can hold the same values without throwing
an error.
• Using the myType.new() function is functionally the same as listing the same values in our [s1, s2, s3,
...] tuple.

5.4.5 Script size and memory

Compiled tokens

Before a script is executed, it is compiled into a tokenized Intermediate Language (IL). Using an IL allows Pine Script™
to accommodate longer scripts by applying various optimizations before it is executed. The compiled form of each
indicator, strategy, and library is limited to 68,000 tokens. When a script imports libraries, the total number of tokens
from all libraries it imports cannot exceed 1 million. There is no way to inspect the number of tokens created during
compilation; you will only know your script exceeds the limit when the compiler reaches it.
Replacing code repetitions with function calls and using libraries to offload some of the workload are the most efficient
ways to decrease the number of tokens your compiled script will generate.
The size of variable names and comments do not affect the number of compiled tokens.

5.4. Limitations 433


Pine Script™ v5 User Manual

Local blocks

Local blocks are segments of indented code used in function definitions or in if, switch, for or while structures, which
allow for one or more local blocks.
Scripts are limited to 500 local blocks.

Variables

A maximum of 1000 variables are allowed per scope. Pine scripts always contain one global scope, and can contain zero
or more local scopes. Local scopes are created by indented code such as can be found in functions or if, switch, for or
while structures, which allow for one or more local blocks. Each local block counts as one local scope.
The branches of a conditional expression using a ?: ternary operator do not count as local blocks.

Collections

Pine Script™ collections (arrays, matrices, and maps) can have a maximum of 100,000 elements. Each key-value pair
in a map contains two elements, meaning maps can contain a maximum of 50,000 key-value pairs.

5.4.6 Other limitations

Maximum bars back

References to past values using the [] history-referencing operator are dependent on the size of the historical buffer
maintained by the Pine Script™ runtime, which is limited to a maximum of 5000 bars. This Help Center page discusses
the historical buffer and how to change its size using either the max_bars_back parameter or the max_bars_back()
function.

Maximum bars forward

When positioning drawings using xloc.bar_index, it is possible to use bar index values greater than that of the
current bar as x coordinates. A maximum of 500 bars in the future can be referenced.
This example shows how we use the maxval parameter in our input.int() function call to cap the user-defined number of
bars forward we draw a projection line so that it never exceeds the limit:
1 //@version=5
2 indicator("Max bars forward example", overlay = true)
3

4 // This function draws a `line` using bar index x-coordinates.


5 drawLine(bar1, y1, bar2, y2) =>
6 // Only execute this code on the last bar.
7 if barstate.islast
8 // Create the line only the first time this function is executed on the last␣
,→bar.

9 var line lin = line.new(bar1, y1, bar2, y2, xloc.bar_index)


10 // Change the line's properties on all script executions on the last bar.
11 line.set_xy1(lin, bar1, y1)
12 line.set_xy2(lin, bar2, y2)
13

14 // Input determining how many bars forward we draw the `line`.


15 int forwardBarsInput = input.int(10, "Forward Bars to Display", minval = 1, maxval =␣
(continues on next page)

434 Chapter 5. Writing scripts


Pine Script™ v5 User Manual

(continued from previous page)


,→ 500)
16

17 // Calculate the line's left and right points.


18 int leftBar = bar_index[2]
19 float leftY = high[2]
20 int rightBar = leftBar + forwardBarsInput
21 float rightY = leftY + (ta.change(high)[1] * forwardBarsInput)
22

23 // This function call is executed on all bars, but it only draws the `line` on the␣
,→last bar.

24 drawLine(leftBar, leftY, rightBar, rightY)

Chart bars

The number of bars appearing on charts is dependent on the amount of historical data available for the chart’s symbol and
timeframe, and on the type of account you hold. When the required historical date is available, the minimum number of
chart bars is:
• 20,000 bars for the Premium plan.
• 10,000 bars for Pro and Pro+ plans.
• 5000 bars for other plans.

Trade orders in backtesting

A maximum of 9000 orders can be placed when backtesting strategies. When using Deep Backtesting, the limit is
200,000.

5.4. Limitations 435


Pine Script™ v5 User Manual

436 Chapter 5. Writing scripts


CHAPTER

SIX

FAQ

• Get real OHLC price on a Heikin Ashi chart


• Get non-standard OHLC values on a standard chart
• Plot arrows on the chart
• Plot a dynamic horizontal line
• Plot a vertical line on condition
• Access the previous value
• Get a 5-days high
• Count bars in a dataset
• Enumerate bars in a day
• Find the highest and lowest values for the entire dataset
• Query the last non-na value

6.1 Get real OHLC price on a Heikin Ashi chart

Suppose, we have a Heikin Ashi chart (or Renko, Kagi, PriceBreak etc) and we’ve added a Pine script on it:

1 //@version=5
2 indicator("Visible OHLC", overlay=true)
3 c = close
4 plot(c)

You may see that variable c is a Heikin Ashi close price which is not the same as real OHLC price. Because close
built-in variable is always a value that corresponds to a visible bar (or candle) on the chart.
So, how do we get the real OHLC prices in Pine Script™ code, if current chart type is non-standard? We should use
request.security function in combination with ticker.new function. Here is an example:

1 //@version=5
2 indicator("Real OHLC", overlay = true)
3 t = ticker.new(syminfo.prefix, syminfo.ticker)
4 realC = request.security(t, timeframe.period, close)
5 plot(realC)

In a similar way we may get other OHLC prices: open, high and low.

437
Pine Script™ v5 User Manual

6.2 Get non-standard OHLC values on a standard chart

Backtesting on non-standard chart types (e.g. Heikin Ashi or Renko) is not recommended because the bars on these kinds
of charts do not represent real price movement that you would encounter while trading. If you want your strategy to enter
and exit on real prices but still use Heikin Ashi-based signals, you can use the same method to get Heikin Ashi values on
a regular candlestick chart:

1 //@version=5
2 strategy("BarUpDn Strategy", overlay = true, default_qty_type = strategy.percent_of_
,→equity, default_qty_value = 10)

3 maxIdLossPcntInput = input.float(1, "Max Intraday Loss(%)")


4 strategy.risk.max_intraday_loss(maxIdLossPcntInput, strategy.percent_of_equity)
5 needTrade() => close > open and open > close[1] ? 1 : close < open and open <␣
,→close[1] ? -1 : 0

6 trade = request.security(ticker.heikinashi(syminfo.tickerid), timeframe.period,␣


,→needTrade())

7 if trade == 1
8 strategy.entry("BarUp", strategy.long)
9 if trade == -1
10 strategy.entry("BarDn", strategy.short)

6.3 Plot arrows on the chart

You may use plotshape with style shape.arrowup and shape.arrowdown:

//@version=5
indicator('Ex 1', overlay = true)
condition = close >= open
plotshape(condition, color = color.lime, style = shape.arrowup, text = "Buy")
plotshape(not condition, color = color.red, style = shape.arrowdown, text = "Sell")

You may use the plotchar function with any unicode character:

438 Chapter 6. FAQ


Pine Script™ v5 User Manual

1 //@version=5
2 indicator('buy/sell arrows', overlay = true)
3 condition = close >= open
4 plotchar(not condition, char='↓', color = color.lime, text = "Buy")
5 plotchar(condition, char='↑', location = location.belowbar, color = color.red, text =
,→"Sell")

6.4 Plot a dynamic horizontal line

There is the function hline in Pine Script™, but it is limited to only plot a constant value. Here is a simple script with
a workaround to plot a changing hline:
1 //@version=5
2 indicator("Horizontal line", overlay = true)
3 plot(close[10], trackprice = true, offset = -9999)
4 // `trackprice = true` plots horizontal line on close[10]
5 // `offset = -9999` hides the plot
6 plot(close, color = #FFFFFFFF) // forces display

6.5 Plot a vertical line on condition

1 //@version=5
2 indicator("Vertical line", overlay = true, scale = scale.none)
3 // scale.none means do not resize the chart to fit this plot
4 // if the bar being evaluated is the last baron the chart (the most recent bar), then␣
,→cond is true

5 cond = barstate.islast
6 // when cond is true, plot a histogram with a line with height value of 100,000,000,
,→000,000,000,000.00

7 // (10 to the power of 20)


(continues on next page)

6.4. Plot a dynamic horizontal line 439


Pine Script™ v5 User Manual

(continued from previous page)


8 // when cond is false, plot no numeric value (nothing is plotted)
9 // use the style of histogram, a vertical bar
10 plot(cond ? 10e20 : na, style = plot.style_histogram)

6.6 Access the previous value

1 //@version=5
2 //...
3 s = 0.0
4 s := nz(s[1]) // Accessing previous values
5 if (condition)
6 s := s + 1

6.7 Get a 5-days high

Lookback 5 days from the current bar, find the highest bar, plot a star character at that price level above the current bar

1 //@version=5
2 indicator("High of last 5 days", overlay = true)
3

4 // Milliseconds in 5 days: millisecs * secs * mins * hours * days


5 MS_IN_5DAYS = 1000 * 60 * 60 * 24 * 5
6

7 // The range check begins 5 days from the current time.


8 leftBorder = timenow - time < MS_IN_5DAYS
9 // The range ends on the last bar of the chart.
10 rightBorder = barstate.islast
11

12 // ————— Keep track of highest `high` during the range.


(continues on next page)

440 Chapter 6. FAQ


Pine Script™ v5 User Manual

(continued from previous page)


13 // Intialize `maxHi` with `var` on bar zero only.
14 // This way, its value is preserved, bar to bar.
15 var float maxHi = na
16 if leftBorder
17 if not leftBorder[1]
18 // Range's first bar.
19 maxHi := high
20 else if not rightBorder
21 // On other bars in the range, track highest `high`.
22 maxHi := math.max(maxHi, high)
23

24 // Plot level of the highest `high` on the last bar.


25 plotchar(rightBorder ? maxHi : na, "Level", "—", location.absolute, size = size.
,→normal)

26 // When in range, color the background.


27 bgcolor(leftBorder and not rightBorder ? color.new(color.aqua, 70) : na)

6.8 Count bars in a dataset

Get a count of all the bars in the loaded dataset. Might be useful for calculating flexible lookback periods based on number
of bars.

1 //@version=5
2 indicator("Bar Count", overlay = true, scale = scale.none)
3 plot(bar_index + 1, style = plot.style_histogram)

6.9 Enumerate bars in a day

1 //@version=5
2 indicator("My Script", overlay = true, scale = scale.none)
3

4 isNewDay() =>
5 d = dayofweek
6 na(d[1]) or d != d[1]
7

8 plot(ta.barssince(isNewDay()), style = plot.style_cross)

6.10 Find the highest and lowest values for the entire dataset

1 //@version=5
2 indicator("", "", true)
3

4 allTimetHi(source) =>
5 var atHi = source
6 atHi := math.max(atHi, source)
7

8 allTimetLo(source) =>
9 var atLo = source
(continues on next page)

6.8. Count bars in a dataset 441


Pine Script™ v5 User Manual

(continued from previous page)


10 atLo := math.min(atLo, source)
11

12 plot(allTimetHi(close), "ATH", color.green)


13 plot(allTimetLo(close), "ATL", color.red)

6.11 Query the last non-na value

You can use the script below to avoid gaps in a series:

1 //@version=5
2 indicator("")
3 series = close >= open ? close : na
4 vw = fixnan(series)
5 plot(series, style = plot.style_linebr, color = color.red) // series has na values
6 plot(vw) // all na values are replaced with the last non-empty value

442 Chapter 6. FAQ


CHAPTER

SEVEN

ERROR MESSAGES

• The if statement is too long


• Script requesting too many securities
• Script could not be translated from: null
• line 2: no viable alternative at character ‘$’
• Mismatched input <…> expecting <???>
• Loop is too long (> 500 ms)
• Script has too many local variables
• Pine Script™ cannot determine the referencing length of a series. Try using max_bars_back in the indicator or
strategy function

7.1 The if statement is too long

This error occurs when the indented code inside an if statement is too large for the compiler. Because of how the compiler
works, you won’t receive a message telling you exactly how many lines of code you are over the limit. The only solution
now is to break up your if statement into smaller parts (functions or smaller if statements). The example below shows a
reasonably lengthy if statement; theoretically, this would throw line 4: if statement is too long.
To fix this code, you could move these lines into their own function:

7.2 Script requesting too many securities

The maximum number of securities in script is limited to 40. If you declare a variable as a request.security
function call and then use that variable as input for other variables and calculations, it will not result in multiple request.
security calls. But if you will declare a function that calls request.security — every call to this function will
count as a request.security call.
It is not easy to say how many securities will be called looking at the source code. Following example have exactly 3 calls
to request.security after compilation:

443
Pine Script™ v5 User Manual

7.3 Script could not be translated from: null

study($)

Usually this error occurs in version 1 Pine scripts, and means that code is incorrect. Pine Script™ of version 2 (and
higher) is better at explaining errors of this kind. So you can try to switch to version 2 by adding a special attribute in the
first line. You’ll get line 2: no viable alternative at character '$':

1 // @version=2
2 study($)

7.4 line 2: no viable alternative at character ‘$’

This error message gives a hint on what is wrong. $ stands in place of string with script title. For example:

1 // @version=2
2 study("title")

7.5 Mismatched input <…> expecting <???>

Same as no viable alternative, but it is known what should be at that place. Example:
line 3: mismatched input 'plot' expecting 'end of line without line continuation'
To fix this you should start line with plot on a new line without an indent:

7.6 Loop is too long (> 500 ms)

We limit the computation time of loop on every historical bar and realtime tick to protect our servers from infinite or very
long loops. This limit also fail-fast indicators that will take too long to compute. For example, if you’ll have 5000 bars,
and indicator takes 500 milliseconds to compute on each of bars, it would have result in more than 16 minutes of loading.
It might be possible to optimize algorithm to overcome this error. In this case, algorithm may be optimized like this:

7.7 Script has too many local variables

This error appears if the script is too large to be compiled. A statement var=expression creates a local variable for
var. Apart from this, it is important to note, that auxiliary variables can be implicitly created during the process of a
script compilation. The limit applies to variables created both explicitly and implicitly. The limitation of 1000 variables
is applied to each function individually. In fact, the code placed in a global scope of a script also implicitly wrapped up
into the main function and the limit of 1000 variables becomes applicable to it. There are few refactorings you can try to
avoid this issue:

var1 = expr1
var2 = expr2
var3 = var1 + var2

can be сonverted into:

444 Chapter 7. Error messages


Pine Script™ v5 User Manual

var3 = expr1 + expr2

7.8 Pine Script™ cannot determine the referencing length of a series.


Try using max_bars_back in the indicator or strategy function

The error appears in cases where Pine Script™ wrongly autodetects the required maximum length of series used in
a script. This happens when a script’s flow of execution does not allow Pine Script™ to inspect the use of series in
branches of conditional statements (if, iff or ?), and Pine Script™ cannot automatically detect how far back the series
is referenced. Here is an example of a script causing this problem:
In order to help Pine Script™ with detection, you should add the max_bars_back parameter to the script’s indi-
cator or strategy function:
You may also resolve the issue by taking the problematic expression out of the conditional branch, in which case the
max_bars_back parameter is not required:
In cases where the problem is caused by a variable rather than a built-in function (vwma in our example), you may use
the max_bars_back function to explicitly define the referencing length for that variable only. This has the advantage
of requiring less runtime resources, but entails that you identify the problematic variable, e.g., variable s in the following
example:
This situation can be resolved using the max_bars_back function to define the referencing length of variable s only,
rather than for all the script’s variables:
When using drawings that refer to previous bars through bar_index[n] and xloc = xloc.bar_index, the
time series received from this bar will be used to position the drawings on the time axis. Therefore, if it is impossible to
determine the correct size of the buffer, this error may occur. To avoid this, you need to use max_bars_back(time,
n). This behavior is described in more detail in the section about drawings.

7.8. Pine Script™ cannot determine the referencing length of a series. Try using max_bars_back
445
in the indicator or strategy function
Pine Script™ v5 User Manual

446 Chapter 7. Error messages


CHAPTER

EIGHT

RELEASE NOTES

• 2023
• 2022
• 2021
• 2020
• 2019
• 2018
• 2017
• 2016
• 2015
• 2014
• 2013

This page contains release notes of notable changes in Pine Script™.

8.1 2023

8.1.1 November 2023

We’ve added the following variables and functions to the strategy.* namespace:
• strategy.grossloss_percent - The total gross loss value of all completed losing trades, expressed as a percentage of
the initial capital.
• strategy.grossprofit_percent - The total gross profit value of all completed winning trades, expressed as a percentage
of the initial capital.
• strategy.max_runup_percent - The maximum rise from a trough in the equity curve, expressed as a percentage of
the trough value.
• strategy.max_drawdown_percent - The maximum drop from a peak in the equity curve, expressed as a percentage
of the peak value.
• strategy.netprofit_percent - The total value of all completed trades, expressed as a percentage of the initial capital.
• strategy.openprofit_percent - The current unrealized profit or loss for all open positions, expressed as a percentage
of realized equity.

447
Pine Script™ v5 User Manual

• strategy.closedtrades.max_drawdown_percent() - Returns the maximum drawdown of the closed trade, i.e., the
maximum possible loss during the trade, expressed as a percentage.
• strategy.closedtrades.max_runup_percent() - Returns the maximum run-up of the closed trade, i.e., the maximum
possible profit during the trade, expressed as a percentage.
• strategy.closedtrades.profit_percent() - Returns the profit/loss value of the closed trade, expressed as a percentage.
Losses are expressed as negative values.
• strategy.opentrades.max_drawdown_percent() - Returns the maximum drawdown of the open trade, i.e., the max-
imum possible loss during the trade, expressed as a percentage.
• strategy.opentrades.max_runup_percent() - Returns the maximum run-up of the open trade, i.e., the maximum
possible profit during the trade, expressed as a percentage.
• strategy.opentrades.profit_percent() - Returns the profit/loss of the open trade, expressed as a percentage. Losses
are expressed as negative values.

8.1.2 October 2023

Pine Script™ Polylines

Polylines are drawings that sequentially connect the coordinates from an array of up to 10,000 chart points using straight
or curved line segments, allowing scripts to draw custom formations that are difficult or impossible to achieve using line
or box objects. To learn more about this new drawing type, see the Polylines section of our User Manual’s page on Lines
and boxes.

8.1.3 September 2023

New functions were added:


• strategy.default_entry_qty() - Calculates the default quantity, in units, of an entry order from strategy.entry() or
strategy.order() if it were to fill at the specified fill_price value.
• chart.point.new() - Creates a new chart.point object with the specified time, index, and price.
• request.seed() - Requests data from a user-maintained GitHub repository and returns it as a series. An in-depth
tutorial on how to add new data can be found here.
• ticker.inherit() - Constructs a ticker ID for the specified symbol with additional parameters inherited from the
ticker ID passed into the function call, allowing the script to request a symbol’s data using the same modifiers that
the from_tickerid has, including extended session, dividend adjustment, currency conversion, non-standard
chart types, back-adjustment, settlement-as-close, etc.
• timeframe.from_seconds() - Converts a specified number of seconds into a valid timeframe string based on our
timeframe specification format.
The dividends.* namespace now includes variables for retrieving future dividend information:
• dividends.future_amount - Returns the payment amount of the upcoming dividend in the currency of the current
instrument, or na if this data isn’t available.
• dividends.future_ex_date - Returns the Ex-dividend date (Ex-date) of the current instrument’s next dividend pay-
ment, or na if this data isn’t available.
• dividends.future_pay_date - Returns the Payment date (Pay date) of the current instrument’s next dividend payment,
or na if this data isn’t available.
The request.security_lower_tf() function has a new parameter:

448 Chapter 8. Release notes


Pine Script™ v5 User Manual

• ignore_invalid_timeframe - Determines how the function behaves when the chart’s timeframe is smaller
than the timeframe value in the function call. If false, the function will raise a runtime error and halt the
script’s execution. If true, the function will return na without raising an error.
Users can now explicitly declare variables with the const, simple, and series type qualifiers, allowing more precise
control over the types of variables in their scripts. For example:

8.1.4 August 2023

Added the following alert placeholders:


• {{syminfo.currency}} - Returns the currency code of the current symbol (“EUR”, “USD”, etc.).
• {{syminfo.basecurrency}} - Returns the base currency code of the current symbol if the symbol refers
to a currency pair. Otherwise, it returns na. For example, it returns “EUR” when the symbol is “EURUSD”.

Pine Script™ Maps

Maps are collections that hold elements in the form of key-value pairs. They associate unique keys of a fundamental
type with values of a built-in or user-defined type. Unlike arrays and matrices, these collections are unordered and do not
utilize an internal lookup index. Instead, scripts access the values of maps by referencing the keys from the key-value
pairs put into them. For more information on these new collections, see our User Manual’s page on Maps.

8.1.5 July 2023

Fixed an issue that caused strategies to occasionally calculate the sizes of limit orders incorrectly due to improper tick
rounding of the limit price.
Added a new built-in variable to the strategy.* namespace:
• strategy.margin_liquidation_price - When a strategy uses margin, returns the price value after which a margin call
will occur.

8.1.6 June 2023

New syminfo.* built-in variables were added:


• syminfo.sector - Returns the sector of the symbol.
• syminfo.industry - Returns the industry of the symbol.
• syminfo.country - Returns the two-letter code of the country where the symbol is traded.

8.1.7 May 2023

New parameter added to the strategy.entry(), strategy.order(), strategy.close(), strategy.close_all(), and strategy.exit()
functions:
• disable_alert - Disables order fill alerts for any orders placed by the function.
Our “Indicator on indicator” feature, which allows a script to pass another indicator’s plot as a source value via the
input.source() function, now supports multiple external inputs. Scripts can use a multitude of external inputs originating
from up to 10 different indicators.
We’ve added the following array functions:

8.1. 2023 449


Pine Script™ v5 User Manual

• array.every() - Returns true if all elements of the id array are true, false otherwise.
• array.some() - Returns true if at least one element of the id array is true, false otherwise.
These functions also work with arrays of int and float types, in which case zero values are considered false, and all
others true.

8.1.8 April 2023

Fixed an issue with trailing stops in strategy.exit() being filled on high/low prices rather than on intrabar prices.
Fixed behavior of array.mode(), matrix.mode() and ta.mode(). Now these functions will return the smallest value when
the data has no most frequent value.

8.1.9 March 2023

It is now possible to use seconds-based timeframe strings for the timeframe parameter in request.security() and re-
quest.security_lower_tf().
A new function was added:
• request.currency_rate() - provides a daily rate to convert a value expressed in the from currency to another in the
to currency.

8.1.10 February 2023

Pine Script™ Methods

Pine Script™ methods are specialized functions associated with specific instances of built-in or user-defined types. They
offer a more convenient syntax than standard functions, as users can access methods in the same way as object fields using
the handy dot notation syntax. Pine Script™ includes built-in methods for array, matrix, line, linefill, label, box, and table
types and facilitates user-defined methods with the new method keyword. For more details on this new feature, see our
User Manual’s page on methods.

8.1.11 January 2023

New array functions were added:


• array.first() - Returns the array’s first element.
• array.last() - Returns the array’s last element.

8.2 2022

8.2.1 December 2022

Pine Objects

Pine objects are instantiations of the new user-defined composite types (UDTs) declared using the type keyword. Expe-
rienced programmers can think of UDTs as method-less classes. They allow users to create custom types that organize

450 Chapter 8. Release notes


Pine Script™ v5 User Manual

different values under one logical entity. A detailed rundown of the new functionality can be found in our User Manual’s
page on objects.
A new function was added:
• ticker.standard() - Creates a ticker to request data from a standard chart that is unaffected by modifiers like extended
session, dividend adjustment, currency conversion, and the calculations of non-standard chart types: Heikin Ashi,
Renko, etc.
New strategy.* functions were added:
• strategy.opentrades.entry_comment() - The function returns the comment message of the open trade’s entry.
• strategy.closedtrades.entry_comment() - The function returns the comment message of the closed trade’s entry.
• strategy.closedtrades.exit_comment() - The function returns the comment message of the closed trade’s exit.

8.2.2 November 2022

Fixed behaviour of math.round_to_mintick() function. For ‘na’ values it returns ‘na’.

8.2.3 October 2022

Pine Script™ now has a new, more powerful and better-integrated editor. Read our blog to find out everything to know
about all the new features and upgrades.
New overload for the fill() function was added. Now it can create vertical gradients. More info about it in the blog post.
A new function was added:
• str.format_time() - Converts a timestamp to a formatted string using the specified format and time zone.

8.2.4 September 2022

The text_font_family parameter now allows the selection of a monospace font in label.new(), box.new() and
table.cell() function calls, which makes it easier to align text vertically. Its arguments can be:
• font.family_default - Specifies the default font.
• font.family_monospace - Specifies a monospace font.
The accompanying setter functions are:
• label.set_text_font_family() - The function sets the font family of the text inside the label.
• box.set_text_font_family() - The function sets the font family of the text inside the box.
• table.cell_set_text_font_family() - The function sets the font family of the text inside the cell.

8.2. 2022 451


Pine Script™ v5 User Manual

8.2.5 August 2022

A new label style label.style_text_outline was added.


A new parameter for the ta.pivot_point_levels() function was added:
• developing - If false, the values are those calculated the last time the anchor condition was true. They remain
constant until the anchor condition becomes true again. If true, the pivots are developing, i.e., they constantly
recalculate on the data developing between the point of the last anchor (or bar zero if the anchor condition was
never true) and the current bar. Cannot be true when type is set to "Woodie".
A new parameter for the box.new() function was added:
• text_wrap - It defines whether the text is presented in a single line, extending past the width of the box if
necessary, or wrapped so every line is no wider than the box itself.
This parameter supports two arguments:
• text.wrap_none - Disabled wrapping mode for box.new and box.set_text_wrap functions.
• text.wrap_auto - Automatic wrapping mode for box.new and box.set_text_wrap functions.
New built-in functions were added:
• ta.min() - Returns the all-time low value of source from the beginning of the chart up to the current bar.
• ta.max() - Returns the all-time high value of source from the beginning of the chart up to the current bar.
A new annotation //@strategy_alert_message was added. If the annotation is added to the strategy, the text
written after it will be automatically set as the default alert message in the Create Alert window.

1 //@version=5
2 // @strategy_alert_message My Default Alert Message
3 strategy("My Strategy")
4 plot(close)

8.2.6 July 2022

It is now possible to fine-tune where a script’s plot values are displayed through the introduction of new arguments for the
display parameter of the plot(), plotchar(), plotshape(), plotarrow(), plotcandle(), and plotbar() functions.
Four new arguments were added, complementing the previously available display.all and display.none:
• display.data_window displays the plot values in the Data Window, one of the items available from the chart’s right
sidebar.
• display.pane displays the plot in the pane where the script resides, as defined in with the overlay parameter of
the script’s indicator(), strategy(), or library() declaration statement.
• display.price_scale controls the display of the plot’s label and price in the price scale, if the chart’s settings allow
them.
• display.status_line displays the plot values in the script’s status line, next to the script’s name on the chart, if the
chart’s settings allow them.
The display parameter supports the addition and subtraction of its arguments:
• display.all - display.status_line will display the plot’s information everywhere except in the
script’s status line.
• display.price_scale + display.status_line will display the plot in the price scale and status
line only.

452 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.2.7 June 2022

The behavior of the argument used with the qty_percent parameter of strategy.exit() has changed. Previously, the
percentages used on successive exit orders of the same position were calculated from the remaining position at any given
time. Instead, the percentages now always apply to the initial position size. When executing the following strategy, for
example:

1 //@version=5
2 strategy("strategy.exit() example", overlay = true)
3 strategy.entry("Long", strategy.long, qty = 100)
4 strategy.exit("Exit Long1", "Long", trail_points = 50, trail_offset = 0, qty_percent␣
,→= 20)

5 strategy.exit("Exit Long2", "Long", trail_points = 100, trail_offset = 0, qty_percent␣


,→= 20)

20% of the initial position will be closed on each strategy.exit() call. Before, the first call would exit 20% of the initial
position, and the second would exit 20% of the remaining 80% of the position, so only 16% of the initial position.
Two new parameters for the built-in ta.vwap() function were added:
• anchor - Specifies the condition that triggers the reset of VWAP calculations. When true, calculations reset;
when false, calculations proceed using the values accumulated since the previous reset.
• stdev_mult - If specified, the ta.vwap() calculates the standard deviation bands based on the main VWAP series
and returns a [vwap, upper_band, lower_band] tuple.
New overloaded versions of the strategy.close() and strategy.close_all() functions with the immediately parameter.
When immediately is set to true, the closing order will be executed on the tick where it has been placed, ignoring
the strategy parameters that restrict the order execution to the open of the next bar.
New built-in functions were added:
• timeframe.change() - Returns true on the first bar of a new timeframe, false otherwise.
• ta.pivot_point_levels() - Returns a float array with numerical values representing 11 pivot point levels: [P, R1,
S1, R2, S2, R3, S3, R4, S4, R5, S5]. Levels absent from the specified type return na values.
New built-in variables were added:
• session.isfirstbar - returns true if the current bar is the first bar of the day’s session, false otherwise.
• session.islastbar - returns true if the current bar is the last bar of the day’s session, false otherwise.
• session.isfirstbar_regular - returns true on the first regular session bar of the day, false otherwise.
• session.islastbar_regular - returns true on the last regular session bar of the day, false otherwise.
• chart.left_visible_bar_time - returns the time of the leftmost bar currently visible on the chart.
• chart.right_visible_bar_time - returns the time of the rightmost bar currently visible on the chart.

8.2. 2022 453


Pine Script™ v5 User Manual

8.2.8 May 2022

Matrix support has been added to the request.security() function.


The historical states of arrays and matrices can now be referenced with the [] operator. In the example below, we reference
the historic state of a matrix 10 bars ago:

1 //@version=5
2 indicator("matrix.new<float> example")
3 m = matrix.new<float>(1, 1, close)
4 float x = na
5 if bar_index > 10
6 x := matrix.get(m[10], 0, 0)
7 plot(x)
8 plot(close)

The ta.change() function now can take values of int and bool types as its source parameter and return the difference in
the respective type.
New built-in variables were added:
• chart.bg_color - Returns the color of the chart’s background from the "Chart settings/Appearance/
Background" field.
• chart.fg_color - Returns a color providing optimal contrast with chart.bg_color.
• chart.is_standard - Returns true if the chart type is bars, candles, hollow candles, line, area or baseline, false
otherwise.
• currency.USDT - A constant for the Tether currency code.
New functions were added:
• syminfo.prefix() - returns the exchange prefix of the symbol passed to it, e.g. “NASDAQ” for “NASDAQ:AAPL”.
• syminfo.ticker() - returns the ticker of the symbol passed to it without the exchange prefix, e.g. “AAPL” for
“NASDAQ:AAPL”.
• request.security_lower_tf() - requests data from a lower timeframe than the chart’s.
Added use_bar_magnifier parameter for the strategy() function. When true, the Broker Emulator uses lower
timeframe data during history backtesting to achieve more realistic results.
Fixed behaviour of strategy.exit() function when stop loss triggered at prices outside the bars price range.
Added new comment and alert message parameters for the strategy.exit() function:
• comment_profit - additional notes on the order if the exit was triggered by crossing profit or limit
specifically.
• comment_loss - additional notes on the order if the exit was triggered by crossing stop or loss specifically.
• comment_trailing - additional notes on the order if the exit was triggered by crossing trail_offset
specifically.
• alert_profit - text that will replace the '{{strategy.order.alert_message}}' placeholder if
the exit was triggered by crossing profit or limit specifically.
• alert_loss - text that will replace the '{{strategy.order.alert_message}}' placeholder if the
exit was triggered by crossing stop or loss specifically.
• alert_trailing - text that will replace the '{{strategy.order.alert_message}}' placeholder
if the exit was triggered by crossing trail_offset specifically.

454 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.2.9 April 2022

Added the display parameter to the following functions: barcolor, bgcolor, fill, hline.
A new function was added:
• request.economic() - Economic data includes information such as the state of a country’s economy or of a particular
industry.
New built-in variables were added:
• strategy.max_runup - Returns the maximum equity run-up value for the whole trading interval.
• syminfo.volumetype - Returns the volume type of the current symbol.
• chart.is_heikinashi - Returns true if the chart type is Heikin Ashi, false otherwise.
• chart.is_kagi - Returns true if the chart type is Kagi, false otherwise.
• chart.is_linebreak - Returns true if the chart type is Line break, false otherwise.
• chart.is_pnf - Returns true if the chart type is Point & figure, false otherwise.
• chart.is_range - Returns true if the chart type is Range, false otherwise.
• chart.is_renko - Returns true if the chart type is Renko, false otherwise.
New matrix functions were added:
• matrix.new<type> - Creates a new matrix object. A matrix is a two-dimensional data structure containing rows
and columns. All elements in the matrix must be of the type specified in the type template (“<type>”).
• matrix.row() - Creates a one-dimensional array from the elements of a matrix row.
• matrix.col() - Creates a one-dimensional array from the elements of a matrix column.
• matrix.get() - Returns the element with the specified index of the matrix.
• matrix.set() - Assigns value to the element at the column and row index of the matrix.
• matrix.rows() - Returns the number of rows in the matrix.
• matrix.columns() - Returns the number of columns in the matrix.
• matrix.elements_count() - Returns the total number of matrix elements.
• matrix.add_row() - Adds a row to the matrix. The row can consist of na values, or an array can be used to provide
values.
• matrix.add_col() - Adds a column to the matrix. The column can consist of na values, or an array can be used to
provide values.
• matrix.remove_row() - Removes the row of the matrix and returns an array containing the removed row’s values.
• matrix.remove_col() - Removes the column of the matrix and returns an array containing the removed column’s
values.
• matrix.swap_rows() - Swaps the rows in the matrix.
• matrix.swap_columns() - Swaps the columns in the matrix.
• matrix.fill() - Fills a rectangular area of the matrix defined by the indices from_column to to_column.
• matrix.copy() - Creates a new matrix which is a copy of the original.
• matrix.submatrix() - Extracts a submatrix within the specified indices.
• matrix.reverse() - Reverses the order of rows and columns in the matrix. The first row and first column become the
last, and the last become the first.

8.2. 2022 455


Pine Script™ v5 User Manual

• matrix.reshape() - Rebuilds the matrix to rows x cols dimensions.


• matrix.concat() - Append one matrix to another.
• matrix.sum() - Returns a new matrix resulting from the sum of two matrices, or of a matrix and a scalar (a numerical
value).
• matrix.diff() - Returns a new matrix resulting from the subtraction between matrices, or of matrix and a scalar (a
numerical value).
• matrix.mult() - Returns a new matrix resulting from the product between the matrices, or between a matrix and a
scalar (a numerical value), or between a matrix and a vector (an array of values).
• matrix.sort() - Rearranges the rows in the id matrix following the sorted order of the values in the column.
• matrix.avg() - Calculates the average of all elements in the matrix.
• matrix.max() - Returns the largest value from the matrix elements.
• matrix.min() - Returns the smallest value from the matrix elements.
• matrix.median() - Calculates the median (“the middle” value) of matrix elements.
• matrix.mode() - Calculates the mode of the matrix, which is the most frequently occurring value from the matrix
elements. When there are multiple values occurring equally frequently, the function returns the smallest of those
values.
• matrix.pow() - Calculates the product of the matrix by itself power times.
• matrix.det() - Returns the determinant of a square matrix.
• matrix.transpose() - Creates a new, transposed version of the matrix by interchanging the row and column index of
each element.
• matrix.pinv() - Returns the pseudoinverse of a matrix.
• matrix.inv() - Returns the inverse of a square matrix.
• matrix.rank() - Calculates the rank of the matrix.
• matrix.trace() - Calculates the trace of a matrix (the sum of the main diagonal’s elements).
• matrix.eigenvalues() - Returns an array containing the eigenvalues of a square matrix.
• matrix.eigenvectors() - Returns a matrix of eigenvectors, in which each column is an eigenvector of the matrix.
• matrix.kron() - Returns the Kronecker product for the two matrices.
• matrix.is_zero() - Determines if all elements of the matrix are zero.
• matrix.is_identity() - Determines if a matrix is an identity matrix (elements with ones on the main diagonal and
zeros elsewhere).
• matrix.is_binary() - Determines if the matrix is binary (when all elements of the matrix are 0 or 1).
• matrix.is_symmetric() - Determines if a square matrix is symmetric (elements are symmetric with respect to the
main diagonal).
• matrix.is_antisymmetric() - Determines if a matrix is antisymmetric (its transpose equals its negative).
• matrix.is_diagonal() - Determines if the matrix is diagonal (all elements outside the main diagonal are zero).
• matrix.is_antidiagonal() - Determines if the matrix is anti-diagonal (all elements outside the secondary diagonal
are zero).
• matrix.is_triangular() - Determines if the matrix is triangular (if all elements above or below the main diagonal are
zero).

456 Chapter 8. Release notes


Pine Script™ v5 User Manual

• matrix.is_stochastic() - Determines if the matrix is stochastic.


• matrix.is_square() - Determines if the matrix is square (it has the same number of rows and columns).
Added a new parameter for the strategy() function:
• risk_free_rate - The risk-free rate of return is the annual percentage change in the value of an investment
with minimal or zero risk, used to calculate the Sharpe and Sortino ratios.

8.2.10 March 2022

New array functions were added:


• array.sort_indices() - returns an array of indices which, when used to index the original array, will access its elements
in their sorted order.
• array.percentrank() - returns the percentile rank of a value in the array.
• array.percentile_nearest_rank() - returns the value for which the specified percentage of array values (percentile)
are less than or equal to it, using the nearest-rank method.
• array.percentile_linear_interpolation() - returns the value for which the specified percentage of array values (per-
centile) are less than or equal to it, using linear interpolation.
• array.abs() - returns an array containing the absolute value of each element in the original array.
• array.binary_search() - returns the index of the value, or -1 if the value is not found.
• array.binary_search_leftmost() - returns the index of the value if it is found or the index of the next smallest element
to the left of where the value would lie if it was in the array.
• array.binary_search_rightmost() - returns the index of the value if it is found or the index of the element to the
right of where the value would lie if it was in the array.
Added a new optional nth parameter for the array.min() and array.max() functions.
Added index in for..in operator. It tracks the current iteration’s index.

Table merging and cell tooltips

• It is now possible to merge several cells in a table. A merged cell doesn’t have to be a header: you can merge cells
in any direction, as long as the resulting cell doesn’t affect any already merged cells and doesn’t go outside of the
table’s bounds. Cells can be merged with the new table.merge_cells() function.
• Tables now support tooltips, floating labels that appear when you hover over a table’s cell. To add a tooltip, pass a
string to the tooltip argument of the table.cell() function or use the new table.cell_set_tooltip() function.

8.2.11 February 2022

Added templates and the ability to create arrays via templates. Instead of using one of the array.new_*() functions,
a template function array.new<type> can be used. In the example below, we use this functionality to create an array filled
with float values:

1 //@version=5
2 indicator("array.new<float> example")
3 length = 5
4 var a = array.new<float>(length, close)
5 if array.size(a) == length
(continues on next page)

8.2. 2022 457


Pine Script™ v5 User Manual

(continued from previous page)


6 array.remove(a, 0)
7 array.push(a, close)
8 plot(array.sum(a) / length, "SMA")

New functions were added:


• timeframe.in_seconds(timeframe) - converts the timeframe passed to the timeframe argument into seconds.
• input.text_area() - adds multiline text input area to the Script settings.
• strategy.closedtrades.entry_id() - returns the id of the closed trade’s entry.
• strategy.closedtrades.exit_id() - returns the id of the closed trade’s exit.
• strategy.opentrades.entry_id() - returns the id of the open trade’s entry.

8.2.12 January 2022

Added new functions to clone drawings:


• line.copy()
• label.copy()
• box.copy()

8.3 2021

8.3.1 December 2021

Linefills

The space between lines drawn in Pine Script™ can now be filled! We’ve added a new linefill drawing type, along
with a number of functions dedicated to manipulating it. Linefills are created by passing two lines and a color to the
linefill.new() function, and their behavior is based on the lines they’re tied to: they extend in the same direction
as the lines, move when their lines move, and are deleted when one of the two lines is deleted.
New linefill-related functions:
• array.new_linefill()
• linefill()
• linefill.delete()
• linefill.get_line1()
• linefill.get_line2()
• linefill.new()
• linefill.set_color()
• linefill.all()

458 Chapter 8. Release notes


Pine Script™ v5 User Manual

New functions for string manipulation

Added a number of new functions that provide more ways to process strings, and introduce regular expressions to Pine
Script™:
• str.contains(source, str) - Determines if the source string contains the str substring.
• str.pos(source, str) - Returns the position of the str string in the source string.
• str.substring(source, begin_pos, end_pos) - Extracts a substring from the source string.
• str.replace(source, target, replacement, occurrence) - Contrary to the existing str.replace_all() function, str.
replace() allows the selective replacement of a matched substring with a replacement string.
• str.lower(source) and str.upper(source) - Convert all letters of the source string to lower or upper case:
• str.startswith(source, str) and str.endswith(source, str) - Determine if the source string starts or ends with the
str substring.
• str.match(source, regex) - Extracts the substring matching the specified regular expression.

Textboxes

Box drawings now supports text. The box.new() function has five new parameters for text manipulation: text,
text_size, text_color, text_valign, and text_halign. Additionally, five new functions to set the text
properties of existing boxes were added:
• box.set_text()
• box.set_text_color()
• box.set_text_size()
• box.set_text_valign()
• box.set_text_halign()

New built-in variables

Added new built-in variables that return the bar_index and time values of the last bar in the dataset. Their values
are known at the beginning of the script’s calculation:
• last_bar_index - Bar index of the last chart bar.
• last_bar_time - UNIX time of the last chart bar.
New built-in source variable:
• hlcc4 - A shortcut for (high + low + close + close)/4. It averages the high and low values with the
double-weighted close.

8.3. 2021 459


Pine Script™ v5 User Manual

8.3.2 November 2021

for…in

Added a new for…in operator to iterate over all elements of an array:

1 //@version=5
2 indicator("My Script")
3 int[] a1 = array.from(1, 3, 6, 3, 8, 0, -9, 5)
4

5 highest(array) =>
6 var int highestNum = na
7 for item in array
8 if na(highestNum) or item > highestNum
9 highestNum := item
10 highestNum
11

12 plot(highest(a1))

Function overloads

Added function overloads. Several functions in a script can now share the same name, as long one of the following
conditions is true:
• Each overload has a different number of parameters:

1 //@version=5
2 indicator("Function overload")
3

4 // Two parameters
5 mult(x1, x2) =>
6 x1 * x2
7

8 // Three parameters
9 mult(x1, x2, x3) =>
10 x1 * x2 * x3
11

12 plot(mult(7, 4))
13 plot(mult(7, 4, 2))

• When overloads have the same number of parameters, all parameters in each overload must be explicitly typified,
and their type combinations must be unique:

1 //@version=5
2 indicator("Function overload")
3

4 // Accepts both 'int' and 'float' values - any 'int' can be automatically cast to
,→'float'

5 mult(float x1, float x2) =>


6 x1 * x2
7

8 // Returns a 'bool' value instead of a number


9 mult(bool x1, bool x2) =>
10 x1 and x2 ? true : false
11

12 mult(string x1, string x2) =>


(continues on next page)

460 Chapter 8. Release notes


Pine Script™ v5 User Manual

(continued from previous page)


13 str.tonumber(x1) * str.tonumber(x2)
14

15 // Has three parameters, so explicit types are not required


16 mult(x1, x2, x3) =>
17 x1 * x2 * x3
18

19 plot(mult(7, 4))
20 plot(mult(7.5, 4.2))
21 plot(mult(true, false) ? 1 : 0)
22 plot(mult("5", "6"))
23 plot(mult(7, 4, 2))

Currency conversion

Added a new currency argument to most request.*() functions. If specified, price values returned by the function
will be converted from the source currency to the target currency. The following functions are affected:
• request.dividends()
• request.earnings()
• request.financial()
• request.security()

8.3.3 October 2021

Pine Script™ v5 is here! This is a list of the new features added to the language, and a few of the changes made. See
the Pine Script™ v5 Migration guide for a complete list of the changes in v5.

New features

Libraries are a new type of publication. They allow you to create custom functions for reuse in other scripts. See this
manual’s page on Libraries.
Pine Script™ now supports switch structures! They provide a more convenient and readable alternative to long ternary
operators and if statements.
while loops are here! They allow you to create a loop that will only stop when its controlling condition is false, or a break
command is used in the loop.
New built-in array variables are maintained by the Pine Script™ runtime to hold the IDs of all the active objects of the
same type drawn by your script. They are label.all, line.all, box.all and table.all.
The runtime.error() function makes it possible to halt the execution of a script and display a runtime error with a custom
message. You can use any condition in your script to trigger the call.
Parameter definitions in user-defined functions can now include a default value: a function defined as f(x = 1) => x
will return 1 when called as f(), i.e., without providing an argument for its x parameter.
New variables and functions provide better script visibility on strategy information:
• strategy.closedtrades.entry_price() and strategy.opentrades.entry_price()
• strategy.closedtrades.entry_bar_index() and strategy.opentrades.entry_bar_index()
• strategy.closedtrades.entry_time() and strategy.opentrades.entry_time()

8.3. 2021 461


Pine Script™ v5 User Manual

• strategy.closedtrades.size() and strategy.opentrades.size()


• strategy.closedtrades.profit() and strategy.opentrades.profit()
• strategy.closedtrades.commission() and strategy.opentrades.commission()
• strategy.closedtrades.max_runup() and strategy.opentrades.max_runup()
• strategy.closedtrades.max_drawdown() and strategy.opentrades.max_drawdown()
• strategy.closedtrades.exit_price()
• strategy.closedtrades.exit_bar_index()
• strategy.closedtrades.exit_time()
• strategy.convert_to_account()
• strategy.convert_to_symbol()
• strategy.account_currency
A new earnings.standardized constant for the request.earnings() function allows requesting standardized earnings data.
A v4 to v5 converter is now included in the Pine Script™ Editor. See the Pine Script™ v5 Migration guide for more
information on converting your scripts to v5.
The Reference Manual now includes the systematic mention of the form and type (e.g., “simple int”) required for each
function parameter.
The User Manual was reorganized and new content was added.

Changes

Many built-in variables, functions and function arguments were renamed or moved to new namespaces in v5. The ven-
erable study(), for example, is now indicator(), and security() is now request.security(). New namespaces now
group related functions and variables together. This consolidation implements a more rational nomenclature and provides
an orderly space to accommodate the many additions planned for Pine Script™.
See the Pine Script™ v5 Migration guide for a complete list of the changes made in v5.

8.3.4 September 2021

New parameter has been added for the dividends(), earnings(), financial(), quandl(), security(),
and splits() functions:
• ignore_invalid_symbol - determines the behavior of the function if the specified symbol is not found: if
false, the script will halt and return a runtime error; if true, the function will return na and execution will
continue.

462 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.3.5 July 2021

tostring now accepts “bool” and “string” types.


New argument for time and time_close functions was added:
• timezone - timezone of the session argument, can only be used when a session is specified. Can be written
out in GMT notation (e.g. “GMT-5”) or as an IANA time zone database name (e.g. “America/New_York”).
It is now possible to place a drawing object in the future with xloc = xloc.bar_index.
New argument for study and strategy functions was added:
• explicit_plot_zorder - specifies the order in which the indicator’s plots, fills, and hlines are rendered. If
true, the plots will be drawn based on the order in which they appear in the indicator’s code, each newer plot being
drawn above the previous ones.

8.3.6 June 2021

New variable was added:


• barstate.islastconfirmedhistory - returns true if script is executing on the dataset’s last bar when
market is closed, or script is executing on the bar immediately preceding the real-time bar, if market is open.
Returns false otherwise.
New function was added:
• round_to_mintick(x) - returns the value rounded to the symbol’s mintick, i.e. the nearest value that can be
divided by syminfo.mintick, without the remainder, with ties rounding up.
Expanded tostring() functionality. The function now accepts three new formatting arguments:
• format.mintick to format to tick precision.
• format.volume to abbreviate large values.
• format.percent to format percentages.

8.3.7 May 2021

Improved backtesting functionality by adding the Leverage mechanism.


Added support for table drawings and functions for working with them. Tables are unique objects that are not anchored
to specific bars; they float in a script’s space, independently of the chart bars being viewed or the zoom factor used. For
more information, see the Tables User Manual page.
New functions were added:
• color.rgb(red, green, blue, transp) - creates a new color with transparency using the RGB color
model.
• color.from_gradient(value, bottom_value, top_value, bottom_color,
top_color) - returns color calculated from the linear gradient between bottom_color to top_color.
• color.r(color), color.g(color), color.b(color), color.t(color) - retrieves the value of
one of the color components.
• array.from() - takes a variable number of arguments with one of the types: int, float, bool, string,
label, line, color, box, table and returns an array of the corresponding type.

8.3. 2021 463


Pine Script™ v5 User Manual

A new box drawing has been added to Pine Script™, making it possible to draw rectangles on charts using the Pine
Script™ syntax. For more details see the Pine Script™ reference and the Lines and boxes User Manual page.
The color.new function can now accept series and input arguments, in which case, the colors will be calculated at
runtime. For more information about this, see our Colors User Manual page.

8.3.8 April 2021

New math constants were added:


• math.pi - is a named constant for Archimedes’ constant. It is equal to 3.1415926535897932.
• math.phi - is a named constant for the golden ratio. It is equal to 1.6180339887498948.
• math.rphi - is a named constant for the golden ratio conjugate. It is equal to 0.6180339887498948.
• math.e - is a named constant for Euler’s number. It is equal to 2.7182818284590452.
New math functions were added:
• round(x, precision) - returns the value of x rounded to the nearest integer, with ties rounding up. If the
precision parameter is used, returns a float value rounded to that number of decimal places.
• median(source, length) - returns the median of the series.
• mode(source, length) - returns the mode of the series. If there are several values with the same frequency,
it returns the smallest value.
• range(source, length) - returns the difference between the min and max values in a series.
• todegrees(radians) - returns an approximately equivalent angle in degrees from an angle measured in ra-
dians.
• toradians(degrees) - returns an approximately equivalent angle in radians from an angle measured in de-
grees.
• random(min, max, seed) - returns a pseudo-random value. The function will generate a different sequence
of values for each script execution. Using the same value for the optional seed argument will produce a repeatable
sequence.
New functions were added:
• session.ismarket - returns true if the current bar is a part of the regular trading hours (i.e. market hours),
false otherwise.
• session.ispremarket - returns true if the current bar is a part of the pre-market, false otherwise.
• session.ispostmarket - returns true if the current bar is a part of the post-market, false otherwise.
• str.format - converts the values to strings based on the specified formats. Accepts certain number modifiers:
integer, currency, percent.

464 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.3.9 March 2021

New assignment operators were added:


• += - addition assignment
• -= - subtraction assignment
• *= - multiplication assignment
• /= - division assignment
• %= - modulus assignment
New parameters for inputs customization were added:
• inline - combines all the input calls with the same inline value in one line.
• group - creates a header above all inputs that use the same group string value. The string is also used as the header
text.
• tooltip - adds a tooltip icon to the Inputs menu. The tooltip string is shown when hovering over the tooltip
icon.
New argument for fill function was added:
• fillgaps - controls whether fills continue on gaps when one of the plot calls returns an na value.
A new keyword was added:
• varip - is similar to the var keyword, but variables declared with varip retain their values between the updates
of a real-time bar.
New functions were added:
• tonumber() - converts a string value into a float.
• time_close() - returns the UNIX timestamp of the close of the current bar, based on the resolution and session
that is passed to the function.
• dividends() - requests dividends data for the specified symbol.
• earnings() - requests earnings data for the specified symbol.
• splits() - requests splits data for the specified symbol.
New arguments for the study() function were added:
• resolution_gaps - fills the gaps between values fetched from higher timeframes when using resolution.
• format.percent - formats the script output values as a percentage.

8.3.10 February 2021

New variable was added:


• time_tradingday - the beginning time of the trading day the current bar belongs to.

8.3. 2021 465


Pine Script™ v5 User Manual

8.3.11 January 2021

The following functions now accept a series length parameter:


• bb()
• bbw()
• cci()
• cmo()
• cog()
• correlation()
• dev()
• falling()
• mfi()
• percentile_linear_interpolation()
• percentile_nearest_rank()
• percentrank()
• rising()
• roc()
• stdev()
• stoch()
• variance()
• wpr()
A new type of alerts was added - script alerts. More information can be found in our Help Center.

8.4 2020

8.4.1 December 2020

New array types were added:


• array.new_line()
• array.new_label()
• array.new_string()
New functions were added:
• str.length() - returns number of chars in source string.
• array.join() - concatenates all of the elements in the array into a string and separates these elements with the
specified separator.
• str.split() - splits a string at a given substring separator.

466 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.4.2 November 2020

• New max_labels_count and max_lines_count parameters were added to the study and strategy func-
tions. Now you can manage the number of lines and labels by setting values for these parameters from 1 to 500.
New function was added:
• array.range() - return the difference between the min and max values in the array.

8.4.3 October 2020

The behavior of rising() and falling() functions have changed. For example, rising(close,3) is now
calculated as following:

close[0] > close[1] and close[1] > close[2] and close[2] > close[3]

8.4.4 September 2020

Added support for input.color to the input() function. Now you can provide script users with color selection
through the script’s “Settings/Inputs” tab with the same color widget used throughout the TradingView user interface.
Learn more about this feature in our blog

1 //@version=4
2 study("My Script", overlay = true)
3 color c_labelColor = input(color.green, "Main Color", input.color)
4 var l = label.new(bar_index, close, yloc = yloc.abovebar, text = "Colored label")
5 label.set_x(l, bar_index)
6 label.set_color(l, c_labelColor)

8.4. 2020 467


Pine Script™ v5 User Manual

Added support for arrays and functions for working with them. You can now use the powerful new array feature to build
custom datasets. See our User Manual page on arrays and our blog

1 //@version=4
2 study("My Script")
3 a = array.new_float(0)
4 for i = 0 to 5
5 array.push(a, close[i] - open[i])
6 plot(array.get(a, 4))

The following functions now accept a series length parameter. Learn more about this feature in our blog:
• alma()
• change()
• highest()
• highestbars()
• linreg()
• lowest()
• lowestbars()
• mom()
• sma()
• sum()

468 Chapter 8. Release notes


Pine Script™ v5 User Manual

• vwma()
• wma()

1 //@version=4
2 study("My Script", overlay = true)
3 length = input(10, "Length", input.integer, minval = 1, maxval = 100)
4 avgBar = avg(highestbars(length), lowestbars(length))
5 float dynLen = nz(abs(avgBar) + 1, length)
6 dynSma = sma(close, int(dynLen))
7 plot(dynSma)

8.4.5 August 2020

• Optimized script compilation time. Scripts now compile 1.5 to 2 times faster.

8.4.6 July 2020

• Minor bug fixes and improvements.

8.4.7 June 2020

• New resolution parameter was added to the study function. Now you can add MTF functionality to scripts
and decide the timeframe you want the indicator to run on.

Please note that you need to reapply the indicator in order for the resolution parameter to appear.

8.4. 2020 469


Pine Script™ v5 User Manual

• The tooltip argument was added to the label.new function along with the label.set_tooltip func-
tion:

1 //@version=4
2 study("My Script", overlay=true)
3 var l=label.new(bar_index, close, yloc=yloc.abovebar, text="Label")
4 label.set_x(l,bar_index)
5 label.set_tooltip(l, "Label Tooltip")

• Added an ability to create alerts on strategies.


• A new function line.get_price() can be used to determine the price level at which the line is located on a certain
bar.
• New label styles allow you to position the label pointer in any direction.

470 Chapter 8. Release notes


Pine Script™ v5 User Manual

• Find and Replace was added to Pine Editor. To use this, press CTRL+F (find) or CTRL+H (find and replace).

• timezone argument was added for time functions. Now you can specify timezone for second, minute, hour,
year, month, dayofmonth, dayofweek functions:

1 //@version=4
2 study("My Script")
3 plot(hour(1591012800000, "GMT+1"))

• syminfo.basecurrency variable was added. Returns the base currency code of the current symbol. For
EURUSD symbol returns EUR.

8.4. 2020 471


Pine Script™ v5 User Manual

8.4.8 May 2020

• else if statement was added


• The behavior of security() function has changed: the expression parameter can be series or tuple.

8.4.9 April 2020

New function was added:


• quandl() - request quandl data for a symbol

8.4.10 March 2020

New function was added:


• financial() - request financial data for a symbol
New functions for common indicators were added:
• cmo() - Chande Momentum Oscillator
• mfi() - Money Flow Index
• bb() - Bollinger Bands
• bbw() - Bollinger Bands Width
• kc() - Keltner Channels
• kcw() - Keltner Channels Width
• dmi() - DMI/ADX
• wpr() - Williams % R
• hma() - Hull Moving Average
• supertrend() - SuperTrend
Added a detailed description of all the fields in the Strategy Tester Report

8.4.11 February 2020

• New Pine Script™ indicator VWAP Anchored was added. Now you can specify the time period: Session, Month,
Week, Year.
• Fixed a problem with calculating percentrank function. Now it can return a zero value, which did not happen
before due to an incorrect calculation.
• The default transparency parameter for the plot(), plotshape(), and plotchar() functions is now
0%.
• For the functions plot(), plotshape(), plotchar(), plotbar(), plotcandle(), plotar-
row(), you can set the display parameter, which controls the display of the plot. The following values can be
assigned to it:
– display.none - the plot is not displayed
– display.all - the plot is displayed (Default)

472 Chapter 8. Release notes


Pine Script™ v5 User Manual

• The textalign argument was added to the label.new function along with the label.set_textalign
function. Using those, you can control the alignment of the label’s text:

8.4.12 January 2020

New built-in variables were added:


• iii - Intraday Intensity Index
• wvad - Williams Variable Accumulation/Distribution
• wad - Williams Accumulation/Distribution
• obv - On Balance Volume
• pvt - Price-Volume Trend
• nvi - Negative Volume Index
• pvi - Positive Volume Index
New parameters were added for strategy.close():
• qty - the number of contracts/shares/lots/units to exit a trade with
• qty_percent - defines the percentage of entered contracts/shares/lots/units to exit a trade with
• comment - addtional notes on the order
New parameter was added for strategy.close_all:
• comment - additional notes on the order

8.5 2019

8.5.1 December 2019

• Warning messages were added.


For example, if you don’t specify exit parameters for strategy.exit - profit, limit, loss, stop or
one of the following pairs: trail_offset and trail_price / trail_points - you will see a warning
message in the console in the Pine Script™ editor.
• Increased the maximum number of arguments in max, min, avg functions. Now you can use up to ten arguments
in these functions.

8.5.2 October 2019

• plotchar() function now supports most of the Unicode symbols:


• New bordercolor argument of the plotcandle() function allows you to change the color of candles’ bor-
ders:

1 //@version=4
2 study("My Script")
3 plotcandle(open, high, low, close, title='Title', color = open < close ? color.green␣
,→: color.red, wickcolor=color.black, bordercolor=color.orange)

8.5. 2019 473


Pine Script™ v5 User Manual

• New variables added:


– syminfo.description - returns a description of the current symbol
– syminfo.currency - returns the currency code of the current symbol (EUR, USD, etc.)
– syminfo.type - returns the type of the current symbol (stock, futures, index, etc.)

8.5.3 September 2019

New parameters to the strategy function were added:


• process_orders_on_close allows the broker emulator to try to execute orders after calculating the strategy
at the bar’s close
• close_entries_rule allows to define the sequence used for closing positions
Some fixes were made:
• fill() function now works correctly with na as the color parameter value
• sign() function now calculates correctly for literals and constants
str.replace_all(source, target, replacement) function was added. It replaces each occurrence of a
target string in the source string with a replacement string

8.5.4 July-August 2019

New variables added:


• timeframe.isseconds returns true when current resolution is in seconds
• timeframe.isminutes returns true when current resolution is in minutes
• time_close returns the current bar’s close time
The behavior of some functions, variables and operators has changed:
• The time variable returns the correct open time of the bar for more special cases than before
• An optional seconds parameter of the timestamp() function allows you to set the time to within seconds
• security() function:
– Added the possibility of requesting resolutions in seconds:
1, 5, 15, 30 seconds (chart resolution should be less than or equal to the requested resolution)
– Reduced the maximum value that can be requested in some of the other resolutions:
from 1 to 1440 minutes
from 1 to 365 days
from 1 to 52 weeks
from 1 to 12 months
• Changes to the evaluation of ternary operator branches:
In Pine Script™ v3, during the execution of a ternary operator, both its branches are calculated, so when this script
is added to the chart, a long position is opened, even if the long() function is not called:

474 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.5.5 June 2019

• Support for drawing objects. Added label and line drawings


• var keyword for one time variable initialization
• Type system improvements:
– series string data type
– functions for explicit type casting
– syntax for explicit variable type declaration
– new input type forms
• Renaming of built-ins and a version 3 to 4 converter utility
• max_bars_back function to control series variables internal history buffer sizes
• Pine Script™ documentation versioning

8.6 2018

8.6.1 October 2018

• To increase the number of indicators available to the whole community, Invite-Only scripts can now be published
by Premium users only.

8.6.2 April 2018

• Improved the Strategy Tester by reworking the Maximum Drawdown calculation formula.

8.7 2017

8.7.1 August 2017

• With the new argument show_last in the plot-type functions, you can restrict the number of bars that the plot
is displayed on.

8.7.2 June 2017

• A major script publishing improvement: it is now possible to update your script without publishing a new one via
the Update button in the publishing dialog.

8.6. 2018 475


Pine Script™ v5 User Manual

8.7.3 May 2017

• Expanded the type system by adding a new type of constants that can be calculated during compilation.

8.7.4 April 2017

• Expanded the keyword argument functionality: it is now possible to use keyword arguments in all built-in functions.
• A new barstate.isconfirmed variable has been added to the list of variables that return bar status. It lets
you create indicators that are calculated based on the closed bars only.
• The options argument for the input() function creates an input with a set of options defined by the script’s
author.

8.7.5 March 2017

• Pine Script™ v3 is here! Some important changes:


– Changes to the default behavior of the security() function: it can no longer access the future data by
default. This can be changes with the lookahead parameter.
– An implicit conversion of boolean values to numeric values was replaced with an implicit conversion of
numeric values (integer and float) to boolean values.
– Self-referenced and forward-referenced variables were removed. Any PineScript code that used those lan-
guage constructions can be equivalently rewritten using mutable variables.

8.7.6 February 2017

• Several improvements to the strategy tester and the strategy report:


– New Buy & Hold equity graph – a new graph that lets you compare performance of your strategy versus a
“buy and hold”, i.e if you just bought a security and held onto it without trading.
– Added percentage values to the absolute currency values.
– Added Buy & Hold Return to display the final value of Buy & Hold Equity based on last price.
– Added Sharpe Ratio – it shows the relative effectiveness of the investment portfolio (security), a measure
that indicates the average return minus the risk-free return divided by the standard deviation of return on an
investment.
– Slippage lets you simulate a situation when orders are filled at a worse price than expected. It can be set
through the Properties dialog or through the slippage argument in the strategy() function.
– Commission allows yot to add commission for placed orders in percent of order value, fixed price or per
contract. The amount of commission paid is shown in the Commission Paid field. The commission size
and its type can be set through the Properties dialog or through the commission_type and commission_value
arguments in the strategy() function.

476 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.8 2016

8.8.1 December 2016

• Added invite-only scripts. The invite-only indicators are visible in the Community Scripts, but nobody can use
them without explicit permission from the author, and only the author can see the source code.

8.8.2 October 2016

• Introduded indicator revisions. Each time an indicator is saved, it gets a new revision, and it is possible to easily
switch to any past revision from the Pine Editor.

8.8.3 September 2016

• It is now possible to publish indicators with protected source code. These indicators are available in the public
Script Library, and any user can use them, but only the author can see the source code.

8.8.4 July 2016

• Improved the behavior of the fill() function: one call can now support several different colors.

8.8.5 March 2016

• Color type variables now have an additional parameter to set default transparency. The transparency can be set with
the color.new() function, or by adding an alpha-channel value to a hex color code.

8.8.6 February 2016

• Added for loops and keywords break and continue.


• Pine Script™ now supports mutable variables! Use the := operator to assign a new value to a variable that has
already been defined.
• Multiple improvements and bug fixes for strategies.

8.8.7 January 2016

• A new alertcondition() function allows for creating custom alert conditions in Pine Script™-based indi-
cators.

8.8. 2016 477


Pine Script™ v5 User Manual

8.9 2015

8.9.1 October 2015

• Pine has graduated to v2! The new version of Pine Script™ added support for if statements, making it easier to
write more readable and concise code.

8.9.2 September 2015

• Added backtesting functionality to Pine Script™. It is now possible to create trading strategies, i.e. scripts that can
send, modify and cancel orders to buy or sell. Strategies allow you to perform backtesting (emulation of strategy
trading on historical data) and forward testing (emulation of strategy trading on real-time data) according to your
algorithms. Detailed information about the strategy’s calculations and the order fills can be seen in the newly added
Strategy Tester tab.

8.9.3 July 2015

• A new editable parameter allows hiding the plot from the Style menu in the indicator settings so that it is not
possible to edit its style. The parameter has been added to all the following functions: all plot-type functions,
barcolor(), bgcolor(), hline(), and fill().

8.9.4 June 2015

• Added two new functions to display custom barsets using PineScipt: plotbar() and plotcandle().

8.9.5 April 2015

• Added two new shapes to the plotshape() function: shape.labelup and shape.labeldown.
• PineScipt Editor has been improved and moved to a new panel at the bottom of the page.
• Added a new step argument for the input() function, allowing to specify the step size for the indicator’s inputs.

8.9.6 March 2015

• Added support for inputs with the source type to the input() function, allowing to select the data source for
the indicator’s calculations from its settings.

8.9.7 February 2015

• Added a new text argument to plotshape() and plotchar() functions.


• Added four new shapes to the plotshape() function: shape.arrowup, shape.arrowdown, shape.square,
shape.diamond.

478 Chapter 8. Release notes


Pine Script™ v5 User Manual

8.10 2014

8.10.1 August 2014

• Improved the script sharing capabilities, changed the layout of the Indicators menu and separated published scripts
from ideas.

8.10.2 July 2014

• Added three new plotting functions, plotshape(), plotchar(), and plotarrow() for situations when
you need to highlight specific bars on a chart without drawing a line.
• Integrated QUANDL data into Pine Script™. The data can be accessed by passing the QUANDL ticker to the
security function.

8.10.3 June 2014

• Added Pine Script™ sharing, enabling programmers and traders to share their scripts with the rest of the Trad-
ingView community.

8.10.4 April 2014

• Added line wrapping.

8.10.5 February 2014

• Added support for inputs, allowing users to edit the indicator inputs through the properties window, without needing
to edit the Pine script.
• Added self-referencing variables.
• Added support for multiline functions.
• Implemented the type-casting mechanism, automatically casting constant and simple float and int values to series
when it is required.
• Added several new functions and improved the existing ones:
– barssince() and valuewhen() allow you to check conditions on historical data easier.
– The new barcolor() function lets you specify a color for a bar based on filling of a certain condition.
– Similar to the barcolor() function, the bgcolor() function changes the color of the background.
– Reworked the security() function, further expanding its functionality.
– Improved the fill() function, enabling it to be used more than once in one script.
– Added the round() function to round and convert float values to integers.

8.10. 2014 479


Pine Script™ v5 User Manual

8.11 2013

• The first version of Pine Script™ is introduced to all TradingView users, initially as an open beta, on December
13th.

480 Chapter 8. Release notes


CHAPTER

NINE

MIGRATION GUIDES

9.1 To Pine Script™ version 5

• Introduction
• v4 to v5 converter
• Renamed functions and variables
• Renamed function parameters
• Removed an `rsi()` overload
• Reserved keywords
• Removed `iff()` and `offset()`
• Split of `input()` into several functions
• Some function parameters now require built-in arguments
• Deprecated the `transp` parameter
• Changed the default session days for `time()` and `time_close()`
• `strategy.exit()` now must do something
• Common script conversion errors
• All variable, function, and parameter name changes

481
Pine Script™ v5 User Manual

9.1.1 Introduction

This guide documents the changes made to Pine Script™ from v4 to v5. It will guide you in the adaptation of existing
Pine scripts to Pine Script™ v5. See our Release notes for a list of the new features in Pine Script™ v5.
The most frequent adaptations required to convert older scripts to v5 are:
• Changing study() for indicator() (the function’s signature has not changed).
• Renaming built-in function calls to include their new namespace (e.g., highest() in v4 becomes ta.highest() in v5).
• Restructuring inputs to use the more specialized input.*() functions.
• Eliminating uses of the deprecated transp parameter by using color.new() to simultaneously define color and
transparency for use with the color parameter.
• If you used the resolution and resolution_gaps parameters in v4’s study(), they will require changing
to timeframe and timeframe_gaps in v5’s indicator().

9.1.2 v4 to v5 converter

The Pine Editor includes a utility to automatically convert v4 scripts to v5. To access it, open a script with //
@version=4 in it and select the “Convert to v5” option in the “More” menu identified by three dots at the top-right of
the Editor’s pane:

Not all scripts can be automatically converted from v4 to v5. If you want to convert the script manually or if your indicator
returns a compilation error after conversion, use the following sections to determine how to complete the conversion. A
list of some errors you can encounter during the automatic conversion and how to fix them can be found in the Common
script conversion errors section of this guide.

482 Chapter 9. Migration guides


Pine Script™ v5 User Manual

9.1.3 Renamed functions and variables

For clarity and consistency, many built-in functions and variables were renamed in v5. The inclusion of v4 function names
in a new namespace is the cause of most changes. For example, the sma() function in v4 is moved to the ta. namespace
in v5: ta.sma(). Remembering the new namespaces is not necessary; if you type the older name of a function without its
namespace in the Editor and press the ‘Auto-complete’ hotkey (Ctrl + Space, or Cmd + Space on MacOS), a popup
showing matching suggestions appears:

Not counting functions moved to new namespaces, only two functions have been renamed:
• study() is now indicator().
• tickerid() is now ticker.new().
The full list of renamed functions and variables can be found in the All variable, function, and parameter name changes
section of this guide.

9.1.4 Renamed function parameters

The parameter names of some built-in functions were changed to improve the nomenclature. This has no bearing on most
scripts, but if you used these parameter names when calling functions, they will require adaptation. For example, we have
standardized all mentions:

// Valid in v4. Not valid in v5.


timev4 = time(resolution = "1D")
// Valid in v5.
timev5 = time(timeframe = "1D")
// Valid in v4 and v5.
timeBoth = time("1D")

The full list of renamed function parameters can be found in the All variable, function, and parameter name changes
section of this guide.

9.1. To Pine Script™ version 5 483


Pine Script™ v5 User Manual

9.1.5 Removed an `rsi()` overload

In v4, the rsi() function had two different overloads:


• rsi(series float, simple int) for the normal RSI calculation, and
• rsi(series float, series float) for an overload used in the MFI indicator, which did a calculation
equivalent to 100.0 - (100.0 / (1.0 + arg1 / arg2)).
This caused a single built-in function to behave in two very different ways, and it was difficult to distinguish which one
applied because it depended on the type of the second argument. As a result, a number of indicators misused the function
and were displaying incorrect results. To avoid this, the second overload was removed in v5.
The ta.rsi() function in v5 only accepts a “simple int” argument for its length parameter. If your v4 code used the now
deprecated overload of the function with a float second argument, you can replace the whole rsi() call with the
following formula, which is equivalent:
100.0 - (100.0 / (1.0 + arg1 / arg2))

Note that when your v4 code used a “series int” value as the second argument to rsi(), it was automatically cast to “series
float” and the second overload of the function was used. While this was syntactically correct, it most probably did not yield
the result you expected. In v5, ta.rsi() requires a “simple int” for the argument to length, which precludes dynamic (or
“series”) lengths. The reason for this is that RSI calculations use the ta.rma() moving average, which is similar to ta.ema()
in that it relies on a length-dependent recursive process using the values of previous bars. This makes it impossible to
achieve correct results with a “series” length that could vary bar to bar.
If your v4 code used a length that was “const int”, “input int” or “simple int”, no changes are required.

9.1.6 Reserved keywords

A number of words are reserved and cannot be used for variable or function names. They are: catch, class, do,
ellipse, in, is, polygon, range, return, struct, text, throw, try. If your v4 indicator uses any of
these, rename your variable or function for the script to work in v5.

9.1.7 Removed `iff()` and `offset()`

The iff() and offset() functions have been removed. Code using the iff() function can be rewritten using the ternary
operator:
// iff(<condition>, <return_when_true>, <return_when_false>)
// Valid in v4, not valid in v5
barColorIff = iff(close >= open, color.green, color.red)
// <condition> ? <return_when_true> : <return_when_false>
// Valid in v4 and v5
barColorTernary = close >= open ? color.green : color.red

Note that the ternary operator is evaluated “lazily”; only the required value is calculated (depending on the condition’s
evaluation to true or false). This is different from iff(), which always evaluated both values but returned only the
relevant one.
Some functions require evaluation on every bar to correctly calculate, so you will need to make special provisions for these
by pre-evaluating them before the ternary:
// `iff()` in v4: `highest()` and `lowest()` are calculated on every bar
v1 = iff(close > open, highest(10), lowest(10))
plot(v1)
(continues on next page)

484 Chapter 9. Migration guides


Pine Script™ v5 User Manual

(continued from previous page)


// In v5: forced evaluation on every bar prior to the ternary statement.
h1 = ta.highest(10)
l1 = ta.lowest(10)
v1 = close > open ? h1 : l1
plot(v1)

The offset() function was deprecated because the more readable [] operator is equivalent:

// Valid in v4. Not valid in v5.


prevClosev4 = offset(close, 1)
// Valid in v4 and v5.
prevClosev5 = close[1]

9.1.8 Split of `input()` into several functions

The v4 input() function was becoming crowded with a plethora of overloads and parameters. We split its functionality
into different functions to clear that space and provide a more robust structure to accommodate the additions planned for
inputs. Each new function uses the name of the input.* type of the v4 input() call it replaces. E.g., there is now
a specialized input.float() function replacing the v4 input(1.0, type = input.float) call. Note that you
can still use input(1.0) in v5, but because only input.float() allows for parameters such as minval, maxval, etc.,
it is more powerful. Also note that input.int() is the only specialized input function that does not use its equivalent v4
input.integer name. The input.* constants have been removed because they were used as arguments for the
type parameter, which was deprecated.
To convert, for example, a v4 script using an input of type input.symbol, the input.symbol() function must be used
in v5:

// Valid in v4. Not valid in v5.


aaplTicker = input("AAPL", type = input.symbol)
// Valid in v5
aaplTicker = input.symbol("AAPL")

The input() function persists in v5, but in a simpler form, with less parameters. It has the advantage of automatically
detecting input types “bool/color/int/float/string/source” from the argument used for defval:

// Valid in v4 and v5.


// While "AAPL" is a valid symbol, it is only a string here because `input.symbol()`␣
,→is not used.

tickerString = input("AAPL", title = "Ticker string")

9.1.9 Some function parameters now require built-in arguments

In v4, built-in constants such as plot.style_area used as arguments when calling Pine Script™ functions corre-
sponded to pre-defined values of a specific type. For example, the value of barmerge.lookahead_on was true,
so you could use true instead of the named constant when supplying an argument to the lookahead parameter in a
security() function call. We found this to be a common source of confusion, which caused unsuspecting programmers to
produce code yielding unintended results.
In v5, the use of correct built-in named constants as arguments to function parameters requiring them is mandatory:

// Not valid in v5: `true` is used as an argument for `lookahead`.


request.security(syminfo.tickerid, "1D", close, lookahead = true)
(continues on next page)

9.1. To Pine Script™ version 5 485


Pine Script™ v5 User Manual

(continued from previous page)


// Valid in v5: uses a named constant instead of `true`.
request.security(syminfo.tickerid, "1D", close, lookahead = barmerge.lookahead_on)

// Would compile in v4 because `plot.style_columns` was equal to 5.


// Won’t compile in v5.
a = 2 * plot.style_columns
plot(a)

To convert your script from v4 to v5, make sure you use the correct named built-in constants as function arguments.

9.1.10 Deprecated the `transp` parameter

The transp= parameter used in the signature of many v4 plotting functions was deprecated because it interfered with
RGB functionality. Transparency must now be specified along with the color as an argument to parameters such as
color, textcolor, etc. The color.new() or color.rgb() functions will be needed in those cases to join a color and its
transparency.
Note that in v4, the bgcolor() and fill() functions had an optional transp parameter that used a default value of 90.
This meant that the code below could display Bollinger Bands with a semi-transparent fill between two bands and a semi-
transparent backround color where bands cross price, even though no argument is used for the transp parameter in its
bgcolor() and fill() calls:

1 //@version=4
2 study("Bollinger Bands", overlay = true)
3 [middle, upper, lower] = bb(close, 5, 4)
4 plot(middle, color=color.blue)
5 p1PlotID = plot(upper, color=color.green)
6 p2PlotID = plot(lower, color=color.green)
7 crossUp = crossover(high, upper)
8 crossDn = crossunder(low, lower)
9 // Both `fill()` and `bgcolor()` have a default `transp` of 90
10 fill(p1PlotID, p2PlotID, color = color.green)
11 bgcolor(crossUp ? color.green : crossDn ? color.red : na)

In v5 we need to explictly mention the 90 transparency with the color, yielding:

1 //@version=5
2 indicator("Bollinger Bands", overlay = true)
3 [middle, upper, lower] = ta.bb(close, 5, 4)
4 plot(middle, color=color.blue)
5 p1PlotID = plot(upper, color=color.green)
6 p2PlotID = plot(lower, color=color.green)
7 crossUp = ta.crossover(high, upper)
8 crossDn = ta.crossunder(low, lower)
9 var TRANSP = 90
10 // We use `color.new()` to explicitly pass transparency to both functions
11 fill(p1PlotID, p2PlotID, color = color.new(color.green, TRANSP))
12 bgcolor(crossUp ? color.new(color.green, TRANSP) : crossDn ? color.new(color.red,␣
,→TRANSP) : na)

486 Chapter 9. Migration guides


Pine Script™ v5 User Manual

9.1.11 Changed the default session days for `time()` and `time_close()`

The default set of days for session strings used in the time() and time_close() functions, and returned by input.session(),
has changed from "23456" (Monday to Friday) to "1234567" (Sunday to Saturday):
// On symbols that are traded during weekends, this will behave differently in v4 and␣
,→v5.

t0 = time("1D", "1000-1200")
// v5 equivalent of the behavior of `t0` in v4.
t1 = time("1D", "1000-1200:23456")
// v5 equivalent of the behavior of `t0` in v5.
t2 = time("1D", "1000-1200:1234567")

This change in behavior should not have much impact on scripts running on conventional markets that are closed dur-
ing weekends. If it is important for you to ensure your session definitions preserve their v4 behavior in v5 code, add
":23456" to your session strings. See this manual’s page on Sessions for more information.

9.1.12 `strategy.exit()` now must do something

Gone are the days when the strategy.exit() function was allowed to loiter. Now it must actually have an effect on the
strategy by using at least one of the following parameters: profit, limit, loss, stop, or one of the following
pairs: trail_offset combined with either trail_price or trail_points. When uses of strategy.exit() not
meeting these criteria trigger an error while converting a strategy to v5, you can safely eliminate these lines, as they didn’t
do anything in your code anyway.

9.1.13 Common script conversion errors

Invalid argument ‘style’/’linestyle’ in ‘plot’/’hline’ call

To make this work, you need to change the “int” arguments used for the style and linestyle arguments in plot()
and hline() for built-in constants:
// Will cause an error during conversion
plotStyle = input(1)
hlineStyle = input(1)
plot(close, style = plotStyle)
hline(100, linestyle = hlineStyle)

// Will work in v5
//@version=5
indicator("")
plotStyleInput = input.string("Line", options = ["Line", "Stepline", "Histogram",
,→"Cross", "Area", "Columns", "Circles"])

hlineStyleInput = input.string("Solid", options = ["Solid", "Dashed", "Dotted"])

plotStyle = plotStyleInput == "Line" ? plot.style_line :


plotStyleInput == "Stepline" ? plot.style_stepline :
plotStyleInput == "Histogram" ? plot.style_histogram :
plotStyleInput == "Cross" ? plot.style_cross :
plotStyleInput == "Area" ? plot.style_area :
plotStyleInput == "Columns" ? plot.style_columns :
plot.style_circles

hlineStyle = hlineStyleInput == "Solid" ? hline.style_solid :


(continues on next page)

9.1. To Pine Script™ version 5 487


Pine Script™ v5 User Manual

(continued from previous page)


hlineStyleInput == "Dashed" ? hline.style_dashed :
hline.style_dotted

plot(close, style = plotStyle)


hline(100, linestyle = hlineStyle)

See the Some function parameters now require built-in arguments section of this guide for more information.

Undeclared identifier ‘input.%input_name%’

To fix this issue, remove the input.* constants from your code:

// Will cause an error during conversion


_integer = input.integer
_bool = input.bool
i1 = input(1, "Integer", _integer)
i2 = input(true, "Boolean", _bool)

// Will work in v5
i1 = input.int(1, "Integer")
i2 = input.bool(true, "Boolean")

See the User Manual’s page on Inputs, and the Some function parameters now require built-in arguments section of this
guide for more information.

Invalid argument ‘when’ in ‘strategy.close’ call

This is caused by a confusion between strategy.entry() and strategy.close().


The second parameter of strategy.close() is when, which expects a “bool” argument. In v4, it was allowed to use
strategy.long an argument because it was a “bool”. With v5, however, named built-in constants must be used
as arguments, so strategy.long is no longer allowed as an argument to the when parameter.
The strategy.close("Short", strategy.long) call in this code is equivalent to strategy.
close("Short"), which is what must be used in v5:

// Will cause an error during conversion


if (longCondition)
strategy.close("Short", strategy.long)
strategy.entry("Long", strategy.long)

// Will work in v5:


if (longCondition)
strategy.close("Short")
strategy.entry("Long", strategy.long)

See the Some function parameters now require built-in arguments section of this guide for more information.

488 Chapter 9. Migration guides


Pine Script™ v5 User Manual

Cannot call ‘input.int’ with argument ‘minval’=’%value%’. An argument of ‘literal float’ type was used
but a ‘const int’ is expected

In v4, it was possible to pass a “float” argument to minval when an “int” value was being input. This is no longer possible
in v5; “int” values are required for “int” inputs:

// Works in v4, will break on conversion because minval is a 'float' value


int_input = input(1, "Integer", input.integer, minval = 1.0)

// Works in v5
int_input = input.int(1, "Integer", minval = 1)

See the User Manual’s page on Inputs, and the Some function parameters now require built-in arguments section of this
guide for more information.

9.1.14 All variable, function, and parameter name changes

Removed functions and variables

v4 v5
input.bool input Replaced by input.bool()
input.color input Replaced by input.color()
input.float input Replaced by input.float()
input.integer input Replaced by input.int()
input.resolution input Replaced by input.timeframe()
input.session input Replaced by input.session()
input.source input Replaced by input.source()
input.string input Replaced by input.string()
input.symbol input Replaced by input.symbol()
input.time input Replaced by input.time()
iff() Use the ?: operator instead
offset() Use the [] operator instead

Renamed functions and parameters

No namespace change

v4 v5
study(<...>, resolution, indicator(<...>, timeframe,
resolution_gaps, <...>) timeframe_gaps, <...>)
strategy.entry(long) strategy.entry(direction)
strategy.order(long) strategy.order(direction)
time(resolution) time(timeframe)
time_close(resolution) time_close(timeframe)
nz(x, y) nz(source, replacement)

9.1. To Pine Script™ version 5 489


Pine Script™ v5 User Manual

“ta” namespace for technical analysis functions and variables

v4 v5
Indicator functions and variables
accdist ta.accdist
alma() ta.alma()
atr() ta.atr()
bb() ta.bb()
bbw() ta.bbw()
cci() ta.cci()
cmo() ta.cmo()
cog() ta.cog()
dmi() ta.dmi()
ema() ta.ema()
hma() ta.hma()
iii ta.iii
kc() ta.kc()
kcw() ta.kcw()
linreg() ta.linreg()
macd() ta.macd()
mfi() ta.mfi()
mom() ta.mom()
nvi ta.nvi
obv ta.obv
pvi ta.pvi
pvt ta.pvt
rma() ta.rma()
roc() ta.roc()
rsi(x, y) ta.rsi(source, length)
sar() ta.sar()
sma() ta.sma()
stoch() ta.stoch()
supertrend() ta.supertrend()
swma(x) ta.swma(source)
tr ta.tr
tr() ta.tr()
tsi() ta.tsi()
vwap ta.vwap
vwap(x) ta.vwap(source)
vwma() ta.vwma()
wad ta.wad
wma() ta.wma()
wpr() ta.wpr()
wvad ta.wvad
Supporting functions
barsince() ta.barsince()
change() ta.change()
correlation(source_a, source_b, length) ta.correlation(source1, source2, length)
cross(x, y) ta.cross(source1, source2)
crossover(x, y) ta.crossover(source1, source2)
crossunder(x, y) ta.crossunder(source1, source2)
cum(x) ta.cum(source)
continues on next page

490 Chapter 9. Migration guides


Pine Script™ v5 User Manual

Table 1 – continued from previous page


dev() ta.dev()
falling() ta.falling()
highest() ta.highest()
highestbars() ta.highestbars()
lowest() ta.lowest()
lowestbars() ta.lowestbars()
median() ta.median()
mode() ta.mode()
percentile_linear_interpolation() ta.percentile_linear_interpolation()
percentile_nearest_rank() ta.percentile_nearest_rank()
percentrank() ta.percentrank()
pivothigh() ta.pivothigh()
pivotlow() ta.pivotlow()
range() ta.range()
rising() ta.rising()
stdev() ta.stdev()
valuewhen() ta.valuewhen()
variance() ta.variance()

“math” namespace for math-related functions and variables

v4 v5
abs(x) math.abs(number)
acos(x) math.acos(number)
asin(x) math.asin(number)
atan(x) math.atan(number)
avg() math.avg()
ceil(x) math.ceil(number)
cos(x) math.cos(angle)
exp(x) math.exp(number)
floor(x) math.floor(number)
log(x) math.log(number)
log10(x) math.log10(number)
max() math.max()
min() math.min()
pow() math.pow()
random() math.random()
round(x, precision) math.round(number, precision)
round_to_mintick(x) math.round_to_mintick(number)
sign(x) math.sign(number)
sin(x) math.sin(angle)
sqrt(x) math.sqrt(number)
sum() math.sum()
tan(x) math.tan(angle)
todegrees() math.todegrees()
toradians() math.toradians()

9.1. To Pine Script™ version 5 491


Pine Script™ v5 User Manual

“request” namespace for functions that request external data

v4 v5
financial() request.financial()
quandl() request.quandl()
security(<...>, resolution, <...>) request.security(<...>, timeframe, <...>)
splits() request.splits()
dividends() request.dividends()
earnings() request.earnings()

“ticker” namespace for functions that help create tickers

v4 v5
heikinashi() ticker.heikinashi()
kagi() ticker.kagi()
linebreak() ticker.linebreak()
pointfigure() ticker.pointfigure()
renko() ticker.renko()
tickerid() ticker.new()

“str” namespace for functions that manipulate strings

v4 v5
tostring(x, y) str.tostring(value, format)
tonumber(x) str.tonumber(string)

9.2 To Pine Script™ version 4

This is a guide to converting Pine Script™ code from @version=3 to @version=4.

492 Chapter 9. Migration guides


Pine Script™ v5 User Manual

9.2.1 Converter

The Pine Editor comes with a utility to automatically convert v3 indicators and strategies to v4. To access it, open a script
with //@version=3 in it and select the Convert to v4 option in the More dropdown menu:

Not all scripts can be automatically converted from v3 to v4. If you want to convert the script manually or if your indicator
returns a compilation error after conversion, consult the guide below for more information.

9.2.2 Renaming of built-in constants, variables, and functions

In Pine Script™ v4 the following built-in constants, variables, and functions were renamed:
• Color constants (e.g red) are moved to the color.* namespace (e.g. color.red).
• The color function has been renamed to color.new.
• Constants for input() types (e.g. integer) are moved to the input.* namespace (e.g. input.integer).
• The plot style constants (e.g. histogram style) are moved to the plot.style_* namespace (e.g. plot.
style_histogram).
• Style constants for the hline function (e.g. the dotted style) are moved to the hline.style_* namespace
(e.g. hline.style_dotted).
• Constants of days of the week (e.g. sunday) are moved to the dayofweek.* namespace (e.g. dayofweek.
sunday).

9.2. To Pine Script™ version 4 493


Pine Script™ v5 User Manual

• The variables of the current chart timeframe (e.g. period, isintraday) are moved to the timeframe.*
namespace (e.g. timeframe.period, timeframe.isintraday).
• The interval variable was renamed to timeframe.multiplier.
• The ticker and tickerid variables are renamed to syminfo.ticker and syminfo.tickerid re-
spectively.
• The n variable that contains the bar index value has been renamed to bar_index.
The reason behind renaming all of the above was to structure the standard language tools and make working with code
easier. New names are grouped according to assignments under common prefixes. For example, you will see a list with
all available color constants if you type ‘color’ in the editor and press Ctrl + Space.

9.2.3 Explicit variable type declaration

In Pine Script™ v4 it’s no longer possible to create variables with an unknown data type at the time of their declaration.
This was done to avoid a number of issues that arise when the variable type changes after its initialization with the na
value. From now on, you need to explicitly specify their type using keywords or type functions (for example, float)
when declaring variables with the na value:

9.3 To Pine Script™ version 3

This document helps to migrate Pine Script™ code from @version=2 to @version=3.

9.3.1 Default behaviour of security function has changed

Let’s look at the simple security function use case. Add this indicator on an intraday chart:

1 // Add this indicator on an intraday (e.g., 30 minutes) chart


2 //@version=2
3 study("My Script", overlay=true)
4 s = security(tickerid, 'D', high, false)
5 plot(s)

This indicator is calculated based on historical data and looks somewhat into the future. At the first bar of every session
an indicator plots the high price of the entire day. This could be useful in some cases for analysis, but doesn’t work for
backtesting strategies.
We worked on this and made changes in Pine Script™ version 3. If this indica-
tor is compiled with //@version=3 directive, we get a completely different picture:

494 Chapter 9. Migration guides


Pine Script™ v5 User Manual

The old behaviour is still available though. We added a parameter to the security function (the fifth one) called
lookahead.
It can take on the form of two different values: barmerge.lookahead_off (and this is the default for Pine Script™
version 3) or barmerge.lookahead_on (which is the default for Pine Script™ version 2).

9.3.2 Self-referenced variables are removed

Pine Script™ version 2 pieces of code, containing a self-referencing variable:

1 //@version=2
2 //...
3 s = nz(s[1]) + close

Compiling this piece of code with Pine Script™ version 3 will give you an Undeclared identifier 's' error.
It should be rewritten as:

1 //@version=3
2 //...
3 s = 0.0
4 s := nz(s[1]) + close

s is now a mutable variable that is initialized at line 3. At line 3 the initial value gives the Pine Script™ compiler the
information about the variable type. It’s a float in this example.
In some cases you may initialize that mutable variable (like s) with a na value. But in complex cases that won’t work.

9.3. To Pine Script™ version 3 495


Pine Script™ v5 User Manual

9.3.3 Forward-referenced variables are removed

1 //@version=2
2 //...
3 d = nz(f[1])
4 e = d + 1
5 f = e + close

In this example f is a forward-referencing variable, because it’s referenced at line 3 before it was declared and initialized.
In Pine Script™ version 3 this will give you an error Undeclared identifier 'f'. This example should be
rewritten in Pine Script™ version 3 as follows:
1 //@version=3
2 //...
3 f = 0.0
4 d = nz(f[1])
5 e = d + 1
6 f := e + close

9.3.4 Resolving a problem with a mutable variable in a security expression

When you migrate script to version 3 it’s possible that after removing self-referencing and forward-referencing variables
the Pine Script™ compiler will give you an error:
1 //@version=3
2 //...
3 s = 0.0
4 s := nz(s[1]) + close
5 t = security(tickerid, period, s)

Cannot use mutable variable as an argument for security function!


This limitation exists since mutable variables were introduced in Pine Script™, i.e., in version 2. It can be resolved as
before: wrap the code with a mutable variable in a function:
1 //@version=3
2 //...
3 calcS() =>
4 s = 0.0
5 s := nz(s[1]) + close
6 t = security(tickerid, period, calcS())

9.3.5 Math operations with booleans are forbidden

In Pine Script™ v2 there were rules of implicit conversion of booleans into numeric types. In v3 this is forbidden. There
is a conversion of numeric types into booleans instead (0 and na values are false, all the other numbers are true).
Example (In v2 this code compiles fine):
1 //@version=2
2 study("My Script")
3 s = close >= open
4 s1 = close[1] >= open[1]
5 s2 = close[2] >= open[2]
6 sum = s + s1 + s2
(continues on next page)

496 Chapter 9. Migration guides


Pine Script™ v5 User Manual

(continued from previous page)


7 col = sum == 1 ? white : sum == 2 ? blue : sum == 3 ? red : na
8 bgcolor(col)

Variables s, s1 and s2 are of bool type. But at line 6 we add three of them and store the result in a variable sum. sum
is a number, since we cannot add booleans. Booleans were implicitly converted to numbers (true values to 1.0 and
false to 0.0) and then they were added.
This approach leads to unintentional errors in more complicated scripts. That’s why we no longer allow implicit conversion
of booleans to numbers.
If you try to compile this example as a Pine Script™ v3 code, you’ll get an error: Cannot call `operator +`
with arguments (series__bool, series__bool); <...> It means that you cannot use the addition
operator with boolean values. To make this example work in Pine Script™ v3 you can do the following:

1 //@version=3
2 study("My Script")
3 bton(b) =>
4 b ? 1 : 0
5 s = close >= open
6 s1 = close[1] >= open[1]
7 s2 = close[2] >= open[2]
8 sum = bton(s) + bton(s1) + bton(s2)
9 col = sum == 1 ? white : sum == 2 ? blue : sum == 3 ? red : na
10 bgcolor(col)

Function bton (abbreviation of boolean-to-number) explicitly converts any boolean value to a number if you really need
this.

9.3. To Pine Script™ version 3 497


Pine Script™ v5 User Manual

498 Chapter 9. Migration guides


CHAPTER

TEN

WHERE CAN I GET MORE INFORMATION?

• External resources
• Download this manual

• A description of all the Pine Script™ operators, variables and functions can be found in the Reference Manual.
• Use the code from one of TradingView’s built-in scripts to start from. Open a new chart and click the “Pine Editor”
button on the toolbar. Once in the editor window, click the “Open” button, then select “Built-in script…” from the
dropdown list to open a dialog box containing a list of TradingView’s built-in scripts.
• There is a TradingView public chat dedicated to Pine Script™ Q&A where active developers of our community
help each other out.
• Information about major releases and modifications to Pine Script™ (as well as other features) is regularly published
on TradingView’s blog.
• TradingView’s Community Scripts contain all user-published scripts. They can also be accessed from charts using
the “Indicators & Strategies” button and the “Community Scripts” tab of the script searching dialog box.

10.1 External resources

• The PineCoders account on TradingView publishes useful information for Pine Script™ programmers. They also
have content on their website.
• Kodify has TradingView tutorials on various topics for beginners and more experienced programmers alike. Topics
include plotting, alerts, strategy orders, and complete example indicators and strategies.
• Backtest Rookies publishes good quality blog articles focusing on realizing specific tasks in Pine Script™.
• You can ask questions about programming in Pine Script™ in the [pine-script] tag on StackOverflow.

10.2 Download this manual

Available versions:
• PDF

499

You might also like