MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第3部)成行注文と取引のコレクション、検索と並び替え

Artyom Trishkin | 28 6月, 2019

内容

検索の整理
Engine基本オブジェクト: ライブラリの中核
アクティブな成行注文とポジションのオブジェクト
アクティブな成行注文とポジションのコレクション
次の段階

記事シリーズの第1部では、MetaTrader 5とMetaTrader 4プラットフォーム用のプログラムの開発を単純化するための大規模なクロスプラットフォームライブラリの作成を始めました。
第2部では、ライブラリの開発を再開し、過去の注文と取引のコレクションを実装しました。


ここでは、コレクションリスト内の注文、取引、およびポジションの便利な選択と並び替えのためのクラスを作成し、Engineと呼ばれる基本ライブラリオブジェクトを実装し、成行注文とポジションのコレクションをライブラリに追加します。

現時点では、特定のデータストレージ構造がすでに登場しています。さまざまなオブジェクト型のコレクションを作成するときには、それに従うつもりです。


単一のEngineオブジェクトは、プログラムとライブラリ間でデータを交換するためだけでなく、コレクションを格納および管理するためにも作成されます。Engineはライブラリ全体の基本オブジェクトになります。ライブラリに基づくプログラムはデータを取得するためにそれを参照します。その上、全体のライブラリの自動化がEngineに蓄積することになります。

検索の整理

ライブラリのコレクションからのデータを簡単にそして便利に使うために、リクエストに応じて便利なデータ検索、並び替え、表示を実装します。これを実現するために、特別なクラスを作成してCSelectと名付けましょう。
すべてのデータ要求はCSelectを介します。

Collectionsライブラリフォルダに、新しいCSelectクラスを作成します。基本クラスを設定する必要はありません。MQLウィザードの操作が完了すると、新しいSelect.mqhファイルがCollectionsフォルダに生成されます。

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSelect
  {
private:

public:
                     CSelect();
                    ~CSelect();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSelect::CSelect()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSelect::~CSelect()
  {
  }
//+------------------------------------------------------------------+

検索を実行するには、すべてのモードを設定します。これを行うには、検索中にオブジェクト比較モードを記述する列挙体を作成します。この列挙体はDefines.mqhファイルに作成されます。

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
//+------------------------------------------------------------------+
//| マクロ置換                                                         |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // 国の言語
#define DFUN           (__FUNCTION__+": ")      //「関数の説明」
#define END_TIME       (D'31.12.3000 23:59:59') // 口座履歴データの要求の終了時
//+------------------------------------------------------------------+
//| 検索                                                              |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| データを検索する                                                   |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // 等しい
   MORE,                                                    // 上回る
   LESS,                                                    // 下回る
   NO_EQUAL,                                                // 不等
   EQUAL_OR_MORE,                                           // 以上
   EQUAL_OR_LESS                                            // 以下
  };
//+------------------------------------------------------------------+

CSelectクラスファイルで、 標準ライブラリから、オブジェクトインスタンスへの動的ポインタのリストのクラスCOrderクラス、DELib.mqhサービス関数のライブラリ(Defines.mqhファイルとも)をリンクします。その上、グローバルレベルでライブラリ全体で利用できる特別な格納オブジェクトを宣言します。これは、並び替え中に作成されたリストのコピーを保存するためのものです。新しく作成されたリストが格納オブジェクトに添付されていない場合は、不要になった後に削除する必要があります。つまり、削除されていないオブジェクトによるロジックチェーンの喪失やメモリリークを引き起こす可能性があるので、どこで、いつ、そしてなぜ特定のリストを必要としていたかを追跡するために余分なリソースを割り当てる必要があるということです。リストに添付すれば、ターミナルサブシステムが追跡を実行して、常に格納リストとその内容の両方を時間内に削除します。

比較するには、そのようなメソッドを作成する必要があります。2つの値を比較するためのテンプレートの静的メソッドを宣言しましょう
//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Order.mqh"
//+------------------------------------------------------------------+
//| 格納リスト                                                         |
//+------------------------------------------------------------------+
CArrayObj   ListStorage; // 並び替えされたコレクションリストを格納するためのストレージオブジェクト
//+------------------------------------------------------------------+
//| 基準を満たすオブジェクトを並び替えるクラス                             |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- 2つの値を比較するためのメソッド
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:

  };
//+------------------------------------------------------------------+

比較メソッドの実装:

//+------------------------------------------------------------------+
//| 2つの値を比較するメソッド                                           |
//+------------------------------------------------------------------+
template<typename T>
bool CSelect::CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode)
  {
   return
     (
      mode==EQUAL && value1==value2          ?  true  :
      mode==NO_EQUAL && value1!=value2       ?  true  :
      mode==MORE && value1>value2            ?  true  :
      mode==LESS && value1<value2            ?  true  :
      mode==EQUAL_OR_MORE && value1>=value2  ?  true  :
      mode==EQUAL_OR_LESS && value1<=value2  ?  true  :  false
     );
  }
//+------------------------------------------------------------------+

同型の2つの値及び比較モードがメソッドに渡されます。
次に、適用されたメソッドに応じた単純な比較が行われ(等しい/等しくない/上回る/下回る/以上/以下)、結果が返されます。

それでは、リストを検索するためのメソッドをいくつか作成しましょう。CSelectクラスのpublicセクションで、指定された条件で注文を検索するための3つの静的メソッドを宣言します

//+------------------------------------------------------------------+
//| 基準を満たすオブジェクトを並び替えるクラス                             |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- 2つの値を比較するメソッド
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- 指定された基準を満たす(1)整数、(2)実数、(3)文字列プロパティのうちの1つを持つ注文のリストを返す
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
  };
//+------------------------------------------------------------------+

クラス本体のすぐ外側で実装しましょう。

//+------------------------------------------------------------------+
//| 整数を持つ注文のリストを返す                                         |
//| 指定された基準を満たすプロパティ                                     |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      long order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| 実数を持つ注文のリストを返す                                         |
//| 指定された基準を満たすプロパティ                                      |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      double order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| 文字列を持つ注文のリストを返す                                       |
//| 指定された基準を満たすプロパティ                                      |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      string order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+

例として文字列基準による検索を使用してもっと詳しく見てみましょう。

指定されたプロパティの最大値および最小値の値を持つ注文のインデックスを検索して戻すためのさらに6つのメソッドを追加します。

//+------------------------------------------------------------------+
//| 基準を満たすオブジェクトを並び替えるクラス                             |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- 2つの値を比較するメソッド
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- 指定された基準を満たす(1)整数、(2)実数、(3)文字列プロパティのうちの1つを持つ注文のリストを返す
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- (1)整数、(2)実数、(3)文字列プロパティの最大値を持つ注文のインデックスを返す
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property); 
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property); 
   //--- (1)整数、(2)実数、(3)文字列プロパティの最小値を持つ注文のインデックスを返す
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property); 
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property); 
  };
//+------------------------------------------------------------------+

以下が実装です。

//+------------------------------------------------------------------+
//|  最大の整数プロパティ値を持つ                                        |
//| リストでの注文のインデックスを返す                                    |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);
      long order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);
      long order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| 最大の実数プロパティ値を持つ                                          |
//| リストでの注文のインデックスを返す                                    |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);
      double order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);
      double order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| 最大の文字列プロパティ値を持つ                                       |
//| リストでの注文のインデックスを返す                                    |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);               
      string order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);                   
      string order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

最大文字列値を持つ注文のインデックスの検索を使用してもっと詳しく見てみましょう。

指定されたプロパティの最小値を持つ注文インデックスを返すメソッドも同様に配置されます。

//+------------------------------------------------------------------+
//| 最小の整数プロパティ値を持つ                                         |
//| リストでの注文のインデックスを返す                                    |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      long order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      long order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+
//| 最小の実数プロパティ値を持つ                                         |
//| リストでの注文のインデックスを返す                                    |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      double order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      double order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+
//| 最小の文字列プロパティ値を持つ                                        |
//| リストでの注文のインデックスを返す                                    |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      string order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      string order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+

これで、過去の注文のコレクションクラスに、時間と指定した条件によるコレクションリストの並べ替えを追加できます。

まず、Defines.mqhファイルに時間による並び替えを追加しましょう。

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"

//+------------------------------------------------------------------+
//| マクロ置換                                                         |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // 国の言語
#define DFUN           (__FUNCTION__+": ")      //「関数の説明」
#define END_TIME       (D'31.12.3000 23:59:59') // 口座履歴データの要求の終了時
//+------------------------------------------------------------------+
//| 検索                                                              |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| データの検索と並び替え                                              |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // 等しい
   MORE,                                                    // 上回る
   LESS,                                                    // 下回る
   NO_EQUAL,                                                // 不等
   EQUAL_OR_MORE,                                           // 以上
   EQUAL_OR_LESS                                            // 以下
  };
//+------------------------------------------------------------------+
//| 時間による選択の可能なオプション                                      |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // 開始時間
   SELECT_BY_TIME_CLOSE,                                    // 終了時間
   SELECT_BY_TIME_OPEN_MSC,                                 // 開始時間(ミリ秒)
   SELECT_BY_TIME_CLOSE_MSC,                                // 終了時間(ミリ秒)を返す
  };
//+------------------------------------------------------------------+

CSelectクラスをHistoryCollection.mqhファイルにインクルードします。これには、文字列を含むサービス関数CSelectクラスのファイルインクルードで置き換えます。

//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\DELib.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+

これで、サービス関数の代わりにCSelectクラスファイルが追加されました。Select.mqhにOrder.mqhをインクルードしましたが、サービス関数ファイルはすでにOrder.mqhファイルにインクルードされています。

CHistoryCollectionクラスのpublicセクションで、日付範囲内の指定された時間までにコレクションから注文を選択するメソッドを宣言する一方、privateセクションでは、値を検索するためのサンプル注文として機能する COrder抽象注文クラスを追加します。
//+------------------------------------------------------------------+
//| 過去の注文と取引のコレクション                                       |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // 過去の注文と取引のリスト
   COrder            m_order_instance;       // プロパティごとに検索する注文オブジェクト
   bool              m_is_trade_event;       // 取引イベントフラグ
   int               m_index_order;          // ターミナルの履歴リストからコレクションに追加された最後の注文のインデックス(MQL4、MQL5)
   int               m_index_deal;           // ターミナルの履歴リストからコレクションに追加された最後の取引のインデックス(MQL5)
   int               m_delta_order;          // 過去の注文数と比較した注文数の違い
   int               m_delta_deal;           // 過去の取引数と比較した取引数の違い
public:
   //--- 完全なコレクションリストを「そのまま」で返す
   CArrayObj        *GetList(void) { return &m_list_all_orders;  }
   //--- 時間がbegin_time~end_timeの注文をコレクションから選択する
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- コンストラクタ
                     CHistoryCollection();
   //--- 注文リストを更新し、新しい注文の数に関するデータを入力し、取引イベントフラグを設定する
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

日付の範囲内でコレクションから注文を選択する方法を実装する前に、Order.mqhファイルのCOrder抽象注文クラスに整数、実数、および文字列のプロパティを配置するメソッドを追加します(この場合、 整数プロパティの記述メソッドでは、サンプル時間データの注文にパラメータを追加する必要があります)

public:
   //--- (1)整数、(2)実数、(3)文字列の注文プロパティを設定する
   void              SetProperty(ENUM_ORDER_PROP_INTEGER property,long value) { m_long_prop[property]=value;                     }
   void              SetProperty(ENUM_ORDER_PROP_DOUBLE property,long value)  { m_long_prop[property]=value                    }
   void              SetProperty(ENUM_ORDER_PROP_STRING property,long value)  { m_long_prop[property]=value                    }
   //--- プロパティ配列から(1)整数、(2)実数、(3)文字列の注文プロパティを返す
   long              GetProperty(ENUM_ORDER_PROP_INTEGER property)      const { return m_long_prop[property];                    }
   double            GetProperty(ENUM_ORDER_PROP_DOUBLE property)       const { return m_double_prop[this.IndexProp(property)];  }
   string            GetProperty(ENUM_ORDER_PROP_STRING property)       const { return m_string_prop[this.IndexProp(property)];  }

   //--- プロパティをサポートしている注文のフラグを返す
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property)         { return true; }

   //--- COrderオブジェクトをすべての可能なプロパティで比較する
   virtual int       Compare(const CObject *node,const int mode=0) const;

//+------------------------------------------------------------------+

HistoryCollection.mqhファイルで、指定時間内の注文を選択するメソッドを実装します

//+------------------------------------------------------------------+
//| begin_time~end_timeの注文を                                       |
//| コレクションから選択する                                            |
//+------------------------------------------------------------------+
CArrayObj *CHistoryCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                             const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE)
  {
   ENUM_ORDER_PROP_INTEGER property=
     (
      select_time_mode==SELECT_BY_TIME_CLOSE       ?  ORDER_PROP_TIME_CLOSE      : 
      select_time_mode==SELECT_BY_TIME_OPEN        ?  ORDER_PROP_TIME_OPEN       :
      select_time_mode==SELECT_BY_TIME_CLOSE_MSC   ?  ORDER_PROP_TIME_CLOSE_MSC  : 
      ORDER_PROP_TIME_OPEN_MSC
     );

   CArrayObj *list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN+TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ?END_TIME : end_time);
   if(begin_time>end_time) begin=0;
   list.FreeMode(false); 
   ListStorage.Add(list);
   //---
   m_order_instance.SetProperty(property,begin);
   int index_begin=m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   m_order_instance.SetProperty(property,end);
   int index_end=m_list_all_orders.SearchLessOrEqual(&m_order_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(m_list_all_orders.At(i));
   return list;
  }
//+------------------------------------------------------------------+

上記をまとめてみましょう。

publicセクションでは、比較された条件を満たす整数、実数、文字列プロパティによってリストを返すメソッドを宣言します。

//+------------------------------------------------------------------+
//| 過去の注文と取引のコレクション                                        |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // 過去の注文と取引のリスト
   COrder            m_order_instance;       // プロパティを検索する注文オブジェクト
   bool              m_is_trade_event;       // 取引イベントフラグ
   int               m_index_order;          // ターミナルの履歴リストからコレクションに追加された最後の注文のインデックス(MQL4、MQL5)
   int               m_index_deal;           // ターミナルの履歴リストからコレクションに追加された最後の取引のインデックス(MQL5)
   int               m_delta_order;          // 過去の注文数と比較した注文数の違い
   int               m_delta_deal;           // 過去の取引数と比較した取引数の違い
public:
   //--- 完全なコレクションリストを「そのまま」で返す
   CArrayObj        *GetList(void) { return &m_list_all_orders;  }
   //--- 時間がbegin_time~end_timeの注文をコレクションから選択する
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- 比較された基準を満たす選択された(1)整数、(2)実数、(3)文字列プロパティののリストを返す
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   //--- コンストラクタ
                     CHistoryCollection();
   //--- 注文リストを更新し、新しい注文の数に関するデータを入力し、取引イベントフラグを設定する
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

メソッドは注文のターゲットプロパティ、比較する、比較モード (上回る/下回る/等しい/等しくない/以上/以下)を受け取ります。必要なプロパティ、値、および比較方法で並び替えされたリストは、前述のCSelectクラスメソッドを使用して返されます。

必要なリストをさまざまな方法でテストしてみましょう。

第2部のテストEA「TestDoEasyPart02.mq5」を使用してMQL5\Experts\TestDoEasyの新しいPart03サブフォルダに TestDoEasyPart03_1.mq5として保存します。日付範囲の開始と終了の選択を入力パラメータに追加し、OnInit()ハンドラのコードを変更して、日付範囲の履歴を要求します

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_1.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//--- include
#include <DoEasy\Collections\HistoryCollection.mqh>
//--- 列挙体
enum ENUM_TYPE_ORDERS
  {
   TYPE_ORDER_MARKET,   // 成行注文
   TYPE_ORDER_PENDING,  // 未決注文
   TYPE_ORDER_DEAL      // 取引
  };
//--- 入力パラメータ
input ENUM_TYPE_ORDERS  InpOrderType   =  TYPE_ORDER_DEAL;  // タイプを表示する
input datetime          InpTimeBegin   =  0;                // 必要な範囲の開始日
input datetime          InpTimeEnd     =  END_TIME;         // 必要な範囲の終了日
//--- グローバル変数
CHistoryCollection history;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                               |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 履歴を更新する
   history.Refresh();
//--- 日付範囲のコレクションリストを取得する
   CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- リストから注文を取得する
      COrder* order=list.At(i);
      if(order==NULL) continue;
      //--- これが取引の場合
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
      //--- これが過去の成行注文の場合
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
      //--- これが削除された未決注文の場合
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                              |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

ここで、完全なリストの代わりに、GetListByTime()メソッドを使用して、指定された日付範囲によって選択されたリストを取得します。デフォルト設定でコンパイルしてEAを起動します。口座履歴全体のすべての取引が操作ログに表示されます。


F7キーを押して、設定で必要な範囲の終了日を設定します。個人的に、私は口座履歴にアクセスし、いつ補充が行われたかを特定し、次の取引の日付(口座補充の後に起こった最初の取引)を定義しました。


そして最初の取引がその外側になるように範囲を選択しました(2018.01.22 - 2018.02.01)。
その結果、操作ログには1つの取引(勘定補充)のみが表示されました。


TestDoEasyPart03_1.mq5 EAをTestDoEasyPart03_2mq5として保存しましょう。入力を削除し、取引に関するデータの受信方法を変更します。

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_2.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//--- include
#include <DoEasy\Collections\HistoryCollection.mqh>
//--- グローバル変数
CHistoryCollection history;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                               |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 履歴を更新する
   history.Refresh();
//--- コレクションリストの取引のみを受信する
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
//--- 取得されたリストを残高操作別に並び替える
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,DEAL_TYPE_BALANCE,EQUAL);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- リストから注文を取得する
      COrder* order=list.At(i);
      if(order==NULL) continue;
      order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

まず、すべての取引のリストを取得(リストの取引タイプの注文ステータスを強調表示)して取得されたリストを「残高操作」型別に並び替えます。両方でEqual比較モードが使用されます。
結果的に、操作ログには「残高補充」操作のみが表示されます。


前の例では、残高操作を表示するために端末の口座履歴タブで取引範囲を調べなければなりませんでしたが、ここでは必要な基準でリストを並び替えた直後にそれを取得しました。

必要なデータを入手する他の方法は同じ原則に従います。たとえば、同じ「残高補充」を取得するために、最も収益性の高い取引と最も収益性の低い取引のインデックスを見つけることができます。
下記はTestDoEasyPart03_3.mq5テストEAでの例です。

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_3.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//--- include
#include <DoEasy\Collections\HistoryCollection.mqh>
//--- グローバル変数
CHistoryCollection history;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                               |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 履歴を更新する
   history.Refresh();
//--- コレクションリストの取引のみを受信する
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   if(list==NULL)
     {
      Print(TextByLanguage("Не удалось получить список","Could not get list"));
      return INIT_FAILED;
     }
//--- 最も収益性の高い取引のインデックスを取得する(最初の残高補充)
   int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- インデックスによってリストから取引を取得する
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с максимальным значением профита","Order index with maximum profit value not found"));
//--- 最も収益性の低い取引のインデックスを取得する
   index=CSelect::FindOrderMin(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- インデックスによってリストから取引を取得する
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с минимальным значением профита","Order index with minimum profit value not found"));
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

完了すると、2つの取引が操作ログに表示されます。1つは最大利益(残高補充)、もう1つは最小利益を持つ取引です。



Engine基本オブジェクト: ライブラリの中核

ライブラリと連携して動作するカスタムプログラムは、ライブラリにデータを送信し、ライブラリからデータを受信する必要があります。これを達成するためには、プログラムにリンクし、ライブラリとプログラム間の通信のために可能なすべてのアクションを蓄積する単一のクラスを持つことがより便利です。もちろん、このクラスは、バックグラウンドで機能してユーザによるコストのかかる操作を必要としない、考えられるすべてのサービス機能を引き受ける必要があります。そのため、ライブラリの基本として基本クラスを作成し、Engineという名前を付けます。

ライブラリのルートフォルダで、CObject基本オブジェクトに基づいて新しいCEngineクラスを作成し、 履歴コレクションクラスに接続します。

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                                |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- 過去の注文と取引のコレクション
   CHistoryCollection   m_history;
public:
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngineコンストラクタ                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngineデストラクタ                                                |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+

テスト用EAの過去の注文の収集に取り組んでいる間に行ったすべてのアクションはOnInit()ハンドラで実行されました。つまり、EAの起動時、再コンパイル時、またはパラメータの変更時に1回だけ実行されていました。これはざっとした確認には十分ですが、実際のプログラムでは受け入れられません。それでは、すべてを整頓しましょう。

まず、クラスのpublicセクションにOnTimer()ハンドラを作成し、クラス本体の外側に実装して、すべてのコレクションを更新します。
//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                                |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- 過去の注文と取引のコレクション
   CHistoryCollection   m_history;
public:
//--- タイマー
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngineコンストラクタ                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngineデストラクタ                                                |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngineタイマー                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   
  }
//+------------------------------------------------------------------+

イベントごとに異なるタイマー遅延が必要になる可能性が非常に高いため、サービスクラス(タイマーカウンタ)を作成しましょう。このクラスは必要な遅延時間を数えるためのもので、それぞれのタイマーカウンタに対して別々のカウンタインスタンスが宣言されます。

まず、Defines.mqhファイルに新しいマクロ置換を追加します。そこで、ライブラリタイマーの頻度コレクションタイマーカウンタの休止時間(ミリ秒)コレクションタイマーカウンタの増分過去の注文/取引更新タイマーカウンタのIDを指定します(ライブラリを開発するとき、個々のIDを必要とする複数のカウンタが必要になる場合があります)。
//+------------------------------------------------------------------+
//| マクロ置換                                                         |
//+------------------------------------------------------------------+
#define COUNTRY_LANG             ("Russian")                // 国の言語
#define DFUN                     (__FUNCTION__+": ")        // 「関数の説明」
#define END_TIME                 (D'31.12.3000 23:59:59')   // 口座履歴データの最終リクエスト時間
#define TIMER_FREQUENCY          (16                      // ライブラリタイマーの最小頻度(ミリ秒)
#define COLLECTION_PAUSE         (250)                      // 注文/取引コレクションタイマーの一時停止時間(ミリ秒)
#define COLLECTION_COUNTER_STEP  (16                      // 注文/取引コレクションタイマーカウンタの一時増分時間(ミリ秒)
#define COLLECTION_COUNTER_ID    (1)                        // 注文/取引コレクションタイマーのカウンタID
//+------------------------------------------------------------------+

ライブラリのルートフォルダで、新しいServicesフォルダを作成し、その中に新しいCTimerCounterクラスを作成します。DELib.mqhサービス関数ファイルをこのフォルダにすぐに移動します。これが正しい場所です。

DELib.mqhを新しいフォルダーに移動した後、Order.mqhファイル内のサービス機能ファイルのアドレスを変更します。

このアドレス

//+------------------------------------------------------------------+
//|                                                        Order.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
#property strict    // mql4で必要
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\DELib.mqh"
//+------------------------------------------------------------------+

アドレスに置き換えます。

//+------------------------------------------------------------------+
//|                                                        Order.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
#property strict    // mql4で必要
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\Services\DELib.mqh"
//+------------------------------------------------------------------+

ここでタイマーカウンタクラスについて考えてみましょう。このクラスはシンプルなので、そのコードを見て、その動作について詳しく見てみましょう。

//+------------------------------------------------------------------+
//|                                                 TimerCounter.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "DELib.mqh"
//+------------------------------------------------------------------+
//| タイマーカウンタクラス                                              |
//+------------------------------------------------------------------+
class CTimerCounter : public CObject
  {
private:  
   int               m_counter_id;   
   ulong             m_counter;      
   ulong             m_counter_step; 
   ulong             m_counter_pause;
public:
   //--- 待機完了フラグを返す
   bool              IsTimeDone(void);
   //--- カウンタパラメータを設定する
   void              SetParams(const ulong step,const ulong pause)         { this.m_counter_step=step; this.m_counter_pause=pause;  }
   //--- カウンタIDを返す
   virtual  int      Type(void)                                      const { return this.m_counter_id;                              }
   //--- カウンタオブジェクトを比較する
   virtual int       Compare(const CObject *node,const int mode=0)   const;
   //--- コンストラクタ
                     CTimerCounter(const int id);
  };
//+------------------------------------------------------------------+
//| CTimerCounterコンストラクタ                                        |
//+------------------------------------------------------------------+
CTimerCounter::CTimerCounter(const int id) : m_counter(0),m_counter_step(16),m_counter_pause(16)
  {
   this.m_counter_id=id;
  }
//+------------------------------------------------------------------+
//| CTimerCounterは一時停止完了フラグを返す                              |
//+------------------------------------------------------------------+
bool CTimerCounter::IsTimeDone(void)
  {
   if(this.m_counter>=ULONG_MAX)
      this.m_counter=0;
   if(this.m_counter<this.m_counter_pause)
     {
      this.m_counter+=this.m_counter_step;
      return false;
     }
   this.m_counter=0;
   return true;
  }
//+------------------------------------------------------------------+
//| CTimerCounterオブジェクトをIDごとに比較する                          |
//+------------------------------------------------------------------+
int CTimerCounter::Compare(const CObject *node,const int mode=0) const
  {
   const CTimerCounter *counter_compared=node;
   int value_compared=counter_compared.Type();
   int value_current=this.Type();
   return(value_current>value_compared ?1 : value_current<value_compared ?-1 : 0);
   return 0;
  }
  
//+------------------------------------------------------------------+

DELib.mqhをカウンタクラスと同じフォルダに移動したので、同じフォルダから直接インクルードする必要があります。Defines.mqhはDELib.mqhにインクルードされています。つまり、クラスはすべてのマクロ置換を認識します。

一時停止完了フラグを返すメソッドは簡単に配置されています。

Type()カウンタIDを返すメソッドは仮想です。CObjectクラスには(Metaeditorパッケージで)、オブジェクトタイプを返す仮想メソッドがあります。

   //--- オブジェクト特定法
   virtual int       Type(void)                                    const { return(0);      }

このメソッドは下位クラスで再定義され、CObject下位クラスオブジェクトのIDを返すことが想定されています(CObject自体の場合はtype = 0が返されます)。この機会を利用して、仮想メソッドを再定義してカウンタIDを返します。

   virtual  int      Type(void)                                    const { return this.m_counter_id; }

2つのカウンタオブジェクトを比較する仮想メソッドは簡潔です。

//+------------------------------------------------------------------+
//| CTimerCounterオブジェクトをIDごとに比較する                          |
//+------------------------------------------------------------------+
int CTimerCounter::Compare(const CObject *node,const int mode=0) const
  {
   const CTimerCounter *counter_compared=node;
   int value_compared=counter_compared.Type();
   int value_current=this.Type();
   return(value_current>value_compared ?1 : value_current<value_compared ?-1 : 0);
   return 0;
  }
//+------------------------------------------------------------------+

ソースオブジェクトへのリンクを取得し、IDを取得し、現在のカウンタのIDを取得 します。次に、上回る/下回る/等しいによる簡単な比較の結果を返します


CEngineクラスの完成を続けましょう。Engine.mqhファイルにタイマーカウンタクラスをインクルードします。

//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+

タイマーカウンタのリストオブジェクト、およびリスト内のカウンタインデックスをIDで返すメソッドをprivateセクションに追加し、カウンタ作成メソッドをpublicセクションに追加します(メソッドに外部からアクセスできるようにし、プログラム内でカスタムカウンタを作成することが可能になります)。

クラスコンストラクタで、ミリ秒タイマーを初期化し、ソート済みリストフラグを設定し、過去の注文と取引コレクション更新タイマーのカウンタを作成します。クラスデストラクタでタイマーを破壊します。
//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                                |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- タイマーカウンタのリスト
   CArrayObj            m_list_counters;           
//--- 過去の注文と取引のコレクション
   CHistoryCollection   m_history;
//--- IDによるカウンタインデックスを返す
   int                  CounterIndex(const int id) const;
public:
//--- タイマーカウンタを作成する
   void                 CreateCounter(const int counter_id,const ulong frequency,const ulong pause);
//--- タイマー
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngineコンストラクタ                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_MIN_PAUSE);
  }
//+------------------------------------------------------------------+
//| CEngineデストラクタ                                                |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
   ::EventKillTimer();
  }
//+------------------------------------------------------------------+

IDでカウンタインデックスを返すメソッドを実装します。

//+------------------------------------------------------------------+
//| リスト内のカウンタインデックスをIDで返す                              |
//+------------------------------------------------------------------+
int CEngine::CounterIndex(const int id) const
  {
   int total=this.m_list_counters.Total();
   for(int i=0;i<total;i++)
     {
      CTimerCounter* counter=this.m_list_counters.At(i);
      if(counter==NULL) continue;
      if(counter.Type()==id) 
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

カウンタはそれほど多くないため、値の検索と比較を特徴とする最も単純な列挙型を手配しました。このIDを持つカウンタがリスト内に見つかった場合、システムはそのインデックスをリスト内に返します。それ以外の場合、システムは-1を返します。

タイマーカウンタを作成するメソッドを考えてみましょう。

//+------------------------------------------------------------------+
//| タイマーカウンタを作成する                                           |
//+------------------------------------------------------------------+
void CEngine::CreateCounter(const int id,const ulong step,const ulong pause)
  {
   if(this.CounterIndex(id)>WRONG_VALUE)
     {
      ::Print(TextByLanguage("Ошибка. Уже создан счётчик с идентификатором ","Error. Already created counter with id "),(string)id);
      return;
     }
   m_list_counters.Sort();
   CTimerCounter* counter=new CTimerCounter(id);
   if(counter==NULL)
      ::Print(TextByLanguage("Не удалось создать счётчик таймера ","Failed to create timer counter "),(string)id);
   counter.SetParams(step,pause);
   if(this.m_list_counters.Search(counter)==WRONG_VALUE)
      this.m_list_counters.Add(counter);
   else
     {
      string t1=TextByLanguage("Ошибка. Счётчик с идентификатором ","Error. Counter with ID ")+(string)id;
      string t2=TextByLanguage(", шагом ",", step ")+(string)step;
      string t3=TextByLanguage(" и паузой "," and pause ")+(string)pause;
      ::Print(t1+t2+t3+TextByLanguage(" уже существует"," already exists"));
      delete counter;
     }
  }
//+------------------------------------------------------------------+

まず、メソッドに渡されたカウンタIDを確認します。そのようなIDがすでに存在する場合は、操作ログに適切なメッセージ(「the counter with this ID already exists」)を表示してメソッドを終了します。 検索はソートされたリストでのみ実行できるため、リストにソートフラグが設定され、新しいカウンタオブジェクトが作成されます。正常に作成されたかどうかが確認され、カウンタの必須プロパティが設定されます

その後、リスト内で同じカウンタの検索が実行されます。見つからない場合は、新しいカウンタがリストに追加されます

その他の場合、すべてのパラメータを含むメッセージが作成され、操作ログに表示されます。その後、似たものが存在するためカウンタオブジェクトが削除されます

既存のものと一致する新しく作成されたカウンタのすべてのパラメータの最後のチェックは現在冗長です。メソッドの最初のIDチェックはすでにリストに存在するIDでオブジェクトを作成することを防ぎます。これは将来の変更の可能性のために残されています。

CEngineクラスにいつ取引状況を処理するかを知らせるためには、過去の注文数と取引数に発生した変化を知っておく必要があります。これを行うには、リストに新しく出現した注文および取引の数を返すメソッドをCHistoryCollectionクラスに追加します。

//+------------------------------------------------------------------+
//| 過去の注文と取引のコレクション                                       |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // 過去の注文と取引のリスト
   COrder            m_order_instance;       // プロパティを検索する注文オブジェクト
   bool              m_is_trade_event;       // 取引イベントフラグ
   int               m_index_order;          // ターミナルの履歴リストからコレクションに追加された最後の注文のインデックス(MQL4、MQL5)
   int               m_index_deal;           // ターミナルの履歴リストからコレクションに追加された最後の取引のインデックス(MQL5)
   int               m_delta_order;          // 過去の注文数と比較した注文数の違い
   int               m_delta_deal;           // 過去の取引数と比較した取引数の違い
public:
   //--- 時間がbegin_time~end_timeの注文をコレクションから選択する
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- 完全なコレクションリストを「そのまま」で返す
   CArrayObj        *GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- 比較された基準を満たす選択された(1)整数、(2)実数、(3)文字列プロパティののリストを返す
   CArrayObj        *GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   //--- (1)新規注文と(2)新規取引の数を返す
   int               NewOrders(void)                                                                     { return m_delta_order; }
   int               NewDeals(void                                                                     { return m_delta_deal;  }
   
   //--- コンストラクタ
                     CHistoryCollection();
   //--- 注文リストを更新し、新しい注文の数に関するデータを入力し、取引イベントフラグを設定する
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

メソッドは、適切なクラスメンバ変数の値を返すだけです。

CEngineでは、クラスタイマーでそれらのステータスを確認することができます。

//+------------------------------------------------------------------+
//| CEngineタイマー                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- 過去の注文と取引のコレクションのタイマー
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter.IsTimeDone())
        {
         this.m_history.Refresh();
         if(this.m_history.NewOrders()>0)
           {
            Print(DFUN,TextByLanguage("Изменилось количество исторических ордеров: NewOrders=","Number of historical orders changed: NewOrders="),this.m_history.NewOrders());
           }
         if(this.m_history.NewDeals()>0)
           {
            Print(DFUN,TextByLanguage("Изменилось количество сделок: NewDeals=","Number of deals changed: NewDeals="),this.m_history.NewOrders());
           }
        }
     }
  }
//+------------------------------------------------------------------+

過去の注文と取引の収集タイマーのカウンタのインデックスを取得し、そのインデックスでタイマーカウンタへのポインタを取得タイマー遅延時間の完了を確認してコレクションを更新します(もしあれば最後に追加された注文または取引のみ)。

過去の注文数が変更された場合は、操作ログにメッセージを表示します取引についても同じです

検証のために簡単なEAを作成しましょう。Experts\TestDoEasy\Part3で、タイマー付きのEA「TestDoEasyPart03_4.mq5」を作成します。タイマー付きEAのテンプレートを作成するには、MQLウィザードの2ページ目でOnTimerをチェックします。


ウィザードの操作が完了するまで[次へ]をクリックします。その結果、空のEAテンプレートが作成されます。メインファイルをリンクし、ライブラリクラスオブジェクトを作成して、EAタイマーでライブラリタイマーを呼び出します

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_4.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//--- include
#include <DoEasy\Engine.mqh>
//--- グローバル変数
CEngine        engine;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| タイマー関数                                                       |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

口座履歴の変更に関するデータを取得するためにEAで実行する必要があるのはこれだけです。

今すぐEAを起動して、未決注文を出して削除すると、履歴の注文数の変更に関するエントリが操作ログに表示されます。
ポジションを開くと、操作ログに2つのエントリが表示されます。

  1. 注文数の変更に関連したもの(成行注文が送信される)および
  2. 取引数の変更に関連したもの(成行注文がアクティブになり、「Market entry」取引が生成される)。

ポジションを決済すると、操作ログに2つのエントリが再び表示されます。

  1. 成約注文の決済について
  2. 新規取引の外観に関連したもの(成約注文の決済がアクティブになり、「Market exit」取引が生成される)

初回実行時に、口座履歴内の注文と取引の変更に関するメッセージが操作ログに表示されます。これは、ライブラリが初回実行時に履歴全体を読み取るため、初回実行時に注文と取引の数がゼロであること(ライブラリは初回実行時にそれらについて何も認識しない)とすべての注文と取引の数の差から生じるためです。履歴ループはその最大数と等しくなります。これは不便であるとともに不要です。したがって、このような誤った数変更メッセージを排除する必要があります。それには2つの方法があります。

  1. 初回実行時には操作ログに何も表示しない
  2. 初回実行時に口座ステータスメッセージを表示する
説明の後の部分で必要な統計の収集と表示を実装するので、最初のオプションを使用し(初回実行時に操作ログに何も表示しない)、初回実行時にスタブを作成します。

CEngineクラスのprivateセクションに、初回実行時のフラグと、そのフラグを確認してリセットするメソッドを追加します。

//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                                |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // 過去の注文と取引のコレクション
   CArrayObj            m_list_counters;                 // タイマーカウンタのリスト
   bool                 m_first_start;                   // 「初回実行」フラグ
//--- IDによるカウンタインデックスを返す
   int                  CounterIndex(const int id) const;
//--- 「初回実行」フラグを返す
   bool                 IsFirstStart(void);
public:
//--- タイマーカウンタを作成する
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- タイマー
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

そして、checkメソッドの実装を追加し、クラス本体外で「初回実行」フラグをリセットします。

//+------------------------------------------------------------------+
//| 初回実行」フラグを返す、フラグをリセットする                           |
//+------------------------------------------------------------------+
bool CEngine::IsFirstStart(void)
  {
   if(this.m_first_start)
     {
      this.m_first_start=false;
      return true;             
     }
   return false;
  }
//+------------------------------------------------------------------+

これはすべて簡単です。フラグが設定されている場合リセットして「true」を返し、その他の場合は「false」を返します
ここで、いつでもアクティブにできるように、初期化リストでフラグを設定する必要があります。

//+------------------------------------------------------------------+
//| CEngineコンストラクタ                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
  }
//+------------------------------------------------------------------+

準備完了です。これからは、プログラムの初回起動時に、最初の履歴の計算に関連する不要な口座履歴イベントが表示されることはありません。

これを確認するにはMQL5\Experts\TestDoEasy\Part2\TestDoEasyPart03_4.mq5からテスト用EAを起動し、初回実行時に口座履歴への注文や取引の追加に関するメッセージが表示されないようにします。

有効な成行注文およびポジションのオブジェクト

CEngineクラスへの機能の追加を一時的にやめてオブジェクトの実装と成行注文とポジションの収集を始める時が来たと思います。実装が完了した後も、Engine基本オブジェクトの機能の開発を続けます。これは、この機能が口座の履歴と現在のステータスの両方に影響するためです。

ライブラリのObjectsフォルダーで、COrder ライブラリの抽象的注文に基づいたCMarketPositionクラスを作成します。これは市場ポジションオブジェクトになります。


[完了]をクリックすると、クラステンプレート「MarketPosition.mqh」が作成されます。今すぐにCOrderクラスをインクルードします。

//+------------------------------------------------------------------+
//|                                               MarketPosition.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| 市場ポジション                                                     |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
private:

public:
                     CMarketPosition();
                    ~CMarketPosition();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketPosition::CMarketPosition()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketPosition::~CMarketPosition()
  {
  }
//+------------------------------------------------------------------+

コンストラクタをポジションチケットが渡されるように変更し、初期化リストで親クラス(COrder)の 「market position」ステータスを設定 し、チケットを送信します。整数、実数、および文字列プロパティをサポートするフラグをポジションで返す3つの仮想メソッドを宣言します。

//+------------------------------------------------------------------+
//| 市場ポジション                                                     |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
public:
   //--- コンストラクタ
                     CMarketPosition(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_POSITION,ticket) {}
   //--- サポートされるポジションプロパティ: (1)実数、(2)整数
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property);
  };
//+------------------------------------------------------------------+

クラスボディの外側にメソッドの実装を追加しましょう。

//+------------------------------------------------------------------+
//| ポジションが渡された整数プロパティをサポートする場合は「true」を返し     |
//| その他の場合は「false」を返す                                       |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_CLOSE     || 
      property==ORDER_PROP_TIME_CLOSE_MSC ||
      property==ORDER_PROP_TIME_EXP       ||
      property==ORDER_PROP_POSITION_BY_ID ||
      property==ORDER_PROP_DEAL_ORDER     ||
      property==ORDER_PROP_DEAL_ENTRY     ||
      property==ORDER_PROP_CLOSE_BY_SL    ||
      property==ORDER_PROP_CLOSE_BY_TP
     #ifdef __MQL5__                      ||
      property==ORDER_PROP_TICKET_FROM    ||
      property==ORDER_PROP_TICKET_TO
     #endif 
     ) return false;
   return true;
}
//+------------------------------------------------------------------+
//| ポジションが渡された実数プロパティをサポートする場合は「true」を返し     |
//| その他の場合は「false」を返す                                       |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_PRICE_CLOSE || property==ORDER_PROP_PRICE_STOP_LIMIT) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| ポジションが渡された文字列プロパティをサポートする場合は「true」を返し    |
//| その他の場合は「false」を返す                                       |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_STRING property)
  {
   if(property==ORDER_PROP_EXT_ID) return false;
   return true;
  }
//+------------------------------------------------------------------+

ここでのすべては、過去の注文や取引のオブジェクトを作成するのに似ており、ライブラリの説明の第2部で説明されています

それでは同様に市場未決注文オブジェクトを作成しましょう。Objectsフォルダ内のCOrderライブラリの抽象的な注文に基づいて新しいクラスCMarketPendingを作成し、それにMQLウィザードによって作成されたクラステンプレートのよく知られている変更を入力します。

//+------------------------------------------------------------------+
//|                                                MarketPending.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| 市場未決注文                                                        |
//+------------------------------------------------------------------+
class CMarketPending : public COrder
  {
public:
   //--- コンストラクタ
                     CMarketPending(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_PENDING,ticket) {}
   //--- サポートされる注文プロパティ: (1)実数、(2)整数
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| 注文が渡された整数プロパティをサポートする場合は「true」を返し           |
//| その他の場合は「false」を返す                                       |
//+------------------------------------------------------------------+
bool CMarketPending::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_DEAL_ORDER        ||
      property==ORDER_PROP_DEAL_ENTRY        ||
      property==ORDER_PROP_TIME_UPDATE       ||
      property==ORDER_PROP_TIME_CLOSE        ||
      property==ORDER_PROP_TIME_CLOSE_MSC    ||
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_TICKET_FROM       ||
      property==ORDER_PROP_TICKET_TO         ||
      property==ORDER_PROP_CLOSE_BY_SL       ||
      property==ORDER_PROP_CLOSE_BY_TP
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| 注文が渡された実数プロパティをサポートする場合は「true」を返し           |
//| その他の場合は「false」を返す                                       |
//+------------------------------------------------------------------+
bool CMarketPending::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_COMMISSION  ||
      property==ORDER_PROP_SWAP        ||
      property==ORDER_PROP_PROFIT      ||
      property==ORDER_PROP_PROFIT_FULL ||
      property==ORDER_PROP_PRICE_CLOSE
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

クラスコンストラクタの初期化リスト内の基本COrderに「pending order」ステータスを渡します

これで、成行注文とポジションのコレクションを作成するために必要なオブジェクトの開発が完了しました。

有効な成行注文およびポジションのコレクション

過去の注文と取引のコレクションを作成している間は、常に全体の履歴を確認することには意味がないという規則に従いました。そのため、番号が変更された場合にのみ、以前に作成した単一のリストに新しい注文と取引を追加しました。市場ポジションのリストを使用するときは、完全に異なるルールを念頭に置く必要があります。これは、各ティックのリストは適切であるべきだということです。

これを達成するためには、下記を実行します。

  1. 未決注文数、ヘッジ勘定の有効ポジション数(ネッティング口座にはポジションが1つしかないため)、およびポジションボリューム(ネッティングポジションのボリュームの増減、ヘッジポジションの部分決済)の変化を追跡します。
  2. 常に関連するポジションステータスデータがあるように、各ティック で既存の各ヘッジポジションまたは単一のネッティングポジションのデータを必ず更新してください。

最後のティックで指定された値を覚えて、現在のものと同じデータと比較し、ポジションを更新するか、変更がある場合はリスト全体を再作成します。幸いなことに、リストはそれほど大きくなく、その再作成にはそれほど時間がかかりません。

始めましょう。Collectionsライブラリフォルダで、新しいCMarketCollectionクラスを作成します。これを行うには、Collectionフォルダーを右クリックして、[New file]を選択します。新しく開いたMQLウィザードで、[新しいクラス]を選択して[次へ]をクリックします。


クラスの名前に「CMarketCollection」を入力して[完了]をクリックします。MarketCollection.mqhクラステンプレートが作成されます。

//+------------------------------------------------------------------+
//|                                             MarketCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:

public:
                     CMarketCollection();
                    ~CMarketCollection();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::~CMarketCollection()
  {
  }
//+------------------------------------------------------------------+

書き入れましょう。

まず、用意されているすべてのクラスと、成行注文とポジションのコレクションを実装するためと検索するのに必要なクラスをインクルードします。

//+------------------------------------------------------------------+
//|                                             MarketCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+

クラスのprivateセクションで、構造体を作成して、その中に前述のすべての追跡値を格納するための変数を指定します(注文数や位置など)。そして、現在および以前のデータを格納するために、この構造体型の2つのクラスメンバ変数を作成します。

//+------------------------------------------------------------------+
//|                                             MarketCollection.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| ファイルをインクルードする                                           |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+
//| 過去の注文と取引のコレクション                                       |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // 口座のすべての注文とポジションのハッシュ合計
      int            total_pending;          // 口座の未決注文数
      int            total_positions;        // 口座のポジション数
      double         total_volumes;          // 口座の注文とポジションの総量
     };
   MqlDataCollection m_struct_curr_market;   // 口座の成行注文およびポジションに関する現在のデータ
   MqlDataCollection m_struct_prev_market;   // 成行注文および口座のポジションに関する現在のデータ
public:
                     CMarketCollection();
                    ~CMarketCollection();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::~CMarketCollection()
  {
  }
//+------------------------------------------------------------------+

構造体内のハッシュ合計について考えてみましょう。
発生した口座イベントを正確に定義したい場合、注文数とポジション数は不十分です。未決注文は削除され、口座の注文数とポジションの合計数が変更されます。一方、未決注文がアクティブになりポジションになることがあります。この場合、注文とポジションの合計は変わりません(ヘッジ口座とMQL4の場合)。ポジションの数は増えますが、注文の数は減ります。その結果、合計数は変わりません。これは私たちには適していません。

チケットを考えてみましょう。未決注文を追加/削除しても口座のチケットの合計金額は変わりますが、保留オーダーを有効にしても口座のチケットの合計金額は変わりません。

全体の数量を考えてみましょう。未決注文の発注/削除 - 口座の合計数量が変更された、ポジションの開閉または変更 - 口座の合計数量が変更されたこのオプションは適しているようですが、未決注文を有効にしても合計数量は変わりません。

それでは、さらに別のポジションプロパティを見てみましょう。これは、ミリ秒単位での変更の時間です。新しいポジションを開くと、全体のポジション変更時間が変わり、部分的に閉じると、ポジション変更時間が変わります。ネッティング口座はポジション変更の合計時間を変更します。

口座に発生した変更を正確に定義するための最も適切なオプションは何でしょうか。それは、チケット+ポジション変更時間です。確認しましょう。

  • ポジションを開く — チケットの合計が変化+ポジション変更時間の合計が変化変化あり
  • ポジションを閉じる — チケットの合計が変化+ポジション変更時間の合計が変化-変化あり
  • 未決注文を出した — チケットの合計が変化+ ポジション変更時間の合計が 変化していない変化あり
  • 未決注文を削除した — チケットの合計が変化+ ポジション変更時間の合計が 変化していない変化あり
  • 未決注文を発動した — チケットの合計が変化していない + ポジション変更時間の合計が変化変化あり
  • ポジションを部分決済した — チケットの合計が変化+ ポジション変更時間の合計が 変化変化あり
  • ポジションにボリュームを追加 — チケットの合計が変化していない + ポジション変更時間の合計が 変化変化あり
したがって、ハッシュの合計にはチケット+ポジションの変更時間(ミリ秒)を使用します。

クラスのprivateセクションで、オブジェクトへのポインタの動的リストを作成して、未決注文とポジションのコレクションリストとして使用します。また、口座に取引イベントフラグ、CEngineクラスの取引イベントを簡単に識別するための発生したポジションボリュームの変化を示すフラグの2つのフラグに加え、ボリューム変更値新規ポジション数、および未決注文を設定するための3つのクラスメンバー変数を作成します。publicセクションでは、コレクションリストをアップデートするメソッドを宣言し、クラスボディの外側でクラスコンストラクタを実装します

//+------------------------------------------------------------------+
//| 成行注文とポジションのコレクション                                    |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // 口座のすべての注文とポジションのハッシュ合計
      int            total_pending;          // 口座の未決注文数
      int            total_positions;        // 口座のポジション数
      double         total_volumes;          // 口座の注文とポジションの総量
     };
   MqlDataCollection m_struct_curr_market;   // 口座の成行注文とポジションの現在のデータ
   MqlDataCollection m_struct_prev_market;   // 口座の成行注文とポジションの以前のデータ
   CArrayObj         m_list_all_orders;      // 口座の未決注文とポジションのリスト
   bool              m_is_trade_event;       // 取引イベントフラグ
   bool              m_is_change_volume;     // 総ボリューム変更フラグ
   double            m_change_volume_value;  // 総ボリューム変更値
   int               m_new_positions;        // 新規ポジション数
   int               m_new_pendings;         // 新規未決注文数
public:
   //--- コンストラクタ
                     CMarketCollection(void);
   //--- 未決注文とポジションのリストを更新する
   void              Refresh(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
  {
   m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
   ::ZeroMemory(this.m_struct_prev_market);
   this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
  }
//+------------------------------------------------------------------+

取引イベントポジションボリュームの変化フラグをリセットしてクラスコンストラクタの初期化リストでボリューム変化値をリセットします。
コンストラクタ本体で、開いた時間による成行注文とポジションリストの並び替えをを設定し以前のハッシュの合計(-1に設定)を除く、口座の以前のステータスのすべての変数構造をリセットします。 (初回実行を識別するため)

口座の注文数とポジションの変更を後で確認するには、クラスのprivateセクションに現在収集されている口座データを前のデータ構造に保存するメソッドを追加します。クラスのpublicセクションに、新規未決注文の数新規ポジションの数口座で発生した取引イベントのフラグを返すメソッドの3つのメソッドを追加します。

//+------------------------------------------------------------------+
//| 成行注文とポジションのコレクション                                    |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // 口座のすべての注文とポジションのハッシュ合計
      int            total_pending;          // 口座の未決注文数
      int            total_positions;        // 口座のポジション数
      double         total_volumes;          // 口座の注文とポジションの総量
     };
   MqlDataCollection m_struct_curr_market;   // 口座の成行注文とポジションの現在のデータ
   MqlDataCollection m_struct_prev_market;   // 口座の成行注文とポジションの以前のデータ
   CArrayObj         m_list_all_orders;      // 口座の未決注文とポジションのリスト
   bool              m_is_trade_event;       // 取引イベントフラグ
   bool              m_is_change_volume;     // 総ボリューム変更フラグ
   double            m_change_volume_value;  // 総ボリューム変更値
   int               m_new_positions;        // 新規ポジション数
   int               m_new_pendings;         // 新規未決注文数
   //--- 現在の口座データステータスを1つ前のものとして保存する
   void              SavePrevValues(void)             { this.m_struct_prev_market=this.m_struct_curr_market;   }
public:
   //--- (1)新規未決注文、(2)新規ポジション、(3)発生した取引イベントフラグの数を返す
   int               NewOrders(void)    const         { return this.m_new_pendings;                            }
   int               NewPosition(void)  const         { return this.m_new_positions;                           }
   bool              IsTradeEvent(void) const         { return this.m_is_trade_event;                          }
   //--- コンストラクタ
                     CMarketCollection(void);
   //--- 未決注文とポジションのリストを更新する
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

現在の市場ステータスを更新するメソッドを実装しましょう。

//+------------------------------------------------------------------+
//| 注文リストを更新する                                                |
//+------------------------------------------------------------------+
void CMarketCollection::Refresh(void)
  {
   ::ZeroMemory(this.m_struct_curr_market);
   this.m_is_trade_event=false;            
   this.m_is_change_volume=false;          
   this.m_new_pendings=0;                  
   this.m_new_positions=0;                 
   this.m_change_volume_value=0;           
   m_list_all_orders.Clear();              
#ifdef __MQL4__
   int total=::OrdersTotal();
   for(int i=0; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS)) continue;
      long ticket=::OrderTicket();
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketPosition *position=new CMarketPosition(ticket);
         if(position==NULL) continue;
         if(this.m_list_all_orders.InsertSort(position))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_positions++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
            delete position;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- MQ5
#else    
//--- ポジション
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      if(this.m_list_all_orders.InsertSort(position))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)::PositionGetInteger(POSITION_TIME_UPDATE_MSC);
         this.m_struct_curr_market.total_volumes+=::PositionGetDouble(POSITION_VOLUME);
         this.m_struct_curr_market.total_positions++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
         delete position;
        }
     }
//--- 注文
   int total_orders=::OrdersTotal();
   for(int i=0; i<total_orders; i++)
     {
      ulong ticket=::OrderGetTicket(i);
      if(ticket==0) continue;
      CMarketPending *order=new CMarketPending(ticket);
      if(order==NULL) continue;
      if(this.m_list_all_orders.InsertSort(order))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
         this.m_struct_curr_market.total_volumes+=::OrderGetDouble(ORDER_VOLUME_INITIAL);
         this.m_struct_curr_market.total_pending++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
         delete order;
        }
     }
#endif 
//--- 初回実行
   if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE)
     {
      this.SavePrevValues();                              
     }                                                    
//--- すべての注文とポジションのハッシュ合計が変更された場合
   if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc)
     {
      this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending;
      this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions;
      this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4);
      this.m_is_change_volume=(this.m_change_volume_value!=0 ?true : false);
      this.m_is_trade_event=true;
      this.SavePrevValues();
     }
  }
//+------------------------------------------------------------------+

このメソッドを分析する前に、少々わき道にそれます。それは、すべての成行注文とポジションに関する適切なデータが常に必要なので、ティックごとにリストをクリアし、市場環境からのデータでそれを埋めることができるということです。あるいは、リストに一度記入して、変更可能なデータのみを変更することもできます。一見すると、変更されたデータだけを修正する方が速く思えます。しかし、これを行うには、次のことが必要です。

  1. 成行注文とターミナルポジションのリストを調べ、それらをライブラリーリストに記入する
  2. ティックごとに、ターミナル成行注文とポジションのリストを調べ、変化するデータを取り出し、ライブラリリストで同じチケットを持つ注文とポジションを探し、既存のデータを更新する
  3. 注文が削除された場合、またはポジションが決済された場合は、ライブラリリストから削除する。

これは、ライブラリリストを単純にクリアして1回のループで成行注文と最終ポジションで埋めるよりもコストがかかるようです。

そのため、もっと簡単な方法でリストをクリアして、もう一度入力してみましょう。もちろん、既存のライブラリリストでデータの検索と更新を行う方法を試すのを妨げるものは何もありません。ライブラリを使って作業をし、その作業速度を改善するときにそれを試します( "単純から複雑へ"の基礎)。

それでは、成行注文とポジションのコレクションリストを更新する方法がどのように調整されるかを見てみましょう。

メソッドの最初の時点で、現在の市場データの構造、イベントフラグ、 出来高の変化値、そして注文数に関するすべての変数 位置がリセットされ、コレクションリストがクリアされます。

次に、MQL4なのかMQL5 なのかが確認されます。
この段階でMQL5コードを準備しているので、MQL5のバージョンを見てみましょう。

MQL 4とは異なり、MQL 5では、注文とポジションは異なるリストに格納されます。
したがって、口座の合計ポジション数を取得sじょてループ内のすべてのターミナルポジションに沿って移動し、次のポジションのチケットを選択ポジションを作成して、アクティブな注文とライブラリのポジションのコレクションリストに追加します。

同様に、現在口座に存在するすべての未決注文を追加します。未決注文についてのみ口座の注文総数を取得します。ターミナルの注文リストをループで反復処理し、注文チケットを受け取り注文オブジェクトをライブラリのアクティブ注文とポジションのコレクションリストに追加します。

両方のループが完了すると、ライブラリのアクティブ注文とポジションのコレクションリストには、現在口座存在する注文とポジションのオブジェクトが含まれます。次に、初回実行フラグをチェックします(ここでは、-1と等しい「前の」ハッシュ合計の値が使用されます)。これが初回実行の場合、SavePrevValues()メソッドを使用して、すべての「過去」の値の値がこれらの値を格納している構造体にコピーされます。これが初回実行ではない場合は、過去のハッシュ合計の値が、口座データの収集のループを収集リストに渡すときに計算された現在のハッシュ合計の値と比較されます。前回のハッシュ合計が現在のハッシュ合計と等しくない場合、口座に変更が発生しています。

この場合、現在の注文数と以前の注文数の差が新しい口座注文数を格納する変数に設定されます。現在のポジション数と以前のポジション数の差新しい口座ポジションの数を格納する変数に設定されます。合計口座バランスが変更された値を保存し、出来高変更フラグを設定、および取引イベント発生フラグを設定し、SavePrevValues()メソッドを使って「前の」値の構造体に新しい値を追加して、後に検証します。

SavePrevValues()メソッドは、現在の値を持つ構造体を以前の値を持つ構造体に単純にコピーします。

成行注文およびポジションリストの更新方法の操作と、過去の注文および取引リストの更新方法との共同作業を確認するには、Part03フォルダーから最後のテストEA「TestDoEasyPart03_4.mq5」を使用します。

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_4.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//--- include
#include <DoEasy\Engine.mqh>
//--- グローバル変数
CEngine        engine;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 
   
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| タイマー関数                                                       |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

成行注文とポジションのコレクションを追加および追跡するときに実装された変更を確認するには、CEngineクラスのTimerイベントハンドラに次の文字列を追加します。

//+------------------------------------------------------------------+
//| CEngineタイマー                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- 過去の注文と取引、および成行注文とポジションの収集のタイマー
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL && counter.IsTimeDone())
        {
         //--- リストを更新する 
         this.m_market.Refresh(); 
         this.m_history.Refresh();
         //--- 初回実行
         if(this.IsFirstStart())
           {
            return;
           }
         //--- 市場状態の変更を確認する
         if(this.m_market.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
           }
         //--- 口座履歴の変更を確認する
         if(this.m_history.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
           }
        }
     }
  }
//+------------------------------------------------------------------+

すべてがシンプルです。収集タイマーカウンタでの待機完了が最初にチェックされます。一時停止が終了すると、成行注文とポジションのリスト、および過去の注文と取引のリストが更新されます。初回実行時には、まだ何もする必要はありません口座イベント発生フラグを受信した場合、操作ログに適切な方法を表示します。口座履歴でイベントフラグを受信した場合も同じです。

コンパイルしてEAを起動します。ポジションを開くと、操作ログに2つのエントリが表示されます。

2019.02.28 17:36:24.678 CEngine::OnTimer: New trading event on the account
2019.02.28 17:36:24.678 CEngine::OnTimer: New trading event in the account history

1つ目は取引イベントが口座で発生したことを示し、2つ目は口座の履歴への新しいイベントの追加をことを通知します。この場合、口座の取引イベントはポジション数を1増やすことで構成され、口座履歴の新しいイベントは1つの新規成行注文と1つの新規取引の出現(「市場へのエントリ」)を示します。

ポジションを決済すると、同じ2つのエントリが操作ログに表示されますが、現在の値は異なります。口座の取引イベントはポジション数が1つ減少することで、口座履歴での新しいイベントはは、単一の新規成約成行注文と単一の新規取引を追加します(「市場からのエグジット」)。

次の段階

次の記事では、メインのライブラリ要素(CEngineクラス)の開発を続け、コレクションからのイベント処理とプログラムへの送信を実装します。

現在のバージョンのライブラリのすべてのファイルは、テスト用EAファイルと一緒に以下に添付されているので、テストするにはダウンロードしてください。
質問、コメント、提案はコメント欄にお願いします。

目次に戻る