Pine script: strange behaviour within for loop

Viewed 52

I am trying to optimise an SMA period within a for loop such that there is at least one cross between the low series and the SMA and the high series and a multiplier of the SMA. To do this, I use a for loop over candidate period values and for each candidate period value, I check whether the crosses mentioned earlier exist.

I notice some very strange behaviour which leads to garbage results.

Here's a sample code. Please try it on the daily BITSTAMP:BTCUSD chart so that the results you get are consistent with mine.

//@version=5
indicator(title="test", shorttitle="test", overlay=true)

// functions
boolCross(series1, series2) =>
    not na(ta.valuewhen(ta.cross(series1, series2), series1, 0))

// initialize
debugText = ""

// demo
var int periodToUse = 0
for thisPeriod = 4999 to 1 by 200 
    periodToUse := thisPeriod
    boolUpperBandTouch = false
    boolLowerBandTouch = false
    sma = ta.sma(close, thisPeriod)
    if boolCross(high, sma * 5) 
        boolUpperBandTouch := true
    if boolCross(low, sma)
        boolLowerBandTouch := true
    if boolUpperBandTouch and boolLowerBandTouch
        break

debugText += "Value of periodToUse: " + str.tostring(periodToUse) + "\n"
debugText += "Is periodToUse == 4999? " + str.tostring(periodToUse == 4999) + "\n" 

plot(ta.sma(close, periodToUse), color = color.red, linewidth = 3)
plot(ta.sma(close, 4999), color = color.blue, linewidth = 3)

// display debug table
var debugTable = table.new(position = position.bottom_right, columns = 1, rows = 1, bgcolor = color.yellow, border_width = 1)
debugText += "End\n"
table.cell(debugTable, column = 0, row = 0, text = debugText, text_color = color.black, bgcolor = color.white)

As you can see, I have added a debug box to do some sanity checks. The for loop ends at the first iteration because apparently there are crosses for a period of 4999. I also check that the value returned == 4999. I then plot the SMA (red colour) band with periodToUse and I also plot another SMA (blue colour) band where I manually set the period to 4999. Obviously, I expect that the two SMAs should overlap. However, this is not the case.

enter image description here

As can be seen, the red SMA weirdly begins shortly after the first-ever candle (although, the period should be 4999), then has an abrupt move down and then it stops. As for the blue SMA, it never appears because a period of 4999 is too high (this is the correct and expected behaviour).

What am I doing wrong?

If I leave the for loop empty (i.e., exclude all the cross-checking logic), it works as expected:

//@

version=5
indicator(title="test", shorttitle="test", overlay=true)

// functions
boolCross(series1, series2) =>
    not na(ta.valuewhen(ta.cross(series1, series2), series1, 0))

// initialize
debugText = ""

// demo
var int periodToUse = 0
for thisPeriod = 4999 to 1 by 200 
    periodToUse := thisPeriod

debugText += "Value of periodToUse: " + str.tostring(periodToUse) + "\n"
debugText += "Is periodToUse == 199? " + str.tostring(periodToUse == 199) + "\n" 

plot(ta.sma(close, periodToUse), color = color.red, linewidth = 3)
plot(ta.sma(close, periodToUse) * 5, color = color.red, linewidth = 3)
plot(ta.sma(close, 199), color = color.blue, linewidth = 3)
plot(ta.sma(close, 199) * 5, color = color.blue, linewidth = 3)

// display debug table
var debugTable = table.new(position = position.bottom_right, columns = 1, rows = 1, bgcolor = color.yellow, border_width = 1)
debugText += "End\n"
table.cell(debugTable, column = 0, row = 0, text = debugText, text_color = color.black, bgcolor = color.white)

enter image description here

Only the blue band is visible because it overlaps the red one as expected.


Edit: in response to @John Baron's request for more information:

First do this on the daily BITSTAMP:BTCUSD chart:

plot(ta.sma(close, 1400), color = color.blue, linewidth = 3)
plot(ta.sma(close, 1400) * 5, color = color.blue, linewidth = 3)

You'll see this. This is our reference SMA:

enter image description here

Now replace that code with:

//@version=5
indicator(title="test", shorttitle="test", overlay=true)

// functions
boolCross(series1, series2) =>
    ta.cum(ta.cross(series1, series2) ? 1 : 0) > 0

// initialize
debugText = ""

// main
var periodToUse = 0
for thisPeriod = 1400 to 1 by 200 
    periodToUse := thisPeriod
    sma = ta.sma(close, thisPeriod)
    boolUpperBandTouch = boolCross(high, sma * 5) 
    boolLowerBandTouch = boolCross(low, sma)
    if boolUpperBandTouch and boolLowerBandTouch
        break

debugText += "Value of periodToUse: " + str.tostring(periodToUse) + "\n"
debugText += "Is periodToUse == 1400? " + str.tostring(periodToUse == 1400) + "\n" 

plot(ta.sma(close, periodToUse), color = color.red, linewidth = 3) // <===== plots garbage
plot(ta.sma(close, periodToUse) * 5, color = color.red, linewidth = 3) // <===== plots garbage

// display debug table
var debugTable = table.new(position = position.bottom_right, columns = 1, rows = 1, bgcolor = color.yellow, border_width = 1)
table.cell(debugTable, column = 0, row = 0, text = debugText, text_color = color.black, bgcolor = color.white)

This is the output: enter image description here

You'll see in the debug box on the bottom right corner that the value of periodToUse was 1400 (the same value we used in the reference SMA above. You will also see that the red SMA plot (which is plotted using periodToUse (plot(ta.sma(close, periodToUse), color = color.red, linewidth = 3)) looks completely different in comparison to the reference SMA (blue) which we plotted manually. Why is this? They should both be identical because we are apparently using the same period value.

1 Answers

Just to simplify your script.

    for thisPeriod = 4999 to 1 by 200 
    periodToUse := thisPeriod
    sma = ta.sma(close, thisPeriod)
    boolUpperBandTouch = boolCross(high, sma * 5) 
    boolLowerBandTouch = boolCross(low, sma)
    if boolUpperBandTouch and boolLowerBandTouch
        break

This is not the challenge, the challenge is using valuewhen, which I think is giving you the irregular return. It seems you are not interested in the value but only interested in a touch.

Perhaps something like this might work better

    boolCross(series1, series2) =>
        ta.cum(ta.cross(series1,series2)?1:0)>0
Related