[MQL]価格が NormalizeDouble されない?

勝率判定をするインジケーターを作成していて価格をNormalizeDouble するかしないかで結果か違っていて
調査をしていて本当にGet Crazyな状態になったので、その調査を纏めてみたいと思います。

今後、同じような経験をされる方がいて役に立てるかと思い記録しておきます。

そもそも価格をNormalizeDouble する必要があるかというと価格を保存するためにdoubleを使用します。
doubleは浮動小点型といわれ小数点以下にとんでもない数字が並びます。

Type

バイトサイズ

最小の正の値

最大値

C++

float

4

1.175494351e-38

3.402823466e+38

float

double

8

2.2250738585072014e-308

1.7976931348623158e+308

double

https://www.mql5.com/ja/docs/basis/types/double

MQLでは有効な数字としては小数点以下15桁が有効な数字らしいです。小数点以下15桁もあるので価格を
NormalizeDoubleであるところで四捨五入していないと 比較する際にマッチしない現象がよく起きます。なので価格を
取得する箇所にはNormalizeDouble を使うようにしていました。

double price = NormalizeDouble(price, Digits);

ベストプラクティスにも記載されています。

トレード処理では、精度がトレーディングサーバーに要求される最低1桁を超える正規化されていない価格を使用することは不可能であることに注意が必要です。
未決注文に対するストップロス、テイクプロフィット、価格値は、値が定義済み変数 Digits に格納されている値精度で正規化します。

ところが価格をNormalizeDouble しても正常に表示されないような現象が確認できました。

[MQL]価格が NormalizeDouble されない?

こちらはAUDJPY1分足なのですが終値は、68.704になります。
MQLで価格を取得してみると、

double TestValue = iClose(NULL, 0, 0);
double TestValue_NormalizeDouble = NormalizeDouble(TestValue, Digits);
Print("TestValue ",TestValue, " TestValue_NormalizeDouble ", TestValue_NormalizeDouble);
結果
AUDJPY,M1: TestValue 68.70399999999999 TestValue_NormalizeDouble 68.70399999999999

-0.00000000000001違います。DoubleToStrを使用してストリングにして表示した場合は、

Print ("TestValueString ", DoubleToStr(TestValue, Digits), " TestValue_NormalizeDoubleString: ", DoubleToStr(TestValue_NormalizeDouble, Digits));

結果
AUDJPY,M1: TestValueString 68.704 TestValue_NormalizeDoubleString: 68.704

こちらは正しくなりました。

これらの現象はMQL5のフォーラムでかなり長い間、熱く議論されております。
英語ですが見たい方はこちら。
https://www.mql5.com/en/forum/
https://www.mql5.com/en/forum/146370/

いろいろ長い間に議論されておりはNormalizeDoubleは使用しないほうがいいのではないかとの意見もありました。
新しい記事では以下の関数を使用するのがいいとのことでした。
https://www.mql5.com/en/forum/333599

double NormalizePrice(double p)
{
double ts=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
return(NormalizeDouble(p/ts,0)*ts);
}
Print("TestValue ",TestValue, " NormalizePrice ", NormalizePrice(TestValue));
Print("NormalizePriceString ", DoubleToStr(NormalizePrice(TestValue), Digits));
結果
AUDJPY,M1: TestValue 68.70399999999999 NormalizePrice 68.70400000000001
AUDJPY,M1: NormalizePriceString 68.704

こちらのほうも0.00000000000001違いますがこちらはPrint()の動作のようです。

正規化数は小数点以下の桁数を多く含むことがあるので、Print() 関数を使用しての操作ログへの出力時にはご注意ください。
Print(76.671)=76.67100000000001

stdlib.mqhに含まれているDoubleToStrMorePrecision関数での15桁までの値の確認してみます。そして値を比較してみます。

#include <stdlib.mqh>
double TestValue_NormalizeDouble = NormalizeDouble(TestValue, Digits);
double TestValue_NormalizePrice = NormalizePrice(TestValue);
Print("DoubleToStrMorePrecision",DoubleToStrMorePrecision(TestValue,15));
Print("DoubleToStrMorePrecision",DoubleToStrMorePrecision(TestValue_NormalizeDouble,15)," ",DoubleToStrMorePrecision(TestValue_NormalizePrice,15));
if (TestValue_NormalizeDouble == TestValue_NormalizePrice)
Print (TestValue_NormalizeDouble," ",TestValue_NormalizePrice, " The value is same");
else
Print (TestValue_NormalizeDouble," ",TestValue_NormalizePrice, " The value is NOT same");
結果
AUDJPY,M1: DoubleToStrMorePrecision 68.703999999999994
AUDJPY,M1: DoubleToStrMorePrecision 68.703999999999994 68.704000000000008
AUDJPY,M1: 68.70399999999999 68.70400000000001 The value is NOT same
※元の価格とNormalizeDoubleした価格は全く同じ
NormalizeDoubleした価格とNormalizePriceした値は違っています。
 

 

 

//比較のため手入力したとします。
double TestValue1 = 68.704;
if (TestValue_NormalizeDouble == TestValue1)
Print (TestValue_NormalizeDouble," ",TestValue1, " NormalizeDouble1 The value is same");
else
Print (TestValue_NormalizeDouble," ",TestValue1, " NormalizeDouble1 The value is NOT same");
if (TestValue_NormalizePrice == TestValue1)
Print (TestValue_NormalizePrice," ",TestValue1, " NormalizePrice1 The value is same");
else
Print (TestValue_NormalizePrice," ",TestValue1, " NormalizePrice1 The value is NOT same");
//手入力した値をNormalizeDoubleとNormalizePriceします。
if (TestValue_NormalizeDouble == NormalizeDouble(TestValue1, Digits))
Print (TestValue_NormalizeDouble," ",TestValue1, " NormalizeDouble2 The value is same");
else
Print (TestValue_NormalizeDouble," ",TestValue1, " NormalizeDouble2 The value is NOT same");
if (TestValue_NormalizePrice == NormalizePrice(TestValue1))
Print (TestValue_NormalizePrice," ",TestValue1, " NormalizePrice2 The value is same");
else
Print (TestValue_NormalizePrice," ",TestValue1, " NormalizePrice2 The value is NOT same");
結果
AUDJPY,M1: 68.70399999999999 68.70399999999999 NormalizeDouble1 The value is same
AUDJPY,M1: 68.70400000000001 68.70399999999999 NormalizePrice1 The value is NOT same
AUDJPY,M1: 68.70399999999999 68.70399999999999 NormalizeDouble2 The value is same
AUDJPY,M1: 68.70400000000001 68.70399999999999 NormalizePrice2 The value is same
#NormalizeDouble1の価格は手入力した価格と同じになりました。
反対にNormalizePriceの価格は同じ価格との判断にはなりませんでした。手入力した価格もNormalizePriceしなければいけないようで表示もそのままでした。

今のところの結論

私が試した限りではNormalizeDoubleは別に問題はなく反対に紹介されている関数を使うとすべての価格に適用する必要があるのではないかと思いました。フォーラム上でNormalizeDoubleを使わないほうがいいよと言われている人は見るとある特定の方でNormalizeDoubleが議論になると登場していました。最初はこの方の言葉を信じていろいろと試してみましたが今のところ問題はただの表示上の問題のように見受けられました。今後もこの件については引き続き調査したいとおもいます。

もしNormalizeDouble関数を使用することで問題があるようであればコメントいただければありがたいです。

この記事が気に入ったら
フォローしよう

最新情報をお届けします

Twitterでフォローしよう

おすすめの記事