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)  // 新バージョン