Pineスクリプトの落とし穴:小数の計算結果は常に正しいとは限らない

例として、下記のユーロドル・ショートの円換算・損益計算を考える。

floor( (1.11170 - 1.11050) * 50000 * 109.450 )
= floor( 0.0012 * 50000 * 109.450 )
= floor( 60 * 109.450 )
= floor( 6567 )
= 6567

※floor(): 小数点以下切り捨て
※決済時に1円未満は切り捨てとなる証券会社を想定

この計算をPineスクリプトで行ったものが下記。


//@version=4
study("float number calc 1", precision=16)

eurusd_entry = 1.11170
eurusd_exit =  1.11050
qty = 50000
usdjpy = 109.450

pl_jpy = floor( (eurusd_entry - eurusd_exit) * qty * usdjpy )

plot(pl_jpy) // 6566 誤り

手計算では何でもない計算なのだが、Pineスクリプトで行うと計算結果は 6567 とはならずに 6566 となる。
※正確には、これはPineスクリプトの問題ではなく、コンピューター上で行う計算において一般的に存在する問題。PythonやC/C++などのプログラミング言語でも正しい結果が得られない。

正しい結果を得るには round() を利用して、下記のように修正する必要がある。


//@version=4
study("float number calc 2", precision=16)

eurusd_entry = 1.11170
eurusd_exit =  1.11050
qty = 50000
usdjpy = 109.450

d = eurusd_entry - eurusd_exit
//d_round = round(d * 100000) / 100000.0 // 旧バージョン round() での四捨五入の方法
d_round = round(d, 5)

//pl_usd_round = round(d_round * qty * 10) / 10.0 // 旧バージョン round() での四捨五入の方法
pl_usd_round = round(d_round * qty, 1)

pl_jpy = floor(pl_usd_round * usdjpy)

plot(pl_jpy) // 6567 正しい

コンピューター上の計算は全て2進数に変換されて行われるが、全ての小数を2進数で完全に表現できるわけではなく、小数によっては数値誤差が生じる。修正版のコードでは、この数値誤差を round() を利用して適宜補正しているため、正しい結果が得られる。
※詳しく知りたい場合は、Googleで「循環小数 数値誤差」と検索すれば情報が得られる

ユーロドルではレートが小数点以下5桁で固定なので、小数点以下5桁に四捨五入する必要がある。Pineスクリプトで面倒なのが、Pineスクリプトで提供されている round() は整数に四捨五入するだけで、指定した小数点以下の桁数に四捨五入することができない。そのため、小数点以下5桁に四捨五入したいときは、下記のように多少面倒な方法で round() を使う必要がある。
※2021年4月9日から round() で四捨五入する桁数の指定が可能になった。2番目の引数に桁数を指定すると、指定した小数点以下の桁数に四捨五入してくれる。

d = eurusd_entry - eurusd_exit
//d_round = round(d * 100000) / 100000.0  // 旧バージョン round()
d_round = round(d, 5)  // 新バージョン round()

また、下記の米ドル換算での損益額の計算については、ユーロドルの最小通貨単位が1万通貨、レートが小数点以下5桁で固定、よって、損益額は小数点以下1桁で固定となるので、下記のように round() を使って小数点以下1桁に四捨五入している。

//pl_usd_round = round(d_round * qty * 10) / 10.0  // 旧バージョン
pl_usd_round = round(d_round * qty, 1)  // 新バージョン

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)

plot() の便利ワザ

plot() を工夫して利用することにより下記のようなトリッキーな表示を行うことができる。

  • 動的に変動する水平線を表示する
  • 垂直線を表示する

動的に変動する水平線を表示する

水平線を表示させる関数として hline() があるが、この関数には定数しか設定できない。変数の値の変化によって、チャート上で動的に変動する水平線を表示させるには、別の方法を考える必要がある。

簡単なものとして plot() を利用する方法がある。plot() の引数には trackprice があり、これを true に設定すると、plot() の直近の値を水平線で表示してくれる。そして、transp=100 に設定し透明度を100にする。すると、plot() の実態の線は透明で見えなくなるが、trackprice=true で表示される水平線は、この透明度設定の影響を受けないため表示が残る。こうして動的に変動する水平線を表示することが可能となる。


//@version=4
study("dynamic horizontal line", overlay=true)

plot(highest(high, 20), trackprice=true, transp=100, color=color.green)
plot(lowest(low, 20), trackprice=true, transp=100, color=color.red)

//plot(close, trackprice=true, transp=100) // debug 水平線が動的に変動するか確認
//plot(highest(high, 20), color=color.green) // debug
//plot(lowest(low, 20), color=color.red) // debug

垂直線を表示する

ある条件になったらチャート上に垂直線を引きたいというとき、簡単な方法として plot() を使う方法がある。

下記の例のように、study()scale=scale.none を指定し、plot() で巨大な値 1e10 をヒストグラムでチャート上に表示させることで垂直線もどきを実現している。

ポイントは scale=scale.none を指定していること。これにより、巨大な値が表示画面に収まるようにチャートがリサイズされるのを防いでいる。


//@version=4
study("vertical line", overlay=true, scale=scale.none)

ma = sma(close, 20)
co = crossover(close, ma)
cu = crossunder(close, ma)

plot(co ? 1e10 : na, style=plot.style_histogram, color=color.green)
plot(cu ? 1e10 : na, style=plot.style_histogram, color=color.red)

規模の大きいシステムを実装する際のコンパイラ制限値まとめ

Pineスクリプトで規模の大きいシステムを実装する際に関係する代表的なコンパイラ制限値を調査した。

  • Pineスクリプトで利用できるローカル変数の数は最大999個まで
  • Pineスクリプトでコンパイル可能なローカルスコープの数は最大499個まで

Pineスクリプトで利用できるローカル変数の数は最大999個まで

Pineスクリプトで利用できるローカル変数の最大数を調べてみたところ、変数が1000個に達した時点で下記のエラーメッセージが出た。

Script has too many local variables (1001) in "#f1": #loc_0, #loc_1, #loc_2, #loc_3, #loc_4... The limit is 1000

1000に達するとエラーとなるので、実際に利用できる変数の最大数は999個までとなる。
openclose 等のビルトイン変数は、この数に含まれない。

変数の最大数を確かめるために、下記のようなコードを利用した。長いので途中は省略している。


//@version=4
study("max limit num of variables")

x1 = open + high + low + close
x2 = x1
x3 = x2
x4 = x3
//
//省略...
//
x997 = x996
x998 = x997
x999 = x998
//x1000 = x999 // Script has too many local variables...The limit is 1000

plot(x999)
//plot(x1000)

また、mutable 変数の場合の制限についても調べてみた。
※変数に := 演算子を使うと、その変数は mutable(変更可能な)変数としてPineコンパイラに認識される。

mutable 変数の場合、値を変更する度に別の一つの変数として数えられるようで、998回まで := 演算子を使った値の変更が可能だが、999回で下記の "The limit is 1000" エラーが出た。

Script has too many local variables (1002) in "#f1": #mut_0, #mut_0, #mut_0, #mut_0, #mut_0... The limit is 1000

mutable 変数の制限の確認には、下記のようなコードを使用した。長いので途中は省略している。


//@version=4
study("max limit num of variables")

x = close

x := x[1]
x := x[1] - x[2]
x := x[2] - x[3]
//
//省略...
//
x := x[996] - x[997]
x := x[997] - x[998]
//x := x[998] - x[999] // Script has too many local variables...The limit is 1000

plot(x)

Pineスクリプトでコンパイル可能なローカルスコープの数は最大499個まで

Pineスクリプトの関数、if文、for文などはそれぞれローカルスコープを持つ。そのローカルスコープを最大どれだけ持てるのか、別の言い方をすると、関数をどれだけ定義できるか、if文やfor文をどれだけ書けるか、その最大数を調べてみた。

簡単なコードで確認したところ、ローカルスコープが500個できるようにした時点でエラーが出た。

Script has too many local scopes: 501. The limit is 500.

500個でエラーが出るので、実際可能なのは499個まで。関数、iffor それぞれの確認コードを作ったが、ローカルスコープの最大数は関数、iffor など合わせて全体で499個までとなる。例えば、関数を499個定義したら、それ以上 iffor など一つも使うことはできないことになる。


//@version=4
study("max limit num of scopes")

x = close

func1(src) => src[1]
x := func1(close)
func2(src) => src[2]
x := func2(close)
func3(src) => src[3]
x := func3(close)
//
//省略...
//
func499(src) => src[499]
x := func499(close)
//func500(src) => src[500] // Script has too many local scopes: 501. The limit is 500.
//x := func500(close)

plot(x)

//@version=4
study("max limit num of scopes")

x = close

if x > close[1]
    x := open[1]
if x > close[2]
    x := open[2]
if x > close[3]
    x := open[3]
//
//省略...
//
if x > close[499]
    x := open[499]
//if x > close[500] // Script has too many local scopes: 501. The limit is 500.
//    x := open[500]

plot(x)

//@version=4
study("max limit num of scopes")

x = close

for i = 1 to 1
    x := close[1]
for i = 2 to 2
    x := close[2]
for i = 3 to 3
    x := close[3]
//
//省略...
//
for i = 499 to 499
    x := close[499]
//for i = 500 to 500 // Script has too many local scopes: 501. The limit is 500.
//    x := close[500]

plot(x)

コンパイル時の典型的エラーと対処法

Pineスクリプトのコンパイル時に発生する典型的なエラーとして下記が挙げられる。この記事では、それぞれのエラーの原因と対処法をサンプルコードを付けてまとめた。

  • Undeclared identifier
  • Value with NA type cannot be assigned to a variable that was defined without type keyword
  • Variable 'x' was declared with 'series[integer]' type. Cannot assign it expression of type 'series[float]'
  • Cannot call 'operator +' with arguments (series[bool], series[bool])

エラー:Undeclared identifier

このエラーが出る典型的な原因として、下記の変数が関係している可能性がある。
  1. self-referenced(自己参照)変数
  2. forward-referenced(前方参照)変数
1. self-referenced(自己参照)変数による Undeclared identifier エラー
下記は self-referenced(自己参照)変数を使ったコードの例。

//@version=4
study("self-referenced variable")

// elapsedは自己参照変数
elapsed = nz(elapsed[1]) + 1

plot(elapsed)

変数 elapsed は、宣言時に自己 elapsed[1] を参照している。このような変数を self-referenced(自己参照)変数という。

自己参照変数はPineスクリプトv2まではサポートされていたが、v3以降では、自己参照変数があるコードは Undeclared identifier というエラーメッセージが出るようになった。v3以降では、下記のように書き直す必要がある。


//@version=4
study("self-referenced variable")

// 変数の初期化を追加
// 整数0で初期化することで、elapsedは整数型であることがPineコンパイラに伝わる
elapsed = 0

// = を := に変更する (elapsedはmutable変数となる)
elapsed := nz(elapsed[1]) + 1

plot(elapsed)

なお、書き直すと、elapsed は自己参照変数ではなくなり、mutable(変更可能な)変数としてPineコンパイラに認識されるようになる。

参考:Self-referenced variables are removed

2. forward-referenced(前方参照)変数による Undeclared identifier エラー

次に、下記は forward-referenced(前方参照)変数を使ったコードの例。変数 s は前方参照変数と呼ばれる。
※変数 s が実際に定義されるのは3行後、その定義前に変数sが参照されるため、「前方参照」変数と呼ばれる


//@version=2
study("forward-referenced variable", overlay=true)

s1 = nz(s[1]) // 変数sは前方参照変数
a = 0.2
s1_w = s1 * (1 - a)
s = close * a + s1_w // 変数sが定義されるのはここ

plot(s)

Pineスクリプトv2でサポートしていた forward-referenced(前方参照)変数は、v3以降サポートされなくなった。そのため、前方参照変数を含むコードをv3以降でコンパイルすると Undeclared identifier エラーが出る。

前方参照変数を含むコードは下記のように修正できる。参照前に変数の宣言を新たに追加し、実際に定義していた箇所の = 演算子を := 演算子に置き換えて mutable 変数に変更する。


//@version=4
study("forward-referenced variable", overlay=true)

s = 0.0 // 参照が行われる前に宣言を新たに追加
s1 = nz(s[1])
a = 0.2
s1_w = s1 * (1 - a)
//s = close * a + s1_w
s := close * a + s1_w // =演算子を:=演算子に変更

plot(s)

参考:Forward-referenced variables are removed


エラー:Value with NA type cannot be assigned to a variable that was defined without type keyword

変数を na で初期化している場合、その変数が原因でこのエラーが出る。

Pineスクリプトv2やv3では、変数を na で初期化しても問題なかったが、v4以降ではコンパイルエラーが出るようになった。


//@version=4
study("init with na", overlay=true)

crossed_line = na // v4以降ではコンパイルエラー

crossed_line := crossed_line[1]

ma = sma(close, 200)

if crossover(close, ma)
    crossed_line := close
if crossunder(close, ma)
    crossed_line := na

plot(crossed_line, style=plot.style_cross, linewidth=2)

上記のコードはv4以降、Value with NA type cannot be assigned to a variable that was defined without type keyword というコンパイルエラーが出る。
※エラーを日本語に訳すと「型キーワードを指定せずに定義された変数にNA型の値を割り当てることはできません」

このエラーへの対処法は、変数を na で初期化したいときは、型キーワードを指定すれば良い。上記コードの例の場合、float 型の close 値を代入する箇所があるので、それに合わせて変数に float 型を指定すれば良い。


//@version=4
study("init with na", overlay=true)

crossed_line = float(na)  // float型を指定してnaを割り当て
//float crossed_line = na // この書き方でも良い

crossed_line := crossed_line[1]

ma = sma(close, 200)

if crossover(close, ma)
    crossed_line := close
if crossunder(close, ma)
    crossed_line := na

plot(crossed_line, style=plot.style_cross, linewidth=2)

参考:na value


エラー:Variable 'x' was declared with 'series[integer]' type. Cannot assign it expression of type 'series[float]'

このエラーが出る典型的な例として、float 型の変数を 0 で初期化している場合が考えられる。

Pineスクリプトv2では、float 型の変数を 0(整数)で初期化しても問題なかったが、v3以降ではコンパイルエラーが発生するようになった。下記はこのようなエラーが発生するコードの例。


//@version=4
study("init with zero")

cost = 0 // v3以降ではエラーになる
cost := nz(cost[1])

if crossover(close[1], sma(close[1], 200))
    cost := open

plot(cost)

上記のコードは、v3では Variable `cost` was declared with series[integer] type. Cannot assign it expression of type series、v4では Variable 'cost' was declared with 'series[integer]' type. Cannot assign it expression of type 'series[float]'. というエラーメッセージが出る。
※エラーメッセージを日本語に訳すと「series[integer] 型(integer 型の一次元データ)で宣言された変数に、series[float] 型(float 型の一次元データ)を割り当てることはできません」

このコンパイルエラーへの対処法は、float 型の値で更新される変数に対して、明示的に 0.0float 型のゼロ)で初期化する必要がある。


//@version=4
study("init with zero")

cost = 0.0 // float型のゼロで初期化
cost := nz(cost[1])

if crossover(close[1], sma(close[1], 200))
    cost := open

plot(cost)

エラー:Cannot call 'operator +' with arguments (series[bool], series[bool])

論理値に対して明示的な型変換をせずに数値演算を行うとこのエラーが出る。

Pineスクリプトv2では、論理値から数値への暗黙的な型変換が行われ、論理値に対して数値演算を行うことができたが、v3以降では、暗黙的な型変換による論理値の数値演算はサポートされなくなり、このようなコードはエラーが出るようになった。
※v2では true1.0false0.0 へ暗黙的に変換される


//@version=2
study("bool to num", overlay=true)

b1 = close > open
b2 = close > close[1]

x = b1 + b2 // 暗黙的な型変換が行われ、v2ではエラーにならない

plotchar(x == 1, char='/', size=size.tiny)
plotchar(x == 2, char='|', size=size.tiny)

v3以降でこのエラーを回避するには、論理値を明示的に数値へ変換する関数を用意すれば良い。下記の修正版コードでは、関数 b_to_n を定義し、この関数を介して、論理値は整数に変換されるように修正している。


//@version=4
study("bool to num", overlay=true)

b_to_n(b) =>
    b ? 1 : 0

b1 = close > open
b2 = close > close[1]

//x = b1 + b2
x = b_to_n(b1) + b_to_n(b2) // 関数 b_to_n() が論理値を整数へ変換

plotchar(x == 1, char='/', size=size.tiny)
plotchar(x == 2, char='|', size=size.tiny)

参考:Math operations with booleans are forbidden

イールドカーブをプロットする

TradingViewでは、国債の金利を表示できるので、security() を使って金利データを参照し、独自にイールドカーブをプロットするインディケーターを作成することができる。

例えば、米国債利回りのシンボル名は下記の通り。シンボル名の US の部分を JP に変えれば日本国債、DE に変えればドイツ国債の利回りになる。

シンボル名説明
US01Y米国債1年 利回り
US02Y米国債2年 利回り
US03Y米国債3年 利回り
US05Y米国債5年 利回り
US07Y米国債7年 利回り
US10Y米国債10年 利回り
US20Y米国債20年 利回り
US30Y米国債30年 利回り

イールドカーブをプロットするPineスクリプトのコードが下記。


//@version=4
study("Yield Curve")

country = input("US", type=input.string)

round_float(v) =>
    round(v * 1000) / 1000

draw(x1, y1, x2, y2, n) =>
    l = line.new(x1, y1, x2, y2, color=color.black, style=line.style_arrow_right)
    txt = tostring(n) + ": " + tostring(y2)
    lb = label.new(x2, y2, txt, textcolor=y2 >= 0 ? color.black : color.red, style=label.style_none)
    [l, lb]

yld01_ = security(country + "01Y", timeframe.period, close)
yld02_ = security(country + "02Y", timeframe.period, close)
yld03_ = security(country + "03Y", timeframe.period, close)
yld05_ = security(country + "05Y", timeframe.period, close)
yld07_ = security(country + "07Y", timeframe.period, close)
yld10_ = security(country + "10Y", timeframe.period, close)
yld20_ = security(country + "20Y", timeframe.period, close)
yld30_ = security(country + "30Y", timeframe.period, close)

yld01 = round_float(yld01_)
yld02 = round_float(yld02_)
yld03 = round_float(yld03_)
yld05 = round_float(yld05_)
yld07 = round_float(yld07_)
yld10 = round_float(yld10_)
yld20 = round_float(yld20_)
yld30 = round_float(yld30_)

x_offset = 10

x1 = bar_index[80]
x2 = x1 + x_offset
lb0 = label.new(x1, yld01, "1: " + tostring(yld01), textcolor=yld01 >= 0 ? color.black : color.red, style=label.style_none)
[l1, lb1] = draw(x1, yld01, x2, yld02, 2)

x1 := x2
x2 := x1 + x_offset
h_line = line.new(x1, yld02, x1 + 60, yld02, color=yld10 > yld02 ? color.green : color.red)
[l2, lb2] = draw(x1, yld02, x2, yld03, 3)

x1 := x2
x2 := x1 + x_offset
[l3, lb3] = draw(x1, yld03, x2, yld05, 5)

x1 := x2
x2 := x1 + x_offset
[l4, lb4] = draw(x1, yld05, x2, yld07, 7)

x1 := x2
x2 := x1 + x_offset
[l5, lb5] = draw(x1, yld07, x2, yld10, 10)

x1 := x2
x2 := x1 + x_offset
[l6, lb6] = draw(x1, yld10, x2, yld20, 20)

x1 := x2
x2 := x1 + x_offset
[l7, lb7] = draw(x1, yld20, x2, yld30, 30)

line.delete(l1[1])
line.delete(l2[1])
line.delete(l3[1])
line.delete(l4[1])
line.delete(l5[1])
line.delete(l6[1])
line.delete(l7[1])
line.delete(h_line[1])

label.delete(lb0[1])
label.delete(lb1[1])
label.delete(lb2[1])
label.delete(lb3[1])
label.delete(lb4[1])
label.delete(lb5[1])
label.delete(lb6[1])
label.delete(lb7[1])

hline(0)

独自に追加した部分として、一般的に景気悪化の予兆として捉えられる長短金利の逆転現象を示す指標を追加している。これは、2年債利回りの水準に水平線を引き、2年債と10年債の利回りが逆転すると、水平線が赤色になる。逆転がない平常時は緑色となる。

イールドカーブは下記のように描画される。これは、インディケーターをチャートに3つ追加し、上から日本、米国、ドイツ国債を指定して表示したもの。

ドルインデックスを自作する

ドルインデックスとは、ドルに対し重み付けした6つの通貨(ユーロ、日本円、英ポンド、カナダドル、スウェーデンクローナ、スイスフラン)の幾何平均を計算したもの。TradingViewでは、シンボルに DXY を指定すれば表示できる。

通貨重み
ユーロ0.576
日本円0.136
英ポンド0.119
カナダドル0.091
スウェーデンクローナ0.042
スイスフラン0.036

元は1973年に米国の中央銀行によって開発されたものだが、1985年にドルインデックスの先物取引が開始されてから、 ICE Futures U.S.(先物取引所)によって算出・公表されている。算出は各通貨のスポット価格の配信レートを元にリアルタイムに約15秒毎に行われる。

計算式は次の通り。

index = 50.14348112 × EURUSD-0.576 × USDJPY0.136 × GBPUSD-0.119 × USDCAD0.091 × USDSEK0.042 × USDCHF0.036

この計算式をPineスクリプトで実装したものが下記。


//@version=4
study("US Dollar Index")

symbol_prefix = input("FX")

vs_eur = pow(security(symbol_prefix + ":EURUSD", timeframe.period, close), -0.576)
vs_jpy = pow(security(symbol_prefix + ":USDJPY", timeframe.period, close), 0.136)
vs_gbp = pow(security(symbol_prefix + ":GBPUSD", timeframe.period, close), -0.119)
vs_cad = pow(security(symbol_prefix + ":USDCAD", timeframe.period, close), 0.091)
vs_sek = pow(security(symbol_prefix + ":USDSEK", timeframe.period, close), 0.042)
vs_chf = pow(security(symbol_prefix + ":USDCHF", timeframe.period, close), 0.036)

index = 50.14348112 * vs_eur * vs_jpy * vs_gbp * vs_cad * vs_sek * vs_chf

plot(index)
plot(security("DXY", timeframe.period, close), color=color.gray)

symbol_prefix (FX:EURUSDFX の部分) のデフォルト値は FX に設定してあり、FXCMの配信レートを使ってインデックスを計算する。例えば、これを OANDA に変更すれば、OANDAの配信レートを使って計算するように変更できる。

参考:U.S. Dollar Index® Contracts - FAQ June 2015 (PDF)

疑似乱数生成器を自作する

Pineスクリプトにはビルトイン関数 rand() が用意されてないので、自分で疑似乱数生成器を実装してみる。実装するのは、C++11の minstd_rand0 とその改良版の minstd_rand

minstd_rand0 は、1988年にStephen K. ParkとKeith Millerが発表[1]した乱数生成器の実装を元にしたもの。論文の中で下記のPascalコードが示されている。

function Random : real;
const
  a = 16807;
  m = 2147483647;
begin
  seed := (a * seed) mod m;
  Random := seed / m
end;

変数 seed1 から 2147483646 までの整数の中から一つを割り当てて初期化する。

これをPineスクリプトで実装すると下記のようになる。seedtimenow の値で初期化している。


//@version=4
study("minstd_rand0")

var seed = timenow

a = 16807
m = 2147483647
seed := (a * seed) % m
//random = seed / float(m)

plot(seed)

変数 seed を参照すれば、C++11の minstd_rand0 のように整数の乱数を得ることができる。float の乱数が欲しい場合は、コメントアウトした random の値を参照すれば良い。

実装が正しいかどうかは、変数 seed1 で初期化し10000番目に生成された seed の値が 1043618065 であることを確認すれば良い。

下記のコードを使って、乱数生成器の実装の正しさを確認した。


//@version=4
study("minstd_rand0 test")

var seed = 1

if barstate.isfirst
    for i = 1 to 10000
        a = 16807
        m = 2147483647
        seed := (a * seed) % m
        //random = seed / float(m)

plot(seed, color=seed == 1043618065 ? color.green : color.red)

次に改良版の minstd_rand を実装する。これは1993年にStephen K. Park、Keith W. Miller、Paul K. Stockmeyerが発表[2]した改良版で、a = 16807 の代わりに a = 48271 を使用することを推奨したものである。

よって、Pineスクリプトの実装は単に a の値を 48271 に変更するだけで済む。


//@version=4
study("minstd_rand")

var seed = timenow

a = 4827
m = 2147483647
seed := (a * seed) % m
//random = seed / float(m)

plot(seed)
References
  1. S. K. Park and K. W. Miller. 1988. Random number generators: good ones are hard to find. Commun. ACM 31, 10 (Oct. 1988), 1192–1201. DOI:https://doi.org/10.1145/63039.63042
  2. Diane Crawford. 1993. Technical correspondence. Commun. ACM 36, 7 (July 1993), 105. DOI:https://doi.org/10.1145/159544.376068

Pineスクリプトの落とし穴:平均足チャートを表示しているときの OHLC の値は実際の値と違う

平均足チャートを表示しているとき、組み込み(ビルトイン)変数 openhighlowclose の値は、実際の銘柄の値と違うことに注意。

違う値となる理由は、組み込み(ビルトイン)変数 openhighlowclose には、チャートに表示しているバー(ロウソク足)に対応する値が入るため。よって、平均足チャートのときは平均足の値が入るため、実際の銘柄の値とは異なる。

これを理解していないと、平均足チャート上に表示しているインディケーター(テクニカル指標)は、実は期待と違うものを表示している可能性がある。

平均足チャートを表示しているときに実際の銘柄の値を取得するには、下記のように security() を使う必要がある。


//@version=4
study("get OHLC with security()")

symbl = tickerid(syminfo.prefix, syminfo.ticker)

o = security(symbl, timeframe.period, open)
h = security(symbl, timeframe.period, high)
l = security(symbl, timeframe.period, low)
c = security(symbl, timeframe.period, close)

//plot(open)
//plot(o, linewidth=2)
//plot(high)
//plot(h, linewidth=2)
//plot(low)
//plot(l, linewidth=2)
plot(close)
plot(c, linewidth=2)

海外の取引所の時間を日本時間に変換する

Pineスクリプトで参照できるビルトイン変数 hour は、取引所のタイムゾーンの時間を保持している。これは、あくまで「取引所のタイムゾーン」であって、TradingViewのチャートで設定してるタイムゾーンの時間ではないことに注意。

TSEやOSEなど日本の取引所の銘柄については、取引所のタイムゾーンが東京のため、変数 hour は日本時間(UTC+9)を保持しているので問題ない。
※協定世界時(UTC)と日本時間の時差は+9時間(UTC+9)

海外の取引所(CME、FXCM、OANDAなど)の銘柄に対しては、日本とタイムゾーンが異なり、変数 hour はチャート上で表示される日本時間と一致しない。

例えば、TradingViewのタイムゾーン設定が "(UTC+9) Tokyo" となっていることを前提にすると、CMEの銘柄をチャートに表示しているとき、Pineスクリプトで hour == 9 の条件で何かプロットしようとしても、チャート上では23時にプロットされる。また、FXCM、OANDAの銘柄の場合は、hour == 9 の条件でプロットしても、チャート上では22時にプロットされる。

このようにそれぞれの取引所のタイムゾーンによって、変数 hour の値とチャート上の時間での表示にずれが生じる。

このような海外の取引所との時差の問題をPineスクリプト上で上手く扱うには、変数 time を利用する。

変数 time は、UTC±0 の時間をミリ秒の値で保持しているので、この値を日本時間(UTC+9)に変換すれば、取引所に関わらずチャート上の時間と一致した表示を行うことができる。


//@version=4
study("local hour", overlay=true, scale=scale.none)

// 1h のチャート表示用

offset = 9 // 協定世界時(UTC)と日本の時差は+9時間
localhour = (time / (1000 * 60 * 60) + offset) % 24 // UTC+offsetの時間に変換

plot(hour == 9 ? 10e20 : na, style=plot.style_histogram, color=color.black)
plot(localhour == 9 ? 10e20 : na, style=plot.style_histogram, color=color.red, linewidth=2)