関数

関数

全てのタスクはサブタスクに分割することができ、それぞれが直接コードの形で表されるか更に小さいサブタスクに分割することが出来ます。このメソッドは段階的詳細化と呼ばれています。関数はサブタスク解決のコードを記述するために使用されます。関数が実行する内容を記述したコードは 関数定義と呼ばれます。

function_header
{
instructions
}

最初のブレースの前にあるのは関数定義のヘッダのみで、ブレースに挟まれているのが関数定義の 本体 です。関数ヘッダーは、戻り値の型、名称(識別子)と仮パラメータの記述を含みます。関数に渡されるパラメータの数は限られており、64 を超えることは出来ません。

関数は、プログラムの他の部分から必要に応じて何回でも呼び出すことが出来ます。実際には、戻り値の型、関数識別子とパラメータ型が関数のプロトタイプを構成します。

関数プロトタイプは、関数宣言ではなく関数定義です。戻り型の明示的な宣言と引数の型のリストによって関数呼び出し時に厳密な型チェックと暗黙の型キャストが可能です。非常に多くの場合、コードの可読性を向上させるために関数の宣言はクラス内で行われています。

関数定義は、確実に宣言と一致する必要があります。宣言された関数は全て定義される必要があります。

例:

double                       // 戻り値の型
linfunc (double a, double b) // 関数名とパラメータリスト
{
// 複合演算子
return (a + b);           // 戻り値
}

return 演算子は自己に含まれる式の値を返すことが出来ます。必要であれば、式の値は関数結果の型に変換されます。返せるものとしては、基本データ型、基本構造体、及びオブジェクトポインタがあります。return 演算子は、配列、クラスオブジェクトや複合構造体の変数を返すことは出来ません。

値を返さない関数は void 型として記述されるべきです。

例:

void errmesg(string s)
{
Print(“error: “+s);
}

関数に渡されたパラメータは、その型の定数によって定義された初期値を持つことが出来ます。

例:

int somefunc(double a,
double d=0.0001,
int n=5,
bool b=true,
string s=“passed string”)
{
Print(“Required parameter a = “,a);
Print(“Pass the following parameters: d = “,d,” n = “,n,” b = “,b,” s = “,s);
return(0);
}

パラメータのいずれかが初期値を持つ場合、それ以降の全てのパラメータは、初期値を持たなければいけません。

下記は不正な宣言の例です。

int somefunc(double a,
double d=0.0001,   // 初期値 0.0001 を宣言
int n,             // 初期値が指定されていない
bool b,             // 初期値が指定されていない
string s=“passed string”)
{
}

関数の呼び出し

以前に記載されていない名称が式の中で表示され左括弧が続く場合、それは文脈上で関数の名称とみなされます。

関数名 (x1, x2,…, xn)

引数(仮パラメータ)は値によって関数に渡されます。つまり x1、•••、XN の各式が算出されその値が関数に渡されます。式の計算順序と値の読み込みの順序は保証されていません。実行中に、システムは関数に渡される引数の数と型をチェックします。このように関数に対処する方法は値渡しと呼ばれています。

関数呼び出しは、関数によって返される値を値とする式です。上記で説明された関数の型は戻り値の型と一致する必要があります。関数はプログラムのグローバルスコープ の任意の部分、つまり関数の外側で宣言または記述されることが出来ます。関数は別の関数の内部で宣言されたり記述されたりすることは出来ません。

例:

int start()
{
double some_array[4]={0.3, 1.4, 2.5, 3.6};
double a=linfunc(some_array, 10.5, 8);
//…
}
double linfunc(double x[], double a, double b)
{
return (a*x[0] + b);
}

デフォルトパラメータを使用した関数の呼び出しでは、渡されるパラメータのリストは制限されることがありますが、最初のデフォルトパラメータの前では無制限です。

例:

void somefunc(double init,
double sec=0.0001, //初期値を設定する
int level=10);
//…
somefunc();                     // 不正な呼び方( 1 番目のパラメータの存在が必要)
somefunc(3.14);                 // 正しい呼び方
somefunc(3.14,0.0002);           // 正しい呼び方
somefunc(3.14,0.0002,10);       // 正しい呼び方

関数を呼び出す時は、初期値を持つパラメータも抜かせません。

somefunc(3.14, , 10);           // 不正な呼び方( 2 番目のパラメータが抜かされた)

パラメータの引き渡し

機械語がサブプログラム(関数)に引数を渡す方法は 2 つあります。1 番目の方法はパラメータの値渡しです。この方法では、引数値が仮関数引数に複製されます。従って、関数内でのこのパラメータの変更は、対応する呼び出しの引数に影響を及ぼしません。

//+——————————————————————+
//| パラメータの値渡し                                                     |
//+——————————————————————+
double FirstMethod(int i,int j)
{
double res;
//—
i*=2;
j/=2;
res=i+j;
//—
return(res);
}
//+——————————————————————+
//| スクリプトプログラムを開始する関数                                          |
//+——————————————————————+
void OnStart()
{
//—
int a=14,b=8;
Print(“a and b before call:”,a,” “,b);
double d=FirstMethod(a,b);
Print(“a and b after call:”,a,” “,b);
}
//— スクリプト実行の結果
//  a and b before call: 14 8
//  a and b after call: 14 8

2 番目の方法は、参照渡しです。この場合、(値でなく)パラメータへの参照が関数パラメータに渡されます。それが、関数の内部で、呼び出しで指定された実際のパラメータを参照するために使用されます。従って、パラメータの変更は関数の呼び出しに使用される引数に影響を与えます。

//+——————————————————————+
//| パラメータの参照渡し                                                   |
//+——————————————————————+
double SecondMethod(int &i,int &j)
{
double res;
//—
i*=2;
j/=2;
res=i+j;
//—
return(res);
}
//+——————————————————————+
//| スクリプトプログラムを開始する関数                                          |
//+——————————————————————+
void OnStart()
{
//—
int a=14,b=8;
Print(“a and b before call:”,a,” “,b);
double d=SecondMethod(a,b);
Print(“a and b after call:”,a,” “,b);
}
//+——————————————————————+
//— スクリプト実行の結果
//  a and b before call: 14 8
//  a and b after call: 28 4

MQL5 言語では、配列、構造型の変数やクラスオブジェクトは常に参照によって渡されますが、その例外を除いて両方法が使用されます。実際のパラメータ(関数呼び出しで渡される引数)の変化を避けるには const アクセス指定子を使用します。const 指定子で宣言された変数の内容を変更しようとするとコンパイラがエラーを生成します。

注意事項

パラメータは逆の順序で関数に渡されること、すなわち、最後のパラメータが最初に計算されて、最後から 2 つ目のパラメータが次に計算されて引き渡されることには留意すべきです。最後に計算されて渡されたパラメータは、括弧を開いた後の最初のパラメータです。

例:

void OnStart()
{
//—
int a[]={0,1,2};
int i=0;

func(a[i],a[i++],“First call (i = “+string(i)+“)”);
func(a[i++],a[i],“Second call (i = “+string(i)+“)”);
// 結果:
// First call (i = 0) : par1 = 1     par2 = 0
// Second call (i = 1) : par1 = 1     par2 = 1

}
//+——————————————————————+
//|                                                                  |
//+——————————————————————+
void func(int par1,int par2,string comment)
{
Print(comment,“: par1 = “,par1,”    par2 = “,par2);
}

最初の呼び出し(上記の例を参照)では、i 変数は最初に文字列の連結に使用されます。

 “First call (i = “+string(i)+“)”

ここではその値は変更されません。その後 i 変数は a[i++] 配列要素の計算に使用されます。インデックス付き配列要素iがアクセスされた時、i 変数は 増加されます。そしてこの後初めて最初のパラメータが変更された i の値をもって計算されます。

2 番目の呼び出しでは、同じ i の(関数呼び出しの最初の段階で計算された)値が 3 つのパラメータの全てを算出する際に使用されます。最初のパラメータが算出された後で初めて i 変数がもう一度変更されます。

関数の多重定義(オーバーロード)

通常、関数名にはその主な目的を反映する傾向があります。原則として、人間によって読まれるのが可能なプログラムには、うまく選択された 識別子が含まれます。しかし、たまに 1 つの目的のために複数の関数が使用されることがあります。例として、double の配列の平均値を算出する関数と整数の配列の平均値を算出する関数について考えましょう。両方とも AverageFromArray と呼ぶのが便利です。

//+——————————————————————+
//| double 型配列の平均値の計算                                          |
//+——————————————————————+
double AverageFromArray(const double & array[],int size)
{
if(size<=0) return 0.0;
double sum=0.0;
double aver;
//—
for(int i=0;i<size;i++)
{
sum+=array[i];   // double の合計
}
aver=sum/size;       // 合計を要素数で割る
//—
Print(“Calculation of the average for an array of double type”);
return aver;
}
//+——————————————————————+
//| int 型配列の平均値の計算                                             |
//+——————————————————————+
double AverageFromArray(const int & array[],int size)
{
if(size<=0) return 0.0;
double aver=0.0;
int sum=0;
//—
for(int i=0;i<size;i++)
{
sum+=array[i];     // int の合計
}
aver=(double)sum/size;// double 型にキャストしてから割る
//—
Print(“Calculation of the average for an array of int type”);
return aver;
}

どちらの関数も Print() 関数を使用してメッセージを出力します。

  Print(“Calculation of the average for an array of int type”);

コンパイラは、引数の型と数に応じて必要な関数を選択します。このような選択ルールは署名マッチングアルゴリズムと呼ばれます。署名とは、関数宣言で使用される型のリストです。

例:

//+——————————————————————+
//| スクリプトプログラムを開始する関数                                          |
//+——————————————————————+
void OnStart()
{
//—
int    a[5]={1,2,3,4,5};
double b[5]={1.1,2.2,3.3,4.4,5.5};
double int_aver=AverageFromArray(a,5);
double double_aver=AverageFromArray(b,5);
Print(“int_aver = “,int_aver,”   double_aver = “,double_aver);
}
//— スクリプトの結果
// Calculate the average for an array of int type
// Calculate the average for an array of double type
// int_aver= 3.00000000    double_aver= 3.30000000

関数オーバーロードとは、パラメータが異なるいくつかの同名の関数を作成する過程です。つまり、関数のオーバーロードバリアントでは引数の数及び/またはその型は異なっていなければなりません。特定の関数バリアントは、関数宣言のパラメータのリストと関数を呼び出す引数のリストの対応関係に基づいて選択されます。

オーバーロードされた関数が呼び出されると、コンパイラに適切な関数を選択するためのアルゴリズムが存在する必要があります。この選択アルゴリズムは存在する型のキャストに基づきます。最良のマッチは一意である必要があります。オーバーロードされた関数は、少なくとも 1 つの引数にとって全てのバリアントの内で最良のマッチである必要があります。同時に、他の全ての引数に対しての一致も他のバリアントに劣らない必要があります。

以下は各引数のマッチングアルゴリズムです。

オーバーロード関数選択アルゴリズム

  1. (可能な場合)厳密なマッチングを使用します。
  2. 標準型の増加を試します。
  3. 標準型キャストを試します。

標準型の増加は、他の標準的な変換よりも優れています。増加とは floatdoubleboolcharshort 及び enumintに変換することです。類似の 整数型 配列の型キャストも型キャストに属します。類似の型は次の通りです。シングルバイト整数である bool、char、及び uchar の 3 型。ダブルバイト整数である short と ushort。 4 バイト整数の int、uint 及び color。long、ulong、及び datetime。

厳密なマッチングが一番です。このような一貫性を達成するために型キャスト を使用することが出来ます。コンパイラは、あいまいな状況に対処することは出来ません。従って、オーバーロード関数をあいまいにする型の微妙な違いや暗黙の変換に依存してはいけません。

はっきりしない場合には厳格な遵守を確保するために明示的な変換を使用してください。

MQL5 におけるオーバーロード関数の例は ArrayInitialize() 関数の例でご覧になれます。

関数オーバーロードの規則はクラスメソッドのオーバーロードにも適応されます。

 

システム関数のオーバーロードは可能ですが、コンパイラが正確に必要な関数を選択することが出来なければいけません。例えば、システム関数 MathMax() のオーバーロードの仕方は 4 つありますが、そのうち2 つのみが正しいものです。

例:

// 1. オーバーロードは可能 – 関数は内蔵された MathMax() と違う数のパラメータを持つ
double MathMax(double a,double b,double c);

// 2. オーバーロードは不可能
// パラメータ数が異なるが、最終パラメータに初期値がある
// これは呼び出す際にシステム関数の隠蔽につながり、容認されない
double MathMax(double a,double b,double c=DBL_MIN);

// 3. オーバーロードは可能 – パラメータ a と b の型による通常のオーバーロード
double MathMax(int a,int b);

// 4. オーバーロードは不可能
// パラメータの数と種類が元の double MathMax(double a,double b) と同じ
int MathMax(double a,double b);

演算子の多重定義(オーバーロード)

コードの読み込みと書き込みを容易にするためには、いくつかの演算のオーバーロードが可能です。演算子のオーバーロードは、キーワード operator を使用して書かれます。可能な演算子のオーバーロードは次の通りです。

  • 二項 +、-、/、*、%、<<、>>、==、!=、<、>、<=、>=、=、+=、-=、/=、*=、%=、&=、|=、^=、<<=、>>=、&&、||、&、|、^
  • 単項 +、-、++、–、!、~
  • 代入演算子 =
  • インデックス作成演算子 []

 

操作のオーバーロードは構造体とクラスのように複雑なオブジェクトの(単純な式の形で書かれた)演算の表記を可能にします。より複雑な実装が隠されているので、オーバーロードを使用して演算の式を書くことでソースコードの表示が簡素化します。

例えば、実部と虚部で構成された複素数を見てみましょう。これらは数学で幅広く使用されています。MQL5 言語には複素数を表現するデータ型がありませんが構造体やクラスを使用して新しいデータ型を作成することが可能です。複雑な構造体を宣言し、四則演算を実装する 4 つのメソッドを定義してみます。

//+——————————————————————+
//| 複素数演算のための構造体                                              |
//+——————————————————————+
struct complex
{
double            re; // 実部
double            im; // 虚部
//— コンストラクタ 
complex():re(0.0),im(0.0) {  }
complex(const double r):re(r),im(0.0) {  }
complex(const double r,const double i):re(r),im(i) {  }
complex(const complex &o):re(o.re),im(o.im) { }
//— 算術演算
complex           Add(const complex &l,const complex &r) const; // 加算
complex           Sub(const complex &l,const complex &r) const; // 減算
complex           Mul(const complex &l,const complex &r) const; // 乗算
complex           Div(const complex &l,const complex &r) const; // 除算
};

さて、コードで複素数を表す変数を宣言して使用することが出来ます。

例えば、

void OnStart()
{
//— complex 型の変数を宣言して初期化する
complex a(2,4),b(-4,-2);
PrintFormat(“a=%.2f+i*%.2f,   b=%.2f+i*%.2f”,a.re,a.im,b.re,b.im);
//— 2 つの数を足す
complex z;
z=a.Add(a,b);
PrintFormat(“a+b=%.2f+i*%.2f”,z.re,z.im);
//— 2 つの数を掛ける
z=a.Mul(a,b);
PrintFormat(“a*b=%.2f+i*%.2f”,z.re,z.im);
//— 2 つの数を割る
z=a.Div(a,b);
PrintFormat(“a/b=%.2f+i*%.2f”,z.re,z.im);
//—
}

しかし複素数の算術演算に「/」、「+」、「-」、「*」及び「/」の 通常の演算子を使用するのが便利です。

Keyword 演算子は型変換を実行するメンバ関数の定義に使用されます。クラスオブジェクトの変数の単項及び二項操作は非静的メンバ関数としてオーバーロードすることが出来ます。これらは暗黙的にクラスオブジェクトに使用されます。

ほとんどの二項演算は、クラス変数、またはこのクラスオブジェクトへのポインタなどの 1 つまたは両方の引数を受ける通常の関数のようにオーバーロードすることが出来ます。例の複素数型は、宣言でオーバーロードすると、次のようになります。

  //— 演算子
complex operator+(const complex &r) const { return(Add(this,r)); }
complex operator-(const complex &r) const { return(Sub(this,r)); }
complex operator*(const complex &r) const { return(Mul(this,r)); }
complex operator/(const complex &r) const { return(Div(this,r)); }

スクリプトの完全な例は下記です。

//+——————————————————————+
//| スクリプトプログラムを開始する関数                                          |
//+——————————————————————+
void OnStart()
{
//— complex 型の変数を宣言して初期化する
complex a(2,4),b(-4,-2);
PrintFormat(“a=%.2f+i*%.2f,   b=%.2f+i*%.2f”,a.re,a.im,b.re,b.im);
//a.re=5;
//a.im=1;
//b.re=-1;
//b.im=-5;
//— 2 つの数を足す
complex z=a+b;
PrintFormat(“a+b=%.2f+i*%.2f”,z.re,z.im);
//— 2 つの数を乗算する
 
z=a*b;
PrintFormat(“a*b=%.2f+i*%.2f”,z.re,z.im);
//— 2 つの数を割る
z=a/b;
PrintFormat(“a/b=%.2f+i*%.2f”,z.re,z.im);
//—
}
//+——————————————————————+
//| 複素数演算のための構造体                                              |
//+——————————————————————+
struct complex
{
double            re; // 実部
double            im; // 虚部
//— コンストラクタ 
complex():re(0.0),im(0.0) {  }
complex(const double r):re(r),im(0.0) {  }
complex(const double r,const double i):re(r),im(i) {  }
complex(const complex &o):re(o.re),im(o.im) { }
//— 算術演算
complex           Add(const complex &l,const complex &r) const; // 加算
complex           Sub(const complex &l,const complex &r) const; // 減算
complex           Mul(const complex &l,const complex &r) const; // 乗算
complex           Div(const complex &l,const complex &r) const; // 除算
//— 二項演算子
complex operator+(const complex &r) const { return(Add(this,r)); }
complex operator-(const complex &r) const { return(Sub(this,r)); }
complex operator*(const complex &r) const { return(Mul(this,r)); }
complex operator/(const complex &r) const { return(Div(this,r)); }
};
//+——————————————————————+
//| 加算                                                              |
//+——————————————————————+
complex complex::Add(const complex &l,const complex &r) const
{
complex res;
//—
res.re=l.re+r.re;
res.im=l.im+r.im;
//— 結果
return res;
}
//+——————————————————————+
//| 減算                                                              |
//+——————————————————————+
complex complex::Sub(const complex &l,const complex &r) const
{
complex res;
//—
res.re=l.re-r.re;
res.im=l.im-r.im;
//— 結果
return res;
}
//+——————————————————————+
//| 乗算                                                              |
//+——————————————————————+
complex complex::Mul(const complex &l,const complex &r) const
{
complex res;
//—
res.re=l.re*r.re-l.im*r.im;
res.im=l.re*r.im+l.im*r.re;
//— 結果
return res;
}
//+——————————————————————+
//| 除算                                                              |
//+——————————————————————+
complex complex::Div(const complex &l,const complex &r) const
{
//— 空の複素数
complex res(EMPTY_VALUE,EMPTY_VALUE);
//— 値がゼロかチェックする
if(r.re==0 && r.im==0)
{
Print(__FUNCTION__+“: number is zero”);
return(res);
}
//— 補助変数
double e;
double f;
//— 計算バージョンを選択する
if(MathAbs(r.im)<MathAbs(r.re))
{
e = r.im/r.re;
f = r.re+r.im*e;
res.re=(l.re+l.im*e)/f;
res.im=(l.im-l.re*e)/f;
}
else
{
e = r.re/r.im;
f = r.im+r.re*e;
res.re=(l.im+l.re*e)/f;
res.im=(-l.re+l.im*e)/f;
}
//— 結果
return res;
}

 

ほとんどの単項演算は、クラス変数、またはこのクラスオブジェクトへのポインタなどの引数を受ける通常の関数のようにオーバーロードすることが出来ます。ここでは単項演算子「 – 」と「 ! 」のオーバーロードを追加します。

//+——————————————————————+
//| 複素数演算のための構造体                                              |
//+——————————————————————+
struct complex
{
double            re;       // 実部
double            im;       // 虚部

//— 単項演算子
complex operator-() const; // 単項マイナス
bool   operator!() const; // 否定
};

//+——————————————————————+
//| 単項マイナス演算子のオーバーロード                                        |
//+——————————————————————+
complex complex::operator-() const
{
complex res;
//—
res.re=-re;
res.im=-im;
//— 結果
return res;
}
//+——————————————————————+
//| 「論理否定」演算子のオーバーロード                                        |
//+——————————————————————+
bool complex::operator!() const
{
//— 複素数の実部と虚部がゼロ?
return (re!=0 && im!=0);
}

 

複素数の値がゼロかをチェックし、負の値を取得します。

//+——————————————————————+
//| スクリプトプログラムを開始する関数                                          |
//+——————————————————————+
void OnStart()
{
//— complex 型の変数を宣言して初期化する
complex a(2,4),b(-4,-2);
PrintFormat(“a=%.2f+i*%.2f,   b=%.2f+i*%.2f”,a.re,a.im,b.re,b.im);
//— 2 つの数を割る
complex z=a/b;
PrintFormat(“a/b=%.2f+i*%.2f”,z.re,z.im);
//— 複素数はデフォルトではゼロ(デフォルトコンストラクタで re==0 及び im==0)
complex zero;
Print(“!zero=”,!zero);
//— 負の値を割り当てる
zero=-z;
PrintFormat(“z=%.2f+i*%.2f,  zero=%.2f+i*%.2f”,z.re,z.im, zero.re,zero.im);
PrintFormat(“-zero=%.2f+i*%.2f”,-zero.re,-zero.im);
//— もう1 回値がゼロかチェックする  
Print(“!zero=”,!zero);
//—
}

基本データ型の構造体 は互いに直接にコピー出来るため、代入演算子「 = 」のオーバーロードは必要がなかったことにご注目ください。このように通常の方法で複素数を含む計算のコードを書くことが出来ます。

索引付け演算子のオーバーロードは、単純かつなじみ深い方法で、オブジェクトで囲まれた配列の値を取得することができ、ソースコードの読みやすさに貢献します。例えば、文字列内の指定された位置にあるシンボルへのアクセスを提供する必要があるとします。MQL5 言語の文字列はシンボルの配列ではない別の string 型ですが、インデックス演算のオーバーロードの助けを借りて、生成された CString クラスで単純かつ分かりやすい利用法を提供することが出来ます。

//+——————————————————————+
//| シンボルの配列のように文字列内のシンボルにアクセスするクラス                       |
//+——————————————————————+
class CString
{
string            m_string;

public:
CString(string str=NULL):m_string(str) { }
ushort operator[] (int x) { return(StringGetCharacter(m_string,x)); }
};
//+——————————————————————+
//| スクリプトプログラムを開始する関数                                          |
//+——————————————————————+
void OnStart()
{
//— 文字列からシンボルを受け取るための配列
int     x[]={ 19,4,18,19,27,14,15,4,17,0,19,14,17,27,26,28,27,5,14,
17,27,2,11,0,18,18,27,29,30,19,17,8,13,6 };
CString str(“abcdefghijklmnopqrstuvwxyz[ ]CS”);
string  res;
//— str 変数のシンボルを使用してフレーズを作る
for(int i=0,n=ArraySize(x);i<n;i++)
{
res+=ShortToString(str[x[i]]);
}
//— 結果を表示する
Print(res);
}

 

索引操作のオーバーロードの別例は、行列演算です。行列は配列のサイズが予め定義されていない 2 次元の動的配列を表します。従って、2 次元目のサイズを指定せずに array[][] の形式で配列を宣言することも、パラメータとしてこの配列を渡すことも出来ません。可能な解決策は、CRow クラスオブジェクトの配列が含まれている特別なクラス CMatrix です。

//+——————————————————————+
//| スクリプトプログラムを開始する関数                                          |
//+——————————————————————+
void OnStart()
{
//— 加算と乗算の行列演算
CMatrix A(3),B(3),C();
//— 配列の行を準備する
double a1[3]={1,2,3}, a2[3]={2,3,1}, a3[3]={3,1,2};
double b1[3]={3,2,1}, b2[3]={1,3,2}, b3[3]={2,1,3};
//— 行列に書き込む
A[0]=a1; A[1]=a2; A[2]=a3;
B[0]=b1; B[1]=b2; B[2]=b3;
//— エキスパート操作ログに行列を出力する
Print(“—- Elements of matrix A”);
Print(A.String());
Print(“—- Elements of matrix B”);
Print(B.String());

//— 行列の加算
Print(“—- Addition of matrices A and B”);
C=A+B;
//— フォーマットされた文字列表現を出力する
Print(C.String());

//— 行列の乗算
Print(“—- Multiplication of matrices A and B”);
C=A*B;
Print(C.String());

//— 次に動的配列 matrix[i][j] のスタイルで値を取得する方法を示す
Print(“Output the values of matrix C elementwise”);
//— ループを使用して行列の行(CRow オブジェクト)を順番にみる
for(int i=0;i<3;i++)
{
string com=“| “;
//— 行列の行の値を形成する
for(int j=0;j<3;j++)
{
//— 行と列の数を使用して行列要素を取得する
double element=C[i][j];// [i] – m_rows[] 配列で CRow にアクセスする
// [j] – CRowでのインデックス作成演算オーバーロード
com=com+StringFormat(“a(%d,%d)=%G ; “,i,j,element);
}
com+=“|”;
//— 行の値を出力する
Print(com);
}
}
//+——————————————————————+
//| Row クラス                                                          |
//+——————————————————————+
class CRow
{
private:
double            m_array[];
public:
//— コンストラクタとデストラクタ
CRow(void)          { ArrayResize(m_array,0);    }
CRow(const CRow &r) { this=r;                    }
CRow(const double &array[]);
~CRow(void){};
//— 行の要素数
int               Size(void) const    { return(ArraySize(m_array));}
//— 値を持つ文字列を返す  
string           String(void) const;
//— インデックス作成演算子
double           operator[](int i) const  { return(m_array[i]);   }
//— 代入演算子
void             operator=(const double  &array[]); // 配列
void             operator=(const CRow & r);         // あと 1 つの CRow オブジェクト
double           operator*(const CRow &o);         //乗算に使用される CRow オブジェクト
};
//+——————————————————————+
//| 行を配列と初期化するためのコンストラクタ                                     |
//+——————————————————————+
void  CRow::CRow(const double &array[])
{
int size=ArraySize(array);
//— 配列が空でない場合
if(size>0)
{
ArrayResize(m_array,size);
//— 値を書き込む
for(int i=0;i<size;i++)
m_array[i]=array[i];
}
//—
}
//+——————————————————————+
//| 配列の代入演算子                                                    |
//+——————————————————————+
void CRow::operator=(const double &array[])
{
int size=ArraySize(array);
if(size==0) return;
//— 配列に値を書き込む
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=array[i];
//—
}
//+——————————————————————+
//| CRow の 代入演算子                                                 |
//+——————————————————————+
void CRow::operator=(const CRow  &r)
{
int size=r.Size();
if(size==0) return;
//— 配列に値を書き込む
ArrayResize(m_array,size);
for(int i=0;i<size;i++) m_array[i]=r[i];
//—
}
//+——————————————————————+
//| 別の行の乗算演算子                                                  |
//+——————————————————————+
double CRow::operator*(const CRow &o)
{
double res=0;
//— 検証
int size=Size();
if(size!=o.Size() || size==0)
{
Print(__FUNCSIG__,“: Failed to multiply two matrices, their sizes are different”);
return(res);
}
//— 配列の要素ごとに乗算し、結果を加算する
for(int i=0;i<size;i++)
res+=m_array[i]*o[i];
//— 結果
return(res);
}
//+——————————————————————+
//| フォーマットされた文字列表現を返す                                         |
//+——————————————————————+
string CRow::String(void) const
{
string out=“”;
//— 配列のサイズがゼロでない場合
int size=ArraySize(m_array);
//— 要素の数がゼロ以外の配列を取り扱う
if(size>0)
{
out=“{“;
for(int i=0;i<size;i++)
{
//— 値を文字列に収集する
out+=StringFormat(” %G;”,m_array[i]);
}
out+=” }”;
}
//— 結果
return(out);
}
//+——————————————————————+
//| Matrix クラス                                                      |
//+——————————————————————+
class CMatrix
{
private:
CRow              m_rows[];

public:
//— コンストラクタとデストラクタ
CMatrix(void);
CMatrix(int rows)  { ArrayResize(m_rows,rows);             }
~CMatrix(void){};
//— 行列のサイズを取得する
int               Rows()       const { return(ArraySize(m_rows));            }
int               Cols()       const { return(Rows()>0? m_rows[0].Size():0); }
//— CRow の行の形で列の値を返す
CRow              GetColumnAsRow(const int col_index) const;
//— マトリックス値を持つ文字列を返す
string           String(void) const;
//— 索引付け演算子は文字列をその数で返す
CRow *operator[](int i) const        { return(GetPointer(m_rows[i]));        }
//— 加算演算子
CMatrix           operator+(const CMatrix &m);
//— 乗算演算子
CMatrix           operator*(const CMatrix &m);
//— 代入演算子
CMatrix          *operator=(const CMatrix &m);
};
//+——————————————————————+
//| サイズがゼロの行の配列を作成するフォルトコンストラクタ                            |
//+——————————————————————+
CMatrix::CMatrix(void)
{
//— 行列の行がゼロ
ArrayResize(m_rows,0);
//—  
}
//+——————————————————————+
//| CRow の行の形で列の値を返す                                           |
//+——————————————————————+
CRow  CMatrix::GetColumnAsRow(const int col_index) const
{
//— 列から値を取得する変数
CRow row();
//— 行列の行数
int rows=Rows();
//— 行の数がゼロより大きい場合、演算を実行する
if(rows>0)
{
//— インデックス col_index で列の値を受け取る配列
double array[];
ArrayResize(array,rows);
//—配列に書き込む
for(int i=0;i<rows;i++)
{
//— 配列の境界を超えるかもしれないので 行 i の列の数を確認する
if(col_index>=this[i].Size())
{
Print(__FUNCSIG__,“: Error! Column number “,col_index,“> row size “,i);
break; // 行は初期化されていないオブジェクトになる
}
array[i]=this[i][col_index];
}
//— 配列の値に基づいて CRow の行を作成する
row=array;
}
//— 結果
return(row);
}
//+——————————————————————+
//| 2 つの行列の加算                                                    |
//+——————————————————————+
CMatrix CMatrix::operator+(const CMatrix &m)
{
//— 渡された行列の行と列の数
int cols=m.Cols();
int rows=m.Rows();
//— 加算結果を受け取る行列
CMatrix res(rows);
//— 行列のサイズが一致していなければいけない
if(cols!=Cols() || rows!=Rows())
{
//— 加算が不可能
Print(__FUNCSIG__,“: Failed to add two matrices, their sizes are different”);
return(res);
}
//— 補助配列
double arr[];
ArrayResize(arr,cols);
//— 行を 1 つづつ加算する
for(int i=0;i<rows;i++)
{
//— 行列の文字列の加算結果を配列に書く
for(int k=0;k<cols;k++)
{
arr[k]=this[i][k]+m[i][k];
}
//— 配列を行列の行に配置する
res[i]=arr;
}
//— 行列の加算の結果を返す
return(res);
}
//+——————————————————————+
//| 2 つの行列の乗算                                                    |
//+——————————————————————+
CMatrix CMatrix::operator*(const CMatrix &m)
{
//— 1 番目の行列の列数、行列に渡された行数
int cols1=Cols();
int rows2=m.Rows();
int rows1=Rows();
int cols2=m.Cols();
//— 加算結果を受け取る行列
CMatrix res(rows1);
//— 行列は調整を必要とする
if(cols1!=rows2)
{
//— 乗算が不可能
Print(__FUNCSIG__,“: Failed to multiply two matrices, format is not compatible “
“- number of columns in the first factor should be equal to the number of rows in the second”);
return(res);
}
//— 補助配列
double arr[];
ArrayResize(arr,cols1);
//— 乗算行列の行に書き込む
for(int i=0;i<rows1;i++)// 行ごとに処理する
{
//— 受け取り側の配列をリセットする
ArrayInitialize(arr,0);
//— 行の要素を 1 つずつみる
for(int k=0;k<cols1;k++)
{
//— CRow の形で行列 m の列 k の値をとる
CRow column=m.GetColumnAsRow(k);
//— 2 つの行を乗算し、i 番目の要素内のベクトルのスカラー乗算の結果を書き込む
arr[k]=this[i]*column;
}
//— 配列 arr[] を行列の i 番目の行に配置する
res[i]=arr;
}
//— 行列の乗算の結果を返す
return(res);
}
//+——————————————————————+
//| 代入演算                                                          |
//+——————————————————————+
CMatrix *CMatrix::operator=(const CMatrix &m)
{
//— 行数を検索して設定する
int rows=m.Rows();
ArrayResize(m_rows,rows);
//— 渡された行列の行の値で行を埋める
for(int i=0;i<rows;i++) this[i]=m[i];
//—
return(GetPointer(this));
}
//+——————————————————————+
//| 行列の文字列表現                                                    |
//+——————————————————————+
string CMatrix::String(void) const
{
string out=“”;
int rows=Rows();
//—文字列で文字列を作る
for(int i=0;i<rows;i++)
{
out=out+this[i].String()+“\r\n”;
}
//— 結果
return(out);
}

外部関数の記述

別のモジュールで定義された外部関数は明示的に記述されるべきです。記述は、戻り型、関数名及び入力パラメータとその型を含みます。そのような記述が存在しないとコンパイル、ビルド、またはプログラムの実行中にエラーが起こることがあります。外部オブジェクトを記述する時は#import キーワードを使用してモジュールを示します。

例:

#import “user32.dll”
int     MessageBoxW(int hWnd ,string szText,string szCaption,int nType);
int     SendMessageW(int hWnd,int Msg,int wParam,int lParam);
#import “lib.ex5”
double  round(double value);
#import

import の助けを借りれば、外部 DLL またはコンパイル済みの EX5 ライブラリから呼び出される関数の記述が容易に出来ます。EX5ライブラリは library プロパティを持つ、コンパイル済みの ex5 ファイルです。export 修飾子 のついた関数のみが EX5 ライブラリからインポート出来ます。

DLL と EX5 ライブラリが一緒にインポートされる場合は(配置されているディレクトリに関係なく)別の名称を持つ必要があるのでご注意ください。インポートされた関数は全てライブラリの「ファイル名」に対応するスコープの解像度を持っています。

例:

#import “kernel32.dll”
int GetLastError();
#import “lib.ex5”
int GetLastError();
#import

class CFoo
{
public:
int           GetLastError() { return(12345); }
void           func()
{
Print(GetLastError());           // クラスメソッドの呼び出し
Print(::GetLastError());         // MQL5 関数の呼び出し
Print(kernel32::GetLastError()); // kernel32.dll からの DLL ライブラリの呼び出し
Print(lib::GetLastError());     // lib.ex5 からの EX5 ライブラリ関数の呼び出し
}
};

void OnStart()
{
CFoo foo;
foo.func();
}

関数のエクスポート

MQL5 プログラムで export 後置修飾語とともに宣言された関数は別の MQL5 プログラムで使用することが出来ます。このような関数は、エクスポータブル(エクスポート可能)と呼ばれ、コンパイル後に、他のプログラムから呼び出すことが出来ます。

int Function() export
{
}

この修飾子は、この EX5 ファイルによってエクスポートされる EX5 関数の表にこの関数を追加するようにコンパイラに指示します。この修飾子を持った関数のみが他の MQL5 プログラムから使用可能また可視です。

library プロパティは、この EX5 ファイルがライブラリになることをコンパイラに伝え、コンパイラがそれを EX5 のヘッダに表示します。

エクスポート可能として計画されている全ての関数は、export 修飾子でマークされる必要があります。

イベント処理の関数

MQL5 言語はいくつかの定義済みイベントの処理を提供します。これらのイベントを処理するための関数は MQL5 プログラムで定義されている必要があります。関数名、戻り値の型、パラメータの組成物(もしあれば)と型は、イベントハンドラ関数の記述に厳格に適合しなければなりません。

クライアント端末のイベントハンドラは、戻り値とパラメータの型によって、各イベントハンドラ関数を識別します。以下の説明に対応していないパラメータが指定された場合、また別の戻り値の型が使用された場合、その関数はイベントハンドラとして使用されません。

OnStart #

OnStart() 関数は Start イベントハンドラでスクリプトの実行のみに自動的に生成されます。この関数はvoid 型でパラメータを持ってはいけません。

void OnStart();

OnStart() 関数では int 型の戻り値を指定することが出来ます。

OnInit #

OnInit() 関数は Init イベントハンドラです。この関数は void または int 型でパラメータを持ちません。

void OnInit();

Initイベントは、エキスパートアドバイザーや指標がダウンロードされた直後に生成されます。このイベントは、スクリプトには生成されません。OnInit() 関数は初期化に使用されます。OnInit() がint 型の戻り値を持つ場合、ゼロ以外のリターンコードは失敗した初期化を意味し初期化解除の理由である REASON_INITFAILED のコードを含む Deinit イベントが生成されます。

エキスパートアドバイザーの入力パラメータを最適化するためには、リターンコードとして ENUM_INIT_RETCODE 列挙の値を使用することをお勧めします。これらの値は、最適なテストエージェントの選択を含んだ、最適化の過程を整理するために使用されています。テストの開始前に起こるエキスパートアドバイザーの初期化中には TerminalInfoInteger() 関数を使用してエージェントの構成とリソース(コア数、空きメモリの量など)に関する情報を要求することが出来ます。取得された情報に基づいて、エキスパートアドバイザーの最適化の際にこのテストエージェントを使用するかどうかを決めることが出来ます。

ENUM_INIT_RETCODE

識別子

説明

INIT_SUCCEEDED

初期化が成功し、エキスパートアドバイザーのテストを続けることが出来ます。

このコードは NULL 値と同じ意味を持ちます。エキスパートアドバイザーはテスタ内で正常に初期化されています。

INIT_FAILED

初期化に失敗しました。致命的なエラーが起こったのでテストを続ける理由がありません。例えば、エキスパートアドバイザーが機能するのに必要とされる指標の作成に失敗しました。

この戻り値はゼロ以外の値と同じ意味です。 テスタ内でのエキスパートアドバイザーの初期化に失敗しました。

INIT_PARAMETERS_INCORRECT

この値は、入力パラメータの不正確さを意味します。このリターンコードを含む結果文字列は、「一般的な最適化」の表に赤色で強調表示されます。

所定のパラメータセットのエキスパートアドバイザーテストは実行されません。エージェントが新しいタスクを受け取ることが出来ます。

この値を受け取ると、ストラテジーテスターはこのタスクを他のエージェントに再試行のために渡さないことがあります。

INIT_AGENT_NOT_SUITABLE

初期化中にエラーはありませんでしたが、エージェントが何らかの理由でテストに適していません。例えば、メモリ不足、OpenCL サポートの不足などです。

このコードが返された後は、エージェントは この最適化が終了するまでタスクを受信しません。

void型の OnInit() 関数は常時初期化の成功を示します。

OnDeinit #

OnDeinit() 関数は初期化解除中に呼び出され Deinit イベントハンドラでもあります。この関数は void して宣言する必要があり const int 型のパラメータを 1 つ持つ必要があります。このパラメータは 初期化解除の理由のコードです。 違う型が宣言された場合、コンパイラは警告を生成しますが、関数は呼び出されません。スクリプトでは Deinit イベントが生成されないので、OnDeinit() 関数はスクリプトでは使用出来ません。

void OnDeinit(const int reason);

以下の場合 Deinit イベントがエキスパートアドバイザーと指標に生成されます。

  • シンボルまたはチャートの期間の変更に伴う再初期化の前にMQL5 プログラムが接続されている場合
  • 入力パラメータの変更に伴う再初期化の前
  • プログラムをアンロードする前

OnTick #

NewTick イベントは、エキスパートアドバイザーが接続されたチャートにシンボルの新しいティックが受け取られた時にエキスパートアドバイザのみ に作成されます。カスタム指標またはスクリプトでは NewTick イベントが生成されないので OnTick() 関数を定義するのは無意味です。

Tick イベントは、エキスパートアドバイザーのために生成されますが、エキスパートアドバイザーには NewTick イベントの他に Timer、BookEvent とChartEvent も生成されるので、エキスパートアドバイザーが 必ずしも OnTick() 関数を必要とするわけではありません。この関数は void 型でパラメータを持ちません。

void OnTick();

OnTimer #

OnTimer() 関数は Timer イベントがエキスパートアドバイザーと指標のシステムタイマーによって生成される時に呼ばれます。スクリプトでは利用出来ません。イベントの発生頻度は EventSetTimer() 関数がこのイベントを受け取る通知にサブスクライブする際に設定されます。

特定のエキスパートアドバイザーの timer イベント通知解除にはEventKillTimer() 関数を使用します。この関数は void 型でパラメータを持ちません。

void OnTimer();

OnInit() 関数で EventSetTimer() 関数を一度呼び出すことがお勧めです。また OnDeinit()で EventKillTimer() 関数を一度呼び出すこと も必要です。

エキスパートアドバイザーならびに指標は、全て独自のタイマーで動作しそこからのみイベントを受けとります。MQL5 プログラムが停止した場合、作成されて EventKillTimer() 関数で無効にされていないタイマーは強制的に破壊されます。

OnTrade #

この関数は Trade イベントの発生時に呼ばれます。 このイベントは 出された注文、ポジション、注文履歴 と約定履歴の表を変更する時に発生します。取引活動(未決注文を出す、注文を出す、ポジションの決済、ストップ設定、未決注文トリガ)が行われると、注文履歴や約定履歴及びポジションと現在の注文のリストがそれに応じて変更されます。

void OnTrade();

このようなイベントが受け取られた場合(取引ストラテジー条件によって必要な場合)、コード内の取引口座状態を検証するコードを実装する必要があります。OrderSend() 関数の呼び出しが正常に完了し trueが返された場合、取引サーバが注文を実行のためにキューに入れてチケット番号を割り当てたことが意味されます。サーバがこの注文を処理するとすぐに Trade イベントが生成されます。チケット値があれば OnTrade() イベント処理の際にこの値を使用して注文の状態を調べることが出来ます。

OnTradeTransaction #

取引口座にいくつかの明確なアクションを実行すると、状態が変更されます。アクションの例は下記です。

  • クライアント端末内の任意の MQL5 アプリケーションから OrderSend と OrderSendAsync 関数を使用して取引リクエストを送信し、取引が実行される。
  • 端末のグラフィカルインターフェースを使用して取引リクエストを送信し、取引が実行される。
  • サーバ上で未決注文と逆指値注文が執行される。
  • 取引サーバ側で操作を行う。

これらのアクションの結果として以下の取引トランザクションが実行されます。

  • 取引リクエストの処理
  • 未執行注文の変更
  • 注文履歴の変更
  • 約定履歴の変更
  • ポジションの変更

例えば、買いの成行注文を送信すると、注文が処理され、買い注文が口座に記録されます。その後注文が実行されオープン注文の表から削除されて注文履歴に追加されます。 約定が履歴に追加され新しいポジションが作成されます。これらのアクションは全て取引トランザクションです。このようなトランザクションの端末への到着が TradeTransaction イベントです。それは OnTradeTransaction ハンドラを呼びます。

void  OnTradeTransaction(
  const MqlTradeTransaction   trans,        // 取引トランザクション構造体
  const MqlTradeRequest&      request,      //リクエスト構造体
  const MqlTradeResult&       result       // 結果構造体
  );

ハンドラには 3 つのパラメータがあります。

  • trans – このパラメータは取引口座に適用される取引トランザクションを記述する MqlTradeTransaction 構造体を取得します。
  • request – このパラメータは取引リクエストを記述する MqlTradeRequest 構造体を取得します。
  • result – このパラメータは取引リクエスト実行の結果を記述する MqlTradeResult 構造体を取得します。

最後の 2 つの requestresult パラメータはTRADE_TRANSACTION_REQUEST 型のトランザクションでのみ値が書き入れられます。トランザクションのデータは trans 変数の type パラメータから受け取られます。この場合 result 変数の request_id フィールドは request 取引リクエストのIDを含んでいます。実行後は trans 変数で記述された 取引トランザクションが終了しています。リクエストIDは実行されるアクション(OrderSend または OrderSendAsync 関数の呼び出し)と OnTradeTransaction() に送られたアクションの結果を関連付けることが出来ます。

手動で端末から送信された、または OrderSend()/OrderSendAsync() 関数によってリクエストされた 1 つの取引リクエストが取引サーバ上で複数の連続したトランザクションを生成することがあります。端末におけるこれらのトランザクションの到着の優先順位は保証されません。よって取引アルゴリズムを開発する際にトランザクション到着の順番を仮定すべきではありません。

  • 全ての取引トランザクションの種類は ENUM_TRADE_TRANSACTION_TYPE 列挙に記述されています。
  • 取引トランザクションを記述する MqlTradeTransaction 構造体はトランザクションの種類によって様々な方法で書き込まれます。例えば TRADE_TRANSACTION_REQUEST 型のトランザクションでは type フィールド(取引トランザクションの種類)の分析のみが必要です。OnTradeTransaction 関数の 1、2 番目のパラメータ( request と result )は追加のデータを得るために分析されなければいけません。詳しくは 取引トランザクション構造体をご覧ください。
  • 取引トランザクションの記述は注文、約定やポジションに関する全ての入手可能な情報の配信を行いません(例えば、コメント)。OrderGet*、HistoryOrderGet*、HistoryDealGet* また PositionGet* 関数は繊細を取得するために使用されます。

取引トランザクションは、クライアント口座への適用後、端末の取引トランザクションのキューに置かれます。ここから端末への到着順に OnTradeTransaction エントリーポイントに送られます。

OnTradeTransaction ハンドラを使用してエキスパートアドバイザーによって取引トランザクションを取り扱う場合、端末は新たに到着した取引トランザクションの取り扱いを継続します。そのため、取引口座の状態はすでに OnTradeTransaction 動作中に変更することがあります。例えば、MQL5 プログラムが新しい注文を追加するイベントを処理している間に、注文が、実行可能の未執行注文のリストから削除され、履歴に移動されることもあります。更にアプリケーションはこれらのイベントの通知を受けます。

トランザクションキューは 1,024 要素を備えます。OnTradeTransaction が新しいトランザクションを処理するのに長くかかりすぎると、代わりにキュー内の新しいトランザクションが処理されることがあります。

  • 一般的に言って、OnTrade と OnTradeTransaction の呼び出しの数の正確な比率はありません。1 つの OnTrade 呼び出しは 1 つまたは複数の OnTradeTransaction に対応します。
  • OnTrade は当該な OnTradeTransaction の後で呼び出されます。

OnTester #

OnTester() 関数は自動的に選択された区間のエキスパートアドバイザーの履歴テストの後に生成される Tester イベントのハンドラです。この関数は double 型でパラメータを 2 つ持ちます。

double OnTester();

この関数は OnDeinit() 呼び出しの直前に呼び出され、同様に double の戻り値を返します。OnTester() はエキスパートアドバイザーのテストに使用出来ます。この関数の主な目的は入力パラメータの遺伝的最適化におけるカスタム最大基準として使用される特定の値を計算することです。

遺伝的最適化では一世代内の結果に降順のソートが適用されます。すなわち最適化基準の観点からすると、最良の結果は(OnTester 関数によって返されるカスタム最大の最適化基準値が考慮されるので)最大値を有するものです。このようなソートでは最悪値が終わりに配置されるので次の世代の形成に関与しません。

OnTesterInit #

OnTesterInit() 関数は、ストラテジーテスターでエキスパートアドバイザーの最適化が始まる前に自動的に生成される TesterInit のハンドラです。この関数は void 型で定義される必要があります。パラメータはありません。

void OnTesterInit();

最適化が始まると、エキスパートアドバイザーは OnTesterDeinit() または OnTesterPass() ハンドラと共に自動的にテスタの別の端末チャートに指定された銘柄と期間と読み込み、TesterInit イベントを受けとります。この関数は最適化の開始前にエキスパートアドバイザーの初期化に使用され、更に 最適化の結果の処理をします。

OnTesterPass #

OnTesterPass() 関数は、ストラテジーテスターにおけるエキスパートアドバイザーの最適化の間にフレームが受信された際に自動的に生成されるTesterPass イベントのハンドラです。この関数は void 型で定義される必要があります。パラメータはありません。

void OnTesterPass();

OnTesterPass() ハンドラを持つエキスパートアドバイザーは自動的にテスタの別の端末チャートにテストのために指定された銘柄と期間と読み込まれ、最適化中にフレームが受信された時に TesterPass イベントを受け取ります。この関数は最適化の結果 をその完了を待たずに動的に「その場で」処理するために使用されます。フレームは FrameAdd() 関数を用いて追加されます。この関数は OnTester() ハンドラ—のシングルパスが完了した後で呼ぶことが出来ます。

OnTesterDeinit #

OnTesterDeinit() は ストラテジーテスターでのエキスパートアドバイザーの最適化の終了時に自動的に生成される TesterDeinit イベントのハンドラです。 この関数は void 型で定義される必要があります。パラメータはありません。

void OnTesterDeinit();

TesterDeinit() ハンドラを持つエキスパートアドバイザーは最適化の開始時に自動的にチャートに読み込まれ、終了時にTesterDeinit を受け取ります。この関数は全ての最適化の結果の最終処理のために使用されます。

OnBookEvent #

OnBookEvent() 関数は BookEvent ハンドラです。BookEvent は板情報の変更時にエキスパートアドバイザーと指標のために生成されます。この関数は void 型で、1 つの文字列型のパラメータを持ちます。

void OnBookEvent (const string& symbol);

任意のシンボルの BookEvent イベントを受け取るには MarketBookAdd() 関数を使用して、このシンボルのBookEvent イベント受信を事前に設定する必要があります。特定のシンボルの BookEvent イベントの受け取りを停止するには MarketBookRelease() を呼び出します。

他のイベントとは異なり BookEvent イベントはブロードキャストされます。これは、1 つのエキスパートアドバイザーが MarketBookAdd を使用して BookEvent イベントの受信に加入した場合、OnBookEvent() ハンドラを持っている他のエキスパートアドバイザーの全てがこのイベントを受け取ることを意味します。よってハンドラに const string& symbol パラメータとして渡された銘柄名を分析する必要があります。

OnChartEvent #

OnChartEvent() はChartEvent イベントグループのハンドラです。

  • CHARTEVENT_KEYDOWN — チャートウィンドウがフォーカスされた時のキーストロークのイベント
  • CHARTEVENT_MOUSE_MOVE — マウス移動イベントとマウスクリックイベント(CHART_EVENT_MOUSE_MOVE=true がチャートに設定された場合)
  • CHARTEVENT_OBJECT_CREATE — グラフィックオブジェクトの作成イベント(CHART_EVENT_OBJECT_CREATE=true がチャートに設定された場合)
  • CHARTEVENT_OBJECT_CHANGE — プロパティダイアログを介してのオブジェクトプロパティ変更イベント
  • CHARTEVENT_OBJECT_DELETE — グラフィックオブジェクト削除イベント(CHART_EVENT_OBJECT_DELETE=trueがチャートに設定された場合)
  • CHARTEVENT_CLICK — チャート上でのマウスクリックイベント
  • CHARTEVENT_OBJECT_CLICK — チャートに属するグラフィックオブジェクトでのマウスクリックイベント
  • CHARTEVENT_OBJECT_DRAG — マウスを使用してのグラフィカルオブジェクトの移動イベント
  • CHARTEVENT_OBJECT_ENDEDIT — LabelEditグラフィックオブジェクトの入力ボックスに完成したテキスト編集のイベント
  • CHARTEVENT_CHART_CHANGE  — チャート変更イベント
  • CHARTEVENT_CUSTOM+n — ユーザイベントID( n は0〜65535 )
  • CHARTEVENT_CUSTOM_LAST — カスタムイベントの最大の使用可能なID(CHARTEVENT_CUSTOM +65535)

この関数はエキスパートアドバイザーと指標のみに呼ばれます。この関数は void 型で 4 つのパラメータを持ちます。

void OnChartEvent(const int id,         // イベント識別子
const long& lparam,   // long 型イベントパラメータ
const double& dparam, // double 型イベントパラメータ
const string& sparam // string 型イベントパラメ—タ
);

OnChartEvent() 関数の入力パラメータはイベントの種類ごとにイベント処理に必要とされる一定の値を有しています。これらのパラメータを使用して渡されたイベントと値は以下の表に記載されています。

イベント

id パラメータ値

lparam パラメータ値

dparam パラメータ値

sparam パラメータ値

キーストロークイベント

CHARTEVENT_KEYDOWN

押されたキーのコード

繰り返し回数(ユーザのキーの長押しがキーストロークの繰り返しとみなされた場合の繰り返し回数)

キーボードボタンの状態を記述するビットマスクの文字列値

マウスイベント (CHART_EVENT_MOUSE_MOVE=true がチャートに設定された場合)

CHARTEVENT_MOUSE_MOVE

X 座標

Y 座標

マウスボタンの状態を記述するビットマスクの文字列の値

グラフィックオブジェクトの作成イベント (CHART_EVENT_OBJECT_CREATE=true がチャートに設定された場合)

CHARTEVENT_OBJECT_CREATE

作成したグラフィックオブジェクトの名称

プロパティダイアログでのオブジェクトプロパティ変更イベント

CHARTEVENT_OBJECT_CHANGE

変更したグラフィックオブジェクトの名称

グラフィックオブジェクト削除イベント(チャートで CHART_EVENT_OBJECT_DELETE=true の場合)

CHARTEVENT_OBJECT_DELETE

削除したグラフィックオブジェクトの名称

チャート上でのマウスクリックイベント

CHARTEVENT_CLICK

X 座標

Y 座標

チャートに属するグラフィックオブジェクトでのマウスクリックイベント

CHARTEVENT_OBJECT_CLICK

X 座標

Y 座標

イベントが発生したグラフィック•オブジェクトの名称

マウスを使用してグラフィックオブジェクトのドラッグイベント

CHARTEVENT_OBJECT_DRAG

移動したグラフィックオブジェクトの名称

LabelEdit グラフィックオブジェクトの入力ボックスでのテキスト編集完成のイベント

CHARTEVENT_OBJECT_ENDEDIT

テキスト編集が完成した LabelEdit グラフィックオブジェクトの名称

チャート変更イベント

CHARTEVENT_CHART_CHANGE

N 番号を使用したユーザイベントID

CHARTEVENT_CUSTOM+N

EventChartCustom() 関数で設定された値

EventChartCustom() 関数で設定された値

EventChartCustom() 関数で設定された値

OnCalculate #

OnCalculate() 関数は Calculate イベントによって指標値の計算が必要な時にカスタム指標でのみ呼ばれます。これは通常指標が計算されたシンボルの新しいティックが受け取られた時に起こります。この指標はこのシンボルの価格表に接続される必要はありません。

OnCalculate() 関数は int 型の戻り値を持たなければなりません。2 つの定義が可能です。1 つの指標に対してこの関数の両バージョンを使用することは出来ません。

1 番目の定義は単一のデータバッファに基づいて計算することが出来る指標ために意図されています。カスタム平均移動線はこの種類の指標の一例です。

int OnCalculate (const int rates_total,     // price[] 配列のサイズ
const int prev_calculated, // 以前の呼び出しで処理されたバー
const int begin,           // 重要データの開始点
const double& price[]       // 計算対象の配列
);

price[] 配列のように、時系列またはいくつかの指標の計算されたバッファが渡されます。price[] 配列内の索引付けの方向を決定するためには ArrayGetAsSeries()を呼び出します。初期値への依存を避けるためにはこれらの配列に対して ArraySetAsSeries() 関数を無条件に呼び出す必要があります。

price[] 配列として使用される必要な時系列または指標は指標開始時に「パラメータ( Parameters )」タブでユーザ選択が出来ます。そのためには「適用( Apply to )」フィールドのドロップダウンリストに必要な項目を指定する必要があります。

他の MQL5 プログラムからカスタム指標の値を受信するには、その後の操作のための指標ハンドルを返すiCustom() 関数が使用されます。また、当該なprice[] 配列または他の指標ハンドルを指定することが出来ます。このパラメータは、カスタム指標の入力変数のリストに最後に送信されるべきです。
例:

void OnStart()
{
//—
string terminal_path=TerminalInfoString(STATUS_TERMINAL_PATH);
int handle_customMA=iCustom(Symbol(),PERIOD_CURRENT, “Custom Moving Average”,13,0, MODE_EMA,PRICE_TYPICAL);
if(handle_customMA>0)
Print(“handle_customMA = “,handle_customMA);
else
Print(“Cannot open or not EX5 file ‘”+terminal_path+“\\MQL5\\Indicators\\”+“Custom Moving Average.ex5′”);
}

この例では、最後のパラメータは PRICE_TYPICAL 値です(ENUM_APPLIED_PRICE 列挙から)。これは、カスタム指標が (高値+安値+終値)/3 で取得された典型的な価格に基づいて構築されることを示します。このパラメータが指定されていない場合は、指標は PRICE_CLOSE 値、すなわち各足の終値に基づいて構築されます。

price[] 配列を指定するために最後のパラメータに指標ハンドラを渡すもう 1 つの例は iCustom() 関数の説明にてみられます。
2 番目の定義は、計算のために複数の時系列を使用する指標のために意図されています。

int OnCalculate (const int rates_total,     // 入力時系列のサイズ
const int prev_calculated, // 以前の呼び出しで処理されたバー
const datetime& time[],     // 時間
const double& open[],       // 始値
const double& high[],       // 高値
const double& low[],       // 安値
const double& close[],     // 終値
const long& tick_volume[], // ティックボリューム
const long& volume[],       // ボリューム
const int& spread[]         // スプレッド
);

open[]、high[]、low[] 及び close[] パラメータは現在の時間軸での始値、高/安値と終値の配列を含みます。time[] パラメータは オープンタイム値の配列、spread[] パラメータはスプレッドの履歴の配列(スプレッドがセキュリティの取引のために提供されている場合)を含みます。volume[] とtick_volume[] パラメータはそれぞれ、取引量とティックボリュームの履歴を含みます。

time[]、open[]、high[]、low[]、close[]、tick_volume[]、volume[] 及び spread[] の索引付けの方法を決定するには ArrayGetAsSeries() を呼び出します。初期値への依存を避けるためにはこれらの配列に対して ArraySetAsSeries() 関数を無条件に呼び出す必要があります。

初めの rates_total パラメータは指標計算に使用出来る、チャートで使用可能なバーの数に相当するバーの数を含みます。

ここで OnCalculate() の戻り値と prev_calculated の 2 番目の入力パラメータとの関係が指摘されるべきです。関数呼び出しの際、prev_calculated パラメータは以前の OnCalculate() 呼び出しに返された 値を持ちます。これは、この関数の前回の実行以降に変更されていない足の反復計算を避けてカスタム指標を計算するための経済的なアルゴリズムを可能にします。

このために、通常は現在の関数呼び出しでの足数を含む rates_total パラメータの値を返すので充分です。価格データが OnCalculate() の最後の呼び出し以降に変更された場合は(履歴ダウンロードや履歴ブランクの書き込み)、端末がprev_calculated 入力パラメータの値をゼロに設定します。

注意事項:OnCalculate がゼロを返した場合、指標値はクライアント端末のデータウィンドウには表示されません。

理解を深めるために、以下にコードが添付されている指標を起動してみてもいいでしょう。

指標の例:

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//—- Line をプロットする
#property indicator_label1 “Line”
#property indicator_type1   DRAW_LINE
#property indicator_color1 clrDarkBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1  1
//— 指標バッファ
double         LineBuffer[];
//+——————————————————————+
//| カスタム指標を初期化する関数                                            |
//+——————————————————————+
int OnInit()
{
//— 指標バッファマッピング
SetIndexBuffer(0,LineBuffer,INDICATOR_DATA);
//—
return(INIT_SUCCEEDED);
}
//+——————————————————————+
//| カスタム指標の反復関数                                                |
//+——————————————————————+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime& time[],
const double& open[],
const double& high[],
const double& low[],
const double& close[],
const long& tick_volume[],
const long& volume[],
const int& spread[])
{
//— 現在のシンボルとチャート期間に利用可能なバーの数を取得する
int bars=Bars(Symbol(),0);
Print(“Bars = “,bars,“, rates_total = “,rates_total,“,  prev_calculated = “,prev_calculated);
Print(“time[0] = “,time[0],” time[rates_total-1] = “,time[rates_total-1]);
//— 次の呼び出しのために prev_calculated の値を返す
return(rates_total);
}
//+——————————————————————+

Originally posted 2019-07-27 10:14:29.

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">