Anyone who has coded in pine-script will no doubt agree that debugging can be a pain in the backside. Without the ability to print to the terminal, we are forced to plot anything and everything we wish to inspect. This process can be even more laborious if the variables that you are plotting work on different scales. The result is often a long exercise of commenting and uncommenting different plots.
With that in mind, this article aims to cover the basics of debugging along with some practical examples and a few tips that might just make your life a little easier.
Since we are not able to print to the terminal, our only option for inspecting variables is to plot everything we need to check. Over the next few paragraphs, we will cover the basics of plotting each type of variable and then move onto some tips and general advice.
Floats and Ints
Floats and integers probably need no introduction. They are the bread and butter of plotting. However, in order to cover the debugging topic completely, at least a couple of sentences should be written.
To plot a float or integer, just stick it inside a
plot()call as the first parameter and you are good to go.
Note that plotting floats and integers is not completely plain sailing. Although any float or integer value can be plotted to the chart, you might run into scaling issues. What do I mean by “scaling issues”? When you plot an integer that has a value of 8000 alongside a float that has a value of 0.5, you are unlikely to be able to see clearly what either of them are doing. This is something we will look at in a bit more detail later.
Boolean variables that are variables that are either
false. If you try to plot a boolean value directly in the
plot()function you will receive the following error:
Add to Chart operation failed, reason: line 130: root_data expression of plot_11 has invalid type: series__bool expected series
In order to debug a boolean value, we need to first convert it to a valid type. One method of doing this is to use a ternary conditional operator to convert it to a float for plotting. Take the following example where we assume
xis a variable that is storing a
x = false)
x_int = x ? 1 : 0
This line creates a new variable called
x_int checks whether it is true. If it is, 1 is stored to
x_int. If not, then the value must be false (because it certainly is not true!) and so 0 is stored to
x_int. Now when we plot
plot(x_int)we are then easily able to see every time x is true in the time series.
Alternatively, you can also do this inside directly the plot without creating a
plot(x ? 1 : 0)
Strings should largely be debugged in the same way as boolean values. However, before we move onto some debugging examples, allow me to take you down a side track on a loosely related topic.
If you have ever tried to plot a string using the
textvariable in one of the plots which supports it, (e.g
plotchar()etc) you might have spent a lot of time “debugging” strings. Tradingview provides a
tostring()function that makes it look like you could convert the most recent series value to a string and plot it only when certain conditions are met. That would be incredibly useful! For example, let’s say you are plotting on an intra-day chart but would like to know what most recent daily ATR value is. If you directly plot the complete ATR values on the main chart, it could end up being cluttered and run into scaling issues. In my opinion, it would be cleaner to just plot the values above the bar at regular intervals. Unfortunately, at the time of writing, you cannot do this! Or, I have not found the magic source at least. The plotting functions only allow constant strings (strings that never change / or are not dynamically created).
To help visualize what I am talking/ranting about, take a look at the following chart:
Wouldn’t it be nice and clean to plot
0.0059below the words ATR? For those interested, the chart above was created with the following code:
study("Daily ATR", overlay=true)
atr = security(tickerid, "D", atr(7))
t = time(res)
change(t) != 0
// Track new bar days
nb = is_newbar("D")
plotchar(nb, location=location.abovebar, text="ATR")
//plotchar(nb, location=location.abovebar, text=tostring(atr))
If take the code above and uncomment the last plotline, you will receive the following error:
Add to Chart operation failed, reason: argument "text" accepts only constant string values
If any readers out there know a workaround or something I have missed, I would LOVE to know. Get in touch!
Moving off the beaten track….
Admittedly, the little detour above might not tell you how to debug a string but I hope it might just save you some head-scratching time. Now let’s get back to the main topic.
As mentioned above, debugging strings is very similar to debugging Boolean values. We must first convert the string to a value which can be plotted. Again you can use a ternary conditional operator to create a plot-able value. In general, you will not run into many scenarios where debugging strings is needed. However, it can be useful if you use the options parameter in the
input() function. This is especially the case if your code following needs to make decisions based on the selected option.
The following code will perform a check on which option has been selected then change the plot value and colour accordingly:
study("Daily ATR", overlay=false)
opts = input(defval='Hammer', title='Operation Mode', options=["Hammer", "Hanging", "Engulfing"])
operation_mode = opts == 'Hammer' ? 1 : opts == 'Hanging' ? 2 : opts == 'Engulfing' ? 3 : 0
plot_color = opts == 'Hammer' ? green : opts == 'Hanging' ? red : opts == 'Engulfing' ? purple : na
plot(operation_mode, color=plot_color, linewidth=2)
Making life a little easier
Now we have the basics down, let’s move onto some topics and general plotting advice that can make your life a little easier.
When debugging lots of different values, it can be difficult to easily recognise which line represents which variable. Especially if they are all plotted as simple blue line charts (the default plot values). Using different plotting styles and options can help some of your results stand out from the crowd.
- Plotshape() and Plotchar(): Are a good option when you have events that happen infrequently. Like in the ATR example above, they help mark events in a clutter-free way.
- Background Coloring: Often overlooked, colouring the background can be a great way to indicate a current state. For example, if you are plotting to the main chart but want to know when we are trading in overbought or oversold conditions.
Let’s take the example above and turn in into code. The code below will change the background colour of the main chart when the RSI indicator is overbought or oversold.
study("RSI Backgound", overlay=true)
rsi = rsi(close, 14)
bcol = rsi > 70 ? red : rsi < 30 ? green : na
And the result on the charts:
If you have complex entry/exit criteria filled with different filters and confirmations, it can be difficult to identify why your script did or didn’t do something. For such strategies, I tend to employ a traffic lights system that provides the status of each check. When your script doesn’t enter, it becomes immediately obvious which entry check or filter is holding it back.
Here is a simple example:
strategy("Traffic Lights", overlay=false)
sma_check = close > sma(close, 14)
vol_check = volume > (volume * 2)
close_check = close > open
sma_plot_col = sma_check ? green : red
vol_plot_col = vol_check ? green : red
close_plot_col = close_check ? green : red
longCondition = sma_check and vol_check and close_check
shortCondition = not sma_check and not vol_check and not close_check
plotshape(2, title='SMA Check', color=sma_plot_col, style=shape.circle, location=location.absolute)
plotshape(4, title='Vol Check', color=vol_plot_col, style=shape.circle, location=location.absolute)
plotshape(6, title='Close Check',color=close_plot_col, style=shape.circle, location=location.absolute)
The code above has three simple checks for the purposes of the example. It will check if we have closed above an SMA value, whether the volume has increased and whether we have closed up or down. The following image shows how it looks on a chart.
Now you can easily see on each bar which test passed or failed. You might argue that it is still hard to tell without memorizing which light is for which test. That is true. However, remember that this is just an example, you can change the shapes, colors, styles etc to make each test more memorable. In addition, even if you do not know which line is which, you can easily see that the first line failed and take a look at the line title.
I often find that when I use this method, I avoid debugging completely. This is because we may often think the code “should” have done something but after reviewing the check that failed, we quickly realize it is correct and move on.
One Keyboard Shortcut To Rule Them All
If you have lots of test plots that you do not want to display all of the time, then knowing this simple keyboard shortcut can significantly speed up the time spent commenting and uncommenting. It is the multiline comment! Highlight a large chunk of code then press
Option+/for mac users). This will comment out the whole block of highlighted text. A real time saver when switching from test plots to your indicator plots.
As alluded to throughout the article, the final thing we should be wary of when debugging is the scale that each line takes. We should adjust our plotting accordingly. For example, if you are plotting the results of a boolean test (checking if the result was true or false) with
1 and then plot it on the same main chart as a bitcoin, you are going to have a visibility issue.
In such scenarios there a few options we can take:
- Rescale the boolean to something more appropriate. For example, you could use the
lowvalues so that it moves from high to low in line with price. (e.g.
x_int = x ? high : low)
- Plot using a different scale. (Using the scale left, right or no scale options). This can unlink the indicator from the price scale.
- Uncomment plots (if any) designed for the main chart and then plot the text plots to a subplot (
overlay=false) for the purpose of testing.
The image above shows an example of plotting an RSI over the main chart using
scale.none. The full code follows:
study(title="Stochastic RSI", shorttitle="Stoch RSI", overlay=true, scale=scale.none)
smoothK = input(3, minval=1)
smoothD = input(3, minval=1)
lengthRSI = input(14, minval=1)
lengthStoch = input(14, minval=1)
src = input(close, title="RSI Source")
rsi1 = rsi(src, lengthRSI)
k = sma(stoch(rsi1, rsi1, rsi1, lengthStoch), smoothK)
d = sma(k, smoothD)
h0 = hline(80)
h1 = hline(20)
fill(h0, h1, color=purple, transp=80)
Enough for Today
This article was originally intended to only cover debugging but grew in something slightly different as it was being crafted. Once you become familiar with the basics of converting everything to a float or integer, debugging becomes largely a question of taste, preferences and plotting. I think that because of this, half the article covers the various plotting options.
At any rate, I hope the article is useful. Happy debugging!