2

In official article, TradingView explains that if you don't call function on every bar, it will return inconsistent values, because it isn't called on every bar.

https://www.tradingview.com/pine-script-docs/en/v5/language/Execution_model.html?highlight=history%20referencing#execution-of-pine-script-functions-and-historical-context-inside-function-blocks

In the article, it is said that if

foo(a) => a[1]

isn't called every bar, but every other bar, it returns value two bars ago, because it returns last value, which was recorded 2 bars ago when function was called...

However, this makes completely no sense, when I tried the following code:

foo(c) => c[3]
bar() => close[3]

plot(open[3], color=color.blue)
plot(bar(),color=color.green)
plot(close[3],color=color.red)
plot(foo(open), color=color.aqua)
plot(foo(close),color=color.lime)

This code produces two same lines. So, close[3], function returning close[3], and function returning [3] on its variable all display the same thing.

But how could that be? If it would return value from 3 executions ago, wouldn't it be like this?

foo(open) called on last bar
foo(close) called on last bar
foo(open) called on current bar
foo(close) called on current bar

so it should return [3], meaning, it should return the value it had when it was called on the last bar, with open of the last bar? So it should return open value of the last bar, no?

If not, then how does it distinguish when it is called with different arguments? And if it doesn't and it does correctly go into history of the variable passed to it (correctly going into history of open and close when provided with that), that means the article is wrong, and there should be no inconsistency?

EDIT: Additional example

foo(c) => c[7]
everyOtherBar = bar_index % 3
plot(high[7]+1, color=color.blue)
plot(foo(everyOtherBar == 2 ? high+1 : everyOtherBar ? hl2+1 : low+1), color=color.lime)
plot(low[7]+1,color=color.red)

So I have made another test. I feed different values to f(a) and it remembers them all properly. The outcome is as expected, without errors. So we must assume when f(a) gets called, it gets WHOLE timeline? Because the function didn't get the argument every bar, like, it doesn't know what's high value 7 bars ago because it didn't see high every bar, so how could it?

By the way, added +1 so it couldn't, like, remember the variable name, because now there's no variable to remember, it's an equation... but still, it works? So it stores the results of all past values of EVERYTHING? And all that, shared with every local function? Like, once f(a) got called this bar, it not only gets current value fed to it, but like, EVERYTHING? Because I can somehow know past values of close+1 even though it only saw the result of open+1?

So supposedly, my global space has 10mb of data, and I call 20 functions from that, I would create extra 200mb of data just then?

EDIT: PS: In case anyone comes up on this question, I figured out the solution is that indeed, every function call in the code is a completely isolated local space for that function, that has its own history. I don't believe it's ever explained anywhere else

This code demonstrates it.

//@version=5
OVERLAY = false
indicator("Pastebin", overlay = OVERLAY)

f(a, inc = 1) =>
    b = a
    var called = 0
    called := called + inc
    var accumulator = 0.
    accumulator += a
    [a[2], called, accumulator]

[fc, called1, _void1] = f(close)
[_void2, called2, accumulator1] = f(open)
[fo, called3, accumulator2] = f(open, 2)
plot(close[2] - fc)
plot(open[2] - fo)
plot(called1)
plot(called2)
plot(called3)
plot(accumulator1 - accumulator2)

Even though function f(a) is called three times, the amount times called is individually stored. If that would not be so, we would see "called" value increase within one price candle from call to call. Also, an variable "accumulator" incremented inside the function itself is isolated, meaning, it stores individual values for individual functions, therefore, between two function calls with same input, it has same output, since each input went into its own "accumulator" value.

So one must assume that every function call inside global code (and from functions) creates its own local space where history is stored. So yes calling function 20 times would produce 20 copies of history, and each copy would individually operate.

This also means one can use local function vars without fear of them being contaminated by multiple function calls. But must expect them to NOT be influenced by multiple function calls. For example, if I would want to have a counter of how many times a particular function was called, in total, I'd have to use an array, otherwise each function call would only calculate times that specific call was executed.

And that's why calling functions every tick is important if they do [] inside, because they would have desynced [] values with the rest of the global space if they weren't. That's also why it's not enough to call a function once for it to count, meaning something like

foo(a) => a[1]
foo(close)
if(close > open)
    foo(open)

Would give a warning, since second function call isn't getting complete history, since it's an isolated local space

Again, if you would want to track history inside a function that does implement this as a feature without warning, you would have a function that starts with if(condition) and whole function is within that if block, and within it, you use an array you unshift an item into every time the function is ran. This way, array.get/set on this array will give you an equivalent of [] for same index value (0 = now, 2 = two executions back etc)

1 Answers1

1

The question relates to your other question here.

It's a tough one.
The key point here is again the conditional structure that you're missing in your tests. That's why there's no difference.
If you call your functions on each bar, like you do in the plots, the structure makes no difference and your results are the same.
However in a ternary operation, like the example of tradingview shows, the case changes the following way.

Example of TradingView:

//@version=5
indicator("My Script", overlay = true)

// Returns the value of "a" the last time the function was called 2 bars ago.
f(a) => a[1]
// Returns the value of last bar's "close", as expected.
f2() => close[1]

oneBarInTwo = bar_index % 2 == 0
plot(oneBarInTwo ? f(close) : na, color = color.maroon, linewidth = 6, style = plot.style_cross)
plot(oneBarInTwo ? f2() : na, color = color.lime, linewidth = 6, style = plot.style_circles)
plot(close[2], color = color.maroon)
plot(close[1], color = color.lime)

In f(a) you have an argument. The first time a gets called it receives a timeline - anyone is welcome to correct me if my explanation is wrong, that's my grasp of it.
This timeline/history is then preserved and used in the future, decoupled from the "real global timeline". It's the function argument at this point that's responsible for that.

This results in a1 in the function block referring to a different past value than close1

In f2() there is no function argument and the function with its scope still works on this "real global timeline".
You can observe sometimes a similar behaviour concerning var within functions, you're going to like it - being ironic.

The safest thing is to declare your variables globally or encapsulate logic parts in functions that are called on each bar and this way you are not going to face any problems.

elod008
  • 1,227
  • 1
  • 5
  • 15
  • Thank you very much for still trying to explain this to me. This is really, really hard to grasp. I have updated my original question, could you comment on that plase? – Alexei Andronov Oct 11 '22 at 09:45
  • Unfortunately that's beyond my knowledge. I just tested some stuff out myself and tried to work my way through the language like you but the official documentations don't go this far. What you're supposing sounds logical to me. Pine script is a very unique programming language to say the least and has some very frustrating limitations. I find the history referencing and var functionality the most difficult to understand and adapt to. We are sometimes somewhat in the dark. – elod008 Oct 11 '22 at 11:01
  • I have marked your question as answer to thank you for all the help provided, but also updated my OP with my new understanding of how this actually works. I's really peculiar, but after studying it, it started to make sense – Alexei Andronov Oct 14 '22 at 14:37
  • Great research! Thanks for the update. What about "f2(a) => a[1] + close[1]"? Does it fit into your theory? – elod008 Oct 14 '22 at 19:19