Pineスクリプトの落とし穴:関数呼び出しは毎回行われる必要がある

あまり知られていないが、Pineスクリプトでは関数呼び出しは毎回行われる必要がある。if文などで条件に合致したときだけ関数呼び出しを行っているコードは期待と違う結果をチャート上に表示している可能性が非常に高い。

コードに問題がある場合、Pine Editorでコードを開いた状態で、Pine Editorにある "Add to Chart" ボタンをクリックすると、Pine Editorのコンソールに下記の警告メッセージが出る。

  • if文ブロック内で関数呼び出しがある場合)
    The function 'anonym_function_0' should be called on each calculation for consistency. It is recommended to extract the call from this scope.
    ※日本語訳:一貫性を保つため、関数は毎回呼ばれるべきです。関数の呼び出しをこのスコープの外に出すことを勧めます。
  • (三項演算子内で関数呼び出しがある場合)
    The function 'anonym_function_0' should be called on each calculation for consistency. It is recommended to extract the call from this ternary operator.
    ※日本語訳:一貫性を保つため、関数は毎回呼ばれるべきです。関数の呼び出しを三項演算子の外に出すことを勧めます。

下記は典型的な問題のあるコードの例。一見、変数 xy は、どちらも偶数番目のバー(変数 bar_index が偶数)のときに一つ前のバーの終値の値を保持するように見えるが、変数 xy の値は異なる値を保持する。


//@version=4
study("function call 1", overlay=true)

func(src) => src[1]

x = bar_index % 2 == 0 ? func(close) : na
y = bar_index % 2 == 0 ? close[1] : na

plot(x, style=plot.style_cross, linewidth=3, color=color.red)
plot(y, style=plot.style_cross, linewidth=3)

期待通りに変数 xy が同じ値を保持するためには、下記のように関数 func を毎回呼び出すように修正する必要がある。


//@version=4
study("function call 2", overlay=true)

func(src) => src[1]

v = func(close) // 関数funcは毎回呼び出される

//x = bar_index % 2 == 0 ? func(close) : na
x = bar_index % 2 == 0 ? v : na
y = bar_index % 2 == 0 ? close[1] : na

plot(x, style=plot.style_cross, linewidth=3, color=color.red)
plot(y, style=plot.style_cross, linewidth=3)

関数を毎回呼び出すか呼び出さないかによって結果に違いが生まれるのは、Pineスクリプト側にバグがあるわけではなく、元々このような挙動をするようにデザインされている。

bar_index が偶数のときだけ関数 func が実行されると、実際にどのようなことが起きるか順に説明すると次のようになる。

  • bar_index==0 の場合 src0 (== close0)
  • bar_index==2 の場合 src1 (== close2)
    • src1[1] == src0 == close0
    • 関数 "func(src) => src[1]" は、func(close2) => close0 となり、"src[1]" と1つ前を意図しているにも関わらず、実際は2つ前の close0 の値を返すことになる
  • bar_index==4 の場合 src2 (==close4)
    • src2[1] == src1 == close2
    • 関数 "func(src) => src[1]" は、func(close4) => close2 となり、"src[1]" と1つ前を意図しているにも関わらず、実際は2つ前の close2の値を返すことになる
  • bar_index==6 の場合 src3 (== close6)
    • src3[1] == src2 == close4
    • 関数 "func(src) => src[1]" は、func(close6) => close4 となり、"src[1]" と1つ前を意図しているにも関わらず、実際は2つ前の close4 の値を返すことになる
  • (以下同様)
    ...

つまり、関数 func で利用される変数 src は、series(一次元)データだが、コードの見かけ上期待する

src0(=close0), src1(=close1), src2(=close2), ...

とならず、bar_index が偶数のときのみ実行されることにより、

src0(=close0), src1(=close2), src2(=close4), ...

というように断片的な series データが構成されてしまうことになる。

Pineスクリプトではこのような挙動があるので、sma() などのビルトイン関数についても同様の配慮が必要となる。

※ただし、下記のビルトイン関数は例外で毎回呼び出す必要はない。

abs, acos, asin, atan, ceil, cos, dayofmonth, dayofweek, exp, floor, heikinashi, hour, kagi, linebreak, log, log10, max, min, minute, month, na, nz, pow, renko, round, second, sign, sin, sqrt, tan, tickerid, time, timestamp, tostring, weekofyear, year

参考:
- Execution of Pine functions and historical context inside function blocks (Pine Script User Manual 4 documentation)
- Exceptions (Pine Script User Manual 4 documentation)