Debugging
Introduction
TradingView’s close integration between the Pine Editor and the chart interface facilitates efficient, interactive debugging of Pine Script™ code, as scripts can produce dynamic results in multiple locations, on and off the chart. Programmers can utilize such results to refine their script’s behaviors and ensure everything works as expected.
When a programmer understands the appropriate techniques for inspecting the variety of behaviors one may encounter while writing a script, they can quickly and thoroughly identify and resolve potential problems in their code, which allows for a more seamless overall coding experience. This page demonstrates some of the handiest ways to debug code when working with Pine Script™.
The lay of the land
Pine scripts can output their results in multiple different ways, any of which programmers can utilize for debugging.
The plot*()
functions can display results in a chart pane, the
script’s status line, the price (y-axis) scale, and the Data Window,
providing simple, convenient ways to debug numeric and conditional
values:
Note that:
- A script’s status line outputs will only show when enabling the “Values” checkbox within the “Indicators” section of the chart’s “Status line” settings.
- Price scales will only show plot values or names when enabling the options from the “Indicators and financials” dropdown in the chart’s “Scales and lines” settings.
The bgcolor() function displays colors in the script pane’s background, and the barcolor() function changes the colors of the main chart’s bars or candles. Both of these functions provide a simple way to visualize conditions:
Pine’s drawing types (line, box, polyline, label) produce drawings in the script’s pane. While they don’t return results in other locations, such as the status line or Data Window, they provide alternative, flexible solutions for inspecting numeric values, conditions, and strings directly on the chart:
The log.*()
functions produce
Pine Logs
results. Every time a script calls any of these functions, the script
logs a message in the
Pine Logs pane,
along with a timestamp and navigation options to identify the specific
times, chart bars, and lines of code that triggered a log:
One can apply any of the above, or a combination, to establish debugging routines to fit their needs and preferences, depending on the data types and structures they’re working with. See the sections below for detailed explanations of various debugging techniques.
Numeric values
When creating code in Pine Script™, working with numbers is inevitable. Therefore, to ensure a script works as intended, it’s crucial to understand how to inspect the numeric (int and float) values it receives and calculates.
Plotting numbers
One of the most straightforward ways to inspect a script’s numeric
values is to use plot*()
functions, which can display results
graphically on the chart and show formatted numbers in the script’s
status line, the price scale, and the Data Window. The locations where a
plot*()
function displays its results depend on the display
parameter. By default, its value is
display.all.
The following example uses the plot() function to display the 1-bar change in the value of the built-in time variable measured in chart timeframes (e.g., a plotted value of 1 on the “1D” chart means there is a one-day difference between the opening times of the current and previous bars). Inspecting this series can help to identify time gaps in a chart’s data, which is helpful information when designing time-based indicators.
Since we have not specified a display
argument, the function uses
display.all,
meaning it will show data in all possible locations, as we see below:
Note that:
- The numbers displayed in the script’s status line and the Data Window reflect the plotted values at the location of the chart’s cursor. These areas will show the latest bar’s value when the mouse pointer isn’t on the chart.
- The number in the price scale reflects the latest available value on the visible chart.
Without affecting the scale
When debugging multiple numeric values in a script, programmers may wish to inspect them without interfering with the price scales or cluttering the visual outputs in the chart’s pane, as distorted scales and overlapping plots may make it harder to evaluate the results.
A simple way to inspect numbers without adding more visuals to the
chart’s pane is to change the display
values in the script’s
plot*()
calls to other display.*
variables or expressions using
them.
Let’s look at a practical example. Here, we’ve drafted the following
script that calculates a custom-weighted moving average by dividing the
sum
of weight * close
values by the
sum
of the weight
series:
Suppose we’d like to inspect the variables used in the average
calculation to understand and fine-tune the result. If we were to use
plot()
to display the script’s weight
, numerator
, and denominator
in all
locations, we can no longer easily identify our average
line on the
chart since each variable has a radically different scale:
While we could hide individual plots from the “Style” tab of the
script’s settings, doing so also prevents us from inspecting the
results in any other location. To simultaneously view the variables’
values and preserve the scale of our chart, we can change the display
values in our debug plots.
The version below includes a debugLocations
variable in the debug
plot()
calls with a value of display.all - display.pane
to specify that all
locations except the chart pane will show the results. Now we can
inspect the calculation’s values without the extra clutter:
From local scopes
A script’s local scopes are sections of indented code within
conditional structures,
functions,
and methods. When working with variables declared within these scopes,
using the plot*()
functions to display their values directly will
not work, as plots only work with literals and global variables.
To display a local variable’s values using plots, one can assign its
results to a global variable and pass that variable to the plot*()
call.
For example, this script calculates the all-time maximum and minimum
change in the
close
price over a lengthInput
period. It uses an
if
structure to declare a local change
variable and update the global
maxChange
and minChange
once every lengthInput
bars:
Suppose we want to inspect the history of the change
variable using a
plot. While we cannot plot the variable directly since the script
declares it in a local scope, we can assign its value to another
global variable for use in a plot*()
function.
Below, we’ve added a debugChange
variable with an initial value of
na to
the global scope, and the script reassigns its value within the
if
structure using the local change
variable. Now, we can use
plot()
with the debugChange
variable to view the history of available
change
values:
Note that:
- The script uses plot.style_areabr in the debug plot, which doesn’t bridge over na values as the default style does.
- When the rightmost visible bar’s plotted value is na the number in the price scale represents the latest non-na value before that bar, if one exists.
With drawings
An alternative approach to graphically inspecting the history of a script’s numeric values is to use Pine’s drawing types, including lines, boxes, polylines, and labels.
While Pine drawings don’t display results anywhere other than the chart pane, scripts can create them from within local scopes, including the scopes of functions and methods (see the Debugging functions section to learn more). Additionally, scripts can position drawings at any available chart location, irrespective of the current bar_index.
For example, let’s revisit the “Periodic changes” script from the
previous section. Suppose we’d like to inspect the history of the local
change
variable without using a plot. In this case, we can avoid
declaring a separate global variable and instead create drawing objects
directly from the
if
structure’s local scope.
The script below is a modification of the previous script that uses
boxes to
visualize the change
variable’s behavior. Inside the scope of the
if
structure, it calls
box.new()
to create a
box
that spans from the bar lengthInput
bars ago to the current
bar_index:
Note that:
- The script includes
max_boxes_count = 500
in the indicator() function, which allows it to show up to 500 boxes on the chart. - We used math.max(change,
0.0)
and math.min(change,
0.0)
in the
box.new()
function as the
top
andbottom
values. - The
box.new()
call includes
str.tostring(change)
as its
text
argument to display a “string” representation of thechange
variable’s “float” value in each box drawing. See this portion of the Strings section below to learn more about representing data with strings.
For more information about using boxes and other related drawing types, see our User Manual’s Lines and boxes page.
Conditions
Many scripts one will create in Pine involve declaring and evaluating conditions to dictate specific script actions, such as triggering different calculation patterns, visuals, signals, alerts, strategy orders, etc. As such, it’s imperative to understand how to inspect the conditions a script uses to ensure proper execution.
As numbers
One possible way to debug a script’s conditions is to define numeric values based on them, which allows programmers to inspect them using numeric approaches, such as those outlined in the previous section.
Let’s look at a simple example. This script calculates the ratio
between the
ohlc4
price and the lengthInput
-bar moving
average.
It assigns a condition to the priceAbove
variable that returns true
whenever the value of the ratio exceeds 1 (i.e., the price is above the
average).
To inspect the occurrences of the condition, we created a debugValue
variable assigned to the result of an expression that uses the ternary
?:
operator to return 1 when priceAbove
is true
and 0 otherwise. The
script plots the variable’s value in all available locations:
Note that:
- Representing “bool” values using numbers also allows scripts to display conditional shapes or characters at specific y-axis locations with plotshape() and plotchar(), and it facilitates conditional debugging with plotarrow(). See the next section to learn more.
Plotting conditional shapes
The
plotshape()
and
plotchar()
functions provide utility for debugging conditions, as they can plot
shapes or characters at absolute or relative chart locations whenever
they contain a true
or non-na series
argument.
These functions can also display numeric representations of the
series
in the script’s status line and the Data Window, meaning
they’re also helpful for debugging
numbers.
We show a simple, practical way to debug numbers with these functions in
the Tips section.
The chart locations of the plots depend on the location
parameter,
which is
location.abovebar
by default.
Let’s inspect a condition using these functions. The following script
calculates an
RSI
with a lengthInput
length and a crossBelow
variable whose value is
the result of a condition that returns true
when the RSI crosses below
30. It calls
plotshape()
to display a circle near the top of the pane each time the condition
occurs:
Note that:
- The status line and Data Window show a value of 1 when
crossBelow
istrue
and 0 when it’sfalse
.
Suppose we’d like to display the shapes at precise locations rather than relative to the chart pane. We can achieve this by using conditional numbers and location.absolute in the plotshape() call.
In this example, we’ve modified the previous script by creating a
debugNumber
variable that returns the rsi
value when crossBelow
is
true
and
na
otherwise. The
plotshape()
function uses this new variable as its series
argument and
location.absolute
as its location
argument:
Note that:
- Since we passed a numeric series to the function, our
conditional plot now shows the values of the
debugNumber
in the status line and Data Window instead of 1 or 0.
Another handy way to debug conditions is to use
plotarrow().
This function plots an arrow with a location relative to the main chart
prices whenever the series
argument is nonzero and not
na. The
length of each arrow varies with the series
value supplied. As with
plotshape()
and
plotchar(),
plotarrow()
can also display numeric results in the status line and the Data Window.
This example shows an alternative way to inspect our crossBelow
condition using
plotarrow().
In this version, we’ve set overlay
to true
in the
indicator()
function and added a
plotarrow()
call to visualize the conditional values. The debugNumber
in this
example measures how far the rsi
dropped below 30 each time the
condition occurs:
Note that:
- We set the
display
value in the plot() of thersi
to display.data_window to preserve the chart’s scale.
To learn more about plotshape(), plotchar(), and plotarrow(), see this manual’s Text and shapes page.
Conditional colors
An elegant way to visually represent conditions in Pine is to create
expressions that return
color
values based on true
or false
states, as scripts can use them to
control the appearance of
drawing objects or the results of plot*()
,
fill(),
bgcolor(),
or
barcolor()
calls.
For example, this script calculates the change in
close
prices over lengthInput
bars and declares two “bool” variables to
identify when the price change is positive or negative.
The script uses these “bool” values as conditions in
ternary
expressions to assign the values of three “color” variables, then uses
those variables as the color
arguments in
plot(),
bgcolor(),
and
barcolor()
to debug the results:
Note that:
- The barcolor() function always colors the main chart’s bars, regardless of whether the script occupies another chart pane, and the chart will only display the results if the bars are visible.
See the Colors, Fills, Backgrounds, and Bar coloring pages for more information about working with colors, filling plots, highlighting backgrounds, and coloring bars.
Using drawings
Pine Script™‘s drawing types provide flexible ways to visualize conditions on the chart, especially when the conditions are within local scopes.
Consider the following script, which calculates a custom filter
with a
smoothing parameter (alpha
) that changes its value within an
if
structure based on recent
volume
conditions:
Suppose we’d like to inspect the conditions that control the alpha
value. There are several ways we could approach the task with chart
visuals. However, some approaches will involve more code and careful
handling.
For example, to visualize the
if
structure’s conditions using
plotted shapes or
background colors, we’d have to create additional variables or expressions in
the global scope for the plot*()
or
bgcolor()
functions to access.
Alternatively, we can use drawing types to visualize the conditions concisely without those extra steps.
The following is a modification of the previous script that calls label.new() within specific branches of the conditional structure to draw labels on the chart whenever those branches execute. These simple changes allow us to identify those conditions on the chart without much extra code:
Note that:
- We added the
label.new()
calls above the
alpha
reassignment expressions, as the returned types of each branch in the if structure must match. - The
indicator()
function includes
max_labels_count = 500
to specify that the script can show up to 500 labels on the chart.
Compound and nested conditions
When a programmer needs to identify situations where more than one condition can occur, they may construct compound conditions by aggregating individual conditions with logical operators (and, or).
For example, this line of code shows a compoundCondition
variable that
only returns true
if condition1
and either condition2
or
condition3
occurs:
One may alternatively create nested conditions using
conditional structures or ternary
expressions.
For example, this
if
structure assigns true
to the nestedCondition
variable if
condition1
and condition2
or condition3
occurs. However, unlike
the logical expression above, the branches of this structure also allow
the script to execute additional code before assigning the “bool”
value:
In either case, whether working with compound or nested conditions in code, one will save many headaches and ensure they work as expected by validating the behaviors of the individual conditions that compose them.
For example, this script calculates an rsi
and the median
of the
rsi
over lengthInput
bars. Then, it creates five variables to
represent different singular conditions. The script uses these variables
in a logical expression to assign a “bool” value to the
compoundCondition
variable, and it displays the results of the
compoundCondition
using a
conditional background color:
As we see above, it’s not necessarily easy to understand the behavior
of the compoundCondition
by only visualizing its end result, as five
underlying singular conditions determine the final value. To effectively
debug the compoundCondition
in this case, we must also inspect the
conditions that compose it.
In the example below, we’ve added five
plotchar()
calls to display
characters on the chart and numeric values in the status line and Data
Window when each singular condition occurs. Inspecting each of these
results provides us with more complete information about the
compoundCondition
’s behavior:
Note that:
- Each
plotchar()
call uses a
conditional number as the
series
argument. The functions display the numeric values in the status line and Data Window. - All the
plotchar()
calls, excluding the one for the
closeBelow
condition, use location.absolute as thelocation
argument to display characters at precise locations whenever theirseries
is not na (i.e., the condition occurs). The call forcloseBelow
uses location.bottom to display its characters near the bottom of the pane. - In this section’s examples, we assigned individual conditions to separate variables with straightforward names and annotations. While this format isn’t required to create a compound condition since one can combine conditions directly within a logical expression, it makes for more readable code that’s easier to debug, as explained in the Tips section.
Strings
Strings are sequences of alphanumeric, control, and other characters (e.g., Unicode). They provide utility when debugging scripts, as programmers can use them to represent a script’s data types as human-readable text and inspect them with drawing types that have text-related properties, or by using Pine Logs.
Representing other types
Users can create “string” representations of virtually any data type, facilitating effective debugging when other approaches may not suffice. Before exploring “string” inspection techniques, let’s briefly review ways to represent a script’s data using strings.
Pine Script™ includes predefined logic to construct “string” representations of several other built-in types, such as int, float, bool, array, and matrix. Scripts can conveniently represent such types as strings via the str.tostring() and str.format() functions.
For example, this snippet creates strings to represent multiple values using these functions:
When working with “int” values that symbolize UNIX timestamps, such as those returned from time-related functions and variables, one can also use str.format() or str.format_time() to convert them to human-readable date strings. This code block demonstrates multiple ways to convert a timestamp using these functions:
When working with types that don’t have built-in “string” representations, e.g., color, map, user-defined types, etc., programmers can use custom logic or formatting to construct representations. For example, this code calls str.format() to represent a “color” value using its r, g, b, and t components:
There are countless ways one can represent data using strings. When choosing string formats for debugging, ensure the results are readable and provide enough information for proper inspection. The following segments explain ways to validate strings by displaying them on the chart using labels, and the section after these segments explains how to display strings as messages in the Pine Logs pane.
Using labels
Labels allow scripts to display dynamic text (“series strings”) at any available location on the chart. Where to display such text on the chart depends on the information the programmer wants to inspect and their debugging preferences.
On successive bars
When inspecting the history of values that affect the chart’s scale or working with multiple series that have different types, a simple, handy debugging approach is to draw labels that display string representations on successive bars.
For example, this script calculates four series: highestClose
,
percentRank
, barsSinceHigh
, and isLow
. It uses
str.format()
to create a formatted “string” representing the series values and a
timestamp, then it calls
label.new()
to draw a
label
that display the results at the
high
on each bar:
While the above example allows one to inspect the results of the script’s series on any bar with a label drawing, consecutive drawings like these can clutter the chart, especially when viewing longer strings.
An alternative, more visually compact way to inspect successive bars’
values with labels is to utilize the tooltip
property instead of the text
property, as a
label
will only show its tooltip when the cursor hovers over it.
Below, we’ve modified the previous script by using the debugString
as
the tooltip
argument instead of the text
argument in the
label.new()
call. Now, we can view the results on specific bars without the extra
noise:
It’s important to note that a script can display up to 500 label drawings, meaning the above examples will only allow users to inspect the strings from the most recent 500 chart bars.
If a programmer wants to see the results from earlier chart bars, one approach is to create conditional logic that only allows drawings within a specific time range, e.g.:
If we use this structure in our previous example with
chart.left_visible_bar_time
and
chart.right_visible_bar_time
as the startTime
and endTime
values, the script will only create
labels on
visible chart bars and avoid drawing on others. With this logic, we
can scroll to view labels on any chart bar, as long as there are up to
max_labels_count
bars in the visible range:
Note that:
- If the visible chart contains more bars than allowed drawings, the script will only show results on the latest bars in the visible range. For best results with this technique, zoom on the chart to keep the visible range limited to the allowed number of drawings.
At the end of the chart
A frequent approach to debugging a script’s strings with labels is to display them at the end of the chart, namely when the strings do not change or when only a specific bar’s values require analysis.
The script below contains a user-defined printLabel()
function that
draws a
label
at the last available time on the chart, regardless of when the script
calls it. We’ve used the function in this example to display a “Hello
world!” string, some basic chart information, and the data feed’s
current OHLCV values:
Note that:
- The
printLabel()
function sets the x-coordinate of the drawn label using the max of the last_bar_time and the chart.right_visible_bar_time to ensure it always shows the results at the last available bar. - When called from the global scope, the function creates a
label
with
text
andy
properties that update on every bar. - We’ve made three calls to the function and added linefeed
characters (
\n
) to demonstrate that users can superimpose the results from multiple labels at the end of the chart if the strings have adequate line spacing.
Using tables
Tables display strings within cells arranged in columns and rows at fixed locations relative to a chart pane’s visual space. They can serve as versatile chart-based debugging tools, as unlike labels, they allow programmers to inspect one or more “series strings” in an organized visual structure agnostic to the chart’s scale or bar index.
For example, this script calculates a custom filter
whose result is
the ratio of the
EMA
of weighted
close
prices to the
EMA
of the weight
series. For inspection of the variables used in the
calculation, it creates a
table
instance on the first bar, initializes the table’s cells on the last
historical bar, then updates necessary cells with “string”
representations of the values from barsBack
bars ago on the latest
chart bar:
Note that:
- The script uses the
var
keyword to specify that the
table
assigned to the
debugTable
variable on the first bar persists throughout the script’s execution. - This script modifies the table within two
if
structures. The first structure initializes the cells with
table.cell()
only on the last confirmed historical bar
(barstate.islastconfirmedhistory).
The second structure updates the
text
properties of relevant cells with string representations of our variables’ values using table.cell_set_text() calls on the latest available bar (barstate.islast).
It’s important to note that although tables can provide debugging utility, namely when working with multiple series or creating on-chart logs, they carry a higher computational cost than other techniques discussed on this page and may require more code. Additionally, unlike labels, one can only view a table’s state from the latest script execution. We therefore recommend using them wisely and sparingly while debugging, opting for simplified approaches where possible. For more information about using table objects, see the Tables page.
Pine Logs
Pine Logs are interactive messages that scripts can output at specific points in their execution. They provide a powerful way for programmers to inspect a script’s data, conditions, and execution flow with minimal code.
Unlike the other tools discussed on this page, Pine Logs have a deliberate design for in-depth script debugging. Scripts do not display Pine Logs on the chart or in the Data Window. Instead, they print messages with timestamps in the dedicated Pine Logs pane, which provides specialized navigation features and filtering options.
To access the Pine Logs pane, select “Pine Logs…” from the
Editor’s “More” menu or from the “More” menu of a script loaded on
the chart that uses log.*()
functions:
Creating logs
Scripts can create logs by calling the functions in the log.*()
namespace.
All log.*()
functions have the following signatures:
log.*(message) → void
log.*(formatString, arg0, arg1, ...) → void
The first overload logs a specified message
in the Pine Logs pane. The
second overload is similar to
str.format(),
as it logs a formatted message based on the formatString
and the
additional arguments supplied in the call.
Each log.*()
function has a different debug level, allowing
programmers to categorize and
filter results shown in the pane:
- The log.info() function logs an entry with the “info” level that appears in the pane with gray text.
- The log.warning() function logs an entry with the “warning” level that appears in the pane with orange text.
- The log.error() function logs an entry with the “error” level that appears in the pane with red text.
This code demonstrates the difference between all three log.*()
functions. It calls
log.info(),
log.warning(),
and
log.error()
on the first available bar:
Pine Logs can execute anywhere within a script’s execution. They allow
programmers to track information from historical bars and monitor how
their scripts behave on realtime, unconfirmed bars. When executing on
historical bars, scripts generate a new message once for each log.*()
call on a bar. On realtime bars, calls to log.*()
functions can create
new entries on each new tick.
For example, this script calculates the average ratio between each
bar’s close - open
value to its high - low
range. When the
denominator
is nonzero, the script calls
log.info()
to print the values of the calculation’s variables on confirmed bars
and
log.warning()
to print the values on unconfirmed bars. Otherwise, it uses
log.error()
to indicate that division by zero occurred, as such cases can affect the
average
result:
Note that:
- Pine Logs do not roll back on each tick in an unconfirmed bar,
meaning the results for those ticks show in the pane until the
script restarts its execution. To only log messages on
confirmed bars, use
barstate.isconfirmed
in the conditions that trigger a
log.*()
call. - When logging on unconfirmed bars, we recommend ensuring those logs contain unique information or use different debug levels so you can filter the results as needed.
- The Pine Logs pane will show up to the most recent 10,000
entries for historical bars. If a script generates more than
10,000 logs on historical bars and a programmer needs to view
earlier entries, they can use conditional logic to limit
log.*()
calls to specific occurrences. See this section for an example that limits log generation to a user-specified time range.
Inspecting logs
Pine Logs include some helpful features that simplify the inspection process. Whenever a script generates a log, it automatically prefixes the message with a granular timestamp to signify where the log event occurred in the time series. Additionally, each entry contains “Source code” and “Scroll to bar” icons, which appear when hovering over it in the Pine Logs pane:
Clicking an entry’s “Source code” icon opens the script in the Pine Editor and highlights the specific line of code that triggered the log:
Clicking an entry’s “Scroll to bar” icon navigates the chart to the specific bar where the log occurred, then temporarily displays a tooltip containing time information for that bar:
Note that:
- The time information in the tooltip depends on the chart’s timeframe, just like the x-axis label linked to the chart’s cursor and drawing tools. For example, the tooltip on an EOD chart will only show the weekday and the date, whereas the tooltip on a 10-second chart will also contain the time of day, including seconds.
When a chart includes more than one script that generates logs, it’s important to note that each script maintains its own independent message history. To inspect the messages from a specific script when multiple are on the chart, select its title from the dropdown at the top of the Pine Logs pane:
Filtering logs
A single script can generate numerous logs, depending on the conditions
that trigger its log.*()
calls. While directly scrolling through the
log history to find specific entries may suffice when a script only
generates a few, it can become unwieldy when searching through hundreds
or thousands of messages.
The Pine Logs pane includes multiple options for filtering messages, which allows one to simplify their results by isolating specific character sequences, start times, and debug levels.
Clicking the “Search” icon at the top of the pane opens a search bar, which matches text to filter logged messages. The search filter also highlights the matched portion of each message in blue for visual reference. For example, here, we entered “confirmed” to match all results generated by our previous script with the word somewhere in their text:
Notice that the results from this search also considered messages with “unconfirmed” as matches since the word contains our query. We can omit these matches by selecting the “Whole Word” checkbox in the options at the right of the search bar:
This filter also supports regular expressions (regex), which allow users to perform advanced searches that match custom character patterns when selecting the “Regex” checkbox in the search options. For example, this regex matches all entries that contain “average” followed by a sequence representing a number greater than 0.5 and less than or equal to 1:
average:\s*(0\.[6-9]\d*|0\.5\d*[1-9]\d*|1\.0*)
Clicking the “Start date” icon opens a dialog that allows users to specify the date and time of the first log shown in the results:
After specifying the starting point, a tag containing the starting time will appear above the log history:
Users can filter results by debug level using the checkboxes available when selecting the rightmost icon in the filtering options. Here, we’ve deactivated the “info” and “warning” levels so the results will only contain “error” messages:
Using inputs
Another, more involved way to interactively filter a script’s logged
results is to create inputs
linked to conditional logic that activates specific log.*()
calls in
the code.
Let’s look at an example. This code calculates an
RMA
of
close
prices and declares a few unique conditions to form a
compound condition. The script uses
log.info()
to display important debugging information in the Pine Logs pane,
including the values of the compoundCondition
variable and the
“bool” variables that determine its result.
We declared the filterLogsInput
, logStartInput
, and logEndInput
variables respectively assigned to an
input.bool()
and two
input.time()
calls for custom log filtering. When filterLogsInput
is true
, the
script will only generate a new log if the bar’s
time
is between the logStartInput
and logEndInput
values, allowing us to
interactively isolate the entries that occurred within a specific time
range:
Note that:
- The
input.*()
functions assigned to thefilterLogsInput
,logStartInput
, andlogEndInput
variables include agroup
argument to oragnize and distinguish them in the script’s settings. - The
input.time()
calls include
confirm = true
so that we can interactively set the start and end times directly on the chart. To reset the inputs, select “Reset points…” from the options in the script’s “More” menu. - The condition that triggers each log.info() call includes barstate.isconfirmed to limit log generation to confirmed bars.
Debugging functions
User-defined functions and methods are custom functions written by users. They encapsulate sequences of operations that a script can invoke later in its execution.
Every user-defined function or method has a local scope that embeds into the script’s global scope. The parameters in a function’s signature and the variables declared within the function body belong to that function’s local scope, and they are not directly accessible to a script’s outer scope or the scopes of other functions.
The segments below explain a few ways programmers can debug the values
from a function’s local scope. We will use this script as the starting
point for our subsequent examples. It contains a customMA()
function
that returns an exponential moving average whose smoothing parameter
varies based on the source
distance outside the 25th and 75th
percentiles
over length
bars:
Extracting local variables
When a programmer wants to inspect a user-defined function’s local variables by plotting its values, coloring the background or chart bars, etc., they must extract the values to the global scope, as the built-in functions that produce such outputs can only accept global variables and literals.
Since the values returned by a function are available to the scope where a call occurs, one straightforward extraction approach is to have the function return a tuple containing all the values that need inspection.
Here, we’ve modified the customMA()
function to return a
tuple containing
all the function’s calculated variables. Now, we can call the function
with a tuple declaration to make the values available in the global
scope and inspect them with
plots:
Note that:
- We used
display.all - display.pane
for the plots of theouterRangeDebug
,totalRangeDebug
, andalphaDebug
variables to avoid impacting the chart’s scale. - The script also uses a
conditional color to highlight the chart pane’s
background
when
debugAlpha
is 0, indicating themaValue
does not change.
Another, more advanced way to extract the values of a function’s local variables is to pass them to a reference type variable declared in the global scope.
Function scopes can access global variables for their calculations. While a script cannot directly reassign the values of global variables from within a function’s scope, it can update the elements or properties of those values if they are reference types, such as arrays, matrices, maps, and user-defined types.
This version declares a debugData
variable in the global scope that
references a
map
with “string” keys and “float” values. Within the local scope of the
customMA()
function, the script puts key-value pairs containing each
local variable’s name and value into the map. After calling the
function, the script plots the stored debugData
values:
Note that:
- We placed each
map.put()
call on the same line as each variable declaration, separated by
a comma, to keep things concise and avoid adding extra lines to
the
customMA()
code. - We used map.get() to retrieve each value for the debug plot() and bgcolor() calls.
Local drawings and logs
Unlike plot.*()
functions and others that require values accessible to
the global scope, scripts can generate
drawing objects and Pine Logs from directly within a function, allowing programmers to
flexibly debug its local variables without extracting values to the
outer scope.
In this example, we used
labels and
Pine Logs to
display
string representations of the values within the customMA()
scope. Inside the
function, the script calls
str.format()
to create a formatted string representing the local scope’s data, then
calls
label.new()
and
log.info()
to respectively display the text on the chart in a tooltip and log an
“info” message containing the text in the
Pine Logs pane:
Note that:
- We included
max_labels_count = 500
in the indicator() function to display labels for the most recent 500customMA()
calls. - The function uses barstate.isconfirmed in an if statement to only call log.info() on confirmed bars. It does not log a new message on each realtime tick.
Debugging loops
Loops are structures that repeatedly execute a code block based on a counter (for), the contents of a collection (for…in), or a condition (while). They allow scripts to perform repetitive tasks without the need for redundant lines of code.
Each loop instance maintains a separate local scope, which all outer scopes cannot access. All variables declared within a loop’s scope are specific to that loop, meaning one cannot use them in an outer scope.
As with other structures in Pine, there are numerous possible ways to debug loops. This section explores a few helpful techniques, including extracting local values for plots, inspecting values with drawings, and tracing a loop’s execution with Pine Logs.
We will use this script as a starting point for the examples in the
following segments. It aggregates the
close
value’s rates of change over 1 - lookbackInput
bars and accumulates
them in a
for
loop, then divides the result by the lookbackInput
to calculate a
final average value:
Note that:
- The
aroc
is a global variable modified within the loop, whereaspastClose
androc
are local variables inaccessible to the outer scope.
Inspecting a single iteration
When a programmer needs to focus on a specific loop iteration, there are multiple techniques they can use, most of which entail using a condition inside the loop to trigger debugging actions, such as extracting values to outer variables, creating drawings, logging messages, etc.
This example inspects the local roc
value from a single iteration of
the loop in three different ways. When the loop counter’s value equals
the debugCounterInput
, the script assigns the roc
to an rocDebug
variable from the global scope for
plotting, draws a vertical
line
from 0 to the roc
value using
line.new(),
and logs a message in the
Pine Logs pane
using
log.info():
Note that:
- The
input.int()
call assigned to the
debugCounterInput
includes agroup
argument to distinguish it in the script’s settings. - The
log.info()
call includes “(confirmed)” in the formatted message whenever
barstate.isconfirmed
is
true
. Searching this text in the Pine Logs pane will filter out the entries from unconfirmed bars. See the Filtering logs section above.
Inspecting multiple iterations
When inspecting the values from several loop iterations, it’s often helpful to utilize collections or strings to gather the results for use in output functions after the loop terminates.
This version demonstrates a few ways to collect and display the loop’s
values from all iterations. It declares a logText
string and a
debugValues
array in the global scope. Inside the local scope of the
for
loop, the script concatenates a
string representation of the length
and roc
with the logText
and calls
array.push()
to push the iteration’s roc
value into the debugValues
array.
After the loop ends, the script
plots the
first
and
last
value from the debugValues
array, draws a
label
with a tooltip showing a
string representation of the
array,
and displays the logText
in the
Pine Logs pane
upon the bar’s confirmation:
Another way to inspect a loop over several iterations is to generate sequential Pine Logs or create/modify drawing objects within the loop’s scope to trace its execution pattern with granular detail.
This example uses Pine Logs to trace the execution flow of our script’s loop. It generates a new “info” message on each iteration to track the local scope’s calculations as the loop progresses on each confirmed bar:
Note that:
- When iteratively generating logs or drawings from inside a loop, make it a point to avoid unnecessary clutter and strive for easy navigation. More is not always better for debugging, especially when working within loops.
Tips
Organization and readability
When writing scripts, it’s wise to prioritize organized, readable source codes. Code that’s organized and easy to read helps streamline the debugging process. Additionally, well-written code is easier to maintain over time.
Here are a few quick tips based on our Style guide and the examples on this page:
- Aim to follow the general script organization recommendations. Organizing scripts using this structure makes things easier to locate and inspect.
- Choose variable and function names that make them easy to identify and understand. See the Naming conventions section for some examples.
- It’s often helpful to temporarily assign important parts of expressions to variables with informative names while debugging. Breaking expressions down into reusable parts helps simplify inspection processes.
- Use comments and annotations (
//@function
,//@variable
, etc.) to document your code. Annotations are particularly helpful, as the Pine Editor’s autosuggest displays variable and function descriptions in a pop-up when hovering over their identifiers anywhere in the code. - Remember that less is more in many cases. Don’t overwhelm yourself with excessive script outputs or unnecessary information while debugging. Keep things simple, and only include as much information as you need.
Speeding up repetitive tasks
There are a few handy techniques we often utilize when debugging our code:
- We use plotchar() or plotshape() to quickly display the results of “int”, “float”, or “bool” variables and expressions in the script’s status line and the Data Window.
- We often use bgcolor() to visualize the history of certain conditions on the chart.
- We use a one-line version of our
printLabel()
function from this section to print strings at the end of the chart. - We use a
label.new()
call with a
tooltip
argument to display strings in tooltips on successive bars. - We use the
log.*()
functions to quickly display data with string representations in the Pine Logs pane.
When one establishes their typical debugging processes, it’s often helpful to create keyboard macros to speed up repetitive tasks and spend less time setting up debug outputs in each code.
The following is a simple AutoHotkey script (not Pine Script™ code) that includes hotstrings for the above five techniques. The script generates code snippets by entering a specified character sequence followed by a whitespace:
; ————— This is AHK code, not Pine Script™. —————
; Specify that hotstrings trigger when they end with space, tab, linefeed, or carriage return.#Hotstring EndChars `t `n `r
:X:,,show::SendInput, plotchar(%Clipboard%, "%Clipboard%", "", color = chart.fg_color, display = display.all - display.pane){Enter}:X:,,highlight::SendInput, bgcolor(bool(%Clipboard%) ? color.new(color.orange, 80) : na, title = "%Clipboard% highlight"){Enter}:X:,,print::SendInput, printLabel(string txt, float price = na) => int labelTime = math.max(last_bar_time, chart.right_visible_bar_time), var label result = label.new(labelTime, na, txt, xloc.bar_time, na(price) ? yloc.abovebar : yloc.price, na, label.style_none, chart.fg_color, size.large), label.set_text(result, txt), label.set_y(result, price), result`nprintLabel(){Left}:X:,,tooltip::SendInput, label.new(bar_index, high, color = color.new(chart.fg_color, 70), tooltip = str.tostring(%Clipboard%)){Enter}:X:,,log::SendInput, log.info(str.tostring(%Clipboard%)){Enter}
The “,,show” macro generates a
plotchar()
call that uses the clipboard’s contents for the series
and title
arguments. Copying a variableName
variable or the close > open
expression and typing “,,show” followed by a space will respectively
yield:
The “,,highlight” macro generates a bgcolor() call that highlights the chart pane’s background with a conditional color based on the variable or expression copied to the clipboard. For example, copying the barstate.isrealtime variable and typing “,,highlight” followed by a space will yield:
The “,,print” macro generates the one-line printLabel()
function and
creates an empty printLabel()
call with the cursor placed inside it.
All you need to do after typing “,,print” followed by a space is enter
the text you want to display:
The “,,tooltip” macro generates a
label.new()
call with a tooltip
argument that uses
str.tostring()
on the clipboard’s contents. Copying the variableName
variable and
typing “,,tooltip” followed by a space yields:
The “,,log” macro generates a
log.info()
call with a message
argument that uses
str.tostring()
on the clipboard’s contents to display string representations of
variables and expressions in the
Pine Logs pane.
Copying the expression bar_index % 2 == 0
and typing “,,log”
followed by a space yields:
Note that:
- AHK is available for Windows devices. Research other software to employ a similar process if your machine uses a different operating system.