Pine Script v5 User Manual (350-509)
Pine Script v5 User Manual (350-509)
1 //@version=5
2 strategy("Buy low, sell high", overlay = true, default_qty_type = strategy.cash,␣
,→default_qty_value = 5000)
9 switch
10 low == lowest => strategy.entry("Buy", strategy.long)
11 high == highest => strategy.entry("Sell", strategy.short)
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:
1 //@version=5
2 strategy("Exit Demo", pyramiding = 2)
3
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:
1 //@version=5
2 strategy("Exit Demo", pyramiding = 2)
3
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.
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:
1 //@version=5
2 strategy("OCA Cancel Demo", overlay=true)
3
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
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
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)
11 else
12 strategy.close_all()
13
`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
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
17
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
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.
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)
9 if close == highest
10 strategy.entry("Buy", strategy.long)
11 if close == lowest
12 strategy.entry("Sell", strategy.short)
13
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:
`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:
1 //@version=5
2 strategy("Intrabar exit", overlay = true, calc_on_order_fills = true)
3
7 if strategy.position_size == 0.0
8 strategy.entry("Buy", strategy.long)
9
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)
`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.
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:
1 //@version=5
2 strategy("Commission Demo", overlay=true, default_qty_value = 2, default_qty_type =␣
,→strategy.percent_of_equity)
9 switch close
10 highest => strategy.entry("Long", strategy.long)
11 lowest => strategy.close("Long")
12
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:
1 //@version=5
2 strategy(
3 "Commission Demo", overlay=true, default_qty_value = 2, default_qty_type =␣
,→strategy.percent_of_equity,
12 switch close
13 highest => strategy.entry("Long", strategy.long)
14 lowest => strategy.close("Long")
15
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.
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
12 //@variable Returns `true` when `ma` has increased and `close` is greater than it,␣
,→`false` otherwise.
18 if longCondition
19 strategy.entry("Buy", strategy.long)
20 if shortCondition
21 strategy.close("Buy")
22
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
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,
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
9 //@variable Draws a line at the limit price of the most recent entry order.
10 var line limitLine = na
11
16 // Place an entry order and draw a new line when the the `high` equals the `highest`␣
,→value and `limitLine` is `na`.
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
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.
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.
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
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.
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.
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}}
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
This script will populate the alert creation dialogue with its default message when the user selects its name from the
“Condition” dropdown tab:
Upon the alert trigger, the strategy will populate the placeholders in the alert message with their corresponding values.
For example:
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 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.
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.
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.
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.
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.")
6 myAtr = ta.atr(atrPeriodInput)
7 if barstate.islast
8 table.cell(atrDisplay, 0, 0, str.tostring(myAtr, format.mintick), text_color =␣
,→color.white)
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.
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.
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
15
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.
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
10 // ————— Function draws a heatmap showing the position of the current `_src` relative␣
,→to its past `_lookBack` values.
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
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.
• 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.
• 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)
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)
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).
• 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:
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))
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))
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”):
4.19.3 `plotshape()`
This function is useful to display pre-defined shapes and/or text on bars. It has the following syntax:
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))
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")
The available shapes you can use with the style parameter are:
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:
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.
• 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)
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.
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.
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
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)
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
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)
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)
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:
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.:
Cloning labels
label.copy(id) → void
Deleting labels
label.delete(id) → void
To keep only a user-defined quantity of labels on the chart, one could use code like this:
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])
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.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
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."
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
Note that:
• 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.
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"
• "Asia/Kolkata"
Non-fractional offsets can be expressed in the "GMT+5" form. "GMT+5.5" is not allowed.
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.
`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
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:
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.
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.
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.
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:
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 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():
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.
`timestamp()`
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)
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)
Timestamps can be formatted using str.format(). These are examples of various formats:
1 //@version=5
2 indicator("", "", true)
3
7 if barstate.islast
8 label.set_xy(lbl, bar_index, hl2[1])
9 label.set_text(lbl, txt)
10
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.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.
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.
FIVE
WRITING SCRIPTS
• 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
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
<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
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 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)
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])
<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
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.
<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)
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
)
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)
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.
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")
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:
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.
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.
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:
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
11 print("Hello world!\n\n\n\n")
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
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.
• 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
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.
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)
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.
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
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)
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)
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)
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)
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.
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)
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
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"
5.2.8 Tips
The two techniques we use most frequently to debug our Pine Script™ code are:
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(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):
,→{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:
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()
Note: AutoHotkey works only on Windows systems. Keyboard Maestro or others can be substituted on Apple systems.
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.
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.
• 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.
Visibility
Public
Private
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.
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.
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. 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.
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.
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.
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.
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.4 Limitations
• Introduction
• Time
• Chart visuals
• `request.*()` calls
• Script size and memory
• Other limitations
5.4.1 Introduction
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.
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.
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:
1 //@version=5
2 indicator("Plot count example")
3
13 // Uses two plot counts for the `close` and `color` series.
14 plot(close, color = isUpColor)
15
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.
39
40 // Uses seven plot counts for the `open`, `high`, `low`, `close`, `color`,␣
,→`wickcolor`, and `bordercolor` series.
42
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)
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
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.
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:
1 //@version=5
2
11 longCondition = bar_index % 2 != 0
12 shortCondition = bar_index % 2 == 0
13
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
11 longCondition = bar_index % 2 != 0
12 shortCondition = bar_index % 2 == 0
13
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.
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.
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
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.
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.
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.
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.
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
23 // This function call is executed on all bars, but it only draws the `line` on the␣
,→last bar.
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.
A maximum of 9000 orders can be placed when backtesting strategies. When using Deep Backtesting, the limit is
200,000.
SIX
FAQ
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
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)
7 if trade == 1
8 strategy.entry("BarUp", strategy.long)
9 if trade == -1
10 strategy.entry("BarDn", strategy.short)
//@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:
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")
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
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
1 //@version=5
2 //...
3 s = 0.0
4 s := nz(s[1]) // Accessing previous values
5 if (condition)
6 s := s + 1
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
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)
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
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)
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
SEVEN
ERROR MESSAGES
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:
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
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($)
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")
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:
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:
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
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
EIGHT
RELEASE NOTES
• 2023
• 2022
• 2021
• 2020
• 2019
• 2018
• 2017
• 2016
• 2015
• 2014
• 2013
8.1 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.
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.
• 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:
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.
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.
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:
• 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.
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.
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.
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.2 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
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.
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.
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.
1 //@version=5
2 // @strategy_alert_message My Default Alert Message
3 strategy("My Strategy")
4 plot(close)
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.
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)
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.
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.
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.
• 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.
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.3 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()
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()
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.
for…in
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'
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()
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()
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.
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.
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.4 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.
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]
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)
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()
• 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)
• Optimized script compilation time. Scripts now compile 1.5 to 2 times faster.
• 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.
• 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")
• 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.
• 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)
• 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.5 2019
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.6 2018
• To increase the number of indicators available to the whole community, Invite-Only scripts can now be published
by Premium users only.
• Improved the Strategy Tester by reworking the Maximum Drawdown calculation formula.
8.7 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.
• 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.
• Expanded the type system by adding a new type of constants that can be calculated during compilation.
• 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.8 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.
• 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.
• 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.
• Improved the behavior of the fill() function: one call can now support several different colors.
• 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.
• A new alertcondition() function allows for creating custom alert conditions in Pine Script™-based indi-
cators.
8.9 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.
• 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.
• 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().
• Added two new functions to display custom barsets using PineScipt: plotbar() and plotcandle().
• 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.
• 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.10 2014
• Improved the script sharing capabilities, changed the layout of the Indicators menu and separated published scripts
from ideas.
• 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.
• Added Pine Script™ sharing, enabling programmers and traders to share their scripts with the rest of the Trad-
ingView community.
• 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.11 2013
• The first version of Pine Script™ is introduced to all TradingView users, initially as an open beta, on December
13th.
NINE
MIGRATION GUIDES
• 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.
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.
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:
The full list of renamed function parameters can be found in the All variable, function, and parameter name changes
section of this guide.
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.
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.
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)
The offset() function was deprecated because the more readable [] operator is equivalent:
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:
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:
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:
To convert your script from v4 to v5, make sure you use the correct named built-in constants as function arguments.
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)
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)
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.
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.
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"])
See the Some function parameters now require built-in arguments section of this guide for more information.
To fix this issue, remove the input.* constants from your code:
// 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.
See the Some function parameters now require built-in arguments section of this guide for more information.
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 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.
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
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)
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
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()
v4 v5
financial() request.financial()
quandl() request.quandl()
security(<...>, resolution, <...>) request.security(<...>, timeframe, <...>)
splits() request.splits()
dividends() request.dividends()
earnings() request.earnings()
v4 v5
heikinashi() ticker.heikinashi()
kagi() ticker.kagi()
linebreak() ticker.linebreak()
pointfigure() ticker.pointfigure()
renko() ticker.renko()
tickerid() ticker.new()
v4 v5
tostring(x, y) str.tostring(value, format)
tonumber(x) str.tonumber(string)
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.
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).
• 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.
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:
This document helps to migrate Pine Script™ code from @version=2 to @version=3.
Let’s look at the simple security function use case. Add this indicator on an intraday chart:
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:
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).
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.
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
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)
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)
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.
TEN
• 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.
• 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.
Available versions:
• PDF
499