English Deutsch
preview
プログラミングパラダイムについて(第2部):オブジェクト指向アプローチによるプライスアクションエキスパートアドバイザーの開発

プログラミングパラダイムについて(第2部):オブジェクト指向アプローチによるプライスアクションエキスパートアドバイザーの開発

MetaTrader 5 | 20 5月 2024, 10:58
106 0
Kelvin Muturi Muigua
Kelvin Muturi Muigua

はじめに

最初の記事ではプログラミングパラダイムを紹介し、MQL5で手続き型プログラミングを実装する方法に焦点を当てました。関数型プログラミングについても探求しました。手続き型プログラミングの仕組みについて理解を深めた後、指数移動平均指標(EMA)とローソク足の価格データを使用して、基本的なプライスアクションEAを作成しました。

この記事では、オブジェクト指向プログラミングのパラダイムをより深く掘り下げていきます。この知識を応用して、第1回で開発したEAの手続き型コードをオブジェクト指向コードに変換します。このプロセスによって、これら2つのプログラミングパラダイムの主な違いについての理解が深まるでしょう。

プライスアクション戦略を紹介することが第一の目的ではないことを心に留めておいていただきたいと思います。その代わりに、さまざまなプログラミングパラダイムがどのように機能し、MQL5でどのように実装できるかを説明し、理解を深めてもらうことを目的としています。開発するシンプルプライスアクションEAは第2の目標であり、実世界の例でこれをどのように適用できるかを示すガイドの役割を果たします。


オブジェクト指向プログラミングについて

オブジェクト指向プログラミング(OOPとも略される)は、オブジェクトの考え方を中心にコードを構成するコーディングスタイルです。それは主に、実際の物事や概念のモデルとしてのアイテムを考慮します。

オブジェクト指向プログラミングの世界に足を踏み入れるとき、初心者はしばしば具体的な疑問を抱きます。このプログラミングパラダイムの理解を確実にするために、これらの質問に対処することから始めます。


オブジェクト指向プログラミングにおけるクラスとは?


クラスとは、オブジェクトを作成するための設計図です。オブジェクトの特性を表すプロパティ(属性)と、要求されるさまざまなタスクを実行する関数(メソッド)のセットがあります。

オブジェクト指向のパラダイムをよりよく説明するために、電話を例にしてみましょう。

新しい携帯電話製造会社を立ち上げ、製品デザイン部門の責任者と会議をしているとします。目標は、自社が製造する理想的な携帯電話の設計図を作成することです。この会議では、すべての携帯電話が備えているべき必須機能や特徴について話し合います。

まず、会社が製造するすべての携帯電話の出発点となる設計図を作成することから始めます。オブジェクト指向プログラミングでは、この設計図をクラスと呼びます

製品デザイナーは、設計図を作るにはまず、携帯電話が実行できるさまざまなタスクのリストを考えなければならないと提案します。あなたは次のようなタスクリストを作成しました。

  • 電話の発信・受信
  • ショートテキストメッセージ(SMS)の送受信
  • インターネットを通じたデータの送受信
  • 写真・ビデオの撮影

オブジェクト指向プログラミングでは、上記の設計図で説明したタスクメソッドと呼びます。メソッドは通常の関数と同じですが、クラス内で作成された場合はメソッドまたはメンバー関数と呼ばれます。

そして、すべての携帯電話には、それをよりよく表現するプロパティや特徴があるはずだと判断します。5分間のブレインストーミングをおこない、次のようなリストができました。

  • モデル番号
  • 入力タイプ
  • 画面タイプ
  • 画面サイズ

オブジェクト指向プログラミングでは、設計図(クラスに記述されたプロパティ特性は、クラス属性またはメンバー変数と呼ばれます。属性はクラスの中で変数として宣言されます。


オブジェクト指向プログラミングにおけるオブジェクトとは?


オブジェクトはクラスの実装です。もっと簡単に言えば、クラスは紙の上の計画や設計図であり、オブジェクトはその計画や設計図を実際に実装したものです。

電話会社の例で続けると、あなたと製品デザイナーは電話機の設計図を紙に書き終えました。あなたは、異なる携帯電話消費者市場向けに2種類の携帯電話を製造することを決定しました。最初のモデルは、通話とテキストメッセージの送受信しかできない廉価版となります。2番目のモデルは、最初の廉価版モデルのすべての機能、ハイエンドカメラ、大容量バッテリー、高解像度のタッチスクリーンを備えたハイエンドバージョン(スマートフォン)となります。

あなたはわくわくしながらエンジニアリング部門に向かい、電話の設計図(クラス)をチーフエンジニアに渡し、設計図のデザインを実現するよう指示を出します。彼はすぐに設計図の作成に取りかかります。エンジニアが電話を完成させるのに、だいたい1週間かかります。完成したら、あなたは渡された完成品をテストします。

今あなたが手にしている携帯電話は、このクラス(設計図)から派生したオブジェクトです。廉価版モデルは一部のクラスメソッドしか実装していませんが、ハイエンドの携帯電話モデル(スマートフォン)はすべてのクラスメソッドを実装しています。

この電話の例を、いくつかのコードで示してみましょう。以下の手順に従って、MetaEditor IDEでクラスファイルを作成します。


手順1:MetaEditor IDEを開き、Newメニューアイテムボタンを使用してMQL Wizardを起動します。

MetaEditor Wizard新規インクルードファイル作成


手順2:[New Class]オプションを選択し、[Next]をクリックします。

MQLウィザード新規クラスファイル


手順3Creating classウィンドウで、[Class Name:]入力ボックスを選択します。クラス名としてPhoneClassと入力し、[Include File:]入力ボックスにExperts\OOP_Article\PhoneClass.mqhと入力します。EAのソースコードと同じフォルダにクラスファイルが保存されます。[Base Class:]入力ボックスは空のままにしておきます。[Finish]をクリックして、新しいMQL5クラスファイルを生成します。

MQLウィザード新規クラスファイル詳細


空白のMQL5クラスファイルができました。クラスのさまざまな部分を分解するために、いくつかのコメントを加えました。新しいMQL5クラスをコーディングするのは簡単なプロセスで、MetaEditor IDEのMQLウィザードが自動的にやってくれます。以下の構文は、適切に構造化されたMQL5クラスファイルの出発点を含んでいるので、良くご覧ください。

class PhoneClass //class name
  {
private: //access modifier

public:  //access modifier
                     PhoneClass(); //constructor method declaration
                    ~PhoneClass(); //destructor method declaration
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
PhoneClass::PhoneClass() //constructor method definition
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
PhoneClass::~PhoneClass() //destructor method definition
  {
  }
//+------------------------------------------------------------------+


当面のトピックとは関係ないので、#propertiesコードには注意を払わないでください。重要な構文は、#property version "1.00"のすぐ下の、冒頭のクラス構文class PhoneClass {がある行から始まります。

//+------------------------------------------------------------------+
//|                                                   PhoneClass.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//---------------------------------------
//Ignore the code segment above
//---------------------------------------


先ほど生成したクラスファイルのさまざまな部分について説明しましょう。

クラスを開く中括弧の後に、privatepublicの2つのアクセス修飾子があります。それが何であるかは、継承のトピックを取り上げるときに詳しく説明するつもりです。

privateアクセス修飾子の下には、電話設計図のクラス属性(電話のプロパティと特性)を追加します。これらのプロパティをグローバル変数として追加します。

private: //access modifier
   //class attributes
   int               modelNumber;
   string            phoneColor;
   string            inputType;
   string            screenType;
   int               screenSize;

public:アクセス修飾子の次の行は、コンストラクタメソッドとデストラクタメソッドの宣言です。これら2つのメソッドは、EAの標準関数OnInit()OnDeInit()に似ています。クラスコンストラクタはOnInit()に似た処理をおこない、クラスデストラクタはOnDeinit()に似た処理をおこないます。

public: //access modifier
                     PhoneClass(); //constructor method declaration
                    ~PhoneClass(); //destructor method declaration


オブジェクト指向プログラミングにおけるコンストラクタとデストラクタとは?


コンストラクタ

コンストラクタとは、クラス内の特別なメソッドのことで、そのクラスのオブジェクトが生成されるときに自動的に呼び出され、実行されます。その主な目的は、オブジェクトの属性を初期化し、オブジェクトが有効で更新された状態になるために必要な設定アクションを実行することです。

コンストラクタの主な特徴

  • クラスと同じ名前:コンストラクタメソッドはクラスと同じ名前です。この命名規則は、プログラミング言語がコンストラクタを識別し、クラスと関連付けるのに役立ちます。MQL5では、すべてのコンストラクタはデフォルトでvoid型であり、値を返しません。
  • 初期化:コンストラクタは、オブジェクトの属性をデフォルト値または提供された値で初期化する役割を担います。これにより、オブジェクトは明確に定義された状態からスタートすることになります。これがPhoneClassでどのように機能するのか、以下に示します。
  • 自動実行:コンストラクタは、オブジェクトが生成されるときに自動的に呼び出されるか、実行されます。これは、オブジェクトがインスタンス化された瞬間に発生します。
  • オプションのパラメータ:コンストラクタはパラメータを取ることができ、オブジェクト生成時のカスタマイズが可能です。これらのパラメータは、コンストラクタがオブジェクトの初期状態を設定するために使用する値です。

1つのクラスに複数のコンストラクタを持つことはできますが、引数やパラメータによって区別する必要があります。パラメータによって、コンストラクタは次のように分類されます。

  • デフォルトのコンストラクタ:パラメータを持たないコンストラクタです。
  • パラメトリックコンストラクタ:パラメータを持つコンストラクタです。このコンストラクタのパラメータのいずれかが同じクラスのオブジェクトを参照している場合、自動的にコピーコンストラクタになります。
  • コピーコンストラクタ:同じクラスのオブジェクトを参照する1つ以上のパラメータを持つコンストラクタです。

新しいPhoneClassオブジェクトを作成するたびに、特定の電話詳細でクラス属性(変数)を初期化または保存する方法が必要です。パラメトリックコンストラクタを使用してこのタスクを達成します。現在のデフォルトコンストラクタを変更し、5つのパラメータを持つパラメトリックコンストラクタに変換してみましょう。新しいパラメトリックコンストラクタを宣言し、その下に定義する前に、デフォルトのコンストラクタ関数の宣言をコメントアウトします。

   //PhoneClass();
   //constructor declaration and definition
   PhoneClass(int modelNo, string colorOfPhone, string typeOfInput, string typeOfScreen, int sizeOfScreen)
     {      
      modelNumber = modelNo;
      phoneColor  = colorOfPhone;
      inputType   = typeOfInput;
      screenType  = typeOfScreen;
      screenSize  = sizeOfScreen;
     }

クラスファイルを保存し、コンパイルします。クラスファイルをコンパイルすると、40行目の13列目にエラー「Error:PhoneClass - member function already defined with different parameters.」があることに気づくでしょう。

コード行の番号付けは、コードのインデントやスタイルの付け方によって、クラスファイルで異なる場合があることに注意してください。正しい行番号については、以下に示すように、ウィンドウの下部にあるMetaEditorコンパイラのエラーログを参照してください。

PhoneClassコンパイルエラー

新しいパラメトリックコンストラクタを1つのコードブロックで宣言・定義し、さらにコードの40行目には、コンストラクタを定義している別のコードセグメントがあります。40行目から42行目までをコメントアウトすれば、クラスファイルをコンパイルする際にエラーや警告が出ることなく、正常にコンパイルできるようになります。(このコードセグメントは、クラスファイルの別のコード行にあるかもしれないことに注意してください)

/*PhoneClass::PhoneClass() //constructor method definition
  {
  }*/


デストラクタ

デストラクタとは、オブジェクトが終了するときに自動的に呼び出され実行される、クラス内の特別なメソッドのことです。その主な目的は、ガベージコレクションであり、オブジェクトの寿命が尽きたときに、オブジェクトが割り当てたリソースをクリーンアップすることです。これはメモリリークやその他のリソース関連の問題を防ぐのに役立ちます。クラスは1つのデストラクタしか持つことができません。

デストラクタの主な特徴

  • クラスと同じ名前:デストラクタはクラスと同じ名前ですが、その前にチルダ文字(~)が付きます。この命名規則は、プログラミング言語がデストラクタを識別し、クラスと関連付けるのに役立ちます。
  • ガベージコレクション:メモリ、文字列、動的配列、自動オブジェクト、ネットワーク接続など、オブジェクトが割り当てたリソースをクリーンアップします。
  • 自動実行:デストラクタは、オブジェクトが終了するときに自動的に呼び出されるか、実行されます。
  • パラメータなし:すべてのデストラクタはパラメータを持たず、デフォルトではvoid型です。値は戻しません。

デストラクタが実行されるたびにテキストを表示するコードを追加してみましょう。デストラクタメソッドのコード定義セグメントに行き、以下のコードを追加します。

PhoneClass::~PhoneClass() //destructor method definition
  {
   Print("-------------------------------------------------------------------------------------");
   PrintFormat("(ModelNo: %i) PhoneClass object terminated. The DESTRUCTOR is now cleaning up!", modelNumber);
  }

クラスのコンストラクタとデストラクタのメソッドを宣言または定義するときに、戻り値の型、つまりvoidを与えていないことに気づくでしょう。MQL5のコンストラクタとデストラクタはすべてvoid型であり、コンパイラが自動的にそうしてくれるという単純なルールなので、戻り値の型を指定する必要はありません。

コンストラクタとデストラクタがどのように機能するかを明確に理解するために、簡単な例を挙げます。キャンプ旅行で、あなたと友人が特定の役割を分担する場面を想像してみてください。到着後、テントを立ててすべての手配を担当する友人は、「コンストラクタ」の役割を果たします。一方、旅の終わりに荷造りと後片付けをするあなたは、破壊者の役割を果たします。オブジェクト指向プログラミングでは、コンストラクタはオブジェクトを初期化し、デストラクタはオブジェクトの寿命が終わるとリソースをクリーンアップします。

次に、クラスメソッド(設計図に記述されているように電話が実行するタスク)を追加します。デストラクタメソッド宣言のすぐ下に、メソッド~PhoneClass();を追加します。

//class methods
   bool              MakePhoneCall(int phoneNumber);
   void              ReceivePhoneCall();
   bool              SendSms(int phoneNumber, string message);
   void              ReceiveSms();
   bool              SendInternetData();
   void              ReceiveInternetData();
   void              UseCamera();
   void virtual      PrintPhoneSpecs();


MQL5の仮想メソッドとは?

MQL5では、仮想メソッドはクラス内の特別な関数であり、派生クラス内の同じ名前のメソッドによってオーバーライドすることができます。メソッドが基本クラスでvirtualとマークされると、派生クラスはそのメソッドの別の実装を提供できるようになります。このメカニズムは、異なるクラスのオブジェクトを共通の基本クラスのオブジェクトとして扱うことができるポリモーフィズムに不可欠です。基本クラスで共通のインターフェイスを維持しながら、サブクラスで特定の動作を定義できるようにすることで、オブジェクト指向プログラミングの柔軟性と拡張性を実現できます。

PrintPhoneSpecs()メソッドをオーバーライドする方法は、オブジェクト指向の継承について説明するときに、さらに詳しく説明します。

PrintPhoneSpecs()メソッドのメソッド定義をコーディングするには、このコードを、クラスファイルの一番下にあるデストラクタメソッドの定義の下に配置します。

void PhoneClass::PrintPhoneSpecs() //method definition
  {
   Print("___________________________________________________________");
   PrintFormat("Model: %i Phone Specs", modelNumber);
   Print("---------------------");
   PrintFormat
      (
         "Model Number: %i \nPhoneColor: %s \nInput Type: %s \nScreen Type: %s \nScreen Size: %i\n",
         modelNumber, phoneColor, inputType, screenType, screenSize
      );  
  }

クラスメソッドを定義するには2つの方法があります。 

  1. クラス本体内:先ほどパラメトリックコンストラクタでおこなったように、メソッドをクラス本体内で一度に宣言・定義します。クラス本体内でのメソッドの宣言と定義の構文は、通常の関数の構文と同じです。
  2. クラス本体外:2つ目の方法は、デストラクタやPrintPhoneSpecs()メソッドでおこなったように、まずクラス本体内でメソッドを宣言し、クラス本体の外でメソッドを定義する方法です。MQL5のメソッドをクラス本体の外で定義するには、まずメソッドの戻り型から始め、クラス名、スコープ解決演算子(::)、メソッド名、そして括弧で囲まれたパラメータリストの順を続けます。次に、メソッド本体を中かっこ{}で囲みます。このように宣言と定義を分離することで、クラス構造とそれに関連するメソッドを明確に構成することができるため、これは好ましい選択肢です。

オブジェクト指向プログラミングにおけるスコープ解決演算子(::)とは?


演算子::はスコープ解決演算子として知られており、C++およびMQL5では、関数やメソッドが属するコンテキストを指定するために使用されます。クラスのメンバーである関数やメソッドを定義したり参照したりすることで、特定のクラスのメンバーであることを明示することができます。

PrintPhoneSpecs()メソッドの定義を使用して、もう少し詳しく説明しましょう。

void PhoneClass::PrintPhoneSpecs() //method definition
  {
   //method body
  }

上のPrintPhoneSpecs()メソッド定義から、クラス名がスコープ演算子「::」の前に置かれていることがわかります。これは、この関数がPhoneClassクラスに属していることを示しています。メソッドとそのメソッドが関連付けられているクラスはこのように関連付けられます。演算子「::」は、クラス内でメソッドを定義したり参照したりするのに不可欠です。これは、関数やメソッドが属するスコープやコンテキストを指定するのに役立ちます。


このクラスには以下のメソッドも宣言されており、定義する必要があります。

  1. MakePhoneCall(int phoneNumber);
  2. ReceivePhoneCall();
  3. SendSms(int phoneNumber, string message);
  4. ReceiveSms();
  5. SendInternetData();
  6. ReceiveInternetData();
  7. UseCamera();

メソッド定義コードをPrintPhoneSpecs()定義コード セグメントの上に配置します。上記のメソッド定義を追加したクラスファイルは次のようになります。

class PhoneClass //class name
  {
private: //access modifier
   //class attributes
   int               modelNumber;
   string            phoneColor;
   string            inputType;
   string            screenType;
   int               screenSize;

public: //access modifier
   //PhoneClass();
   //constructor declaration and definition
   PhoneClass(int modelNo, string colorOfPhone, string typeOfInput, string typeOfScreen, int sizeOfScreen)
     {      
      modelNumber = modelNo;
      phoneColor  = colorOfPhone;
      inputType   = typeOfInput;
      screenType  = typeOfScreen;
      screenSize  = sizeOfScreen;
     }
     
   ~PhoneClass(); //destructor method declaration
   
   //class methods
   bool              MakePhoneCall(int phoneNumber);
   void              ReceivePhoneCall();
   bool              SendSms(int phoneNumber, string message);
   void              ReceiveSms();
   bool              SendInternetData();
   void              ReceiveInternetData();
   void              UseCamera();
   void virtual      PrintPhoneSpecs();
  };

/*PhoneClass::PhoneClass() //constructor method definition
  {
  }*/

PhoneClass::~PhoneClass() //destructor method definition
  {
   Print("-------------------------------------------------------------------------------------");
   PrintFormat("(ModelNo: %i) PhoneClass object terminated. The DESTRUCTOR is now cleaning up!", modelNumber);
  }

bool PhoneClass::MakePhoneCall(int phoneNumber) //method definition
  {
      bool callMade = true;
      Print("Making phone call...");
      return(callMade);
  }

void PhoneClass::ReceivePhoneCall(void) { //method definition
      Print("Receiving phone call...");
   }

bool PhoneClass::SendSms(int phoneNumber, string message) //method definition
  {
      bool smsSent = true;
      Print("Sending SMS...");
      return(smsSent);
  }

void PhoneClass::ReceiveSms(void) { //method definition
      Print("Receiving SMS...");
   }

bool PhoneClass::SendInternetData(void) //method definition
  {
      bool dataSent = true;
      Print("Sending internet data...");
      return(dataSent);
  }
  
void PhoneClass::ReceiveInternetData(void) { //method definition
      Print("Receiving internet data...");
   }

void PhoneClass::UseCamera(void) { //method definition
      Print("Using camera...");
   }

void PhoneClass::PrintPhoneSpecs() //method definition
  {
   Print("___________________________________________________________");
   PrintFormat("Model: %i Phone Specs", modelNumber);
   Print("---------------------");
   PrintFormat
      (
         "Model Number: %i \nPhoneColor: %s \nInput Type: %s \nScreen Type: %s \nScreen Size: %i\n",
         modelNumber, phoneColor, inputType, screenType, screenSize
      );  
  }

PhoneClass.mqhの完全なコードは、記事の終わりに添付されています。

次にPhoneClassオブジェクトを作成します。これは、先ほどの電話会社の例で説明したように、電話のエンジニアが電話の設計図を、さまざまなタスク(例:電話の着受信)を実行できる物理的な製品に作り変えたのと同じことです。

クラスファイルは拡張子.mqhで保存され、インクルードファイルと呼ばれます。PhoneClass.mqhがある同じフォルダに新しいEAを作成します。PhoneClass.mqhファイルをファイルパス「Experts\OOP_Article\」に保存しました。MetaEditor MQLウィザードを使用して新しいEA(テンプレート)を生成し、ディレクトリパス「ExpertsOOP_Article」に保存します。新しいEAにPhoneObject.mq5という名前を付けます。

次のコードを、EA#property version   "1.00"コードセグメントの下に配置します。

// Include the PhoneClass file so that the PhoneClass code is available in this EA
#include "PhoneClass.mqh"

int OnInit()
  {
//---
   // Create instaces or objects of the PhoneClass with specific parameters
   // as specified in the 'PhoneClass' consturctor
   PhoneClass myPhoneObject1(101, "Black", "Keyboard", "Non-touch LCD", 4);
   PhoneClass myPhoneObject2(102, "SkyBlue", "Touchscreen", "Touch AMOLED", 6);

   // Invoke or call the PrintPhoneSpecs method to print the specifications
   myPhoneObject1.PrintPhoneSpecs();
   myPhoneObject2.PrintPhoneSpecs();
//---
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason){}
void OnTick(){}

完全なPhoneObject.mq5のコードは記事の終わりに添付されています。

PhoneObject.mq5のEAコードが何をしているのか、詳しく見てみましょう。

Include文を使用してクラスファイルを追加する

まず、#include PhoneClass.mqhを使用してPhoneClassのコードをEAに追加し、新しく作成したEA(PhoneObject.mq5)で使用できるようにします。このクラスは、EAのすべてのセクションで利用できるよう、グローバルスコープに含まれています。


PhoneClassオブジェクトの作成

EAのOnInit()関数の内部で、PhoneClassの2つのインスタンスまたはオブジェクトを作成しました。これらのオブジェクトを作成するために、まずクラス名から始め、電話のインスタンスの説明的な名前(myPhoneObject1myPhoneObject2)を付けました。次に、PhoneClassコンストラクタのパラメータで指定されたモデル番号、色、入力タイプ、画面タイプ、画面サイズなど、電話の仕様の値を括弧で囲みました。


クラスメソッドの呼び出しまたは起動

myPhoneObject1.PrintPhoneSpecs()myPhoneObject2.PrintPhoneSpecs()は、PhoneClassオブジェクトのPrintPhoneSpecs()メソッドを呼び出して、電話仕様を表示します。


電話仕様の出力

PhoneObjectEAを実行するために、MT5取引端末の銘柄チャートにEAを読み込み、ツールボックスウィンドウに移動し、出力された携帯電話の仕様を確認するために[エキスパート]タブを選択します。

出力されたデータには、PhoneClassデストラクタ (~PhoneClass()) からのテキストメッセージも表示されます。各電話オブジェクトは、ユニークな独立したデストラクタを作成し、オブジェクトの終了時にそれを呼び出すことがわかります。

PhoneObjectEAエキスパートログ


オブジェクト指向プログラミングにおける継承とは?


継承とは、サブクラスや子クラスと呼ばれる新しいクラスが、親クラスや基本クラスと呼ばれる既存のクラスから属性や振る舞い(プロパティやメソッド)を継承できるという概念です。これにより、子クラスは親クラスの機能を再利用し、拡張することができます。

簡単に言えば、継承は家系図のようなものです。基本クラスを「親」あるいは「母親」とイメージしてください。このクラスは特定の特性(プロパティとメソッド)を持っています。さて、「サブクラス」とは「子」あるいは「娘」のことだと考えてください。サブクラスは、子が親から特徴を受け継ぐのと同じように、基本クラスのすべての特徴(プロパティとメソッド)を自動的に継承します。

例えば、母親が茶色の目であれば、明言しなくても娘も茶色の目です。プログラミングでは、サブクラスは基本クラスからメソッドや属性を継承し、コードを整理して再利用するための階層を作ります。

この「家族」構造は、コードの整理と再利用に役立ちます。子(サブクラス)は、親(基本クラス)が持っているものすべてを手に入れ、独自の機能を追加することもできます。プログラマーは、これらの「家族」をさまざまな言葉で表現します。

  • 基本クラス:親クラス、スーパークラス、ルートクラス、基礎クラス、マスタークラス
  • サブクラス:子クラス、派生クラス、子孫クラス、継承クラス

以下は、PhoneClassコードのコンテキストで継承を実装する方法です。

  • PhoneClassは、電話の機能の基本的な構成要素を定義する基本クラス(設計図)として機能します。
  • 先に電話会社の例で説明したハイエンド(スマートフォン)電話モデルを実装するために、別のクラスを作成します。
  • この新しいクラスをSmartPhoneClassと呼ぶことにします。PhoneClassからすべてのプロパティとメソッドを継承しつつ、スマートフォン特有の新機能を導入し、PhoneClassからの既存のPrintPhoneSpecs()メソッドをオーバーライドしてスマートフォンの動作を実装します。
新しい空のSmartPhoneClass.mqhクラスファイルを生成するには、MetaEditorのMQLウィザードを使用してPhoneClassを作成した手順に従ってください。SmartPhoneClass.mqhの本体に以下のコードを挿入します。

#include "PhoneClass.mqh" // Include the PhoneClass file
class SmartPhoneClass : public PhoneClass
{
private:
    string operatingSystem;
    int numberOfCameras;

public:
    SmartPhoneClass(int modelNo, string colorOfPhone, string typeOfInput, string typeOfScreen, int sizeOfScreen, string os, int totalCameras)
        : PhoneClass(modelNo, colorOfPhone, typeOfInput, typeOfScreen, sizeOfScreen)
    {
        operatingSystem = os;
        numberOfCameras = totalCameras;
    }

    void UseFacialRecognition()
    {
        Print("Using facial recognition feature...");
    }

    // Override methods from the base class if needed
    void PrintPhoneSpecs() override
    {
        Print("-----------------------------------------------------------");
        Print("Smartphone Specifications (including base phone specs):");
        Print("-----------------------------------------------------------");
        PrintFormat("Operating System: %s \nNumber of Cameras: %i", operatingSystem, numberOfCameras);
        PhoneClass::PrintPhoneSpecs(); // Call the base class method
        Print("-----------------------------------------------------------");
    }
};

以下は、完全なSmartPhoneClass.mqhコードへの直接リンクです。 

上記の例では、SmartPhoneClassPhoneClassを継承しています。これは、新しいプロパティ(operatingSystemnumberOfCameras)と新しいメソッド(UseFacialRecognition)を導入しています。SmartPhoneClassのコンストラクタは基本クラスのコンストラクタ(PhoneClass)も呼び出します。これには:PhoneClass(...)を使用します。また、基本クラスからPrintPhoneSpecs()メソッドをオーバーライドしていることにもお気づきでしょう。SmartPhoneClassPrintPhoneSpecs()メソッド定義にoverride指定子を含め、基本クラスのメソッドを意図的にオーバーライドしていることをコンパイラに知らせます。

このようにして、通常の電話(PhoneClass)のすべての機能と、スマートフォン特有の新しい追加機能を含むSmartPhoneClassのインスタンスを作成することができます。


アクセス修飾子

アクセス修飾子は、基本クラスのメンバー(属性やメソッド)を継承し、派生クラスでどのようにアクセスするかを定義することで、オブジェクト指向プログラミングにおける継承において重要な役割を果たします。

  • Public Publicアクセスは、クラスのプロパティやメソッドにクラスの外部からアクセスできるようにするものです。publicメンバーは、プログラムのどの部分からでも自由に使用したり変更したりすることができます。これは最もオープンなアクセスレベルです。
  • Private:Privateアクセスは、プロパティやメソッドの可視性を、クラス自体の中でのアクセスや変更に制限します。privateと宣言されたメンバーは、クラスの外から直接アクセスすることはできません。これは、実装の詳細を隠し、データの完全性を強制するのに役立ちます。これがカプセル化と呼ばれるものです。
  • Protected:Protectedアクセスは、publicとprivateの中間です。protectedと宣言されたメンバーは、クラスとそのサブクラス(派生クラス)内でアクセス可能です。これにより、外部からのアクセスを制限しつつも、関連するクラス間で一定レベルの管理された共有が可能になります。

SmartPhoneClassオブジェクトを作成するには、新しいEAを作成してSmartPhoneObject.mq5として保存し、以下のコードを挿入します。

// Include the PhoneClass file so that it's code is available in this EA
#include "SmartPhoneClass.mqh"

int OnInit()
  {
//---
   // Create instaces or objects of the PhoneClass with specific parameters
   // as specified in the 'PhoneClass' consturctor (base/mother class)
   PhoneClass myPhoneObject1(103, "Grey", "Touchscreen", "Touch LCD", 8);
   
   // as specified in the 'SmartPhoneClass' consturctor
   SmartPhoneClass mySmartPhoneObject1(104, "White", "Touchscreen", "Touch AMOLED", 6, "Android", 3);

   // Invoke or call the PrintPhoneSpecs method to print the specifications
   myPhoneObject1.PrintPhoneSpecs(); // base class method
   mySmartPhoneObject1.PrintPhoneSpecs(); // overriden method by the derived class (SmartPhoneClass)
//---
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason){}
void OnTick(){}

完全な SmartPhoneObject.mq5のコード一式は、記事の一番下に添付されています。

SmartPhoneObject.mq5ソースコードを保存してコンパイルしてから、MT5取引端末の銘柄チャートに読み込んでSmartPhoneClassオブジェクトを実行します。ツールボックスウィンドウに移動し、[エキスパート]タブを選択して、表示されているEA出力を確認します。


オブジェクト指向プログラミングの主な特性

OOPの6つの主な特性は以下の通りです。

  1. カプセル化:データやメソッドを1つのユニット(クラス)にまとめ、内部の詳細を隠す
  2. 抽象化:本質的な特性や挙動に焦点を当てることで、複雑なシステムを単純化する
  3. 継承:コードの再利用性を高めるために、クラスが他のクラスからプロパティや動作を継承できるようにする
  4. ポリモーフィズム:柔軟性を持たせるために、異なるクラスのオブジェクトを共通の基本クラスのオブジェクトとして扱えるようにする
  5. クラスとオブジェクト:クラスは設計図であり、オブジェクトはインスタンスである
  6. メッセージの受け渡し:オブジェクトはメッセージを送ることでコミュニケーションをとり、相互作用を促進する

EIP(カプセル化、継承、ポリモーフィズム):これらは、コードの構成と柔軟性のためのオブジェクト指向プログラミングの核となる原則です。これらの核となる特性を理解し、適用することで、より整理された、保守可能で再利用可能なコードを書くことができます。


MQL5クラス命名規則

MQL5でクラス名によく使用される命名規則は、プレフィックスにCを付けることです。しかし、この慣例に従う義務はありません。Cは「クラス」を表し、特定の識別子がクラスを表すことを明確にするための一般的な慣行です。

たとえば、MQL5のコードではCExpertCIndicatorCStrategyといったクラス名を目にします。この命名規則は、クラスを関数や変数のような他のタイプの識別子と区別するのに役立ちます。

接頭辞としてCを使用することは慣例であり、一般的にはわかりやすさのために推奨されますが、MQL5ではクラスの命名に関する厳格な規則はありません。厳密に言えばCという接頭辞を付けずにクラス名を付けることもできますが、コードの可読性と保守性を高めるためには、確立された慣例に従うのがよい習慣です。


プライスアクションEAを開発するためのオブジェクト指向アプローチ

オブジェクト指向プログラミングパラダイムについてすべて理解したところで、いよいよ実践的な例題に取り組み、以前に開発したプライスアクションベースのEAを手続き型コードからオブジェクト指向コードに変換してみましょう。この記事の一番下に、プライスアクションEAの手続き型コードProcedural_PriceActionEMA.mq5を添付しました。

ここでは、プライスアクション取引戦略の詳細を簡単に説明します。取引戦略のより詳細な説明は、最初の記事にあります。


プライスアクションEMA戦略


この戦略は非常にシンプルで、指数移動平均(EMA)とローソク足の価格のみを使用して取引判断を下します。ストラテジーテスターを使用して、最適なEMA設定と時間枠に最適化する必要があります。私は、より良い結果を得るために1時間足以上の時間枠で取引することを好みます。

エントリルール

  • 買う:直近で閉じたローソク足が買いローソク足(始値<終値)で、安値と高値が指数移動平均(EMA)線を上回ったら買いポジションを建てる。
  • 売る: 直近で閉じたローソク足が売りローソク足(始値>終値)で、安値と高値が指数移動平均(EMA)線を下回ったら売りポジションを建てる。
  • 上記の条件のいずれかが満たされた場合、新しいローソク足が形成されたときに新規の買いポジションまたは売りポジションを建て続ける。

エグジットルール

  • ユーザーが指定した口座の利益または損失のパーセンテージが達成されたら、すべての未決済ポジションを自動的にクローズする
  • あるいは、あらかじめ定義された伝統的な損切り注文や利食い注文を使用して、ポジションを管理・決済することもできる
プライスアクションEMA戦略の概要


この記事の目的は、上記の戦略をオブジェクト指向の原則を使用してmql5 EAに開発する方法を紹介することなので、先にコードを書いてみましょう。

CEmaExpertAdvisorクラスの新規作成


MQL5ウィザードを使用して、EmaExpertAdvisorをクラスファイル名とする空白のクラスファイルを作成し、ファイルパス「Experts\OOP_Article\PriceActionEMA\」に保存します。新しく作成したEmaExpertAdvisor.mqhクラスファイル内に、CEmaExpertAdvisorという名前で新しいクラスを作成します。 EAの動作をカプセル化するためにCEmaExpertAdvisorクラスを使用し、その状態を表すためにメンバー変数を使用します。

CEmaExpertAdvisorクラスに以下のクラスプロパティ/メンバー変数とメソッドのコードを挿入します。

//+------------------------------------------------------------------+
// Include the trade class from the standard library
//--- 
#include <Trade\Trade.mqh>

class CEmaExpertAdvisor
  {

public:
   CTrade            myTrade;
   
public:
   // Constructor
                     CEmaExpertAdvisor(
      long _magicNumber, ENUM_TIMEFRAMES _tradingTimeframe,
      int _emaPeriod, int _emaShift, bool _enableTrading,
      bool _enableAlerts, double _accountPercentageProfitTarget,
      double _accountPercentageLossTarget, int _maxPositions, int _tp, int _sl
   );

   // Destructor
                    ~CEmaExpertAdvisor();
  };

クラスキーワードの前に、MQL5標準ライブラリのTradeクラスを入れて、より少ないコードで様々な取引操作を効率的に管理できるようにしました。つまり、ManageProfitAndLoss()メソッドとBuySellPosition(...)メソッドを、この新しい効率的なアップグレードに対応するように書き直さなければなりません。

#include <Trade\Trade.mqh>

この後、CTradeクラスをインスタンス化し、新しいポジションのオープンとクローズに使用するmyTradeという名前のオブジェクトを作成したことがわかります。

//Create an instance/object of the included CTrade class
   CTrade myTrade;

手続き型コードのすべてのユーザー入力グローバル変数は、CEmaExpertAdvisorクラスのprivateグローバル変数になります。EAユーザー入力変数は、クラスがインスタンス化されるとすぐに初期化される必要があるので、コンストラクタにパラメータとして渡すことでこれを実現します。これは、初期化処理をクラス内にカプセル化するのに役立ちます。

private:
   // Private member variables/attributes (formerly procedural global variables)
   //------------------------
   // User input varibles
   long              magicNumber;
   ENUM_TIMEFRAMES   tradingTimeframe;
   int               emaPeriod;
   int               emaShift;
   bool              enableTrading;
   bool              enableAlerts;
   double            accountPercentageProfitTarget;
   double            accountPercentageLossTarget;
   int               maxPositions;
   int               TP;
   int               SL;

手続き型コードの残りのグローバル変数はpublicとして宣言され、クラスのグローバル変数として定義されます。

public:
   //--- EA global variables
   // Moving average variables
   double            movingAverage[];
   int               emaHandle;
   bool              buyOk, sellOk;
   string            movingAverageTrend;

   // Strings for the chart comments
   string            commentString, accountCurrency, tradingStatus, accountStatus;

   // Capital management variables
   double            startingCapital, accountPercentageProfit;

   // Orders and positions variables
   int               totalOpenBuyPositions, totalOpenSellPositions;
   double            buyPositionsProfit, sellPositionsProfit, buyPositionsVol, sellPositionsVol;

   datetime          closedCandleTime;//used to detect new candle formations

デストラクタメソッド宣言のすぐ上、中かっこでクラスを閉じる構文の下に、すべての手続き型コード関数をクラスメソッド宣言として追加します。

// Class method declarations (formerly procedural standalone functions)
   int               GetInit();
   void              GetDeinit();
   void              GetEma();
   void              GetPositionsData();   
   bool              TradingIsAllowed();
   void              TradeNow();
   void              ManageProfitAndLoss();
   void              PrintOnChart();
   bool              BuySellPosition(int positionType, string positionComment);
   bool              PositionFound(string symbol, int positionType, string positionComment);

C++のコーディングスタイルを使い、以下のようにクラス本体の下にすべてのクラスメソッドを定義します。

//+------------------------------------------------------------------+
//|   METHODS DEFINITIONS                                                                |
//+------------------------------------------------------------------+
CEmaExpertAdvisor::CEmaExpertAdvisor(long _magicNumber, ENUM_TIMEFRAMES _tradingTimeframe,
                                   int _emaPeriod, int _emaShift, bool _enableTrading,
                                   bool _enableAlerts, double _accountPercentageProfitTarget,
                                   double _accountPercentageLossTarget,
                                   int _maxPositions, int _tp, int _sl)
  {
   magicNumber = _magicNumber;
   tradingTimeframe = _tradingTimeframe;
   emaPeriod = _emaPeriod;
   emaShift = _emaShift;
   enableTrading = _enableTrading;
   enableAlerts = _enableAlerts;
   accountPercentageProfitTarget = _accountPercentageProfitTarget;
   accountPercentageLossTarget = _accountPercentageLossTarget;
   maxPositions = _maxPositions;
   TP = _tp;
   SL = _sl;
  }
//+------------------------------------------------------------------+
CEmaExpertAdvisor::~CEmaExpertAdvisor() {}
//+------------------------------------------------------------------+
int CEmaExpertAdvisor::GetInit()
  {
    //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::GetDeinit()
  {
    //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::GetEma()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::GetPositionsData()
  {
   //method body....
  }
//+------------------------------------------------------------------+
bool CEmaExpertAdvisor::TradingIsAllowed()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::TradeNow()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::ManageProfitAndLoss()
  {
   //method body....
  }
//+------------------------------------------------------------------+
void CEmaExpertAdvisor::PrintOnChart()
  {
   //method body....
  } 
//+------------------------------------------------------------------+
bool CEmaExpertAdvisor::BuySellPosition(int positionType, string positionComment)
  {
   //method body....
  }
//+------------------------------------------------------------------+
bool CEmaExpertAdvisor::PositionFound(string symbol, int positionType, string positionComment)
  {
   //method body....
  }

ManageProfitAndLoss()メソッドとBuySellPosition(...)メソッドが、先にクラスコードにインポートしたCTradeクラスの新しく作成されたmyTradeオブジェクトを使用するようにアップグレードされている以外は、すべてのメソッド定義は手続き型コードの構文と同じです。

以下は、更新された新しいManageProfitAndLoss()メソッドです。

void CEmaExpertAdvisor::ManageProfitAndLoss()
  {
//if the account percentage profit or loss target is hit, delete all positions
   double lossLevel = -accountPercentageLossTarget;
   if(
      (accountPercentageProfit >= accountPercentageProfitTarget || accountPercentageProfit <= lossLevel) ||
      ((totalOpenBuyPositions >= maxPositions || totalOpenSellPositions >= maxPositions) && accountPercentageProfit > 0)
   )
     {
      //delete all open positions
      if(PositionsTotal() > 0)
        {
         //variables for storing position properties values
         ulong positionTicket;
         long positionMagic, positionType;
         string positionSymbol;
         int totalPositions = PositionsTotal();

         //scan all the open positions
         for(int x = totalPositions - 1; x >= 0; x--)
           {
            positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
            positionMagic = PositionGetInteger(POSITION_MAGIC);
            positionSymbol = PositionGetString(POSITION_SYMBOL);
            positionType = PositionGetInteger(POSITION_TYPE);
            int positionDigits= (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS);
            double positionVolume = PositionGetDouble(POSITION_VOLUME);
            ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            if(positionMagic == magicNumber && positionSymbol == _Symbol) //close the position
              {
               //print the position details
               Print("*********************************************************************");
               PrintFormat(
                  "#%I64u %s  %s  %.2f  %s [%I64d]",
                  positionTicket, positionSymbol, EnumToString(positionType), positionVolume,
                  DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), positionDigits), positionMagic
               );
               
               //print the position close details
               PrintFormat("Close #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
               //send the tradeRequest
               if(myTrade.PositionClose(positionTicket, SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * 3)) //success, position has been closed
                 {
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " PROFIT LIQUIDATION: Just successfully closed POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Just successfully closed position: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  myTrade.PrintResult();
                 }
               else  //trade tradeRequest failed
                 {
                  //print the information about the operation
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " ERROR ** PROFIT LIQUIDATION: closing POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Position clossing failed: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("OrderSend error %d", GetLastError());//print the error code
                 }
              }
           }
        }
     }
  }


以下は、更新された新しいBuySellPosition(...)メソッドです。

bool CEmaExpertAdvisor::BuySellPosition(int positionType, string positionComment)
  {
   double volumeLot = NormalizeDouble(((SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) * AccountInfoDouble(ACCOUNT_EQUITY)) / 10000), 2);
   double tpPrice = 0.0, slPrice = 0.0, symbolPrice;
   
   if(positionType == POSITION_TYPE_BUY)
     {
      if(sellPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((sellPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      
      volumeLot = NormalizeDouble(volumeLot, 2);
      symbolPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      
      if(TP > 0)
        {
         tpPrice = NormalizeDouble(symbolPrice + (TP * _Point), _Digits);
        }
      if(SL > 0)
        {
         slPrice = NormalizeDouble(symbolPrice - (SL * _Point), _Digits);
        }
      //if(myTrade.Buy(volumeLot, NULL, 0.0, 0.0, 0.0, positionComment)) //successfully openend position
      if(myTrade.Buy(volumeLot, NULL, 0.0, slPrice, tpPrice, positionComment)) //successfully openend position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend BUY POSITION!");
           }
         myTrade.PrintResult();
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a BUY POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a BUY POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }

   if(positionType == POSITION_TYPE_SELL)
     {
      if(buyPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((buyPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      volumeLot = NormalizeDouble(volumeLot, 2);
      symbolPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if(TP > 0)
        {
         tpPrice = NormalizeDouble(symbolPrice - (TP * _Point), _Digits);
        }
      if(SL > 0)
        {
         slPrice = NormalizeDouble(symbolPrice + (SL * _Point), _Digits);
        }
      if(myTrade.Sell(volumeLot, NULL, 0.0, slPrice, tpPrice, positionComment)) //successfully openend position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend SELL POSITION!");
           }
           myTrade.PrintResult();
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a SELL POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a SELL POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }
   return(false);
  }

すべてのメソッド/メンバー関数を、元の手続き型コードのロジックで実装することを忘れないでください。CEmaExpertAdvisorの完全なコードは、この記事の最後に添付されているEmaExpertAdvisor.mqhインクルードファイルの中にあります。


新しいOOP_PriceActionEMA EAの作成


取引戦略の設計図(CEmaExpertAdvisorクラス)が完成したら、いよいよ実行します。取引を実行できる現実のオブジェクトを作成することで、設計図に命を吹き込みます。

新しいEAを作成し、名前をOOP_PriceActionEMA.mq5とし、指定したファイルパス「Experts\OOP_Article\PriceActionEMA」に保存します。このEAが取引戦略の実行を担当します。

CEmaExpertAdvisorクラスを含む EmaExpertAdvisor.mqhインクルードファイルをインポートすることから始めます。

// Include the CEmaExpertAdvisor file so that it's code is available in this EA
#include "EmaExpertAdvisor.mqh"

次に、ユーザー入力変数をグローバル変数として宣言・定義します。これらは、EAを設定するためのユーザー入力変数で、手続き型バージョンで以前はグローバル変数だったパラメータに似ています。

//--User input variables
input long magicNumber = 101;//Magic Number (Set 0 [Zero] to disable

input group ""
input ENUM_TIMEFRAMES tradingTimeframe = PERIOD_H1;//Trading Timeframe
input int emaPeriod = 15;//Moving Average Period
input int emaShift = 0;//Moving Average Shift

input group ""
input bool enableTrading = true;//Enable Trading
input bool enableAlerts = false;//Enable Alerts

input group ""
input double accountPercentageProfitTarget = 6.0;//Account Percentage (%) Profit Target
input double accountPercentageLossTarget = 10.0;//Account Percentage (%) Loss Target

input group ""
input int maxPositions = 3;//Max Positions (Max open positions in one direction)
input int TP = 5000;//TP (Take Profit Points/Pips [Zero (0) to diasable])
input int SL = 500;//SL (Stop Loss Points/Pips [Zero (0) to diasable])

続いて、CEmaExpertAdvisorのインスタンスを作成します。この行は、コンストラクタを使用してCEmaExpertAdvisorクラスのインスタンス(ea)を生成し、ユーザー入力変数の値で初期化します。

//Create an instance/object of the included CEmaExpertAdvisor class
//with the user inputed data as the specified constructor parameters
CEmaExpertAdvisor ea(
      magicNumber, tradingTimeframe, emaPeriod, emaShift,
      enableTrading, enableAlerts, accountPercentageProfitTarget,
      accountPercentageLossTarget, maxPositions, TP, SL
   );

OnInit 関数では、EAインスタンスのGetInit メソッドを呼び出します。このメソッドはCEmaExpertAdvisorクラスの一部であり、EAの初期化を担当します。初期化に失敗した場合はINIT_FAILEDを返し、そうでない場合はINIT_SUCCEEDEDを戻します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(ea.GetInit() <= 0)
     {
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

OnDeinit関数では、EAインスタンスのGetDeinitメソッドを呼び出す。このメソッドはCEmaExpertAdvisorクラスの一部であり、EAの初期化を解除します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ea.GetDeinit();
  }

OnTick関数では、GetEmaGetPositionsDataTradingIsAllowedTradeNowManageProfitAndLossPrintOnChartなど、EAインスタンスのさまざまなメソッドを呼び出します。これらのメソッドは、EAの動作のさまざまな側面をカプセル化し、コードをよりモジュール化し、整理します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   ea.GetEma();
   ea.GetPositionsData();
   if(ea.TradingIsAllowed())
     {
      ea.TradeNow();
      ea.ManageProfitAndLoss();
     }
   ea.PrintOnChart();
  }

EAのソースコード一式は、この記事の一番下にある OOP_PriceActionEMA.mq5ファイルに添付してあります。


ストラテジーテスターでEAをテストする

EAが計画通りに作動していることを確認することは必須です。これは、アクティブな銘柄チャートに読み込んでデモ口座で取引するか、徹底的な評価のためにストラテジーテスターを利用することによっておこなうことができます。デモ口座でテストすることもできますが、今はストラテジーテスターを使用してそのパフォーマンスを評価することにしましょう。

以下は、ストラテジーテスターで適用する設定です。

  • 証券会社: MT5 Metaquotesデモ口座(MT5のインストール時に自動的に作成)

  • 銘柄: EURJPY

  • テスト期間(日付): 1年2ヶ月(2023年1月~2024年3月)

  • モデリング: 実際のティックに基づいたすべてのティック

  • 保証金:10,000米ドル

  • レバレッジ: 1:100

OOP_PriceActionEMAテスター設定

OOP_PriceActionEMAテスター入力


最適化されたEAセットアップにより、EURJPYペアで10,000ドルの資金で取引を開始し、1:100のレバレッジ口座を利用し、わずか5%の低資本ドローダウンを維持した場合、シンプルプライスアクション戦略は年間41%の利益を生み出します。この戦略は可能性を示しており、特に複数の銘柄に同時に適用する場合、より良い結果を得るためにテクニカル指標を追加統合したり、最適化したりすることでさらに強化することができます。


OOP_PriceActionEMAバックテストグラフ

OOP_PriceActionEMAバックテスト結果

OOP_PriceActionEMAバックテスト結果



結論

ソフトウエアを構築するための強力なツールであるオブジェクト指向プログラミングパラダイムについての探求は、ここまでとなりました。コードをモジュール化された再利用可能な構造に変換するこの強力なパラダイムの複雑さをナビゲートしてきました。手続き型プログラミングからオブジェクト指向プログラミングへの移行は、新しいレベルの組織化、カプセル化、抽象化をもたらし、複雑なプロジェクトを管理するための強固な枠組みを開発者に提供します。

この記事では、クラス、オブジェクト、継承の重要性を強調しながら、オブジェクト指向の原則を使用して手続き型MQL5コードをオブジェクト指向コードに変換する方法も学びました。データや機能をクラス内にカプセル化することで、コードのモジュール性と保守性を高めることができます。

独自のMQL5プロジェクトに取り組むときは、オブジェクト指向プログラミングの強みは、現実世界のエンティティと関係をモデル化し、それが表すシステムの複雑さを反映するコードを育成できることにあることを思い出してください。記事の最後に、作成した様々なクラスとEAのソースコードファイルをすべて添付しました。

さまざまなプログラミングパラダイムへのディープダイブにお付き合いいただき、ありがとうございました。ここで発見した原則と実践によって、読者のコーディング活動がより豊かになれば幸いです。広く愛されている強力なMQL5言語を使用してシンプルで実用的な取引システムを開発するという私たちの継続的な探求において、さらなる洞察と実用的な例をご期待ください。

この記事を読むために時間を費やしていただきありがとうございました。読者のMQL5開発の旅と取引の試みが最高のものになることを祈っています。



MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14161

Pythonを使用した深層学習GRUモデルとEAによるONNX、GRUとLSTMモデルの比較 Pythonを使用した深層学習GRUモデルとEAによるONNX、GRUとLSTMモデルの比較
Pythonを使用してGRU ONNXモデルを作成する深層学習のプロセス全体を説明し、最後に取引用に設計されたエキスパートアドバイザー(EA)の作成と、その後のGRUモデルとLSTNモデルの比較をおこないます。
MQL5における修正グリッドヘッジEA(第3部):シンプルヘッジ戦略の最適化(I) MQL5における修正グリッドヘッジEA(第3部):シンプルヘッジ戦略の最適化(I)
この第3部では、以前に開発したシンプルヘッジとシンプルグリッドエキスパートアドバイザー(EA)を再考します。最適な戦略の使用を目指し、数学的分析と総当り攻撃アプローチを通じてシンプルヘッジEAを改良することに焦点を移します。戦略の数学的最適化について深く掘り下げ、後の回でコーディングに基づく最適化を探求するための舞台を整えます。
MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ(第6回) MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ(第6回)
この記事は、MQTT 5.0プロトコル用のネイティブMQL5クライアントの開発ステップを説明する連載の第6部です。今回は、私たちの最初のリファクタリングにおける主な変更点、私たちがどのようにしてパケット構築クラスのための実行可能な設計図にたどり着いたか、どのようにPUBLISHとPUBACKパケットを構築しているか、そしてPUBACK Reason Codeの背後にあるセマンティクスについてコメントします。
MQL5入門(第5部):MQL5における配列関数の入門ガイド MQL5入門(第5部):MQL5における配列関数の入門ガイド
全くの初心者のために作られた第5部では、MQL5配列の世界を探検してみましょう。この記事は、複雑なコーディングの概念を簡素化し、明快さと包括性に重点を置いています。質問が受け入れられ、知識が共有される、学習者のコミュニティに仲間入りしてください。