MQL5オブジェクト指向のプログラミングアプローチを使ったExpert Advisorのプログラミング

Samuel Olowoyo | 6 10月, 2015

はじめに

初稿では、MQL5におけるExpert Advisorの基本的な作成、デバッグ、検証段階を見ていきました。

行ったことはすべてシンプルで興味深いものでした。けれど、新しいMQL5言語ではもっとできることがあるのです。 本稿では初稿で行ったことに対するオブジェクト指向のアプローチ について考察していきたいと思います。ほとんどの方はこれは難しいと思われるでしょう。しかし本稿を読み終わるまでにみなさんはブジェクト由来でExpert Advisorを書けるようになっている、という点をはっきり述べておきたいと思います。

初稿ですでに習得した内容は繰り返しません。よってまず申し上げたいのは、まだでしたら本稿をすべて読んでいただきたいということです。


1. オブジェクト指向パラダイム

新しいMQL5がMQL4に比べてより力強く安定してできることの一つはOOP (オブジェクト指向のプログラミング)アプローチです。

OOPでは、オブジェクト実装の詳細を明らかにするべきではないとしています。この方法で、オブジェクトを使用するコードを変更せずにその実装を変更することが可能です。これは、クラスがプログラマーの書いたクラスがどのように実装されているかを隠す(また変更を防ぐ)ことができる、ということです。

もっとわかりやすくするには、『クラス』と『オブジェクト』という用語についてすこし考えてみます。

1.1. クラス宣言

クラスには基本的にクラスから作成したいオブジェクトメンバーー(プロパティと関数またはメソッド)の記述が含まれます。, 例を見てみます。</span

ドア、椅子、タイヤ、重量などを持ち、開始、ギアチェンジ、停止そして クラクションを鳴らすが可能なオブジェクトを作成したい場合、それに対するクラスを書く必要があります。ドア、椅子、タイヤ、重量、開始、ギアチェンジ、停止そして クラクションを鳴らすはクラスメンバーです。

もちろん、これらメンバーーは分類されると判るでしょう。オブジェクトにただ含まれるもの(プロパティ)もあれば、一方でオブジェクトが行う事柄(アクション、すなわち関数やメソッド)があります。クラスを宣言するにはそれを表現するよい名前を考える必要があります。ここではクラスをクルマとしましょう。このクルマクラスは前述のようにメンバーとしてプロパティと関数を持ちます。

クラスを宣言するためにキーワードを打つことから始めます。クラス に続くのはクラス名です。 それに続くのはクラスメンバーーを伴う一組の括弧です。

よって、クラスの基本フォーマットは以下のようなものとなります。

class class_name 
{
  access_keyword_1:
    members1;

  access_keyword_2:
    members2;
  ...
};

ここで、class_nameは書きたいクラスについて有効な識別子です。members1およびmembers2はクラスのデータメンバーーです。

access_keywordはクラスメンバーへのアクセス権を指定します。 access_keywordはプライベート保護、またはパブリックです。今回、プログラムを書いた本人や他人が使うことのできるクラス実装の詳細は明かさずに書こうとしていることを思い出してください。それがアクセス権が必要な理由です。

このクラス外からのアクセスを許可したくないクラスメンバーもあるかもしれません。これらはプライベートまたは保護キーワードを使ってプライベートアクセスセクション内で宣言します。このクラス外からのアクセスを許可するメンバーはパブリックキーワードを使って、パブリック アクセス セクション内で宣言します。これでここでの新規クルマクラスは以下のような記述となります。

class CAR 
{
  private:
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

われわれのクルマクラスは、キーワードクラスを使って宣言されています。このクラスには、プライベートアクセス設定のメンバー8個と、パブリックアクセス設定のメンバー4個があります。プライベートセクションの4つのメンバーはデータメンバーです。3つは整数(int)データタイプで、1つはダブルデータタイプです。これらメンバーはこのクラス外で宣言されたいかなる関数でもアクセスできません。

パブリックセクションの4つのメンバーは関数メンバーです。2つはブールデータタイプを返し、あとの2つはボイドタイプを返します。これらはわれわれのクラスを利用するだれからも作成されるときはいつでもこのクラスのいかなるオブジェクトもアクセス可能なメンバーです。クラスのオブジェクトが作成されたら、これらメンバーはいつでも使用可能です。

アクセスキーワード (プライベート、保護、パブリック)には常にコロンが続きます。クラス宣言はセミコロンで終わります。メンバーは正しいデータタイプによって宣言されます。

ひとたびクラスを宣言したら、上記で行ったようにはっきり指定しない限り、すべてのクラスメンバーにはプライベートアクセス権が与えられます。たとえば、以下で宣言するクラスでは、

class CAR 
{
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

上でパブリックアクセスキーワードを宣言された4個のメンバーは自動でプライベートアクセスとなります。

われわれのクラスを使用するのには、まずクラスオブジェクトを作成する必要があります。では、われわれのクラスタイプであるオブジェクトを作成していきましょう。これを行うために、オブジェクトにつけたい名前が続くクラス名を使います。

CAR Honda;

また、別のオブジェクトを作成することができます。

CAR Toyota;

ホンダまたは トヨタクルマのタイプで、メンバー関数がパブリックアクセスセクションで宣言されているわれわれの提供されているクルマクラスのメンバー関数すべてにアクセス可能です。この件についてはのちにまた戻ってくることにします。

われわれはこのクラスのオブジェクトとして作りたいだけ作れることがお分かりでしょう。これはオブジェクト指向のプログラミングのメリットのひとつです。

ここで、MQL5のクラスフォーマットについて詳しく見ていきます。

class class_name 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

これは、class_nameはクラス名である場合のクラス宣言です。このクラスにはメンバーが9個ありますが、このうち2個は 特別メンバーです。

コンストラクタ
コンストラクタ(class_name()として表されています。)は、クラスタイプの新規オブジェクトが作成されると自動的に呼び出される関数です。今回の場合ですと、このクラスタイプのオブジェクトを作成するとき、

class_nameobject;

コンストラクタclass_name()が自動的に呼び出されます。コンストラクタ名はクラス名と一致している必要があり、そのためコンストラクタをclass_name()と名付けました。MQL5では、コンストラクタは入力パラメータを取らず、結果を返すタイプではありません。コンストラクタが呼ばれるときは通常、メモリアロケーションとクラスメンバーの初期化が行われます。コンストラクタはメンバー関数の常連であるかのようにはっきりと呼ばれることはありません。そのクラスの新規メンバーが作成されるときのみ実行される関数です。MQL5のクラスは一つだけコンストラクタを持つことができます。

デストラクタ
二番目の特別メンバーは~class_name()として表されます。これはクラス名の前にtide (~) を伴って書かれるクラスデストラクタです。それはクラスオブジェクトが破壊されるとき自動的に呼び出されます。最初期化が必要なクラスメンバーはすべて、この段階で最初期化し、デストラクタを明確に宣言したかどうかは特に問題ではありません。

データメンバー
クラスメンバーはどのようなリーガルデータタイプ、クラスタイプ構造タイプであってもかまいません。 別の言い方をすれば、クラスのメンバー変数を宣言するとき、どのようなリーガルデータタイプ(int、double、stringなど)、その他のクラスオブジェクト、または構造タイプも使用可能です。(たとえばMQL5 MqlTradeRequestなど)

関数メンバー
これらは、データメンバーの変更やクラスの主要な関数またはメソッドを実行するのに使用されるクラスメンバーです。関数メンバーの返しタイプはどんなリーガル返しタイプでもありえます。(ool, void, double, stringなど)

プライベート
このセクション内で宣言されたメンバーはクラスの関数メンバーのみアクセス可能です。クラス外関数からは一切アクセスできません。

保護
このセクション内で宣言されたメンバーは、クラスの関数メンバーにアクセス可能で、またこのクラスから派生したその他クラスのメンバー関数からもアクセス可能です。これは、このクラスから新しいクラスを作成することができるということです。この場合、このクラスから派生した新規クラスは元のクラス(今は基本クラスとなっているもの)の保護されたメンバーにアクセス可能です。これはOOPにおける継承概念です。それについてはすぐに述べます。いまはまだ落ち着いてください。

パブリック
このセクション内で宣言されたメンバーはクラスオブジェクトによるクラス外での使用が可能です。これは他のプログラムでクラスを使用する必要がある関数のいくつかを宣言する場所です。

クラスの基本フォーマットは観てきました。まだ退屈されていないとよいのですが。というのもわれわれのExpert Advisorのクラスラッパの作成に飛んでいく前にまだ興味深いクラスの側面について見ていく必要があるからです。

1.2. 継承

この基本のクラスbase_classから別のクラスを作成したいとします。最初のクラスから新規クラスを派生するフォーマットは以下のようなものです。

基本クラス

class base_class 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

派生クラス

class new_class : access_keyword base_class 
{
  private:
    members8;

  public:
    new_class()  //Constructor;
    ~new_class() //Destructor;
    Members9();
};

詳細に入っていく前に少々説明をいたします。クラスnew_classは、上述のようにコロンとaccess_keywordを使用し、クラスbase_classから派生しています。base_classから派生/作成されたnew_classはbase_classのメンバーにパブリック、保護のどちらでもアクセス(または継承)可能ですが、 base_classのプライベートなメンバーにはアクセス(または継承)できません。new_classはまた、 base_classとは異なる新規メンバーメソッドまたは関数を実装することが可能です。 言い換えると、new_classはまた、base_classから継承したものとは異なる独自のデータや関数メンバーを持つことができる、ということです。

パブリックキーワードが派生クラスの作成に使われると、 それは、基本クラスのパブリックや保護のメンバーが派生クラスのパブリックや保護のメンバーとして継承される ということを意味します。保護キーワードが使われると、 基本クラスのパブリックや保護のメンバーが派生クラスのパブリックや保護のメンバーとして継承されます。プライベートキーワードが使われると、 基本クラスのパブリックや保護のメンバーが派生クラスのパブリックや保護のメンバーとして継承されます。

知っておくべき重要点があります。new_class (派生クラス)の新規オブジェクトが作成されると、new_classのコンストラクタが呼ばれる前にbase_classのコンストラクタが呼ばれます。一方、オブジェクトが破壊されるときは、base_classのデストラクタが呼ばれる前にまずnew_class (派生クラス)のデストラクタが呼ばれます。

継承の概念をよりよく理解するために、以前のクラス、『クルマ』に戻りましょう。

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();

  private:
    int        tyres;
};

このクラスから別のクラス『サルーン』を派生させます。『クルマ』クラスの3つのデータメンバーを保護として宣言していることをお知らせしておきます。新規クラス『サルーン』がこれらメンバーを継承できるようにそうしました。

また、アクセスキーワードをセットする順番は重要ではないことも知っておいてください。重要なのは、アクセスキーワードのもとに宣言されたメンバーはすべてそのキーワードに属するということです。

class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void       runathighspeed();
};

われわれの派生クラス『サルーン』は2個のメンバーを持ち、基本クラス『クルマ』から同時に7個のメンバー(保護、パブリック)を継承しています。これは、ひとたび『サルーン』のオブジェクトが作成されると、自分のオブジェクトrunathighspeed()と共に、『クルマ』のパブリックメンバー関数である、start()changegear()stop() horn()にもアクセスが可能だということを意味します。これが継承の概念です。

父親/両親(基本クラス)の性格/行動(メソッド)がわれわれ子供(派生クラス)に現れるようなものです。なぜならそういう行動(メソッド/関数)を遺伝的か何かで引き継いでいるからです。失礼しました。私は医学分野の人間ではないですが、私の言わんとすることをみなさんにご理解いただけていることと思います。とはいうものの、MQL5は複数の継承をサポートしていませんから、そのことを語る必要はありません。

ふぅー。。。OOPまたは CLASSと呼ばれるミステリアスな黒い覆いが少しずつ剥がれていっているとよいのですが。。。 いやにならないでください。この時点ではまだここで語っていることが何なのかはっきりしないと思います。リラックスしていただきたいので、コーヒーでも召し上がって、それからまた戻ってきて読み進んでくさい。思っているほどミステリアスではありませんよ。

ここで戻ってこられた方は、私の説明をご理解いただいている方だと思います。われわれの基本クラス『クルマ』からあといくつのクラスを派生させるのか話していただけますか?どうぞお答えください。まじめに訊いているのですよ。それらに名前をつけて宣言を記述し、私にメールしてください。すべてに名前をつけることのできる方はランチにご招待しますよ!(冗談だとお思いですか?)

さあ、先に進む準備ができましたね。それでは続けましょう。

私が書くとき、私の父ように書く、というのは真実です。父の筆跡は整っていて、ひじょうに美しいのです。ちょうど私の筆跡のように。これは私が父から受け継いだものだと思います。しかし、何でしょう。父は左利きですが、私は右利きなのですが、筆跡を見ると似通っているので区別するのは非常に難しいのです。ここで問題となるのは?私は父から美しい筆跡を受け継いでいますが、私は父のように左手で書くわけではありません。このことが意味するのは、それが私が受け継いだことで、似ているとしても私のやり方は父のやり方と違っているということです。筋が通っていますか?これはOOPにおいて、多相性と呼ばれるものです。

派生クラス(上述の例では私自身)は、基本クラス(私の父)からメンバー関数(writefine() :筆跡にあたるもの)を引き継いでいますが、それ(私)は、基本クラス(私の父)とは異なる方法で関数(writefine() )を実装するのです。

『クルマ』クラスと派生クラスの『サルーン』に戻ります。

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool               start();
    virtual void       changegear(){return(0);}
    void               stop();
    bool               horn();

  private:
    int        tyres;
};
class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void               runathighspeed();
    virtual  void       changegear(){gear1=reverse; gear2=low; gear3=high;}
  };

class WAGON : public CAR 
{
  private:
    bool               hasliftback;

  public:
   virtual  void       changegear(){gear1=low; gear2=high; gear3=reverse;}
};

ここでの変更点をいくつか見ていきます。まず、『クルマ』から派生した新規クラスを2つのメンバーを持つ『ワゴン』として宣言しました。また、メンバー関数changegear()を、基本クラスで バーチャル関数となるように変更しました。 なぜchangegear()バーチャル関数にしたのでしょうか?単に、独自の方法で実装することのできる関数を基本クラスから引き継ぐクラスをなんでもよいので欲しいからです。

言い換えれば、あるクラスのバーチャルメンバー関数は、宣言されるクラスから派生したあらゆるクラスに異なった方法でオーバーライドまたは実装することのできるメンバー関数です。メンバー関数の本体は派生クラスの新規実装設定と置き換えが可能です。派生クラスではバーチャルという言葉は使いませんが、つねにそれを派生クラスで使うのはプログラミング練習に役立ちます。

前述の例から、『サルーン』と『ワゴン』は独自の方法で関数changegear()を実装します。

1.3. クラスメソッド(メンバー関数)の定義

クラス宣言の仕方はいくばくかわかっているので、先へ進んでクラスのメンバー関数定義の方法について述べていきます。クラス宣言後、次にすることはクラスのメンバー関数定義です。ふたたびわれわれの『クルマ』クラスを見てみます。

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    void       CAR() // Constructor
    bool       start();
    void       changegear();
    void       stop();
    bool       horn(){press horn;}

  private:
    int        tyres;
};

 void CAR::CAR()
{
 // initialize member variables here
}

bool CAR::start()
{
 // car start procedure here
}

void CAR::changegear()
{
// car changegear procedure here
}

void CAR::stop()
{
// car stop procedure here
}

メンバー関数の定義に際しスコープオペレータと呼ばれるダブルコロン(::) オペレータを使いました。これはちょうど普通の関数のように書かれます。唯一の相違点は、追加されるクラス名とスコープオペレータです。また、関数の一つはすでにクラス内で定義されている(メンバー関数horn())のがお分かりでしょう。ここで見られるように、メンバー関数はクラス宣言またはクラス外宣言で定義が可能です。

先に進む前に関数の概念に目を通すことが重要だと思います。

1.4. 関数

ところで、関数とはなんでしょうか?

家に子供が3人います。うちの一人が家の仕事を全部するよりも、一人が毎日夕食後のお皿洗い、一人が掃除、もう一人が毎朝ベッドメイキングをする仕事を与えられている状況があります。

家事に関して、一人の子供にすべて任せる代わりに、3人で仕事を分担するのです。一人に負担を負わせるよりは、分担したほうが各自にとって仕事がやりやすく、負担は軽くてすみます。また、子供の一人が仕事をさぼったら、だれを叱ればよいかすぐにわかります。これが関数の背後にある考え方です。

往々にして、多くのタスクを行うコードを書きたいと思います。これが関数のなんたるかです。タスクを小さいタスクに分けて、おのおのの小さいタスクを処理するように関数を書きます。 関数は処理のセットを行ったり実装するコードのかたまりです。プログラムのある地点で呼ばれるといつでも実行される記述のグループです。

関数は以下のように定義することができます。

Return_type function_name (parameters1,parameters2,…)
{
  Expressions; //(actions to carry out by the function)
}

関数例

int doaddition (int x, int y)
{
 return (x+y);
}

リターンタイプの関数は整数 (int)、 doadditionは関数名、int x and int y はパラメータです。関数が行うことは供給される2つの入力パラメータを加え、結果を返すことです。よって、関数に2つの整数値2、3を与えると、関数は足し算を行い、結果として5を返します。

int doaddition(2,3) // returns 5

関数についてのさらなる情報はMQL5参照資料をご覧ください。

理論はこれで十分でしょう。それでは作業を始めます

本稿の基礎はMQL5にあるオブジェクト指向のアプローチを使って各自のExpert Advisorにクラスを書く方法をお伝えすることです。

その時間がやってきました。


2. Expert Advisorの記述

ここでは、初稿で作成したExpert Advisorに参照を作成します。初稿をお読みでないなら、まずそれを読んでいただくと、ここで述べることがお解りいただけると思います。必要と思われることを少し振り返ります。

みなさんのクラスを書く前に、まず腰を落ち着けてご自身のトレーディング戦略を考えてください。これはすでに 初稿で行いました。次は、われわれのクラスにデリゲートしたい機能性を選択します。それら機能性はクラスのメンバー変数を決定します。初稿からトレーディング戦略をちょっと要約します。

EA が行うこと

  • 特定のインディケータを監視し、ある条件が合えば(または複数の条件が満たされれば)、現在満たされている条件に応じて取引をセットします。(ショート/売りまたはロング/買いのいずれか)

上記がトレーディング戦略と呼ばれます。EAを書く前にまずEAで自動化したい戦略を作成する必要があります。今回の場合、上記の記述を変更して、EAに作成したい戦略に反映するようにします。

  • 期間8で「移動平均」と呼ばれるインディケータを使います。(期間は選択自由ですが、ここでの戦略に対する目的のために8とします。)
  • EAにしてほしいのは、移動平均8(話がしやすいようにMA-8とします)が上向きに増加し、価格がそれを上回りそうなときはロング(買い)取引をセットし 、またMA-8が下向きに減少し価格が下回りそうなときはショート(売り)をセットすることです。
  • また、『方向性指数(ADX)』と呼ばれる別のインディケータを期間8で使用し、マーケットがトレンドかそうでないか判断する助けとします。 これは、マーケットがレンジ(トレンドではありません)のときトレンドでリラックスしているときにのみ取引にはいりたいからです。これを達成するには、上記の条件が満たされ、ADX 値22より大きいとき取引(買いまたは売り)をセットします。ADX22より大きいが減少している、またはADXが22より小さいと、条件Bが満たされていたとしても取引は行いません。
  • また、30 pipsのストップロスを設定することで自分自身を守ります。収益目標について100 pipsとします。
  • 新規バーが形成されるときのみ EAが買い/売り機会を探すようにし、買い条件が満たされるときに必ず買いポジションが開かれるように 、すでに開かれたものがないようにします。また、売りについても同様です。

ついでながら、取引をセットする際使用できるフリーマージンのパーセントを確実に調整できるようにしたいと思います。そして、取引をセットするまえに使用可能なフリーマージンを確実に確認できるようにしたいと思います。 われわれのEAは利用可能なマージンが取引に十分ある場合にのみ取引をセットします。

これでしたいことがはっきりしました。われわれのクラスのためにデリゲートしたい関数は以下です。

基本的にこれはすべてわれわれのEA にやってもらうことです。これら2つの機能性が主要なものですが、他にもまだあります。たとえば、買い/売りポジションのチェックにはインディケータを使う必要があります。これはクラスでインディケータ値を取得する必要があるということです。そこで以下を含めます。

インディケータ値を取得する際、われわれのクラスはMA、ADX期間、チャート期間、シンボル(使っている通貨ペア)を知る必要があります。

また取引セット前のフリーマージンチェックのため、以下を含めます。

これで、すでにわれわれのクラスにどんな変数と関数が必要なのかわかりました。

オッケー、みなさんのために考えました。ここからはコードを書いていきます。

2.1. クラス記述

MetaEditorの起動から始めましょう。(これはすでにご存じのことと思いますが) MetaEditorを開いたら、新規ツールバーをクリックするか、Ctrl+Nで新規MQLドキュメントを起動します。ウィザードウィンドウで"インクルード"を選択し、NEXTボタンをクリックします。

図1 新規MQL5ドキュメントを起動します。

図1 新規MQL5ドキュメントを起動します。

下記のファイル名をタイプし、終了をクリックします。

図2 新規ドキュメントの命名

図2 新規ドキュメントの命名

「インクルード」を選択しました。EAが使用準備できたとき、われわれのクラスがEAのコードに含まれるインクルードファイルとなるためです。そのため、入力パラメータのための空間はありません。

通常、エディタが みなさんがしたいことだと思うスケルトンを提供します。

始めるに当たり、下記の"#property link …" コード行を完全に削除してください。そうすると以下のようになっているはずです。

//+------------------------------------------------------------------+
//|                                              my_expert_class.mqh |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"

こんどはクラスの宣言を書きます。クラスはMyExpertと呼びます。

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{

クラス宣言を分析します。宣言はクラス名で始まっています。次にクラスのプライベートメンバーを宣言します。

プライベートメンバー

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{
//--- private members
private:
   int               Magic_No;   // Expert Magic Number
   int               Chk_Margin; // Margin Check before placing trade? (1 or 0)
   double            LOTS;       // Lots or volume to Trade
   double            TradePct;   // Percentage of Account Free Margin to trade
   double            ADX_min;    // ADX Minimum value
   int               ADX_handle; // ADX Handle
   int               MA_handle;  // Moving Average Handle
   double            plus_DI[];  // array to hold ADX +DI values for each bars
   double            minus_DI[]; // array to hold ADX -DI values for each bars
   double            MA_val[];   // array to hold Moving Average values for each bars
   double            ADX_val[];  // array to hold ADX values for each bars
   double            Closeprice; // variable to hold the previous bar closed price 
   MqlTradeRequest   trequest;    // MQL5 trade request structure to be used for sending our trade requests
   MqlTradeResult    tresult;     // MQL5 trade result structure to be used to get our trade results
   string            symbol;     // variable to hold the current symbol name
   ENUM_TIMEFRAMES   period;      // variable to hold the current timeframe value
   string            Errormsg;   // variable to hold our error messages
   int               Errcode;    // variable to hold our error codes

以前にお話ししたとおり、これらプライベートメンバー変数はクラス外からはアクセス不能です。変数は宣言で明確ですので、それの説明に時間を割くことはしません。

ただ、メンバー関数はいかなるリーガルデータタイプ、ストラクチャ、クラスも可能であるとお話したことを思い出してください。

MqlTradeRequestタイプおよびMqlTradeResultsタイプの宣言でおわかりのことと思います。

コンストラクタ

//--- Public member/functions
public:
   void              MyExpert();                                  //Class Constructor

コンストラクタは入力パラメータを取りません。ご自身のクラスを書くときこのことを覚えておいてください。

メンバー関数

//--- Public member/functions
public:
   void              MyExpert();                                 //Class Constructor
   void              setSymbol(string syb){symbol = syb;}         //function to set current symbol
   void              setPeriod(ENUM_TIMEFRAMES prd){period = prd;} //function to set current symbol timeframe/period
   void              setCloseprice(double prc){Closeprice=prc;}   //function to set prev bar closed price
   void              setchkMAG(int mag){Chk_Margin=mag;}          //function to set Margin Check value
   void              setLOTS(double lot){LOTS=lot;}               //function to set The Lot size to trade
   void              setTRpct(double trpct){TradePct=trpct/100;}   //function to set Percentage of Free margin to use for trading
   void              setMagic(int magic){Magic_No=magic;}         //function to set Expert Magic number
   void              setadxmin(double adx){ADX_min=adx;}          //function to set ADX Minimum values

これらメンバー関数を定義し、関数を処理するのにクラスで必要となる重要な変数が設定できるようになりました。これら関数を使うことなくクラスで変数を使用するのは不可能です。またご存じのように、クラスにて対応する変数を宣言しました。それは関数により設定されるとこれら変数を保有します。

もうひとつ注意が必要なのは、これらメンバー関数をクラス宣言のなかで定義したことです。以前に申し上げたようにそれは許可されています。それはその他のメンバー関数を定義する際、それらを再び定義する必要はないということです。それはすぐにお分かりになります。

通常の関数同様に、各関数の返し値に応じてそれらは正しいデータタイプのパラメータを持ちます。これは特に珍しいことではないでしょう

void              doInit(int adx_period,int ma_period);         //function to be used at our EA intialization
void              doUninit();                                  //function to be used at EA de-initializatio
bool              checkBuy();                                  //function to check for Buy conditions
bool              checkSell();                                 //function to check for Sell conditions
void              openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,
                         double TP,int dev,string comment="");   //function to open Buy positions
void              openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,
                          double TP,int dev,string comment="");  //function to open Sell positions

これらのメンバー関数を宣言するだけで、定義はしません。定義はあとでします。これらはクラスのメンバー変数に保存される値を操作します。同時に、クラスの主要な役割を担う関数を形成します。それについてはもっとあとにお話します。

保護されたメンバー

これらメンバーはわれわれのクラスから派生するあらゆるクラスに受け継がれます。このクラスから派生クラスを設ける気がなければ特に必要なものではありません。またそれらをプライベートメンバーとしてセットすることもできます。今これをしているのは、みなさんに単に前にクラスについて話したさまざまなことがらをご理解いただくためです。

//--- Protected members
protected:
   void              showError(string msg, int ercode);   //function for use to display error messages
   void              getBuffers();                       //function for getting Indicator buffers
   bool              MarginOK();                         //function to check if margin required for lots is OK

これら3つの関数はまたクラスの内部的にたいへん重要なものです。showErrorはエラーを表示し、getBuffersはインディケータバッファを取得するのに使われます。MarginOKはポジションを開くのに十分なマージンがあるかチェックします。

クラス宣言が終わったら、セミコロンをつけるのを忘れないでください。それはひじょうに重要です。

};   // end of class declaration

次クラス宣言後すぐに行うことは、宣言セクションで行われなかった関数定義です。

//+------------------------------------------------------------------+
// Definition of our Class/member functions
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|  This CLASS CONSTRUCTOR
//|  *Does not have any input parameters
//|  *Initilizes all the necessary variables                                          
//+------------------------------------------------------------------+
void MyExpert::MyExpert()
  {
//initialize all necessary variables
   ZeroMemory(trequest);
   ZeroMemory(tresult);
   ZeroMemory(ADX_val);
   ZeroMemory(MA_val);
   ZeroMemory(plus_DI);
   ZeroMemory(minus_DI);
   Errormsg="";
   Errcode=0;
  }

これはわれわれのクラスコンストラクタです。 ここでわれわれはクラス名とメンバー関数名の間にコロンをふたつ(::)使いました。 (スコープオペレータ)言わんとすることは次のようなことです。

このメンバー関数定義をクラス宣言の外でおこなったとしても、まだクラス範囲内におさまっています。それは名前がふたつのコロン(スコープオペレータ)の前にきているクラスのメンバーなのです。

それは入力パラメータを持ちません。必要なメンバー変数はほとんと初期化します。それにはZeroMemory関数を使用します。

void ZeroMemory(
void & variable // reset variable
);

この関数はそれに渡される変数値をリセットします。この場合、ストラクチャタイプ(MqlTradeRequestおよび MqlTradeResult) と配列の値をリセットするのに使用します。

showError関数

//+------------------------------------------------------------------+
//|  SHOWERROR FUNCTION
//|  *Input Parameters - Error Message, Error Code                                                               
//+------------------------------------------------------------------+
void MyExpert::showError(string msg,int ercode)
  {
   Alert(msg,"-error:",ercode,"!!"); // display error
  }

これは、保護されており、クラスのオブジェクトを処理する間遭遇するすべてのエラーを表示するのに使われるメンバー関数です。2つの引数/パラメータを取ります。エラー記述とエラーコードです。

getBuffers関数

//+------------------------------------------------------------------+
//|  GETBUFFERS FUNCTION                                                                
//|  *No input parameters
//|  *Uses the class data members to get indicator's buffers
//+------------------------------------------------------------------+
void MyExpert::getBuffers()
  {
   if(CopyBuffer(ADX_handle,0,0,3,ADX_val)<0 || CopyBuffer(ADX_handle,1,0,3,plus_DI)<0
      || CopyBuffer(ADX_handle,2,0,3,minus_DI)<0 || CopyBuffer(MA_handle,0,0,3,MA_val)<0)
     {
      Errormsg="Error copying indicator Buffers";
      Errcode = GetLastError();
      showError(Errormsg,Errcode);
     }
  }

この関数はそれぞれのインディケータハンドルを使用しメンバー変数に指定した配列にすべてのインディケータバッファをコピーするのに使われます。

CopyBuffer関数については初稿に説明があります。getBuffers関数は入力パラメータをもちません。その理由はクラスのメンバー変数から値を使っているからです。

ここでは内部エラー関数を使ってバッファのコピー中に発生するエラーを表示しています。

MarginOK 関数

//+------------------------------------------------------------------+
//|  MARGINOK FUNCTION
//| *No input parameters
//| *Uses the Class data members to check margin required to place a trade
//|  with the lot size is ok
//| *Returns TRUE on success and FALSE on failure
//+------------------------------------------------------------------+
bool MyExpert::MarginOK()
  {
   double one_lot_price;                                                        //Margin required for one lot
   double act_f_mag     = AccountInfoDouble(ACCOUNT_FREEMARGIN);                //Account free margin
   long   levrage       = AccountInfoInteger(ACCOUNT_LEVERAGE);                 //Leverage for this account
   double contract_size = SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);  //Total units for one lot
   string base_currency = SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);        //Base currency for currency pair
                                                                                //
   if(base_currency=="USD")
     {
      one_lot_price=contract_size/levrage;
     }
   else
     {
      double bprice= SymbolInfoDouble(symbol,SYMBOL_BID);
      one_lot_price=bprice*contract_size/levrage;
     }
// Check if margin required is okay based on setting
   if(MathFloor(LOTS*one_lot_price)>MathFloor(act_f_mag*TradePct))
     {
      return(false);
     }
   else
     {
      return(true);
     }
  }
  

この関数は2つのジョブをこなします。取引をセットするのに十分なマージンがあることを確認するためチェックし、また取引をセットするのに使用できるフリーマージンの指定されたパーセンテージ以上は使わないことを確認します。このようにしてそれぞれの取引にいくらのお金を使うか調整することができます。

AccountInfoDouble()関数を ENUM_ACCOUNT_INFO_DOUBLE識別子とともに用い、 アカウントにフリーマージンを得ます。 また、AccountInfoInteger() 関数を ENUM_ACCOUNT_INFO_INTEGER識別子とともに用い、アカウントのレバレッジを得ます。 AccountInfoInteger()関数とAccountInfoDouble()関数はアカウント関数でEAで現在使用されているアカウントの詳細情報を取得するのに使われます。

double  AccountInfoDouble(
   int  property_id      // identifier of the property
   );

また、シンボルプロパティ関数SymbolInfoDouble()および、SymbolInfoString()を使用し、契約サイズと使用中通貨の基本通貨(通貨ペア)情報をそれぞれ取得します。SymbolInfoDouble()関数はシンボル名と ENUM_SYMBOL_INFO_DOUBLE 識別子を取ります。一方、 SymbolInfoString()関数はシンボル名と ENUM_SYMBOL_INFO_STRING識別子をパラメータとして取ります。これら関数の結果はそれぞれのでエータタイプに宣言された変数に保存されます。

double  SymbolInfoDouble(
   string  name,        // symbol
   int     prop_id      // identifier of the property
   );

ここで行った計算はひじょうにシンプルなものです。

取引をセットするのに要求されるマージンを得るため、2とおりの状況を考察します。

  1. 基本通貨がUSD (USD/CAD、USD/CHF、USD/JPYなど)

要求されるマージン = ロットまたはレバレッジごとの契約サイズ

2.基本通貨がUSDでない (EUR/USDなど)

要求されるマージン = シンボルの現在価格 * ロットまたはレバレッジごとの契約サイズ

指定のロットサイズやボリュームの取引に要求されるマージンが取引に使用したいフリーマージンのパーセンテージより大きいか確認します。要求されるマージン がフリーマージンのパーセンテージより小さければ、関数はRUEを返し、取引はセットされます。それ以外は FALSEを返し、取引はセットされません。

doInit関数

//+-----------------------------------------------------------------------+
// OUR PUBLIC FUNCTIONS                                                   |
//+-----------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| DOINIT FUNCTION
//| *Takes the ADX indicator's Period and Moving Average indicator's 
//| period as input parameters 
//| *To be used in the OnInit() function of our EA                                                               
//+------------------------------------------------------------------+
void MyExpert::doInit(int adx_period,int ma_period)
  {
//--- Get handle for ADX indicator
   ADX_handle=iADX(symbol,period,adx_period);
//--- Get the handle for Moving Average indicator
   MA_handle=iMA(symbol,period,ma_period,0,MODE_EMA,PRICE_CLOSE);
//--- What if handle returns Invalid Handle
   if(ADX_handle<0 || MA_handle<0)
     {
      Errormsg="Error Creating Handles for indicators";
      Errcode=GetLastError();
      showError(Errormsg,Errcode);
     }
// Set Arrays as series
// the ADX values arrays
   ArraySetAsSeries(ADX_val,true);
// the +DI value arrays
   ArraySetAsSeries(plus_DI,true);
// the -DI value arrays
   ArraySetAsSeries(minus_DI,true);
// the MA values arrays
   ArraySetAsSeries(MA_val,true);
  }

これはパブリック関数で、EAの OnInit()関数に使います。この関数はこれからすぐに書きますが、2つのことを行います。

まず、インディケータにハンドルを設定し、また配列変数上でarray-set-as-series actionを実行します。EAコードから供給される入力パラメータを2つ持ちます。

doUninit関数

//+------------------------------------------------------------------+
//|  DOUNINIT FUNCTION
//|  *No input parameters
//|  *Used to release ADX and MA indicators handleS                                                                |
//+------------------------------------------------------------------+
void MyExpert::doUninit()
  {
//--- Release our indicator handles
   IndicatorRelease(ADX_handle);
   IndicatorRelease(MA_handle);
  }
  

これもパブリックメンバー関数で、EAのUnDeInit 関数で使われ、使ったインディケータのハンドルをすべて解放します。それは入力パラメータを持ちません。

checkBuy関数

//+------------------------------------------------------------------+
//| CHECKBUY FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Buy setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Buy conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkBuy()
  {
/*
    Check for a Long/Buy Setup : MA increasing upwards, 
    previous price close above MA, ADX > ADX min, +DI > -DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Buy Conditions
   bool Buy_Condition_1=(MA_val[0]>MA_val[1]) && (MA_val[1]>MA_val[2]); // MA Increasing upwards
   bool Buy_Condition_2=(Closeprice>MA_val[1]);         // previous price closed above MA
   bool Buy_Condition_3=(ADX_val[0]>ADX_min);          // Current ADX value greater than minimum ADX value
   bool Buy_Condition_4=(plus_DI[0]>minus_DI[0]);       // +DI greater than -DI
//--- Putting all together   
   if(Buy_Condition_1 && Buy_Condition_2 && Buy_Condition_3 && Buy_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

この関数は買いの条件が設定されているかいないかを確認します。この関数の返し値タイプがブールなのはこのためです。TRUE かFALSEを返すことを意味します。これがわれわれが買い戦略を定義した場所です。買い条件が定義し戦略に基づいて満たされればTRUEを返します。また買い条件が 満たされなければ、FALSEを返します。われわれのコードでこの関数を使用する際、それがTRUEを返したら買いをセットします。

ここで最初に行ったことは、内部メンバー関数getBuffers()を呼ぶことで、それは対応する配列変数にたいしてcheckBuy関数が必要とする配列値をすべてコピーします。

ここでコードされる条件については初稿に説明があります。

checkSell関数

//+------------------------------------------------------------------+
//| CHECKSELL FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Sell setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Sell conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkSell()
  {
/*
    Check for a Short/Sell Setup : MA decreasing downwards, 
    previous price close below MA, ADX > ADX min, -DI > +DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Sell Conditions
   bool Sell_Condition_1=(MA_val[0]<MA_val[1]) && (MA_val[1]<MA_val[2]);  // MA decreasing downwards
   bool Sell_Condition_2=(Closeprice <MA_val[1]);                         // Previous price closed below MA
   bool Sell_Condition_3=(ADX_val[0]>ADX_min);                            // Current ADX value greater than minimum ADX
   bool Sell_Condition_4=(plus_DI[0]<minus_DI[0]);                        // -DI greater than +DI

//--- Putting all together
   if(Sell_Condition_1 && Sell_Condition_2 && Sell_Condition_3 && Sell_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

checkBuyのように、この関数は売りの条件が設定されているかいないかを確認します。この関数の返し値タイプもブールなのはこのためです。TRUE かFALSEを返すことを意味します。 これがわれわれが売り戦略を定義した場所です。売り条件が定義した戦略に基づいて満たされれば、TRUEを返します。また売り条件が 満たされなければ、FALSEを返します。

われわれのコードでこの関数を使用する際、それがTRUEを返したら売りをセットします。checkBuyのようにまず内部関数getBuffers() を呼びます。ここでコードされる条件についても<a0>初稿</a0>に説明があります。

openBuy関数

//+------------------------------------------------------------------+
//| OPENBUY FUNCTION
//| *Has Input parameters - order type, Current ASK price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=askprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Buy order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=askprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Buy order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

この関数はEAで呼ばれるときはいつも買いポジションをオープンします。入力パラメータとして取引をセットするのに必要な変数をほとんど有しています。いくつかの変数EAコードによって提供されます。初稿で述べられていることですが、ここではMqlTraderequestタイプ変数を使用していることに気づいてください。

EAコードではそれらを使う必要はありません。取引がセットされる前に、ユーザーがマージンチェックをしたいかChk_Marginの値( EAから取得されます)が1か、 確認したいと思います。それからそれを行うためにMarginOK()関数を呼びます。この関数結果は次に行うべきステップを決定します。ただ、ユーザーがマージンチェックをしたくなければ、続けて取引をセットします。

openSell関数

//+------------------------------------------------------------------+
//| OPENSELL FUNCTION
//| *Has Input parameters - order type, Current BID price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=bidprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Sell order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=bidprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Sell order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

openBuy 関数同様、この関数はEAで呼ばれるときはいつも売りポジションをオープンします。入力パラメータとして取引をセットするのに必要な変数をほとんど有しています。いくつかの変数EAコードによって提供されます。

買いポジションをオープンするときに行ったと同様、取引がセットされる前に、ユーザーがマージンチェックをしたいか確認し、Chk_Margin の値( EAから取得されます)が1であれば、 それを行うためにMarginOK()関数を呼びます。

この関数結果は次に行うべきステップを決定します。ただ、ユーザーがマージンチェックをしたくなければ、続けて取引をセットします。

これでクラスの宣言と定義が終わりました。がまだ、われわれのEAコードの中で扱いたいタスクがいくつか残っています。使用可能なバーの確認、新規バーの確認、使用可能なオープンしているポジションの確認です。それらはわれわれのEAコードにおいて行われます。

すべてのクラスの関数とメソッドのリストを見るには、以下に示すようにMetaEditorの関数コマンド/メニューをクリックします。関数は、われわれのコードでは特に宣言しなかったデストラクタを含めすべてのメンバー関数を表示します。

保護されたメンバーはグリーンの矢印で示され、コンストラクタやデストラクタはブルーの矢印で示されます。

クラスメンバーー関数

図3 クラスデストラクタを表示するクラスメンバー関数

では次は?

デバッグ?という声が聞こえます。たぶん正しいでしょう。コードにエラーがないか検証し確認するのは常に薦められることです。そうでないと公表したときにがっかりすることになるかもしれません。ここで問題となるのは、これはインクルードファイルに過ぎないということです。チャートに添付されているかもしれないコードやスクリプトやインディケータコードではありません。ここでは2つの選択肢があります(私の経験から),

図4 .mqhファイルはコンパイルできません。

2.2. EXPERT ADVISORのプログラミング

みなさんのエディタは開いていると思います。再び新規ドキュメントを起動しますが、今回Expert Advisorを選択します。(詳細については初稿を参照ください。)そして今回はみなさんの名はEA ‘my_oop_ea’とします。

これが今みなさんがいるべき場所です。

EAに基づく OOPを書く準備完了です。

ここでまずしようと思うことは、#includeプリプロセッサコマンドに書いたばかりのクラスをインクルードすることです。最後のプリプロセッサプロパティコマンドの直後にクラスをインクルードします。

//+------------------------------------------------------------------+
//|                                                    my_oop_ea.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
// Include  our class
#include <my_expert_class.mqh>

ファイルインクルード方法は2つあります。

// Include  using angle brackets
#include <my_expert_class.mqh>
// Include  using quotations
#include "my_expert_class.mqh"

山括弧 (< ... >)を使うときは、インクルードするファイルを標準インクルードディレクトリ(それはMQL5ディレクトリの内部のフォルダをインクルードします)から取得するということを意味します。 現在のディレクトリ(MQL5ディレクトリの内部のExpertsフォルダ)はファイル検索先とはみなされません。ただし、ファイルがクオーテーションマーク(" ... ")で囲まれていたら、ファイルは現在使用中ディレクトり(Expertsフォルダ)にあるとみなされます。また、標準ディレクトリ(インクルードフォルダ)は確認されません。

みなさんのクラスがインクルードフォルダ(標準ディレクトリ)に保存されていて、山括弧のかわりにクオーテーションマークを使っているかその逆なら、コードコンパイル時にエラーが発生します。

図5 インクルードファイルが見つからないときに表示されるエラー

EA入力パラメータ

//--- input parameters
input int      StopLoss=30;      // Stop Loss
input int      TakeProfit=100;   // Take Profit
input int      ADX_Period=14;    // ADX Period
input int      MA_Period=10;     // Moving Average Period
input int      EA_Magic=12345;   // EA Magic Number
input double   Adx_Min=22.0;     // Minimum ADX Value
input double   Lot=0.2;          // Lots to Trade
input int      Margin_Chk=0;     // Check Margin before placing trade(0=No, 1=Yes)
input double   Trd_percent=15.0; // Percentage of Free Margin To use for Trading

ここにあるパラメータのほとんどは新しいものではありません。新しいパラメータについてお話します。

マージンチェックをしたければ整数変数は値1、したくなければ0を保持することは知っています。ポジションを開くために使うフリーマージンのパーセンテージの最高値を保持するもう一つの変数を宣言します。これらの値はのちにクラスオブジェクトを作成するときそこで使用します。

入力パラメータの直後に、操作したい(5桁、3桁価格をcaterするため)その他パラメータを2つ(STP および TKP)定義します。入力パラメータの値を変えることはできないからです。それからEA コードで使用するクラスのオブジェクトを作成します。

//--- Other parameters
int STP,TKP;   // To be used for Stop Loss & Take Profit values
// Create an object of our class
MyExpert Cexpert;

以前にお話ししたように、クラスオブジェクトを作成するには、作成したいオブジェクト名が続くクラス名を使います。ここではMyExpertクラスタイプのオブジェクトCexpertを作成しました。CexpertMyExpertクラスのパブリックメンバー関数すべてにアクセスするため使用可能です。

初期化セクション

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- Run Initialize function
   Cexpert.doInit(ADX_Period,MA_Period);
//--- Set all other necessary variables for our class object
   Cexpert.setPeriod(_Period);     // sets the chart period/timeframe
   Cexpert.setSymbol(_Symbol);     // sets the chart symbol/currency-pair
   Cexpert.setMagic(EA_Magic);    // sets the Magic Number
   Cexpert.setadxmin(Adx_Min);    // sets the ADX miniumm value
   Cexpert.setLOTS(Lot);          // set the Lots value
   Cexpert.setchkMAG(Margin_Chk); // set the margin check variable
   Cexpert.setTRpct(Trd_percent); // set the percentage of Free Margin for trade
//--- Let us handle brokers that offers 5 digit prices instead of 4
   STP = StopLoss;
   TKP = TakeProfit;
   if(_Digits==5 || _Digits==3)
     {
      STP = STP*10;
      TKP = TKP*10;
     }  
//---
   return(0);
  }

ここではわれわれのクラスのdoInit関数を呼び、それに ADXおよび MA期間変数を渡します。次に作成したばかりのオブジェクトが必要とするその他変数をすべて設定し、クラスを書いているときすでに記述した関数を使っているオブジェクトのメンバー変数に保存されるようにします。

コードの次の行は奇妙ではありません。ストップロスとテイクプロフィットの値を3桁と5桁の価格と決めたばかりです。

再初期化セクション

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Run UnIntilialize function
   Cexpert.doUninit();
  }

EAの初期化関数において作成されたはずのインディケータハンドルをすべて解放するため、クラスのdoUninit 関数を呼びます。

EA ONTICK セクション

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Do we have enough bars to work with
   int Mybars=Bars(_Symbol,_Period);
   if(Mybars<60) // if total bars is less than 60 bars
     {
      Alert("We have less than 60 bars, EA will now exit!!");
      return;
     }

//--- Define some MQL5 Structures we will use for our trade
   MqlTick latest_price;      // To be used for getting recent/latest price quotes
   MqlRates mrate[];          // To be used to store the prices, volumes and spread of each bar
/*
     Let's make sure our arrays values for the Rates
     is store serially similar to the timeseries array
*/
// the rates arrays
   ArraySetAsSeries(mrate,true);

ここでまずすることは、使用可能なバーの合計を確認することです。われわれのEAが取引するのに十分であれば、取引を行います。そうでなければ、十分なバー(それは60バーです)を得るまで取引はしません。それからMQL5ストラクチャの2つの変数を宣言します。 (MqlTickおよびMqlRates)です。最後に割合配列のArraySetAsSeries関数を使用します。

//--- Get the last price quote using the MQL5 MqlTick Structure
   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("Error getting the latest price quote - error:",GetLastError(),"!!");
      return;
     }

//--- Get the details of the latest 3 bars
   if(CopyRates(_Symbol,_Period,0,3,mrate)<0)
     {
      Alert("Error copying rates/history data - error:",GetLastError(),"!!");
      return;
     }

//--- EA should only check for new trade if we have a new bar
// lets declare a static datetime variable
   static datetime Prev_time;
// lest get the start time for the current bar (Bar 0)
   datetime Bar_time[1];
// copy time
   Bar_time[0] = mrate[0].time;
// We don't have a new bar when both times are the same
   if(Prev_time==Bar_time[0])
     {
      return;
     }
//copy time to static value, save
   Prev_time = Bar_time[0]; 
  

Here, we used the SymbolInfoTick function to get the latest price quote and used CopyRates to get the last rates for the past three bars (present bar inclusive). 新規バーがある場合のコード内次行の確認2つの日時変数を宣言しました。静的変数(Prev_Time) ともうひとつはBar_Timeです。

新規バーがある場合は、バー時刻が静的変数Prev_Timeに保存され、その値を次のティックのBar_Timeの値と比較することができます。 次のティックでは、Prev_TimeBar_Timeと等しければ、 時刻が保存されているのとまだ同じバーです。われわれのEAはrelax.

Bar_TimePrev_Timeと等しくなければ、新規バーがあるということです。新規バーの開始時刻を静的日時変数Prev_Time に保存し、EA はここから新規買いまたは売り機会の確認に進みます。

//--- we have no errors, so continue
//--- Do we have positions opened already?
    bool Buy_opened = false, Sell_opened=false; // variables to hold the result of the opened position
    
    if (PositionSelect(_Symbol) ==true)  // we have an opened position
    {
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            Buy_opened = true;  //It is a Buy
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            Sell_opened = true; // It is a Sell
         }
    }

すでにオープンされたポジションをがあるか確認することにします。すでにオープンになっている買いがないとき、買い取引をオープンし、すでにオープンの売りがないときに売りをオープンすることを確認したいと思います

// Copy the bar close price for the previous bar prior to the current bar, that is Bar 1
   Cexpert.setCloseprice(mrate[1].close);  // bar 1 close price
//--- Check for Buy position
   if(Cexpert.checkBuy()==true)
     {
      // Do we already have an opened buy position
      if(Buy_opened)
        {
         Alert("We already have a Buy Position!!!");
         return;    // Don't open a new Buy Position
        }
      double aprice = NormalizeDouble(latest_price.ask,_Digits);              // current Ask price
      double stl    = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss
      double tkp    = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take profit
      int    mdev   = 100;                                                    // Maximum deviation
      // place order
      Cexpert.openBuy(ORDER_TYPE_BUY,aprice,stl,tkp,mdev);
     }

では、作成したオブジェクトに戻ります。なぜでしょうか?われわれのオブジェクトが動作するために必要な確認はもうできたからです。

最初に行うことは、オブジェクトメンバー関数setClosepriceを使って、前回バーのクローズ価格を得ることです。

それからcheckBuy関数を呼び、買い条件が設定されているか確認します。 TRUEが返ったら、すでにオープンしている買いポジションがないことを確認します。でにオープンしている買いポジションがなければ、注文(注文タイプ、現在ASK価格、ストップロス、テイクプロフィット そして 最大偏差)に使えるよう要求される変数を準備してopenBuy 関数を呼びます。書いたクラスを使うのはいとも簡単だということがおわかりでしょう。

//--- Check for any Sell position
   if(Cexpert.checkSell()==true)
     {
      // Do we already have an opened Sell position
      if(Sell_opened)
        {
         Alert("We already have a Sell position!!!");
         return;    // Don't open a new Sell Position
        }
      double bprice=NormalizeDouble(latest_price.bid,_Digits);                 // Current Bid price
      double bstl    = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss
      double btkp    = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit
      int    bdev=100;                                                         // Maximum deviation
      // place order
      Cexpert.openSell(ORDER_TYPE_SELL,bprice,bstl,btkp,bdev);
     }

これは上記で行ったと同じ事です。買いの確認をしているので、checkSell 関数を呼び、それがTRUEを返し、なおかつすでにオープンしている売りポジションがなければ、要求される変数を準備して注文(注文タイプ、現在ASK価格、ストップロス、テイクプロフィット そして 最大偏差)を出します。そしてopenSell 関数を呼びます。

とても簡単ですよね?コードは書きあがりました。こんどはコードをデバッグする番です。デバッガーの使用法をご存じなければ初稿をご一読ください。よりよい理解が得られます。

F5かデバッグボタンを押すと、インクルードファイル(またはクラス)がインクルードされチェックされます。エラーがあればその報告がきます。エラーがあればコードに戻りエラーを修正します。

図6 われわれのインクルードファイルはメインのEAコードをデバッグする際インクルードされます。

問題なければ、うまくやれたということです。では、ストラテジーテスタを使って EA の検証を行います。検証の前にストラテジーテスタを使ってEAのコンパイルを行う必要があります。これには、コンパイルボタンをクリックするか、お手持ちのコンピュータのキーボドでF7を押します。

図7 コードをコンパイルするためコンパイルメニューボタンをクリックします。

トレーディング端末のメニューバーから、ビュー --> ストラテジーテスタと進むか、 CONTROL+Rを押して、ストラテジーテスタを起動します。(テスタの使用法詳細は初稿を参照ください。)

ストラテジーテスタでEAを検証するためにまずすべてをコンパイルする必要があります。 コンパイルしないと、ストラテジーテスタの設定バーでExpert Advisorを選択するときエラーが発生します。(これは端末の新バージョンで発見しました。)

図8 EAコードはストラテジーテスタで使用する前にコンパイルします。

OOPを基にした Expert Advisorのストラテジー検証結果は以下を参照ください。

図9 OOPを基にした Expert Advisor用取引結果

グラフ

図10 OOPを基にした Expert Advisorのグラフ結果

トレード活動レポート/ジャーナル

図11 OOPを基にした Expert Advisorの取引活動結果

検証用チャート

図12 OOPを基にした Expert Advisorの取引チャート結果


おわりに

本稿では、あるレベルにおいて、クラスの基本とそれをシンプルなExpert Advisorのプログラミングで使用する方法について述べました。クラスの発展した領域はまだそれほど掘り下げていませんが、本稿で述べたことはみなさんがご自身のオブジェクト指向のExpert Advisorコードを書けるレベルに引き上げるに十分な助けとなるものです。

また、オープンしたいポジションに十分使用可能なフリーマージンがないとき、われわれのEA が取引を行わないようにするためのフリーマージンの確認方法についても述べました。

新しいMQL5言語から提供されるものはかなりあり、みなさんはこの新言語をからメリットを得るためにプログラミングのグルである必要はない、ということに賛同いただけることでしょう。 段階的なガイドを書く背後にあるのはそんな理由です。