MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第4部): 取引イベント

17 7月 2019, 13:33
Artyom Trishkin
0
130

内容

第1部では、MetaTrader 5とMetaTrader 4用のプログラムの開発を単純化するための大規模なクロスプラットフォームライブラリの作成を始めました。その後の記事では、ライブラリの開発を継続し、エンジンライブラリの基本オブジェクトと成行注文とポジションのコレクションを完成しました。本稿では、基本オブジェクトの開発を続け、口座での取引イベントが識別できるようにします。

取引イベントをプログラムに送信する

3番目の記事の直近に作成されたテストEAを振り返ってみると、ライブラリは口座で発生した取引イベントを定義できることがわかります。ただし、操作にライブラリを使用しているプログラムにイベントを送信するには、発生するすべてのイベントをその種類で正確に分割する必要があります。
これには、イベントを定義するメソッドとイベントタイプを定義するメソッドを作成する必要があります。

どの取引イベントを特定する必要があるのか考えてみましょう。

  • 未決注文を出す
  • 未決注文を削除する
  • 未決注文を発動してポジションを生成する
  • 未決注文を部分的に発動してポジションを生成する
  • ポジションを開く
  • ポジションを閉じる
  • ポジションを部分的に開く
  • ポジションを部分的に閉じる
  • 反対方向のポジションによってポジションを閉じる
  • 反対方向のポジションによってポジションを部分的に閉じる
  • 口座に入金する
  • 口座から出金する
  • 口座に残高操作が発生する
    以下は現在追跡されていないイベントです。
  • 未決注文が修正される(発動価格の変更、ストップロスとテイクプロフィットの追加/削除)
  • ポジションが修正される(ストップロスとテイクプロフィットの追加/削除/変更)

上記に基づいて、イベントを明確に特定する方法を決定する必要があります。この時点で口座の種類に応じてソリューションを分割することをお勧めします。

ヘッジ口座

  1. 未決注文数が増加する: 未決注文の追加(市場環境でのイベント)
  2. 未決注文数が減少する
    1. ポジション数が増加する: 未決注文の発動(市場環境および履歴環境でのイベント)
    2. ポジション数が増加しない: 未決注文の削除(市場環境でのイベント)
  3. 未決注文数が減少しない
    1. ポジション数が増加する: 新規ポジションが開いた(市場環境および履歴環境でのイベント)
    2. ポジション数が減少する: ポジションが閉じた(市場環境および履歴環境でのイベント)
    3. ポジション数が変化しないがボリュームが減少する: ポジションが部分的に閉じた(履歴環境でのイベント)

ネッティング口座

  1. 未決注文数が増加する: 未決注文の追加
  2. 未決注文数が減少する
    1. ポジション数が増加する: 未決注文の発動
    2. ポジション数が変化しないが、ポジション変更時間が変化し、ボリュームが変化しない: 未決注文の発動 とポジションボリュームの増加
    3. ポジション数が減少する: ポジションの決済
  3. 未決注文数が減少しない
    1. ポジション数が増加する: 新規ポジションを開く
    2. ポジション数が減少する: ポジションの決済
    3. ポジション数が変化しないが、ポジション変更時間が変化し、ボリュームが増加する: ポジションへのボリュームを追加
    4. ポジション数が変化しないが、ポジション変更時間が変化し、ボリュームが減少する: 部分的なポジション決済

取引イベントを識別するためには、プログラムが動作する口座を知る必要があります。ヘッジ口座タイプのフラグをCEngineクラスのprivateセクションに追加して、口座タイプをクラスコンストラクタで定義してその結果を個のフラグ変数に書き入れます

//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                               |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // 過去の注文と取引のコレクション
   CMarketCollection    m_market;                        // 注文と取引のコレクション
   CArrayObj            m_list_counters;                 // タイマーカウンタのリスト
   bool                 m_first_start;                   // 初期実行フラグ
   bool                 m_is_hedge;                      // ヘッジ口座フラグ
//--- 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();
  };
//+------------------------------------------------------------------+
//| 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);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

初期実行時には、クラスオブジェクトの構築中に、プログラムが起動されたときの口座タイプがそのコンストラクタで定義されます。したがって、取引イベントを定義するメソッドは直ちにヘッジまたはネッティングのどちらかの口座に起因するようになります。

着信取引イベントを定義したら、そのコードを保存する必要があります。コードは次のイベントまで変わらないため、プログラムは常に口座の直近のイベントを定義できます。イベントコードは一連のフラグで構成されます。各フラグは特定のイベントを説明します。たとえば、ポジション決済イベントは、それをより正確に特徴付ける特定のサブセットに分割することができます。

  1. 完全決済
  2. 部分決済
  3. 反対方向のポジションによる決済
  4. ストップロスによる決済
  5. テイクプロフィットによる決済
  6. など

これらすべての属性は、1つの「ポジション決済」イベントに固有のものです。つまり、イベントコードにばこれらすべてのデータが含まれている必要があります。フラグを使用してイベントを構築するために、ライブラリルートフォルダからDefines.mqhファイルに、追跡するアカウント2つの新しい列挙(取引イベントフラグ可能な取引イベント)を作成しましょう。

//+------------------------------------------------------------------+
//| 口座の取引イベントリスト                                            |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT_FLAGS
  {
   TRADE_EVENT_FLAG_NO_EVENT        =  0,                   // イベントなし
   TRADE_EVENT_FLAG_ORDER_PLASED    =  1,                   // 未決注文が出された
   TRADE_EVENT_FLAG_ORDER_REMOVED   =  2,                   // 未決注文の削除
   TRADE_EVENT_FLAG_ORDER_ACTIVATED =  4,                   // 未決注文の発動
   TRADE_EVENT_FLAG_POSITION_OPENED =  8,                   // ポジションが開いた
   TRADE_EVENT_FLAG_POSITION_CLOSED =  16,                  // ポジションが閉じた
   TRADE_EVENT_FLAG_ACCOUNT_BALANCE =  32,                  // 残高操作(取引タイプで分類)
   TRADE_EVENT_FLAG_PARTIAL         =  64,                  // 部分実行
   TRADE_EVENT_FLAG_BY_POS          =  128,                 // 反対方向ポジションによる実行
   TRADE_EVENT_FLAG_SL              =  256,                 // ストップロスによる実行
   TRADE_EVENT_FLAG_TP              =  512                  // テイクプロフィットによる実行
  };
//+------------------------------------------------------------------+
//| 口座で可能な取引イベントのリスト                                     |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT,                                    // 取引イベントなし
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // 未決注文が出された
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // 未決注文の削除
//--- ENUM_DEAL_TYPE列挙体メンバに一致する列挙体メンバ
   TRADE_EVENT_ACCOUNT_CREDIT,                              // クレジット課金
   TRADE_EVENT_ACCOUNT_CHARGE,                              // 追加の課金
   TRADE_EVENT_ACCOUNT_CORRECTION,                          // エントリの修正
   TRADE_EVENT_ACCOUNT_BONUS,                               // ボーナスの課金
   TRADE_EVENT_ACCOUNT_COMISSION,                           // 追加手数料
   TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                     // 一日の終わりに請求される手数料
   TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                   // 月末に請求される手数料
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,               // 取引日の終わりに請求されるエージェント手数料
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,             // 月末に請求される代理人手数料
   TRADE_EVENT_ACCOUNT_INTEREST,                            // フリーファンドへの利息の発生
   TRADE_EVENT_BUY_CANCELLED,                               // キャンセルされた買い取引
   TRADE_EVENT_SELL_CANCELLED,                              // キャンセルされた売り取引
   TRADE_EVENT_DIVIDENT,                                    // 配当金の発生
   TRADE_EVENT_DIVIDENT_FRANKED,                            // 適格配当金の発生
   TRADE_EVENT_TAX,                                         // 税金の発生
//--- ENUM_DEAL_TYPE列挙からのDEAL_TYPE_BALANCE取引タイプに関連する列挙のメンバ
   TRADE_EVENT_ACCOUNT_BALANCE_REFILL,                      // 口座への入金
   TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL,                  // 口座からの出金
//---
   TRADE_EVENT_PENDING_ORDER_ACTIVATED,                     // 価格による未決注文の発動
   TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,             // 格による未決注文の部分的な発動
   TRADE_EVENT_POSITION_OPENED,                             // ポジションが開いた
   TRADE_EVENT_POSITION_OPENED_PARTIAL,                     // ポジションが部分的に開いた
   TRADE_EVENT_POSITION_CLOSED,                             // ポジションが閉じた
   TRADE_EVENT_POSITION_CLOSED_PARTIAL,                     // ポジションが部分的に閉じた
   TRADE_EVENT_POSITION_CLOSED_BY_POS,                      // 反対方向のポジションによってポジションが閉じた
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,              // 反対方向のポジションによってポジションが部分的に閉じた
   TRADE_EVENT_POSITION_CLOSED_BY_SL,                       // ストップロスによってポジションが閉じた
   TRADE_EVENT_POSITION_CLOSED_BY_TP,                       // テイクプロフィットによってポジションが閉じた
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,               // ストップロスによってポジションが部分的に閉じた
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,               // テイクプロフィットによってポジションが部分的に閉じた
   TRADE_EVENT_POSITION_REVERSED,                           // ポジション反転(ネッティング)
   TRADE_EVENT_POSITION_VOLUME_ADD                          // ボリュームをポジションに追加(ネッティング)
  };
//+------------------------------------------------------------------+

ここでは、ENUM_TRADE_EVENT列挙体についていくつか明確にしておく必要があります。
一部の取引イベントではプログラムや人的介入(課金手数料、手数料、ボーナスなど)が不要なため、MQL5の取引タイプ(ENUM_DEAL_TYPE列挙)からそのデータを取得します。後でイベントを追跡するのを簡単にするために、イベントがENUM_DEAL_TYPE列挙の値と一致するようにする必要があります。
口座残高の補充資金の引き出しの2つのイベントに残高操作を分けます。DEAL_TYPE_CREDITから始まる取引タイプの列挙のその他のイベントは、残高の操作には関係ない売買(DEAL_TYPE_BUYおよびDEAL_TYPE_SELL)を除き、ENUM_DEAL_TYPEの列挙と同じ値になります。

成行注文とポジションコレクションのクラスを改善しましょう。
CMarketCollectionクラスのprivateセクションに標本注文を追加して、指定された注文プロパティで検索を実行します。一方、クラスのpublicセクションは注文とポジションの全リストを取得するためのメソッド、指定の時間範囲で選択された注文とポジションのリスト、注文またはポジションの整数、実数、文字列プロパティから返される注文とポジションのリスト を受け取ります。これにより、コレクションから成行注文とポジションの必要なリストを受け取ることができます(ライブラリの説明の第2部第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;      // 口座の未決注文とポジションのリスト
   COrder            m_order_instance;       // プロパティごとに検索する注文オブジェクト
   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:
   //--- 未決注文とポジションのリストを返す
   CArrayObj*        GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- 開いた時間がbegin_time~end_timeの注文とポジションのリストを返す
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
   //--- 比較された条件に適合する選択された(1)double、(2)integer、(3)文字列プロパティによって注文とポジションのリストを返す
   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_INTEGER property,long 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)新規ポジション数、(3)取引発生イベントフラグ数、(4)ボリューム変更数を返す
   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;                                         }
   double            ChangedVolumeValue(void)                                                   const    { return this.m_change_volume_value;                                    }
   //--- コンストラクタ
                     CMarketCollection(void);
   //--- 未決注文とポジションのリストを更新する
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

クラス本体を超えた時間で注文とポジションを選択するためのメソッドを実装します。

//+------------------------------------------------------------------------+
//| begin_timeからend_timeの範囲内の時間でコレクションから                     |
//| 成行注文またはポジションを選択します                                       |
//+------------------------------------------------------------------------+
CArrayObj* CMarketCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0)
  {
   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);
   list.FreeMode(false);
   ListStorage.Add(list);
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,begin);
   int index_begin=m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,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;
  }
//+------------------------------------------------------------------+

このメソッドは、第3部で説明した過去の注文と取引を時間ごとに選択するメソッドとほぼ同じです。必要に応じて、3番目の記事の適切なセクションの説明をもう一度読んでください。このメソッドと過去のコレクションクラスのものとの違いは、時間順を選択することを選択していないことです。成行注文とポジションには開始時間しかありません。

また、過去の注文と取引のコレクションのクラスコンストラクタを変更します。MQL5注文システムには、決済時間の概念はありません。すべての注文と取引は、それらの配置時間(またはMQL4注文システムによると開始時間)に従ってリストに配置されます。これを行うには、CHistoryCollectionクラスのコンストラクタで、過去の注文と取引のコレクションリストで並べ替え方向を定義する文字列を変更します。

//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CHistoryCollection::CHistoryCollection(void) : m_index_deal(0),m_delta_deal(0),m_index_order(0),m_delta_order(0),m_is_trade_event(false)
  {
   this.m_list_all_orders.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN #else SORT_BY_ORDER_TIME_CLOSE #endif );
   this.m_list_all_orders.Clear();
  }
//+------------------------------------------------------------------+

MQL5では過去の注文および取引コレクション内のすべての注文および取引は、デフォルトでそれらの配置時間によって並び替えられる一方、MQL4では、これらは注文プロパティで定義された決済時間で並び替えられます。

それでは、MQL5の注文システムの別の機能に移りましょう。反対のポジションでポジションを決済するときは、ORDER_TYPE_CLOSE_BYタイプの特別決済注文が出されますが、ストップ注文でポジションを決済するときは、代わりにポジションを決済する成行注文が出されます。
成行注文を考慮するには、基本注文の整数プロパティを受け取り、返すメソッドにさらに別のプロパティを追加する必要がありました(例として注文のマジックナンバーの受け取って返す)。

//+------------------------------------------------------------------+
//| マジックナンバーを返す                                              |
//+------------------------------------------------------------------+
long COrder::OrderMagicNumber() const
  {
#ifdef __MQL4__
   return ::OrderMagicNumber();
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_MAGIC);           break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_MAGIC);                 break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_MAGIC);   break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_MAGIC); break;
      default                             : res=0;                                              break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

このステータスを実際に考慮する必要がある基本注文の整数プロパティを受信および返すすべてのメソッドで、このような変更(またはそのメソッドに論理的に対応するもの)を加えました。そのようなステータスが存在するため、そのような注文タイプを格納するために、ライブラリのObjectsフォルダに成行注文の新しいクラスであるCMarketOrderを作成します。このクラスは、以前に作成された残りの市場および過去の注文と取引のオブジェクトとまったく同じです。そのため、ここではリストのみを提供します。

//+------------------------------------------------------------------+
//|                                                  MarketOrder.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 CMarketOrder : public COrder
  {
public:
   //--- コンストラクタ
                     CMarketOrder(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_ORDER,ticket) {}
   //--- サポートされる注文プロパティ: (1)実数、(2)整数
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| 注文が渡された整数プロパティをサポートする場合は「true」を返し          |
//| その他の場合は「false」を返す                                       |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_EXP          || 
      property==ORDER_PROP_DEAL_ENTRY        || 
      property==ORDER_PROP_TIME_UPDATE       || 
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_TIME_CLOSE        ||
      property==ORDER_PROP_TIME_CLOSE_MSC    ||
      property==ORDER_PROP_TICKET_FROM       ||
      property==ORDER_PROP_TICKET_TO
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| 注文が渡された整数プロパティをサポートする場合は「true」を返し          |
//| その他の場合は「false」を返す                                       |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_PROFIT            || 
      property==ORDER_PROP_PROFIT_FULL       || 
      property==ORDER_PROP_SWAP              || 
      property==ORDER_PROP_COMMISSION        ||
      property==ORDER_PROP_PRICE_CLOSE       ||
      property==ORDER_PROP_SL                ||
      property==ORDER_PROP_TP                ||
      property==ORDER_PROP_PRICE_STOP_LIMIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

ライブラリのDefines.mqhファイルで、新しいステータス(成行注文)を書きます。

//+------------------------------------------------------------------+
//| 抽象注文型(ステータス)                                             |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATUS
  {
   ORDER_STATUS_MARKET_PENDING,                             // 市場の未決注文
   ORDER_STATUS_MARKET_ORDER,                               // 成行注文
   ORDER_STATUS_MARKET_POSITION,                            // 市場のポジション
   ORDER_STATUS_HISTORY_ORDER,                              // 過去の成行注文
   ORDER_STATUS_HISTORY_PENDING,                            // 削除された未決注文
   ORDER_STATUS_BALANCE,                                    // 残高操作
   ORDER_STATUS_CREDIT,                                     // クレジット操作
   ORDER_STATUS_DEAL,                                       // 取引
   ORDER_STATUS_UNKNOWN                                     // 未知のステータス
  };
//+------------------------------------------------------------------+

ここで、リストに注文を追加するブロック (成行注文とポジションのリストを更新するCMarketCollectionクラスのRefresh()メソッド)で、 注文タイプのチェックを実装します。タイプに応じて、成行注文オブジェクトまたは未決注文オブジェクトをコレクションリストに追加します。

//+------------------------------------------------------------------+
//| 注文リストを更新する                                               |
//+------------------------------------------------------------------+
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;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketOrder *order=new CMarketOrder(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_market++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить маркет-ордер в список","Failed to add market order to list"));
            delete order;
           }
        }
      else
        {
         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 pending 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_market=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market;
      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();
     }
  }
//+------------------------------------------------------------------+

ORDER_TYPE_CLOSE_BY型の注文の決済を検討できるようにするには、CHistoryCollectionクラスの過去の注文と取引のリストを更新するRefresh()メソッドの注文型定義ブロックこの注文型を追加して、そのような注文がコレクションに含まれるようにします。さもないと、CEngineライブラリのベースオブジェクトは、ポジションが反対のポジションによって決済されたことを定義することはできません。

//+------------------------------------------------------------------+
//| 注文とポジションのリストを更新する                                   |
//+------------------------------------------------------------------+
void CHistoryCollection::Refresh(void)
  {
#ifdef __MQL4__
   int total=::OrdersHistoryTotal(),i=m_index_order;
   for(; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
      //--- 決済済みのポジいしょんおよび残高/クレジット操作
      if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         //--- 削除された未決注文  
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//---
   int delta_order=i-m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;
   this.m_is_trade_event=(this.m_delta_order!=0 ?true : false);
//--- __MQL5__
#else 
   if(!::HistorySelect(0,END_TIME)) return;
//--- 注文
   int total_orders=::HistoryOrdersTotal(),i=m_index_order;
   for(; i<total_orders; i++)
     {
      ulong order_ticket=::HistoryOrderGetTicket(i);
      if(order_ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY)
        {
         CHistoryOrder *order=new CHistoryOrder(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         CHistoryPending *order=new CHistoryPending(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- 最後に追加された注文のインデックスと前回のチェックと比較した差を保存する
   int delta_order=i-this.m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;

//--- 取引
   int total_deals=::HistoryDealsTotal(),j=m_index_deal;
   for(; j<total_deals; j++)
     {
      ulong deal_ticket=::HistoryDealGetTicket(j);
      if(deal_ticket==0) continue;
      CHistoryDeal *deal=new CHistoryDeal(deal_ticket);
      if(deal==NULL) continue;
      this.m_list_all_orders.InsertSort(deal);
     }
//--- 直近に追加された取引のインデックスと前回のチェックと比較した差を保存する
   int delta_deal=j-this.m_index_deal;
   this.m_index_deal=j;
   this.m_delta_deal=delta_deal;
//--- 履歴に新規イベントフラグを設定する
   this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
#endif 
  }
//+------------------------------------------------------------------+

発生する口座イベントを定義するためにCEngineクラスをテストする際に、サービスメソッドのいくつかのマイナーな欠陥を検出して修正しました。パフォーマンスへの影響はないのでここで記述することには意味がありません。かえって重要なライブラリ機能の開発から注意をそらすことになります。すべての変更はすでにクラスのコードに加えられているので、以下に添付されているライブラリファイルでご覧になれます。


イベント定義に関する作業を続けましょう。

取引イベント定義をデバッグした後、発生したすべてのイベントは一連のフラグとして作成された単一のクラスメンバ変数にパックされます。特定のイベントを特徴付けるコンポーネントにその値を分解するために変数からデータを読み込むメソッドは後で作成されます。

取引イベントコードを保存するためのクラスメンバ変数、ヘッジおよびネッティング勘定の取引イベントを検証するメソッド、およびCEngineクラスのprivateセクションに必要な注文オブジェクトを返すメソッドを追加します。
publicセクションでは、市場ポジションと未決注文のリストを返すメソッド、過去の市場注文と取引m_trade_event_code変数から取引イベントコードを返すメソッド、ヘッジ勘定フラグを返すメソッドを宣言します。

//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                               |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // 過去の注文と取引のコレクション
   CMarketCollection    m_market;                        // 注文と取引のコレクション
   CArrayObj            m_list_counters;                 // タイマーカウンタのリスト
   bool                 m_first_start;                   // 初期実行フラグ
   bool                 m_is_hedge;                      // ヘッジ口座フラグ
   bool                 m_is_market_trade_event;         // 口座取引イベントフラグ
   bool                 m_is_history_trade_event;        // 口座過去の取引イベントフラグ
   int                  m_trade_event_code;              // 口座取引イベントステータスコード
//--- IDによるカウンタインデックスを返す
   int                  CounterIndex(const int id) const;
//--- 「初回実行」フラグを返す
   bool                 IsFirstStart(void);
//--- (1)ヘッジ勘定(2)ネッティング勘定コレクションの使用
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(void);
//--- 直近の(1)市場未決注文、(2)成行注文、(3)直近のポジション、(4)チケット別ポジションを返す
   COrder*              GetLastMarketPending(void);                    
   COrder*              GetLastMarketOrder(void);                      
   COrder*              GetLastPosition(void);                         
   COrder*              GetPosition(const ulong ticket);               
//--- 直近の(1)削除済み未決注文、(2)過去の成行注文、(3)チケット別の過去の成行注文を返す
   COrder*              GetLastHistoryPending(void);                   
   COrder*              GetLastHistoryOrder(void);                     
   COrder*              GetHistoryOrder(const ulong ticket);           
//--- (1)すべてのポジション注文のリストからの最初および(2)直近の成行注文、(3)直近の取引を返す
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id); 
   COrder*              GetLastDeal(void);                             
public:
   //--- 市場の(1)ポジション、(2)未決注文、(3)成行注文のリストを返す
   CArrayObj*           GetListMarketPosition(void);                     
   CArrayObj*           GetListMarketPendings(void);                     
   CArrayObj*           GetListMarketOrders(void);                       
   //--- 過去の(1)注文、(2)削除済み未決注文、(3)取引、(4)ID別の成行注文のポジションリストを返す
   CArrayObj*           GetListHistoryOrders(void);                      
   CArrayObj*           GetListHistoryPendings(void);                    
   CArrayObj*           GetListHistoryDeals(void);                       
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- (1)取引イベントコードと(2)ヘッジ勘定フラグを返す
   int                  TradeEventCode(void)             const { return this.m_trade_event_code;   }
   bool                 IsHedge(void)                    const { return this.m_is_hedge;           }
//--- タイマー口座を作成する
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- タイマー
   void                 OnTimer(void);
//--- コンストラクタ/デストラクタ
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

コンストラクタの初期化リストで取引イベントコードを初期化します。

//+------------------------------------------------------------------+
//| CEngineコンストラクタ                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

宣言されたメソッドをクラス本体の外側に実装します。

//+------------------------------------------------------------------+
//| 市場ポジションのリストを返す                                        |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 市場未決注文のリストを返す                                          |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPendings(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 成行注文のリストを返す                                              |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketOrders(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 過去の注文のリストを返す                                            |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryOrders(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 削除された未決注文のリストを返す                                     |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryPendings(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 取引のリストを返す                                                 |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryDeals(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//|  ポジション注文のリストを返す                                       |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 直近のポジションを返す                                              |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| チケット別のポジションを返す                                         |
//+------------------------------------------------------------------+
COrder* CEngine::GetPosition(const ulong ticket)
  {
   CArrayObj* list=this.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| 直近の取引を返す                                                   |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastDeal(void)
  {
   CArrayObj* list=this.GetListHistoryDeals();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| 直近の市場未決注文を返す                                            |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketPending(void)
  {
   CArrayObj* list=this.GetListMarketPendings();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| 直近の過去の未決注文を返す                                          |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryPending(void)
  {
   CArrayObj* list=this.GetListHistoryPendings();
   if(list==NULL) return NULL;
   list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| 直近の成行注文を返す                                               |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketOrder(void)
  {
   CArrayObj* list=this.GetListMarketOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| 直近の過去の成行注文を返す                                          |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryOrder(void)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| チケット別の過去の成行注文を返す                                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| すべてのポジション注文のリストの                                     |
//| 最初の過去の成行注文を返す                                          |
//+------------------------------------------------------------------+
COrder* CEngine::GetFirstOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(0);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+
//| 直近の過去の成行注文を返す                                          |
//| 最初の過去の成行注文を返す                                          |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+

次の例を使用してリストがどのように取得されるのかを見てみましょう。

//+------------------------------------------------------------------+
//| 市場ポジションのリストを返す                                        |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

すべてが非常に単純で便利です。GetList() コレクションメソッドを使用して成行注文とポジションのコレクションからポジションの全リストを受け取ります。その後、CSelectクラスから指定されたプロパティで注文を選択するメソッドを使用して、「ポジション」ステータスを持つ注文を選択します。このメソッドはライブラリ説明の第3部の記事で説明されました。取得されたリストを返します。
リストが空(NULL)になっている可能性があるため、このメソッドによって返される結果を呼び出し側プログラムで確認する必要があります。

次の例を使って必要な注文を受け取るメソッドを見てみましょう。

//+------------------------------------------------------------------+
//| 直近のポジションを返す                                              |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ?order : NULL);
  }
//+------------------------------------------------------------------+

まず、上記のGetListMarketPosition()メソッドを使用して、ポジションの全リストを受け取りますリストが空の場合は、NULLを返します。次に、リストをミリ秒単位の開始時間で並び替えて(直近のポジションを受け取るので、リストは時間順に並び替えられる必要があります)、直近の注文を選択します。その結果、リストから取得した注文を呼び出し側プログラムに返します
メソッドによって返された注文を検索した結果は空になり(注文が見つからない)、NULLになる可能性があります。そのため、アクセスする前にNULLの取得結果を確認してください。

ご覧のとおり、構造は非常に迅速で簡単です。将来作成するものだけでなく、既存の任意のコレクションリストから任意のデータを受け取るための任意のメソッドを構築することができます。<これにより、このようなリストをより柔軟に使用できます。
リストを受け取るためのメソッドはpublicクラスセクションに配置されており、カスタムプログラムのリストから任意のデータを受け取ることができます。これは上記のメソッドで行われたのと同じです。
必要な注文を受け取るメソッドはprivateセクションに隠されています。これらは、内部的なニーズ、特に直近の注文、取引、およびポジションに関するデータを取得するために、CEngineクラスでのみ必要です。カスタムプログラムでは、指定されたプロパティで特定の注文を受け取るためのカスタム関数を作成できます(例は上に表示されています)。コレクションからデータを簡単に取得するために、最終的にエンドユーザー向けにさまざまな機能を作成します
これは注文のコレクションの取り扱いに関する最終記事で行われます。

それでは、取引イベントを確認するためのメソッドを実装しましょう
現時点では、これはMQL5ヘッジ勘定でのみ機能します。その後、MQL5ネッティング勘定とMQL4の取引イベントを確認するメソッドを開発します。メソッド操作をテストするために、確認とイベント結果の表示が特徴とされます。この機能は、口座の取引イベントを確認するメソッドをデバッグした後に作成される別のメソッドで実行されるため、後で削除される予定です。

//+------------------------------------------------------------------+
//| 取引イベントを確認する(ヘッジ)                                       |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- 取引イベントコードとフラグを初期化する
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- リストを更新する 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- 初回実行時のアクション
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- 市場の状態のダイナミクスとアカウントの履歴を確認する
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();
//---
#ifdef __MQL4__

#else // MQL5
//--- イベントが成行注文とポジションのみにある場合
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
      //--- 未決注文数が増加する場合
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- 未決注文をインストールするためのフラグを追加する
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
         string text=TextByLanguage("Установлен отложенный ордер: ","未決注文が出された: ");
         //--- 直近の成行注文を取得する
         COrder* order=this.GetLastMarketPending();
         if(order!=NULL)
           {
            //--- メッセージにチケットを追加する
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- 操作ログにメッセージを追加する
         Print(DFUN,text);
        }
      //--- 成行注文数が増加した場合
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- イベントフラグを追加しない
         //--- ...
         string text=TextByLanguage("Выставлен маркет-ордер: ","Market order placed: ");
         //--- 直近の成行注文を取得する
         COrder* order=this.GetLastMarketOrder();
         if(order!=NULL)
           {
            //--- メッセージにチケットを追加する
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- 操作ログにメッセージを追加する
         Print(DFUN,text);
        }
     }
   
//--- イベントが過去の注文と取引のみの場合
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
      //--- 新規取引がある場合
      if(this.m_history.NewDeals()>0)
        {
         //--- 口座残高イベントフラグを追加する
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
         string text=TextByLanguage("Новая сделка: ","New deal: ");
         //--- 直近の取引を取得する
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- テキストに説明を追加する
            text+=deal.TypeDescription();
            //--- 取引が残高操作の場合
            if((ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE)==DEAL_TYPE_BALANCE)
              {
              //--- 取引利益を確認してメッセージにイベントを追加する(資金の入金または出金)
               text+=(deal.Profit()>0 ?TextByLanguage(": Пополнение счёта: ",": Account Recharge: ") : TextByLanguage(": Вывод средств: ",": Withdrawal: "))+::DoubleToString(deal.Profit(),(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS));
              }
           }
         //--- 操作ログでメッセージを表示する
         Print(DFUN,text);
        }
     }
   
//--- イベントが市場にあって過去の注文とポジションの場合
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новые торговые события на счёте и в истории счёта","New trading events on account and in account history"));
      
      //--- 未決注文数が減少して新規取引がない場合
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- 未決注文を削除するためのフラグを追加する
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
         string text=TextByLanguage("Удалён отложенный ордер: ","Removed pending order: ");
         //--- 直近の過去の未決注文を取得する
         COrder* order=this.GetLastHistoryPending();
         if(order!=NULL)
           {
            //--- チケットを適切なメッセージに追加する
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- 操作ログでメッセージを表示する
         Print(DFUN,text);
        }
      
      //--- 新規取引と新規過去注文がある場合
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- 直近の取引を取得する
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- 市場エントリ取引の場合
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- ポジションオープンフラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               string text=TextByLanguage("Открыта позиция: ","Position opened: ");
               //--- 未決注文数が減少した場合
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- 未決注文発動フラグを追加する
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                  text=TextByLanguage("Сработал отложенный ордер: ","Pending order activated: ");
                 }
               //--- 注文から注文のチケットを取得する
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- 現在の注文量が0を超える場合
                  if(order.VolumeCurrent()>0)
                    {
                     //--- 部分的実行フラグを追加する
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично открыта позиция: ","Position partially open: ");
                    }
                  //--- メッセージに注文の方向を追加する
                  text+=order.DirectionDescription();
                 }
               //--- 取引からポジションチケットをメッセージに追加し、操作ログにメッセージを表示する
               text+=" #"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- 市場エグジット取引の場合
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- ポジション決済フラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               string text=TextByLanguage("Закрыта позиция: ","Position closed: ");
               //--- 取引から取引のチケットを取得する
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- 取引のポジションが市場にまだ存在する場合
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- 部分的実行フラグを追加する
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично закрыта позиция: ","Partially closed position: ");
                    }
                  //--- その他の場合に、ポジションが完全に決済されている場合
                  else
                    {
                     //--- 注文にストップロスによる決済のフラグがある場合
                     if(order.IsCloseByStopLoss())
                       {
                        //--- ストップロスによる決済のフラグを追加する
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                        text=TextByLanguage("Позиция закрыта по StopLoss: ","Position closed by StopLoss: ");
                       }
                     //--- 注文にテイクプロフィットによる決済のフラグがある場合
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- テイクプロフィットによる決済のフラグを追加する
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                        text=TextByLanguage("Позиция закрыта по TakeProfit: ","Position closed by TakeProfit: ");
                       }
                    }
                  //--- メッセージに注文の逆方向を追加する
                  //--- 売りポジションの買い注文と買いポジションの売り注文を決済する、
                  //--- したがって、決済されたポジションを正しく記述するために注文の方向を逆にする
                  text+=(order.DirectionDescription()=="Sell" ?"Buy " : "Sell ");
                 }
               //--- 取引のポジションのチケットをメッセージに追加し、操作ログにメッセージを表示する
               text+="#"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- 反対ポジションによる決済
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- ポジション決済フラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- 反対ポジションによる決済のフラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               string text=TextByLanguage("Позиция закрыта встречной: ","Position closed by opposite position: ");
               //--- 取引注文を取得する
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- メッセージに注文の逆方向を追加する
                  //--- 売りポジションの買い注文と買いポジションの売り注文を決済する、
                  //--- したがって、決済されたポジションを正しく記述するために注文の方向を逆にする
                  text+=(order.DirectionDescription()=="Sell" ?"Buy" : "Sell");
                  text+=" #"+(string)order.PositionID()+TextByLanguage(" закрыта позицией #"," closed by position #")+(string)order.PositionByID();
                  //--- 注文のポジションが市場にまだ存在する場合
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- 部分的実行フラグを追加する
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text+=TextByLanguage(" частично"," partially");
                    }
                 }
               //--- 操作ログでメッセージを表示する
               Print(DFUN,text);
              }
           //--- 直近の取引を処理するブロックの終わり
           }
        }
     }
#endif 
  }
//+------------------------------------------------------------------+

メソッドは単純ですが、かなり長いです。そのため、このコードにはすべてのチェックと対応するアクションが含まれており、メソッド内で何が行われているのかを簡単に確認できます。今のところ、このメソッドではMQL5ヘッジ勘定の確認が実装されています。実際には、すべては口座の履歴または市場で新しく出現した注文や取引の数の確認です。操作ログに表示する場合、データは直近のポジション、直近の取引、直近の注文、または直近の取引の順番から取得されます。これは、コードの実行を確認するために操作ログにデータを表示するために行われます。この機能は後でコードから削除され、ライブラリを扱うプログラムで使用される単一のメソッドに置き換えられます。

取引イベント処理のテスト

口座の取引イベントを定義するメソッドを確認するためのテスト用EAを作成しましょう。
新しいイベントを管理するためにボタンのセットを実装しましょう。
必要な操作と対応するボタンは次のとおりです。

  • 買いポジションを開く
  • BuyLimit未決注文を出す
  • BuyStop未決注文を出す
  • BuyStopLimit未決注文を出す<分
  • 買いポジションを決済する
  • 買いポジションの半分を決済する
  • 買いポジションを反対の売りポジションによって決済する
  • 売りポジションを開く
  • SellLimit未決注文を出す
  • SellStop未決注文を出す
  • SellStopLimit未決注文を出す
  • 売りポジションを決済する
  • 売りポジションの半分を決済する
  • 売りポジションを反対の買いポジションによって決済する
  • すべてのポジションを決済する
  • 口座から出金する

入力セットは次の通りです。

  • Magic number - マジックナンバー
  • Lots - ポジションボリューム
  • ポイント単位のストップロス
  • ポイント単位のテイクプロフィット
  • 未決注文の距離(ポイント単位)
  • StopLimit注文の距離(ポイント単位)
    StopLimit注文は、未決注文距離の値で設定された価格からの距離で出されます。
    価格が指定された注文に達して発動されるとすぐに、その価格レベルはStopLimit注文距離値によって設定された価格からの距離で指値注文を出すために使用されます。
  • スリッページ(ポイント単位)
  • 出勤(テスター) - テスターで口座から引き出された資金

StopLevel、逆指値注文、およびポジションボリュームに対する発注価格を定義するための正しい値を計算するための関数が必要になります。この段階で、取引クラスと銘柄クラスがまだない限り、DELib.mqhファイル内のサービス関数のライブラリに関数を追加しましょう。

//+------------------------------------------------------------------+
//| 銘柄の最小ロットを返す                                              |
//+------------------------------------------------------------------+
double MinimumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
  }
//+------------------------------------------------------------------+
//| 銘柄の最大ロットを返す                                              |
//+------------------------------------------------------------------+
double MaximumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
  }
//+------------------------------------------------------------------+
//| 銘柄のロット変更ステップを返す                                       |
//+------------------------------------------------------------------+
double StepLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_STEP);
  }
//+------------------------------------------------------------------+
//| 正規化されたロットを返す                                            |
//+------------------------------------------------------------------+
double NormalizeLot(const string symbol_name, double order_lots) 
  {
   double ml=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
   double mx=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
   double ln=NormalizeDouble(order_lots,int(ceil(fabs(log(ml)/log(10)))));
   return(ln<ml ?ml : ln>mx ?mx : ln);
  }
//+------------------------------------------------------------------+
//| StopLevelに相対した正しいストップロスを返す                          |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ? 
      NormalizeDouble(fmin(price-lv*pt,stop_loss),dg) :
      NormalizeDouble(fmax(price+lv*pt,stop_loss),dg)
     );
  }
//+------------------------------------------------------------------+
//| StopLevelに相対した正しいストップロスを返す                          |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmin(price-lv*pt,price-stop_loss*pt),dg) :
      NormalizeDouble(fmax(price+lv*pt,price+stop_loss*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| StopLevelに相対した正しいテイクプロフィットを返す                     |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmax(price+lv*pt,take_profit),dg) :
      NormalizeDouble(fmin(price-lv*pt,take_profit),dg)
     );
  }
//+------------------------------------------------------------------+
//| StopLevelに相対した正しいテイクプロフィットを返す                     |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      ::NormalizeDouble(::fmax(price+lv*pt,price+take_profit*pt),dg) :
      ::NormalizeDouble(::fmin(price-lv*pt,price-take_profit*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| StopLevelに相対した                                               |
//| 正しい発注価格を返す                                               |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| StopLevelに相対した                                               |
//| 正しい発注価格を返す                                               |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ?SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| StopLevelに相対したポイント単位のストップレベルを確認する              |
//+------------------------------------------------------------------+
bool CheckStopLevel(const string symbol_name,const int stop_in_points,const int spread_multiplier)
  {
   return(stop_in_points>=StopLevel(symbol_name,spread_multiplier));
  }
//+------------------------------------------------------------------+
//| ポイント単位のStopLevelを返す                                      |
//+------------------------------------------------------------------+
int StopLevel(const string symbol_name,const int spread_multiplier)
  {
   int spread=(int)SymbolInfoInteger(symbol_name,SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ?spread*spread_multiplier : stop_level);
  }
//+------------------------------------------------------------------+ 

これらの関数は単に、サーバに設定されている制限に違反しないように正しい値を計算します。銘柄とは別に、StopLevel計算関数はスプレッド乗数も受け取ります。これは、サーバ上のStopLevelがゼロに設定されている場合はレベルが浮動であり、StopLevelを計算するためには、特定の数値を掛けたスプレッド値を使用する必要があるためです。この乗数は関数に渡され、EAの設定でハードコードするか計算することができます。

時間を節約するために、カスタム取引関数は作成しません。代わりに、標準ライブラリの既製の取引クラス、つまり取引操作を実行するためのCTradeクラスを使用します。機能するチャートボタンを作成するために、特定のボタンが押されたことを確認するためのボタン名、ラベル、および値を設定するメンバを持つ列挙を実装します。

MQL5\Experts\TestDoEasy\Part04\で、新しいTestDoEasy04.mqh EAを作成します(EAを作成するときにMQLウィザードのOnTimerおよびOnChartEventイベントハンドラを確認してください。):


MQLウィザードでEAテンプレートを作成したら、カスタムライブラリと標準ライブラリの取引クラスをインクルードします。入力パラメータも追加します。

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.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>
#include <Trade\Trade.mqh>  
//--- 列挙体
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- 構造体
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- 入力変数
input ulong    InpMagic       =  123;  // マジックナンバー
input double   InpLots        =  0.1;  // ロット
input uint     InpStopLoss    =  50;   // ストップロス(ポイント単位)
input uint     InpTakeProfit  =  50;   // テイクプロフィット(ポイント単位)
input uint     InpDistance    =  50;   // 未決注文の距離(ポイント単位)
input uint     InpDistanceSL  =  50;   // StopLimit注文の距離(ポイント単位)
input uint     InpSlippage    =  0;    // スリッページ(ポイント単位)
input double   InpWithdrawal  =  10;   // 出金(テスター)
//--- グローバル変数
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ?0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+

CEngineライブラリのメインオブジェクトCTrade取引クラスをインクルードします。
次に、必要なボタンを指定した列挙体を作成します。
列挙の順序は、ボタン作成の順序とチャート上のポジションを設定するので重要です。

その後、ボタンのグラフィックオブジェクトの名前とボタンに刻まれるテキストを格納するための構造体を宣言します。
入力ブロックで、上記のEAパラメータ変数をすべて設定します。EAのグローバル変数ブロックで、ライブラリオブジェクト取引クラスオブジェクトボタン構造体配列OnInit()ハンドラで入力変数が割り当てされる変数を宣言します。

//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 口座の種類を確認する
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- グローバル変数を設定する
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- ボタンを作成する
   if(!CreateButtons())  
      return INIT_FAILED;
//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

OnInit()ハンドラで、口座の種類を確認して、ヘッジでなければ、そのことをエラーメッセージとして表示してプログラムを終了します。
次に、(EAがそのオブジェクトを認識できるように)オブジェクト名の接頭辞を設定し、ボタンの数だけ反復して、ループ内でボタンデータを構造体の配列に書き入れます
ボタンオブジェクト名は、ループインデックスに対応するENUM_BUTTONS列挙体の接頭辞+文字列表現として設定される一方、ボタンテキストは、EnumToButtText()関数を使用して、ループインデックスに対応する列挙の文字列表現を変換することによってコンパイルされます。

その後、ポジションと注文のロットが計算されます。ポジションの半分が決済されるので、ポジションのロットは最低ロットの少なくとも2倍であるべきです。したがって、最大ロット数は次の2つから取られます。
1) 入力、2) fmax(InpLots,MinimumLots(Symbol())*2.0)での最小ロットの2倍、取得されたロットは正規化されてlotグローバル変数に割り当てられます。その結果、入力でユーザが入力したロットが最小ロットの2倍より小さい場合、その2倍の最小ロットが使用されます。その他の場合は、ユーザによって入力されたロットが適用されます。

残りの入力は適切なグローバル変数に割り当てられ、前のステップですでにボタンデータが書き入れられた構造体配列からボタンを作成するためにCreateButtons()関数が呼び出されます。 ButtonCreate()関数を使用してボタンを作成出来なかった場合は、エラーメッセージが表示されてプログラムが初期化エラーによって終了します

最後に、CTradeクラスが初期化されます。

//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---
  • スリッページ(ポイント単位)を設定します 
  • マジックナンバーを設定します
  • 現在の銘柄の設定に従って、注文実行の種類を設定します
  • 現在の口座設定に従って、証拠金計算モードを設定します 
  • 操作ログにエラーメッセージのみを表示するようにメッセージログレベルを設定します
    (テスターではデフォルトですべてのログの表示がオンになっています)

OnDeinit()ハンドラで、オブジェクト名の接頭辞によるすべてのボタンの削除を実装します。

//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- オブジェクトを削除する
   ObjectsDeleteAll(0,prefix);
  }
//+------------------------------------------------------------------+

それでは、ライブラリタイマーとEAイベントハンドラの起動順序を決定しましょう。

  • EAがテスターで起動されていない場合は、ライブラリタイマーはEAタイマーから起動され、イベントハンドラは通常モードで動作します。
  • EAがテスターで起動された場合、ライブラリタイマーはEAのOnTick()ハンドラから起動されます。ボタン押下イベントはOnTick()でも追跡されます。

以下はEAのOnTick()、OnTimer()、OnChartEvent()ハンドラです。

//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+
//| タイマー関数                                                      |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent関数                                                   |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }  
  }
//+------------------------------------------------------------------+

OnTick()で

  • EAの起動場所を確認します。テスターで起動されている場合、 ライブラリのOnTimer()ハンドラを呼び出します
  • 次に、オブジェクト名をループで現在のすべてのチャートオブジェクトに対して確認します。ボタンに一致した場合、ボタン押下ハンドラを呼び出します

OnTimer()で

  • EAの起動場所を確認します。テスターで起動されていない場合ライブラリのOnTimer()ハンドラを呼び出します

OnChartEvent()で

  • EAの起動場所を確認します。テスターで起動されている場合ハンドラを終了します
  • 次に、イベントIDがチェックされます。これがグラフィカルオブジェクトをクリックするイベントであり、オブジェクト名にボタンに属するテキストが含まれている場合、適切なボタン押下ハンドラが呼び出されます

CreateButtons()関数:

//+------------------------------------------------------------------+
//| ボタンパネルを作成する                                              |
//+------------------------------------------------------------------+
bool CreateButtons(void)
  {
   int h=18,w=84,offset=10;
   int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/2)+h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ?w+2 : 0);
      if(i==TOTAL_BUTT-2) x=cx;
      y=(cy-(i-(i>6 ?7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ?w : w*2+2),h,butt_data[i].text,(i<4 ?clrGreen : i>6 && i<11 ?clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+

この関数では、ENUM_BUTTONS列挙型メンバの数だけループ内でボタンの座標と色を計算することになります。ENUM_BUTTONS列挙型メンバの番号を示すループインデックスに基づいて、座標と色が計算されます。x座標とy座標を計算した後、ループ内で計算された座標と色の値を持つボタン作成関数が呼び出されます。

EnumToButtText()関数:

//+------------------------------------------------------------------+
//| 列挙をボタンテキストに変換する                                       |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   return txt;
  }
//+------------------------------------------------------------------+

ここではすべて簡単です。関数が列挙型メンバを受け取り不要なテキストを削除した文字列に変換します。次に、取得した文字列のすべての文字を小文字に変換し、不適切なエントリをすべて必要なものに置き換えます。
テキストに変換された入力列挙文字列が最終的に返されます

以下は、ボタンを作成して配置し、そのステータスを受け取るための関数です。

//+------------------------------------------------------------------+
//| ボタンを作成する                                                   |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку!Код ошибки=","Could not create button!Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| ボタンの状態を返す                                                 |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| ボタンの状態を設定する                                              |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+

すべてがシンプルで明確なので、説明は必要ありません。

以下は、ボタン押下を処理する関数です。

//+------------------------------------------------------------------+
//| ボタン押下の処理                                                   |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- ボタン名を文字列IDに変換する
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- ボタンが押下された場合
   if(ButtonState(button_name))
     {
      //--- BUTT_BUYボタンが押下されたら、買いポジションを開く
      if(button==EnumToString(BUTT_BUY))
        {
         //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- 買いポジションを開く
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- BUTT_BUY_LIMITボタンが押下されたら、BuyLimit注文を出す
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- StopLevelに相対した正しい注文配置を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- BuyLimit注文を設定する
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_BUY_STOPボタンが押下されたら、BuyStopを設定する
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- StopLevelに相対した正しい注文価格を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- BuyStop注文を設定する
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_BUY_STOP_LIMITボタンが押下されたら、BuyStopLimitを設定する
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- StopLevelに相対した正しいBuyStop注文配置を取得する
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- StopLevelを考慮して、BuyStopレベルに相対したBuyLimit注文価格を計算する
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- BuyStopLimit注文を設定する
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- BUTT_SELLボタンが押下されたら、売りポジションを開く
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- 売りポジションを開く
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- BUTT_SELL_LIMITボタンが押下されたら、SellLimitを設定する
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- StopLevelに相対した正しい注文価格を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- SellLimit注文を設定する
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_SELL_STOPボタンが押下されたら、SellStopを設定する
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- StopLevelに相対した正しい注文配置価格を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- SellStop注文を設定する
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_SELL_STOP_LIMITボタンが押下されたら、SellStopLimitを設定する
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- StopLevelに相対した正しいSellStop注文価格を取得する
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- StopLevelを考慮して、SellStopレベルに相対したSellLimit注文価格を計算する
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- SellStopLimit注文を設定する
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- BUTT_CLOSE_BUYボタンが押下されたら、最大利益を持つ買いポジションを決済する
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 買いポジションチケットを取得し、チケットでポジションを決済する
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_BUY2ボタンが押下されたら、最大利益を持つ買いポジションを半分決済する
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 決済された量を計算し、チケットで買いポジションの半分を決済する
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- BUTT_CLOSE_BUY_BY_SELLボタンが押下されたら、最大利益を持つ買いポジションを反対方向の売りで決済する
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを選択する
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- 最大の利益を持つ買いポジションを選択する
            COrder* position_buy=list_buy.At(index_buy);
            //--- 最大の利益を持つ売りポジションを選択する
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- 買いポジションを反対の売りポジションによって決済する
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_SELLボタンが押下されたら、最大利益を持つ売りポジションを決済する
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 売りポジションチケットを取得し、チケットでポジションを決済する
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_SELL2ボタンが押下されたら、最大利益を持つ売りポジションを半分決済する
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 決済された量を計算し、チケットで売りポジションの半分を決済する
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- BUTT_CLOSE_SELL_BY_BUYボタンが押下されたら、最大利益を持つ売りポジションを反対方向の買いで決済する  
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを取得する
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- 最大の利益を持つ売りポジションを選択する
            COrder* position_sell=list_sell.At(index_sell);
            //--- 最大の利益を持つ買いポジションを選択する
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- 売りポジションを反対の買いポジションによって決済する
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_ALLボタンが押下されたら、最小利益を持つポジションから初めて、すべてのポジションを決済する
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- 最小の利益を持つポジションからの反復処理
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- 個々のポジションをチケットで決済する
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- BUTT_PROFIT_WITHDRAWALボタンが押下されたら、口座から出金する
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- プログラムがテスターで起動された場合
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- 資金出金のエミュレーション
            TesterWithdrawal(withdrawal);
           }
        }
      //--- 0.1秒待つ
      Sleep(100);
      //--- ボタンを放してチャートを再描画する
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

この関数は非常に大きいですが単純です。文字列IDに変換されるボタンオブジェクトの名前を受け取ります。次にボタンのステータスが確認され、押された場合は文字列IDが確認されます。適切なif-else分岐が実行され、すべてのレベルが計算され、StopLevelの制限に違反しないように必要な調整が適用されます。取引クラスの対応するメソッドが実行されます。
すべての説明は、コードのコメント文字列に直接書かれています。

テストEAでは、実際の口座にとって重要な他のすべてのチェックをスキップして、必要最小限のチェックを行いました。現在最も重要なことは、EAを実際の口座で動作できるように開発するのではなく、ライブラリの動作を確認することです。

以下はテストEAのコード全部です。

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.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>
#include <Trade\Trade.mqh>
//--- 列挙体
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- 構造体
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- 入力変数
input ulong    InpMagic       =  123;  // マジックナンバー
input double   InpLots        =  0.1;  // ロット
input uint     InpStopLoss    =  50;   // ストップロス(ポイント単位)
input uint     InpTakeProfit  =  50;   // テイクプロフィット(ポイント単位)
input uint     InpDistance    =  50;   // 未決注文の距離(ポイント単位)
input uint     InpDistanceSL  =  50;   // StopLimit注文の距離(ポイント単位)
input uint     InpSlippage    =  0;    // スリッページ(ポイント単位)
input double   InpWithdrawal  =  10;   // 出金(テスター)
//--- グローバル変数
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ?0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+
//| エキスパート初期化関数                                              |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 口座の種類を確認する
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- グローバル変数を設定する
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- ボタンを作成する
   if(!CreateButtons())
      return INIT_FAILED;
//--- 取引パラメータを設定する
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| エキスパート初期化解除関数                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- オブジェクトを削除する
   ObjectsDeleteAll(0,prefix);
  }
//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
   if(engine.TradeEventCode()!=TRADE_EVENT_FLAG_NO_EVENT)
     {
      
      Print(DFUN,EnumToString((ENUM_TRADE_EVENT_FLAGS)engine.TradeEventCode()));
     }
   engine.TradeEventCode();
  }
//+------------------------------------------------------------------+
//| タイマー関数                                                      |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent関数                                                   |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }  
  }
//+------------------------------------------------------------------+
//| ボタンパネルを作成する                                              |
//+------------------------------------------------------------------+
bool CreateButtons(void)
  {
   int h=18,w=84,offset=10;
   int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/2)+h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ?w+2 : 0);
      if(i==TOTAL_BUTT-2) x=cx;
      y=(cy-(i-(i>6 ?7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ?w : w*2+2),h,butt_data[i].text,(i<4 ?clrGreen : i>6 && i<11 ?clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| ボタンを作成する                                                   |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку!Код ошибки=","Could not create button!Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| ボタンの状態を返す                                                 |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| ボタンの状態を設定する                                              |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+
//| 列挙をボタンテキストに変換する                                       |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   return txt;
  }
//+------------------------------------------------------------------+
//| ボタン押下を処理する                                                |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- ボタン名を文字列IDに変換する
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- ボタンが押下された場合
   if(ButtonState(button_name))
     {
      //--- BUTT_BUYボタンが押下されたら、買いポジションを開く
      if(button==EnumToString(BUTT_BUY))
        {
         //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- 買いポジションを開く
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- BUTT_BUY_LIMITボタンが押下されたら、BuyLimitを設定する
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- StopLevelに相対した正しい注文価格を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- BuyLimit注文を設定する
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_BUY_STOPボタンが押下されたら、BuyStopを設定する
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- StopLevelに相対した正しい注文価格を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- BuyStop注文を設定する
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_BUY_STOP_LIMITボタンが押下されたら、BuyStopLimitを設定する
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- StopLevelに相対した正しいBuyStop価格を取得する
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- StopLevelを考慮して、BuyStopレベルに相対したBuyLimit注文価格を計算する
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- BuyStopLimit注文を設定する
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- BUTT_SELLボタンが押下されたら、売りポジションを開く
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- 売りポジションを開く
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- BUTT_SELL_LIMITボタンが押下されたら、SellLimitを設定する
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- StopLevelに相対した正しい注文価格を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- SellLimit注文を設定する
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_SELL_STOPボタンが押下されたら、SellStopを設定する
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- StopLevelに相対した正しい注文価格を取得する
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- SellStop注文を設定する
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- BUTT_SELL_STOP_LIMITボタンが押下されたら、SellStopLimitを設定する
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- StopLevelに相対した正しいSellStop注文価格を取得する
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- StopLevelを考慮して、SellStopレベルに相対したSellLimit注文価格を計算する
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- SellStopLimit注文を設定する
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- BUTT_CLOSE_BUYボタンが押下されたら、最大利益を持つ買いポジションを決済する
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 買いポジションチケットを取得し、チケットでポジションを決済する
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_BUY2ボタンが押下されたら、最大利益を持つ買いポジションを半分決済する
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 決済された量を計算し、チケットで買いポジションの半分を決済する
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- BUTT_CLOSE_BUY_BY_SELLボタンが押下されたら、最大利益を持つ買いポジションを反対方向の売りで決済する
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを選択する
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- 最大の利益を持つ買いポジションを選択する
            COrder* position_buy=list_buy.At(index_buy);
            //--- 最大の利益を持つ売りポジションを選択する
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- 買いポジションを反対の売りポジションによって決済する
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_SELLボタンが押下されたら、最大利益を持つ売りポジションを決済する
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 売りポジションチケットを取得し、チケットでポジションを決済する
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_SELL2ボタンが押下されたら、最大利益を持つ売りポジションを半分決済する
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを選択する
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 決済された量を計算し、チケットで売りポジションの半分を決済する
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- BUTT_CLOSE_SELL_BY_BUYボタンが押下されたら、最大利益を持つ売りポジションを反対方向の買いで決済する
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- リストから売りポジションのみを選択する
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ売りポジションのインデックスを取得する
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- すべてのポジションのリストを取得する
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- リストから買いポジションのみを選択する
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 最大の利益を持つ買いポジションのインデックスを取得する
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- 最大の利益を持つ売りポジションを選択する
            COrder* position_sell=list_sell.At(index_sell);
            //--- 最大の利益を持つ買いポジションを選択する
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- 売りポジションを反対の買いポジションによって決済する
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- BUTT_CLOSE_ALLボタンが押下されたら、最小利益を持つポジションから初めて、すべてのポジションを決済する
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- すべてのポジションのリストを取得する
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- 手数料とスワップを考慮して、リストを利益順に並べ替える
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- 最小の利益を持つポジションからの反復処理
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- 個々のポジションをチケットで決済する
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- BUTT_PROFIT_WITHDRAWALボタンが押下されたら、口座から出金する
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- プログラムがテスターで起動された場合
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- 資金出金のエミュレーション
            TesterWithdrawal(withdrawal);
           }
        }
      //--- 0.1秒待つ
      Sleep(100);
      //--- ボタンを放してチャートを再描画する
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

コードは非常に長いです... ただし、これは始まりにすぎません。エンドユーザーのためにすべてが簡素化されるためです。ここで実行するほとんどのアクションはライブラリコードに隠されていますが、ライブラリと対話するためのよりユーザーフレンドリーなメカニズムが導入されています。

テスターでEAを起動してボタンを試してみましょう。

すべてが正しく活動化され、操作ログは発生したイベントに関するメッセージを受け取ります。

現在、直近のイベントは常に修正されています。言い換えれば、複数のポジションを同時に決済した場合、直近のポジションのみがイベント内に存在します。複数の決済は、履歴内の新規の取引または注文の数によって追跡できます。それから、それらの数によってすべての新しく決済されたポジションのリストを得て、セット全体を定義することは可能です。そのために別のイベントコレクションクラスを開発しましょう。プログラム内で発生したすべてのイベントに常にアクセスできるようになります。

現在、テスタージャーナル内のすべてのイベントメッセージは、ライブラリベースオブジェクトのCEngine::WorkWithHedgeCollections()メソッドに表示されます。口座で発生したことを「理解」するには、カスタムプログラムでイベントコードを知る必要があります。これにより、イベントに応じてプログラムの応答ロジックを作成することができます。それを実現する能力をテストするために、ライブラリのベースオブジェクトに2つのメソッドを作成します。1つは直近のイベントのコードを保存するものであり、もう1つは一連のイベントフラグからなるこのコードをデコードするものです。次回の記事では、口座イベントを処理するための本格的なクラスを作成します。

CEngineクラス本体で、イベントコードをデコードして口座の取引イベントコードを設定するメソッドイベントコード内のイベントフラグの存在をチェックするメソッド呼び出し側プログラムから直近の取引イベントを受信するためのメソッド直近の取引イベントの値をリセットするためのメソッドを定義します。

//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                               |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // 過去の注文と取引のコレクション
   CMarketCollection    m_market;                        // 注文と取引のコレクション
   CArrayObj            m_list_counters;                 // タイマーカウンタのリスト
   bool                 m_first_start;                   // 初期実行フラグ
   bool                 m_is_hedge;                      // ヘッジ口座フラグ
   bool                 m_is_market_trade_event;         // 口座取引イベントフラグ
   bool                 m_is_history_trade_event;        // 口座過去の取引イベントフラグ
   int                  m_trade_event_code;              // 口座取引イベントステータスコード
   ENUM_TRADE_EVENT     m_acc_trade_event;               // 口座取引イベント
//--- イベントコードをデコードし、アカウントに取引イベントを設定する
   void                 SetTradeEvent(void);
//--- IDによるカウンタインデックスを返す
   int                  CounterIndex(const int id) const;
//--- (1)初回実行フラグ、(2)取引イベントでのフラグの存在を返す
   bool                 IsFirstStart(void);
   bool                 IsTradeEventFlag(const int event_code)    const { return (this.m_trade_event_code&event_code)==event_code;  }
//--- (1)ヘッジ勘定(2)ネッティング勘定コレクションの使用
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(void);
//--- 直近の(1)市場未決注文、(2)成行注文、(3)直近のポジション、(4)チケット別ポジションを返す
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- 直近の(1)削除済み未決注文、(2)過去の成行注文、(3)チケット別の過去の成行注文を返す
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- (1)すべてのポジション注文のリストからの最初および(2)直近の成行注文、(3)直近の取引を返す
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- 市場の(1)ポジション、(2)未決注文、(3)成行注文のリストを返す
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- 過去の(1)注文、(2)削除済み未決注文、(3)取引、(4)ID別の成行注文のポジションリストを返す
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListHistoryDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- 直近の取引イベントをリセットする
   void                 ResetLastTradeEvent(void)                       { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;  }
//--- (1)直近の取引イベント、(2)取引イベントコード、(3)ヘッジ勘定フラグを返す
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;                }
   int                  TradeEventCode(void)                      const { return this.m_trade_event_code;               }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;                       }
//--- タイマーカウンタを作成する
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- タイマー
   void                 OnTimer(void);
//--- コンストラクタ/デストラクタ
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

クラス本体の外で、取引イベントをデコードするメソッドを書きます(すべての説明を直接コードに書きます):

//+------------------------------------------------------------------+
//| イベントコードを出コードして取引イベントを設定する                     |
//+------------------------------------------------------------------+
void CEngine::SetTradeEvent(void)
  {
//--- 取引イベントExit
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_NO_EVENT)
      return;
//--- 未決注文が設定される(ここではフラグが1つしかない場合があるため、イベントコードが一致するかどうかを確認する)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- 未決注文が削除される(ここではフラグが1つしかない場合があるため、イベントコードが一致するかどうかを確認する)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- ポジションが開かれる(イベントコードの複数のフラグを確認する)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- 未決注文が価格によって発動した場合
      if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ?TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ?TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- ポジションが決済される(イベントコードの複数のフラグを確認する)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- ポジションがストップロスによって決済された場合
      if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- 部分決済フラグを確認し、「ストップロスによって決済されたポジション」または「ストップロスによって部分的に決済されたポジション」取引イベントを設定する
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ?TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- ポジションがテイクプロフィットによって決済された場合
      else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- 部分決済フラグを確認し、「テイクプロフィットによって決済されたポジション」または「テイクプロフィットによって部分的に決済されたポジション」取引イベントを設定する
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ?TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- ポジションが反対方向のポジションによって決済された場合
      else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- 部分決済フラグを確認し、「反対方向のポジションによって決済されたポジション」または「反対方向のポジションによって部分的に決済されたポジション」取引イベントを設定する
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ?TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- ポジションが決済された場合
      else
        {
         //--- 部分決済フラグを確認し、「ポジションが決済された」または「ポジションが部分的に決済された」取引イベントを設定する
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ?TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
     }
//--- 口座の残高操作(取引タイプ別にイベントを明確にする)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- 取引イベントを初期化する
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      //--- 直近の取引を取得する
      COrder* deal=this.GetLastDeal();
      if(deal!=NULL)
        {
         ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE);
         //--- 取引が残高操作の場合
         if(deal_type==DEAL_TYPE_BALANCE)
           {
           //--- 取引利益を確認してイベントを設定する(資金の入金または出金)
            this.m_acc_trade_event=(deal.Profit()>0 ?TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
           }
         //--- 残りの口座操作タイプは、DEAL_TYPE_CREDITで始まるENUM_DEAL_TYPE列挙と一致する
         else if(deal_type>DEAL_TYPE_BALANCE)
           {
           //--- イベントを設定する
            this.m_acc_trade_event=(ENUM_TRADE_EVENT)deal_type;
           }
        }
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
  }
//+------------------------------------------------------------------+

取引イベント暗号化メソッドの呼び出しを追加し、WorkWithHedgeCollections()メソッドの直近のチェックで操作ログでの取引イベントの説明の表示を削除し、取引イベントコードを作成します。

//+------------------------------------------------------------------+
//| 取引イベントを確認する(ヘッジ)                                      |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- 取引イベントコードとフラグを初期化する
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- リストを更新する 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- 初回実行時のアクション
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- 市場ステータスと口座履歴の変更を確認する
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();
//---
#ifdef __MQL4__

#else // MQL5
//--- イベントが成行注文とポジションのみに関連する場合
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      //--- 未決注文数が増加する場合
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- 未決注文発注フラグを追加する
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
        }
      //--- 成行注文数が増加した場合
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- イベントフラグを追加しない
         //--- ...
        }
     }
   
//--- イベントが過去の注文と取引のみに関連する場合
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      //--- 新規取引がある場合
      if(this.m_history.NewDeals()>0)
        {
         //--- 口座残高イベントフラグを追加する
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
        }
     }
   
//--- イベントが市場と過去の注文とポジションに関連する場合
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      //--- 未決注文数が減少して新規取引がない場合
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- 未決注文を削除するためのフラグを追加する
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
        }
      
      //--- 新規取引と新規過去注文がある場合
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- 直近の取引を取得する
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- 市場エントリ取引の場合
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- ポジションオープンフラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               //--- 未決注文数が減少した場合
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- 未決注文発動フラグを追加する
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                 }
               //--- 注文のチケットを取得する
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- 現在の注文量が0を超える場合
                  if(order.VolumeCurrent()>0)
                    {
                     //--- 部分的実行フラグを追加する
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
            
            //--- 市場エグジット取引の場合
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- ポジション決済フラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- 取引のチケットを取得する
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- 取引のポジションが市場にまだ存在する場合
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- 部分的実行フラグを追加する
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                  //--- その他の場合に、ポジションが完全に決済されている場合
                  else
                    {
                     //--- 注文にストップロスによる決済のフラグがある場合
                     if(order.IsCloseByStopLoss())
                       {
                        //--- ストップロスによる決済のフラグを追加する
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                       }
                     //--- 注文にテイクプロフィットによる決済のフラグがある場合
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- テイクプロフィットによる決済のフラグを追加する
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                       }
                    }
                 }
              }
            
            //--- 反対方向のポジションによる決済の場合
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- ポジション決済フラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- 反対ポジションによる決済のフラグを追加する
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               //--- 取引注文を取得する
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- 注文のポジションが市場にまだ存在する場合
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- 部分的実行フラグを追加する
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
           //--- 直近の取引を処理するブロックの終わり
           }
        }
     }
#endif 
   this.SetTradeEvent();
  }
//+------------------------------------------------------------------+

したがって、WorkWithHedgeCollections() メソッドでイベントコードを作成した後で、イベントデコードメソッドを呼び出します。すぐにデコードできない理由は何でしょうか。要は、現在の復号化方法は一時的なものであるということです。デコード処理を確認する必要があります。本格的な取引イベントクラスは、今後の記事で作成される予定です。
SetTradeEvent()メソッドは取引イベントを定義し、その値をm_acc_trade_eventクラスメンバ変数に書き込みます。一方、LastTradeEvent()およびResetLastTradeEvent()メソッドを使用すると、口座の直近の取引イベントとして変数の値を読み取り、それを呼び出し側プログラムでリセットできます(GetLastError()と同様)。

TestDoEasyPart04.mqhテストEAのOnTick()ハンドラで、直近の取引イベントを読み取り、それをチャートのコメントのように表示するために文字列を追加します。

//+------------------------------------------------------------------+
//| エキスパートティック関数                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
   if(engine.LastTradeEvent()!=last_event)                                  
     {                                                                      
      Comment("\nLast trade event: ",EnumToString(engine.LastTradeEvent()));
      last_event=engine.LastTradeEvent();                                   
     }                                                                      
  }
//+------------------------------------------------------------------+

テスターでこのEAを実行してボタンをクリックすると、CEngine::SetTradeEvent()メソッドからの進行中の取引イベントが操作ログに表示されます。一方、チャートのコメントには、LastTradeEvent()メソッドを使用してEAがライブラリから受信した、口座で直近に発生したイベントの説明が表示されます。


次の段階

次の記事では、注文とポジションの集まりに似たイベントオブジェクトのクラスとイベントオブジェクトのコレクションを開発し、基本ライブラリオブジェクトがプログラムにイベントを送信できるようにします。

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

目次に戻る

シリーズのこれまでの記事:

第1部
第2部
第3部

MetaQuotes Software Corp.によりロシア語から翻訳された
元の記事: https://www.mql5.com/ru/articles/5724

添付されたファイル |
MQL5.zip (44.67 KB)
フラクタル指数とハースト指数の財務時系列を予測する能力の評価 フラクタル指数とハースト指数の財務時系列を予測する能力の評価

金融データのフラクタル行動の探索に関する研究は、経済時系列の一見混沌とした行動の背後に、参加者の集団行動の隠されたメカニズムがあることを前提にしています。 これらのメカニズムは、価格シリーズの特性を定義することができ、取引所の価格ダイナミクスの出現につながることができます。 これをトレーディングに適用すると、実際に関連するスケールと時間枠のフラクタルパラメータを効率的かつ確実に推定できるインジケータの恩恵を受けることができます。

トレードにおけるOLAPの適用(パート2):インタラクティブな多次元データ分析結果の可視化 トレードにおけるOLAPの適用(パート2):インタラクティブな多次元データ分析結果の可視化

この記事では、OLAP技術を使用して口座ヒストリーとトレードレポートの処理に設計されたMQLプログラム用のインタラクティブなグラフィカルインタフェースの作成について考察します。 視覚的な結果を得るために、最大化可能でスケーラブルなウィンドウ、ラバーコントロールの適応レイアウト、および図を表示するための新しいコントロールを使用します。 ビジュアライゼーション関数を提供するために、座標軸に沿った変数の選択と、集計関数、ダイアグラムタイプ、並べ替えオプションの選択を含むGUIを実装します。

ローソク足分析技術の研究(第3部): パターン操作のライブラリ ローソク足分析技術の研究(第3部): パターン操作のライブラリ

本稿の目的は、カスタムツールを作成して、前述のパターンに関する一連の情報全体を受信して使用できるようにすることです。ユーザが独自の指標、取引パネル、エキスパートアドバイザーなどで使用できるパターン関連関数のライブラリが作成されます。

MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第5部): 取引イベントのクラスとコレクション、プログラムへのイベント送信 MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第5部): 取引イベントのクラスとコレクション、プログラムへのイベント送信

前の記事では、MetaTrader 5とMetaTrader 4プラットフォーム用のプログラムの開発を単純化するための大規模なクロスプラットフォームライブラリの作成を始めました。第4部では、口座の取引イベントの追跡をテストしました。本稿では、取引イベントクラスを開発してイベントコレクションに配置します。そこからは、これらはエンジンライブラリの基本オブジェクトとコントロールプログラムチャートに送信されます。