MQL4 でダブルスの作業をする

MetaQuotes | 22 4月, 2016

はじめに

MQL プログラミングは自動売買にとって新しいチャンスを開けてくれます。世界中の多くの人がそれをすでに享受しています。

取引用の Expert Advisor を書くとき、それが正しく動作することを確認する必要があります。

初心者の多くが、数学計算の一部の結果が期待と異なるとき、疑問を持つことが多いものです。プログラムはコンパイルされ、動作するが、期待どおりにはいかないのです。彼らはコードを繰り返しチェックし、言語、実装、関数ンなどに新しい『間違い』を見つけます。

ほとんどの場合、注意して分析すると、言語とコンパイラは正常に動作しているのに、コードに小さなエラーがあり、それを見つけ出し修正するのに時間がかかるのです。

本稿では、一般的なプログラムエラーについて考察します。それは MQL4 プログラムでdouble の数字を処理するときに発生するものです。


1. 数値の確認

計算結果を確認し、デバッグするには、標準ライブラリ、stdlib.mq4の関数 DoubleToStrMorePrecision(double の数字、int 精度) を使用します。それにより指定の精度に対する double 数の数値を制御することができるのです。

潜在的エラー検索にかかる時間を節約してくれるのです。

#include <stdlib.mqh>
int start()
  {
   double a=2.0/3;
   Alert("Standard output:",a,", 8 digits precision:",DoubleToStr(a,8),", 15 digits precision:", DoubleToStrMorePrecision(a,15));
   return(0);
  } 

結果

標準アウトプット:0.6667、8 桁の精度:0.66666667、15 桁精度:0.666666666666667

数の数値を表示するケースの一部(たとえばPrintAlert Comment)では、関数 DoubleToStr および DoubleToStrMorePrecision(stdlib.mq4より)を使用し、標準的な4桁のアウトプット精度より正確な値を表示する方が良いです。

#include <stdlib.mqh>
int start()
  {
   double a=2.0/100000;
   Alert("Standard output=",a,", More precise output=",DoubleToStrMorePrecision(a,15));
   return(0);
  }

は以下を返します: 『標準的アウトプット=0、より精度の高いアウトプット=0.000020000000000』。


2. 桁精度の正確さ

ダブル精度の浮動小数点形式のため、そのストレージの精度には制限があります。

たとえば、理論どおりに無制限の精度を持つと仮定すると、任意の double 数字 A と B に対して次の式がつねに有効です。

(A/B) × (B)=A,

A-(A ÷ B) × B=0,

(A ÷ B) × (B ÷ A)=1 など。

コンピュータ内の小数桁ストレージの精度はフラクションサイズできまり、52ビットに制限されています。これを説明するのに以下の例を考察します。

1番目のサイクル(i)では、23(2~23の整数のプロダクト)の階乗を計算し、結果は23!=25852016738884976640000 です。この結果は double タイプの変数 a に格納されます。

次のサイクル(j)では、結果として得られた値 a を 23~2 の整数すべてで割ります。最終的にt a=1 を予想することができます。

#include <stdlib.mqh>
int start()
  {
   int maxfact=23;
   double a=1;
   for (int i=2; i<=maxfact; i++) { a=a*i; }
   for (int j=maxfact; j>=2; j--) { a=a/j; }
   Alert(" a=",DoubleToStrMorePrecision(a,16));
   return(0);
  }

代わりに以下を取得します。

a=1.0000000000000002

見てのとおり、16桁目が誤りです。

計算を 35! まで増やすと、a=0.9999999999999998を取得します。

MQL 言語は関数NormalizeDouble を持ちます。それにより double の数を指定の精度に四捨五入することができます。

double タイプの定数は double 変数同様の方法でメモリに格納されます。よって、定義で有意な15桁の制限を考慮する必要があります。

ただし、小数桁精度を double 数の計算精度と混同しないでください。

double 数に対する可能な値の範囲は、 -1.7*e-308~1.7*e308ともっと広いものです。

double 数の最小指数を推定します。
int start()
  {
  double R=1;
  int minpwr=0;
  while (R>0) {R=R/10; minpwr--;}
  Alert(minpwr);
  return(0);
  }

プログラムは -324 を表示しますが、フラクション(+15)の小数桁を考慮する必要があります。そうして、最小 double 数の指数 に対するおよその値を取得するのです。

3. 関数 NormalizeDouble

関数NormalizeDouble (double 値、整数数字)は浮動小数点の値を処置の精度に四捨五入します。double タイプの正規化された値を返します。

int start()
  {
   double a=3.141592653589;
   Alert("a=",DoubleToStr(NormalizeDouble(a,5),8));
   return(0);
  }

結果は以下です。

a=3.14159000

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


4. 2つの数の等価性確認

stdlib.mq4 ライブラリの関数CompareDoubles(double 数1、double 数2) によって2つの数を比較することが推奨されます。以下がその表記です。

//+------------------------------------------------------------------+
//| correct comparison of 2 doubles                                  |
//+------------------------------------------------------------------+
bool CompareDoubles(double number1,double number2)
  {
   if(NormalizeDouble(number1-number2,8)==0) return(true);
   else return(false);
  }

この関数は double タイプの数1と数2を小数点以下8桁までの精度で比較します。

#include <stdlib.mqh>
int start()
  {double a=0.123456781;
   double b=0.123456782; 
   if (CompareDoubles(a,b)) {Alert("They are equal");}
   else {Alert("They are different");}
  }

出力します。

それらは等しくなっています。

なぜなら違いは9桁目のみだからです。

必要に応じて、同様の方法でご自身の比較関数(お望みの精度で)を書くことができます。

5. 整数の割り算

2つの整数を割り、結果の整数値を取得することを覚えておくことが必要です。

それがコード

int start()
  {
   Alert(70/100);
   return(0);
  }

がゼロを出力する理由です。というのも、70 と 100 は整数だからです。

C/C++ 言語同様、 MQL でも2つの整数を割った結果は整数で、この場合は0です。

ただし、分子か分母が double であれば(すなわち分数部分を持つ)、結果は double です。よって(70 ÷ 100.0)は正しい値 0.7 を出力します。


int start()
  { double a=1/3;
    double b=1.0/3;
   Alert("a=",a,", b=",b);
   return(0);
  }

は『a=0, b=0.3333』を出力します。

6. 整数および double 数に対するTypecasting

以下のコードを考察します。
double xbaseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + (xBid - xbaseBid)/xPoint;
Alert(i);

100を取得しますが、それは101のように見えます。なぜなら明らかな等価性:0.0001/0.0001=1 だからです。

以下は C/C++ での同様の例です。
double baseBid=1.2972,Bid=1.2973,Point=0.0001;
int i = 100 + (Bid - baseBid)/Point;
printf("%d\n",i);

上記もまた結果は100となります。

この事実の理由を判断するため、以下のコードを考えます。

double a=0.99999999999999;
int i = 100 + a;
Alert(i);

結果は i=100 です。

ただし、以下に対してなんらかの精度改善を行うなら、

double a=0.999999999999999;
int i = 100 + a;
Alert(i);

101を取得します。

この理由は、精度が低い値によって、整数と double 数の使用が複雑になったことです。

よって、そのようなタイプの処理では、関数 MathRound(double 値)
によって同様の式を四捨五入することが推奨されます。この関数はもっとも近い整数を四捨五入された double 値を返します。

double baseBid=1.2972;
double xBid=1.2973;
double xPoint=0.0001;
int i = 100 + MathRound((xBid - baseBid)/xPoint);
Alert(i);

その場合、正しい値101を取得します。

関数 OrderModify を使う際にも共通のエラーがあります。特にトレーリングストップのプログラミングについてです。関数OrderModify は、注文に対して新規の値がすでに定義されている値に等しい場合、エラー No.1 :ERR_NO_RESULT を返します。よって、等しさに対しては注意して確認する(NormalizeDoubleによって)必要があり、ポイントサイズ計算にも注意が必要です。

新規の値がすでに定義済みのものと最低1ポイント違うと、クライアントターミナルでは、注文パラメータを変更することができることを覚えておきます。


7. 関数 MathMod の特徴


MQL では、 MathMod(double v1、double v2)関数が MSVC6の関数 fmod(double v1、double v2)完全に対応しています。というのも、C Runtime ライブラリから fmod 関数への直接呼出しが行われるためです。

関数fmod of MSVC6(とMathModもまた) が誤った結果を出す場合もあります。

ご自身のプログラム内で関数 MathMod を使用する場合、それを以下の関数と置き換えてください。
double MathModCorrect(double a, double b)
{ int tmpres=a/b;
return(a-tmpres*b);
}

この備考は MQL4 のみに対するものです。MQL5 はこの関数を数学的定義で計算することに留意します。

おわりに

上記リストはexhaustive,ではありません。ここにない何か新しいことを見つけたら、コメントを見てください。

ソリューションが見つからない場合は、コメントに記述し、コードに追加すれば、MQL コミュニティの専門家がお手伝いいたします。