As we saw in the Indexing tutorial, every variable in Pine script is actually a long list of values. One one hand this is great because we are easily able to check the historical value of the variable at any moment in the past. On the other hand, because we take a snapshot on every bar, it might not be immediately obvious how to track when something happened and access the correct historical values. This list is always changing so we cannot simply use Indexing on its own.
The Problem
Often we want to perform some type of check in the code and if it is true, assign a value to a variable. Once we have done this, it might seem natural to check the last value by writing myvar[1]
(where myvar is the name of the variable you created). However, unless you actually did just assign the value one bar ago, you are unlikely to get the value you are looking for. This is because “something” is always saved to the variable on every bar. This could be na
or some other default value you wrote in the code. So what actually happens is that with each passing bar, the value you actually want moves further and further away in the list. To help visualize, imagine that the following list represents myvar
:
[na,na,na,1.3045,na,na,na,na,na,1.3067,na,na,na]
So as you can see, the last time my condition returned true
was 4 bars ago. On the next bar, it will be 5 bars ago. Since we do not know when our condition will be true again, we need to find a way to track and retrieve our data.
Use Cases
Before we head to the solutions, it is worth giving a couple of examples of when or why we may wish to track when something happened. Actually, there are a number of reasons why but for this post we shall focus on two:
- You guessed it, we might need to retrieve the value of the variable at the time something happened for further comparison.
- Also, we may want to know how long it has been since a certain condition was met. Either for indexing or to perform other actions within a time window.
Taking those two examples a step further, you might want to compare the close price first bar of the session with the close price on the first bar of the session yesterday. Or perhaps you want to go long only after an RSI drops below 30 AND breaks back above 30 within 5 bars.
These tasks can be even more difficult achieve if you want your script to work on multiple time-frames. For example, looking at the first example, you could not just count the number of bars in the session and use that as the index value. If you do this, the script will only work on the time-frame it was written on.
E.g. You are working on the hourly and there are 8 hours in the session. You could not simply reference close[-8]
because if you moved to the 15-minute chart, 8 bars back would not be the start of the previous day’s session.
Solutions
Since we have 2 problems, we shall also look at two possible solutions. Pine script actually provides us with built-in functions that are designed to help with these use-cases.
Value When
The valuewhen()
function does exactly what it says on the tin. It returns the value when a certain condition was met. You can additionally set a parameter to return the Nth
occurrence so that you can take a look at the value when the condition was true the time before last… and the time before that…. and so on.
Here is the official doc’s for valuewhen():
valuewhen
Source series value when the condition was true on the n-th most recent occurrence.valuewhen(condition, source, occurrence) → seriesRETURNSSource value when condition was true
Let’s apply this to our first use case. The following code will provide a very basic strategy that compares the close of the current opening bar against the close of yesterdays opening bar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//@version=3 strategy("Value When Example", overlay=true) // Taken from: https://www.tradingview.com/wiki/Sessions_and_Time_Functions is_newbar(res) => t = time(res) change(t) != 0 ? 1 : 0 // Detect New Day new_day = is_newbar("D") // Get Previous close of the opening bar. last_close = valuewhen(new_day, close, 1) strategy.entry("Long", true, when=new_day and close > last_close) strategy.entry("Short", false, when=new_day and close < last_close) plot(last_close, style=circles, color=orange) |
When plotted on the chart, it should look something like this:
Due to the way it was coded, this will take the opening bar on any time frame for comparison. You can try this on any intraday timeframe. It is a simple example but should be enough to see how the function works. Play around with the valuewhen()
function. Change the close
to high
or volume
. Additionally, you could change the number of days back or pass it a different variable like a simple moving average. Once you play around with it, it will become easy to see how useful this function can be.
Bars Since
Our second example looks at the barssince()
built-in function. It will return a number that represents the number of bars that have passed since a condition was true. In this example, we are going to use the barssince()
function to track how many bars it has been since the RSI crossed below 30. If it crosses back above 30 within 5 bars, we will go long.
Here are the official docs for barssince():
barssince
The barssince function counts a number of bars since condition was true.barssince(condition) → series[integer]RETURNSNumber of bars since condition was true.
Because barssince()
will return a different value on each bar, we will code this example in two stages. The first stage will allow you to see the value returned and get a real feeling for how it works. As such, we will just plot the value returned from barssince()
and feed it the output from a crossunder()
check. We will ignore the long entry condition for now.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//@version=3 strategy("Barssince Example") rsi_len = input(14,title="RSI Length") rsi_src = input(close, title="RSI Source") rsi = rsi(rsi_src, rsi_len) rsi_x_under = crossunder(rsi, 30) since_x_under = barssince(rsi_x_under) plot(since_x_under) |
In the image above, we have plotted an actual RSI indicator separately above our barssince()
example code. This should make it easier to see how the barssince()
values line up with the real indicator. We can see the values returned from barssince()
grow by 1 for every bar that passes until the RSI crossed back below the 30 level.
Going a step further
Next, we will take it a step further and move onto the second stage. Here we will add our entry condition to go long if the RSI crosses back over within 5 bars.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//@version=3 strategy("Barssince Example") rsi_len = input(14,title="RSI Length") rsi_src = input(close, title="RSI Source") rsi_ent = input(30, title="RSI Entry Level") rsi_ext = input(50, title="RSI Exit Level") rsi_lim = input(5, title="Cross Back Up Bar Limit") rsi = rsi(rsi_src, rsi_len) rsi_x_under = crossunder(rsi, rsi_ent) rsi_x_over = crossover(rsi, rsi_ent) rsi_exit = crossover(rsi, rsi_ext) since_x_under = barssince(rsi_x_under) strategy.entry("Long", true, when=rsi_x_over and since_x_under <=rsi_lim) strategy.close("Long", when=rsi_exit) plot(rsi, color=blue, linewidth=3) hline(rsi_ent, linestyle=dashed, linewidth=2) hline(rsi_ext, linestyle=dashed, linewidth=2) |
So now that we are able to count the number of bars since the RSI crossed under a certain level, we just needed to simply make a check each bar to see if the RSI crosses back up AND the barssince()
is less then our desired number of bars. If so, we enter a position.
Finally, to make this a complete strategy, we add a very simple exit condition. In this case, it is as simple as the RSI crossing up over a second level.
Now let’s take a look at how this looks on the chart.
This chart was specifically selected as it shows 2 things which help us to verify that the code is working as expected. First, we can see that the RSI dropped below 30 on 3 occasions. However, during one of those dips, it took 6 bars before it crossed back above 30. As you will see on the chart, the strategy did not take the trade. Secondly, we are able to confirm the positive cases where the RSI dipped below 30, and then crossed back up within 5 bars.
Hi guys,
Great blog and resource. I have a question in reply to the barssince blog. I will try to explain the problem as clearly as possible.
What I want to find: The highest value since a strategy position was open.
Problem: barssince returns integer. highest requires series(integer) and so causes error
First step: I can correctly return the number of bars since the position was opened quite easily by working with the strategy.position_size function.
Second step: I use the result from this as a condition in the highest() function
Error: Cannot call
highest
with arguments (series); available overloads: highest(series, integer) => series; highest(integer) => seriesIs this a bug in Pine Script and is there any way around this issue? it appears that Pine Script doesn’t have a function to parse integer to series(integer)
Cheers
Guy
I have the exact same problem trying to get “highest(close, barsince(condition1))”.
Did you manage to solve this ??
all help gratefully accepted.
I solved the problem by creating a custom function.
// Custom Functions
// highest() function
max_inSeries(src2, series) =>
max_bars_back(src2, 2000)
max = src2[0]
for i = 1 to series[0]
if src2[i] > max
max := src2[i]
max
Hi,
It is long time since you wrote, could you explain the code that you proposed? I have the same problem but I do not understand the code you wrote…what is a custom function? What does the code perform? Thanks.