O(1)の単純移動平均 (SMA) インディケーターを自作する

TradingViewにはすでに単純移動平均(SMA)のインディケーターが用意されているが、それを自作してみる。あえて自作してみることで、計算の工夫の仕方に気付いたり、Pineスクリプトに特有の簡潔な書き方などノウハウを学ぶことができる。

Pineスクリプトで単純移動平均を計算するコードを素直に書くと次のようになる。


//@version=4
study("my sma", overlay=true)

length = input(9)

sum = close
for i = 1 to length - 1
    sum := sum + close[i]
ma = sum / length

plot(bar_index >= length - 1 ? ma : na)

sum を求める処理は、ループ長が length に比例する for ループを使うため、計算量はO(n)となる。だが、この計算量は計算を工夫することによって、O(1)へ減らすことができる。

length=9 とした場合、最初から順に sum の計算を書き出してみると、


sum0 = close0
sum1 = close1 + close0
sum2 = close2 + close1 + close0
...
sum7  =  close7 + close6 + close5 + close4 + close3 + close2 + close1 + close0
sum8  =  close8 + close7 + close6 + close5 + close4 + close3 + close2 + close1 + close0
sum9  =  close9 + close8 + close7 + close6 + close5 + close4 + close3 + close2 + close1
sum10 = close10 + close9 + close8 + close7 + close6 + close5 + close4 + close3 + close2
...

ここで、sum9 に注目してみると、


sum9
= close9 + close8 + close7 + close6 + close5 + close4 + close3 + close2 + close1
= close9 + sum8 - close0

のように変形することができる。

sum10 でも同様に変形できることが分かる。


sum10
= close10 + close9 + close8 + close7 + close6 + close5 + close4 + close3 + close2
= close10 + sum9 - close1

これを踏まえて、下記のようにコードを書き直すことができる。


//@version=4
study("my sma2", overlay=true)

length = input(9)

sum = 0.0

if bar_index < length
    sum := close
    for i = 1 to bar_index
        sum := sum + close[i]
else
    sum := sum[1] + close - close[length]

ma = sum / length

plot(bar_index >= length - 1 ? ma : na)

さらに、関数 nz() を使うことで、このコードは下記のようにシンプルにすることができる。


//@version=4
study("my sma3", overlay=true)

length = input(9)

sum = 0.0
sum := nz(sum[1]) + close - nz(close[length])

ma = sum / length

plot(bar_index >= length - 1 ? ma : na)

最初のコードは、lengthに比例する for ループを使う計算があり計算量はO(n)だったが、書き直したコードでは length に関わらず計算量は一定でO(1)となる。

Pineスクリプトでは nz() をうまく利用できると、かなりシンプルなコードになる。Pineスクリプトプログラミングで多用する重要なテクニックの一つである。