English Русский 中文 Español Deutsch Português
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第10部): MQL4との互換性 - ポジションオープンイベントと指値注文発動イベント

MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第10部): MQL4との互換性 - ポジションオープンイベントと指値注文発動イベント

MetaTrader 5 | 16 9月 2019, 08:18
1 179 0
Artyom Trishkin
Artyom Trishkin

内容


テストEA

前の記事では、MQL4とMQL5の違いに関連するライブラリファイルのエラーを削除し、MQL4の注文とポジション履歴のコレクションを導入しました。本稿では、ライブラリでのMQL4とMQL5の合併を継続し、ポジションを開くイベントと未決注文の発動イベントを定義します。
改善手順の順序は逆になります。以前は、機能を導入してからEAをテストしました。ここでは、何を改善する必要があるのかを理解するために、テストEAを起動して、何が機能して何がしないかを確認する必要があります。機能しないものは改善すべきものです。

これを達成するには、\MQL5\Experts\TestDoEasy\Part08フォルダにある第8部のテストEATestDoEasyPart08.mq5をMetaTrader 4フォルダー( \MQL4\Experts\TestDoEasy\Part10)にTestDoEasyPart10.mq4という名前で保存します。

コンパイルしてみると、最終的に34件のコンパイルエラーが発生します。それらのほとんどすべては、MQL4標準ライブラリにト取引クラスが存在しないことに関連しています。


インクルードファイルが存在しないことを示す最初のエラーに移動して、


修正します。ファイルはMQL5でのみインクルードします

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart08.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>
#ifdef __MQL5__
#include <Trade\Trade.mqh>
#endif 
//--- 列挙体

エラーが33件になっています。再び最初のエラーに移動します。これは、CTrade取引クラスオブジェクトを宣言するときに欠落している型を示します。この型はMQL4には存在しません。

以前と同様に、条件付きコンパイルディレクティブを使用しましょう。

//--- グローバル変数
CEngine        engine;
#ifdef __MQL5__
CTrade         trade;
#endif 
SDataButt      butt_data[TOTAL_BUTT];

コンパイルします。今度のエラーは、CTradeクラスの「trade」オブジェクトがMQL4に知られていないことによるので、これを同じように修正します。

//--- setting trade parameters
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//---
   return(INIT_SUCCEEDED);
  }

#elseディレクティブを使用して、すべてのtradeオブジェクトインスタンスをEAコード全体で条件付きコンパイルディレクティブに収めます。 MQL4コードは そこに配置します。以前の編集とコンパイルを行った後、未知のtradeタイプの最初のエラーを使用しましょう。

      //--- 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);
         //--- 買いポジションを開く
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
                                            
         #endif 
        }

すべての「trade」オブジェクトインスタンスを条件付きコンパイルディレクティブに入れた後には、パラメータの欠如によって、コンパイラが使用するオーバーロード関数の呼び出しを正確に定義できないことを示す別のエラーが発生します。


コードをよく見ると、コンパイラが混乱する理由が明らかになります。

//+------------------------------------------------------------------+
//| Return the flag of a prefixed object presence                    |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

MQL5では、この関数には単一の呼び出し形式のみがあります。

int  ObjectsTotal(
   long  chart_id,           // chart ID
   int   sub_window=-1,      // window index
   int   type=-1             // object type     
   );

ここで、1番目のパラメータはチャートIDです(0~現在)。

一方、MQL4では、この関数には、しばらくの間2つの呼び出し形式が存在してきました。1番目のものはMQL5と同じです。

int  ObjectsTotal(
   long  chart_id,           // chart ID
   int   sub_window=-1,      // window index
   int   type=-1             // object type     
   );

2番目のものは古く、パラメータは1つのみです。

int  ObjectsTotal(
   int   type=EMPTY         // object type     
   );

MQL5では、関数にチャートIDとして0(現在のチャート)を渡すことによって矛盾や疑いが生じることはありませんが、MQL4では、コンパイラは渡されたパラメータを使用して呼び出しタイプを定義する必要があります。この場合、現在のチャートID(0)が渡されていて1番目のの呼び出しフォームを使用する必要があるのか(結局のところ、他の2つのパラメータはデフォルト値に設定されるため、関数に渡す必要はありません)、ウィンドウインデックス(またはオブジェクトタイプ)が渡されていて2番目の呼び出しフォームを使用する必要があるのかを正確に定義できません。

ここでの解決策は簡単で、2番目のパラメータとしてサブウィンドウインデックス(0=メインチャートウィンドウ)を渡すことです。

//+------------------------------------------------------------------+
//| Return the flag of a prefixed object presence                    |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0,0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

そして

//+------------------------------------------------------------------+
//| Manage button status                                             |
//+------------------------------------------------------------------+
void PressButtonsControl(void)
  {
   int total=ObjectsTotal(0,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);
     }
  }
//+------------------------------------------------------------------+

これで、すべてがエラーなしでコンパイルされます。テストを開始する前に、条件付きコンパイルディレクティブを使用してコードから除外したため、EAにはMQL4取引関数が含まれていないことに注意してください。これらを追加する必要があります。
テスター用のコードを作成する際、実際の口座またはデモ口座で取引する際に必要なチェックを実装せず、代わりに最小限のチェックに制限します。
注文チケットとポジションチケット、および計算された価格レベルが関数に渡されるため、行うべきことは、そのチケットで注文/ポジションを選択し、決済タイプと時間を確認することだけです。タイプが注文またはポジションのタイプと一致しない場合、適切なメッセージを表示し、エラーで関数を終了します。注文が削除されるかポジションが決済された場合、メッセージを表示してエラーで終了します。次に、開始/決済/変更のための関数を呼び出し、その実行結果を返します。

DELib.mqhファイルのリストの最後に、必要なすべてのMQL4テスター関数を記述します。

#ifdef __MQL4__
//+------------------------------------------------------------------+
//| MQL4 temporary functions for the tester                          |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Open Buy position                                                |
//+------------------------------------------------------------------+
bool Buy(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   double price=0;
   ResetLastError();
   if(!SymbolInfoDouble(sym,SYMBOL_ASK,price))
     {
      Print(DFUN,TextByLanguage("Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "),(string)GetLastError());
      return false;
     }
   if(!OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось открыть позицию Buy. Ошибка ","Failed to open a Buy position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending BuyLimit order                                       |
//+------------------------------------------------------------------+
bool BuyLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_BUY_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер BuyLimit. Ошибка ","Could not place order BuyLimit. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending BuyStop order                                        |
//+------------------------------------------------------------------+
bool BuyStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_BUY_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер BuyStop. Ошибка ","Could not place order BuyStop. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Open Sell position                                               |
//+------------------------------------------------------------------+
bool Sell(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   double price=0;
   ResetLastError();
   if(!SymbolInfoDouble(sym,SYMBOL_BID,price))
     {
      Print(DFUN,TextByLanguage("Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "),(string)GetLastError());
      return false;
     }
   if(!OrderSend(sym,ORDER_TYPE_SELL,volume,price,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось открыть позицию Sell. Ошибка ","Failed to open a Sell position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending SellLimit order                                      |
//+------------------------------------------------------------------+
bool SellLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_SELL_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер SellLimit. Ошибка ","Could not place order SellLimit. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending SellStop order                                       |
//+------------------------------------------------------------------+
bool SellStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_SELL_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер SellStop. Ошибка ","Could not place order SellStop. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Close position by ticket                                         |
//+------------------------------------------------------------------+
bool PositionClose(const ulong ticket,const double volume=0,const int deviation=2)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   double price=0;
   color  clr=clrNONE;
   if(type==ORDER_TYPE_BUY)
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_BID);
      clr=clrBlue;
     }
   else
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_ASK);
      clr=clrRed;
     }
   double vol=(volume==0 || volume>OrderLots() ? OrderLots() : volume);
   ResetLastError();
   if(!OrderClose((int)ticket,vol,price,deviation,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию. Ошибка ","Could not close position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Close position by an opposite one                                |
//+------------------------------------------------------------------+
bool PositionCloseBy(const ulong ticket,const ulong ticket_by)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   ResetLastError();
   if(!OrderSelect((int)ticket_by,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать встречную позицию. Ошибка ","Could not select the opposite position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Встречная позиция уже закрыта","Opposite position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type_by=(ENUM_ORDER_TYPE)OrderType();
   if(type_by>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Встречная позиция не является позицией: ","Error. Opposite position is not a position: "),OrderTypeDescription(type_by)," #",ticket_by);
      return false;
     }
   color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderCloseBy((int)ticket,(int)ticket_by,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию встречной. Ошибка ","Could not close position by opposite position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Remove a pending order by ticket                                 |
//+------------------------------------------------------------------+
bool PendingOrderDelete(const ulong ticket)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ордер уже удалён","Order already deleted"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderDelete((int)ticket,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось удалить ордер. Ошибка ","Could not delete order. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Modify position by ticket                                        |
//+------------------------------------------------------------------+
bool PositionModify(const ulong ticket,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбрана закрытая позиция: ","Error. Closed position selected for modification: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,OrderOpenPrice(),sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать позицию. Ошибка ","Failed to modify position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Modify pending order by ticket                                   |
//+------------------------------------------------------------------+
bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,price_set,sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
#endif 

これらの関数は一時的なもので、近い将来にMQL5およびMQL4の本格的な取引クラスが作成されたときに削除されます。

ここで、EAコードでMQL4取引関数を呼び出すために残しておいた場所に、新しく記述された関数の呼び出しを追加する必要があります。Ctrl+Fを押して、検索ボックスにtradeと入力して、MQL4取引関数の呼び出しが設定されるコードのパッセージをすばやく見つけます。

ボタン押下イベントを処理するためのPressButtonEvents()関数から最後まで必要なMQL4取引関数の呼び出しを実装します。コードは非常にかさばりますが、必要な関数の選択は明確です。したがって、ここではコードを提示しないので、記事に添付されているファイルでご覧ください。ここでは、2つのボタン(買いポジションを開くボタンBuyLimit指値注文ボタン)の押下の処理のみを見ていきます。

//+------------------------------------------------------------------+
//| ボタン押下を処理する                                                |
//+------------------------------------------------------------------+
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);
         //--- 買いポジションを開く
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
            Buy(lot,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- 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注文を設定する
         #ifdef __MQL5__
            trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
         #else 
            BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- BUTT_BUY_STOPボタンが押下されたら、BuyStopを設定する

ライブラリコードをテストすると、奇妙なことに気付きました。コードの改善なしでは、MQL4によって確認されるイベントは、しばらくしてから操作ログに表示されるということです。問題を掘り下げて調べたところ、その理由はCEngineタイマーで動作するコレクションタイマーのカウンターにあることがわかりました。コレクションタイマーカウンターの最小遅延は、第3部でライブラリの基本オブジェクトを作成するときに16ミリ秒に設定されています。しかし、テスターでタイマーを使用せず、OnTick()から直接OnTimer()ライブラリハンドラーを呼び出してティックで動作するため、16ミリ秒の遅延は16ティックの遅延に変わるのです。これを修正するために、テスターフラグを返すメソッドを導入し、OnTimer()ハンドラーでテスターの作業を処理するメソッドを導入するCEngineクラスを少し変更しました。これは、テスターで作業するときにEAのOnTick()から呼び出されます。

この変更を加えるために、privateクラスメンバー変数変数値を返すメソッドが作成されました。

//+------------------------------------------------------------------+
//| ライブラリ基本クラス                                                |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // 過去の注文と取引のコレクション
   CMarketCollection    m_market;                        // 注文と取引のコレクション
   CEventsCollection    m_events;                        // Collection of events
   CArrayObj            m_list_counters;                 // タイマーカウンタのリスト
   bool                 m_first_start;                   // 初期実行フラグ
   bool                 m_is_hedge;                      // ヘッジ勘定フラグ
   bool                 m_is_tester;                     // Flag of working in the tester
   bool                 m_is_market_trade_event;         // Flag of an account trading event
   bool                 m_is_history_trade_event;        // Flag of an account history trading event
   ENUM_TRADE_EVENT     m_acc_trade_event;               // 口座取引イベント
//--- Return counter index by id

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*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- 直近の取引イベントをリセットする
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Return the (1) last trading event, (2) hedge account flag, (3) flag of working in the tester
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
   bool                 IsTester(void)                            const { return this.m_is_tester;             }
//--- タイマーカウンタを作成する
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- タイマー
   void                 OnTimer(void);
//--- コンストラクタ/デストラクタ
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

このテスターフラグ変数値はクラスコンストラクタで設定されます

//+------------------------------------------------------------------+
//| CEngineコンストラクタ                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
         ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
         ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   #endif 
  }
//+------------------------------------------------------------------+

CEngineクラスのOnTimer()ハンドラーで、テスターでの作業を確認し、作業がテスターで実行されているどうかに応じて、タイマーカウンターまたはティックのいずれかで動作します。

//+------------------------------------------------------------------+
//| CEngineタイマー                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer of historical orders, deals, market orders and positions collections
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- If this is not a tester
         if(!this.IsTester())
           {
            //--- If unpaused, work with the collections events
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

EAをコンパイルし、テスターで起動してボタンを試してください。


このメッセージは、ライブラリがいくつかのイベント(指値注文の設定と、注文およびポジションパラメータの変更)を確認していることを示しています。他のイベントは確認できません。

エラーに対処しましょう。

ライブラリの改善

最初に確認する必要があるのは、指値注文の削除がライブラリに表示されない理由です。
すべてのイベントはイベントコレクションクラスのCEventsCollection::Refresh()メソッドで追跡されます。ここで興味があるのは口座履歴イベントです。メソッドに渡して、MQL5履歴注文と取引のコレクションの変更の追跡を担当するコードを見てみましょう
     }
//--- If the event is in the account history
   if(is_history_event)
     {
      //--- If the number of historical orders increased
      if(new_history_orders>0)
        {
         //--- Receive the list of removed pending orders only
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            Print(DFUN);
            //--- Sort the new list by order removal time
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Take the number of orders equal to the number of newly removed ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list. If this is a removed pending order without a position ID, 
               //--- this is an order removal - set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      //--- If the number of deals increased

ポジションIDを指定する注文プロパティは未記入(ゼロに等しい)です。正しいパッセージを見つけた後、MQL5で未決注文の削除(発動ではなく)を正確に識別するためにその機能を使用したことがわかります(MQL5では、注文が発動されて取引とポジションに至った場合、ポジションIDは注文の発動の結果として開かれたポジションのIDと等しくなります)。MQL4では、このフィールドには注文チケットがすぐに入力されますが、これは誤りです。
注文の抽象クラスのClosedコンストラクタに移動し、ポジションIDを含む注文プロパティ文字列を見つけます

//+------------------------------------------------------------------+
//| Closedパラメトリックコンストラクタ                                   |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- 整数型プロパティを保存する
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                               = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                                = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                               = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_OPEN]                            = (long)(ulong)this.OrderOpenTime();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = (long)(ulong)this.OrderCloseTime();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = (long)(ulong)this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                                 = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                                = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                            = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                          = this.OrderPositionID();
   this.m_long_prop[ORDER_PROP_REASON]                               = this.OrderReason();
   this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]                    = this.DealOrderTicket();
   this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                           = this.DealEntry();
   this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                       = this.OrderPositionByID();
   this.m_long_prop[ORDER_PROP_TIME_OPEN_MSC]                        = this.OrderOpenTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE_MSC]                       = this.OrderCloseTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = (long)(ulong)this.PositionTimeUpdate();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE_MSC]                      = (long)(ulong)this.PositionTimeUpdateMSC();
   
//--- 実数型プロパティを保存する
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]         = this.OrderOpenPrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]        = this.OrderClosePrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]             = this.OrderProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]         = this.OrderCommission();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]               = this.OrderSwap();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]             = this.OrderVolume();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]                 = this.OrderStopLoss();
   this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]                 = this.OrderTakeProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]     = this.OrderVolumeCurrent();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]   = this.OrderPriceStopLimit();
   
//--- 文字列プロパティを保存する
   this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]             = this.OrderSymbol();
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]            = this.OrderComment();
   this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]             = this.OrderExternalID();
   
//--- 追加の整数型プロパティを保存する
   this.m_long_prop[ORDER_PROP_PROFIT_PT]                            = this.ProfitInPoints();
   this.m_long_prop[ORDER_PROP_TICKET_FROM]                          = this.OrderTicketFrom();
   this.m_long_prop[ORDER_PROP_TICKET_TO]                            = this.OrderTicketTo();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_SL]                          = this.OrderCloseByStopLoss();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_TP]                          = this.OrderCloseByTakeProfit();
   this.m_long_prop[ORDER_PROP_GROUP_ID]                             = 0;
   
//--- 追加の実数型プロパティを保存する
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
  }
//+------------------------------------------------------------------+

これはOrderPositionID()メソッドで行われます。お分かりのように、MQL4ではチケットは直ちにIDとして設定されます

//+------------------------------------------------------------------+
//| ポジションIDを返す                                                  |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return ::OrderTicket();
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_IDENTIFIER);            break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);   break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

最初は、そこに0を設定する必要があります(注文を削除するときにポジションはありません)。以下をご覧ください

//+------------------------------------------------------------------+
//| ポジションIDを返す                                                  |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return 0;
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_IDENTIFIER);            break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);   break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

EAをコンパイルし、テスターで起動して、未決注文を設定および削除します。


ここで未決注文削除イベントが追跡されます。

未決注文の発動を待つと、このイベントは、単純なポジションが開かれる場合と同様、ライブラリには表示されないことが再びわかります。理由を定義しましょう。

覚えておいでのように、すべてがCEngineクラスのOnTimer()ハンドラから始まります。

//+------------------------------------------------------------------+
//| CEngineタイマー                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer of historical orders, deals, market orders and positions collections
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- If this is not a tester
         if(!this.IsTester())
           {
            //--- If unpaused, work with the collections events
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

コードによると、イベントはTradeEventsControl()メソッドで管理されます。どんなイベントの場合でも、イベントコレクションクラスのイベントを更新するメソッドであるCEventsCollection::Refresh()を呼び出します

//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the trading events code and flags
   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;
     }
//--- Check the changes in the market status and account history 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Get the account's last trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

ここで、履歴コレクションと市場コレクションのリスト、コレクション変更のフラグ、新しい過去の注文とアクティブな市場注文とポジションの数、新しい取引の数をメソッドに送信します。しかし、よく見ると、メソッドが受信するのは、新しい市場ポジションの数ではなくライブラリでまだ使用していない新しい市場での注文の数であることがわかります。これは私のエラーです。当初、すべてはMQL5用に開発されましたが、MQL4メソッド用に新しいポジションの数を送信する必要があります。MQL5では、新しいポジションは取引の数によって定義されます。MQL4メソッドに渡されたデータを入力すると、エラーが発生しました。これで、このメソッドが新しい市場ポジションを確認できない理由が明らかになりました。
これを修正して、ついでに別の問題を解決しましょう。MQL5とは異なり、MQL4はポジションに至った注文を見つけることができません。ただし、注文とポジションのプロパティの変更を追跡するための管理注文のリストは既にあります。この不要なデータのリストはまだクリアされていません。このリストは、ポジションに至った注文を追跡し、イベント(成行注文または未決注文の発動)を識別するのに役立ちます。


コントロール注文のリストを返すpublicメソッドを市場の注文とポジションのコレクション(MarketCollection.mqhファイルのCMarketCollectionクラス)に追加します。

public:
//--- Return the list (1) of all pending orders and open positions, (2) control orders and positions
   CArrayObj*        GetList(void)                                                                       { return &this.m_list_all_orders;                                       }
   CArrayObj*        GetListChanges(void)                                                                { return &this.m_list_changed;                                          }
   CArrayObj*        GetListControl(void)                                                                { return &this.m_list_control;                                          }
//--- 開いた時間が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);  }
//--- Return the number of (1) new market orders, (2) new pending orders, (3) new positions, (4) occurred trading event flag, (5) changed volume
   int               NewMarketOrders(void)                                                         const { return this.m_new_market_orders;                                      }
   int               NewPendingOrders(void)                                                        const { return this.m_new_pendings;                                           }
   int               NewPositions(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);
  };
//+------------------------------------------------------------------+
リストのデータを使用するには、CEventsCollectionクラスのRefresh()メソッドに渡す必要があります。

これを行うには、上記の必要なすべての変更を記述します。

//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the trading events code and flags
   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;
     }
//--- Check the changes in the market status and account history 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,                                  
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),                                 
                            this.m_market.NewPositions(),this.m_history.NewDeals());                                     
      //--- Get the account's last trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

ここで、CEngineクラスのTradeEventsControl()メソッドに、CEventsCollectionクラスのRefresh()メソッドにさらに別のリスト(管理注文のリスト)の受け渡しを追加し、メソッドには誤って新しい市場の注文数が渡されるかわりに新しいポジション数が渡されます

CEventsCollectionクラス本体のRefresh()メソッドの定義を修正しましょう。

public:
//--- Select events from the collection with time within the range from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Return the full event collection list "as is"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- 比較された基準を満たす選択された(1)整数、(2)実数、(3)文字列プロパティののリストを返す
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Update the list of events
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             CArrayObj* list_changes,
                             CArrayObj* list_control,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- Set the control program chart ID
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Return the last trading event on the account
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- 直近の取引イベントをリセットする
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- コンストラクタ
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+

クラス本体の外部の実装も修正します。

//+------------------------------------------------------------------+
//| Update the event list                                            |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals)
  {

イベントコレクションクラスのイベントのイベントリストを更新するメソッドには、MQL4のポジションを開くイベントの処理がまだありません。 このために、いくつかのメソッドが必要になります。
ポジションのリストを取得するには、それを取得する方法が必要です。また、注文のタイプを定義するために管理注文のリストを使用するメソッドがありません。
また、管理注文のリストにある発注タイプポジションIDを保存するために、2つのprivateクラスメンバーが必要です。 タイプとIDは、MQL4の市場ポジションオープンイベントを処理するためのコードブロックで定義されます。
これらをprivateクラスセクションに追加します。

//+------------------------------------------------------------------+
//| Collection of account events                                     |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // List of events
   bool              m_is_hedge;                      // Hedge account flag
   long              m_chart_id;                      // Control program chart ID
   int               m_trade_event_code;              // Trading event code
   ENUM_TRADE_EVENT  m_trade_event;                   // Account trading event
   CEvent            m_event_instance;                // Event object for searching by property
   MqlTick           m_tick;                          // Last tick structure
   ulong             m_position_id;                   // Position ID (MQL4)  
   ENUM_ORDER_TYPE   m_type_first;                    // Opening order type (MQL4)
   
//--- Create a trading event depending on the order (1) status and (2) change type
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
   void              CreateNewEvent(COrderControl* order);
//--- Create an event for a (1) hedging account, (2) netting account
   void              NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
   void              NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
//--- Select from the list and return the list of (1) market pending orders, (2) open positions
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
   CArrayObj*        GetListPositions(CArrayObj* list);
//--- Select from the list and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders 
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Return the list of (1) all position orders by its ID, (2) all deal positions by its ID
//--- (3) all market entry deals by position ID, (4) all market exit deals by position ID,
//--- (5) all position reversal deals by position ID
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the total volume of all deals (1) IN, (2) OUT of the position by its ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the (1) first, (2) last and (3) closing order from the list of all position orders,
//--- (4) an order by ticket, (5) market position by ID,
//--- (6) the last and (7) penultimate InOut deal by position ID
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket);
   COrder*           GetPositionByID(CArrayObj* list,const ulong position_id);
//--- Return the type of the opening order by the position ticket (MQL4)
   ENUM_ORDER_TYPE   GetTypeFirst(CArrayObj* list,const ulong ticket);
//--- Return the flag of the event object presence in the event list
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Existing order/position change event handler
   void              OnChangeEvent(CArrayObj* list_changes,const int index);

public:

クラス本体外ポジションのリストを受け取るためのメソッドを実装します

//+------------------------------------------------------------------+
//| Select only market positions from the list                       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListPositions(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of the market collection"));
      return NULL;
     }
   CArrayObj* list_positions=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list_positions;
  }
//+------------------------------------------------------------------+

市場の注文とポジションの完全なリストはメソッドに渡され、 「成行ポジション」ステータスで並び替えられます呼び出し側プログラム結果のリストが返されます

ポジションに至った注文のタイプを返すメソッドを作成しましょう。

//+------------------------------------------------------------------+
//| Return the type of an opening order by position ticket (MQL4)    |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket)
  {
   if(list==NULL)
      return WRONG_VALUE;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      COrderControl* ctrl=list.At(i);
      if(ctrl==NULL)
         continue;
      if(ctrl.Ticket()==ticket)                   
         return (ENUM_ORDER_TYPE)ctrl.TypeOrder();
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

管理注文のリスト新しく開かれたポジションがメソッドに渡されます。次に、リストの先頭からループで(未決注文が他のポジションの前に配置され、そのチケットが先に来ると仮定)リストから管理注文を取得してチケットを関数に渡されたチケットと比較します。チケットが見つかったら、この注文は、チケットがメソッドに渡されたポジションを開いた注文なので、注文タイプを返します。そのようなチケットを持つ注文が見つからない場合は、-1を返します。

ここで、MQL4でポジションでイベントの処理を改善できます。

イベントリスト更新メソッドにMQL4の「ポジションを開く」イベントの処理を追加します。

//+------------------------------------------------------------------+
//| Update the event list                                            |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals)
  {
//--- Exit if the lists are empty
   if(list_history==NULL || list_market==NULL)
      return;
//--- If the event is in the market environment
   if(is_market_event)
     {
      //--- if the order properties were changed
      int total_changes=list_changes.Total();
      if(total_changes>0)
        {
         for(int i=total_changes-1;i>=0;i--)
           {
            this.OnChangeEvent(list_changes,i);
           }
        }
      //--- if the number of placed pending orders increased
      if(new_market_pendings>0)
        {
         //--- Receive the list of the newly placed pending orders
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Sort the new list by order placement time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of orders equal to the number of newly placed ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_market_pendings;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list, if this is a pending order, set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      #ifdef __MQL4__
      //--- If the number of positions increased
      if(new_market_positions>0)
        {
         //--- Get the list of open positions
         CArrayObj* list=this.GetListPositions(list_market);
         if(list!=NULL)
           {
            //--- Sort the new list by a position open time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of positions equal to the number of newly placed open positions from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_market_positions;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive a position from the list. If this is a position, search for opening order data and set a trading event
               COrder* position=list.At(i);
               if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION)
                 {
                  //--- Find an order and set (1) a type of an order that led to opening a position and a (2) position ID 
                  this.m_type_first=this.GetTypeFirst(list_control,position.Ticket());
                  this.m_position_id=position.Ticket();
                  this.CreateNewEvent(position,list_history,list_market);
                 }
              }
           }
        }
      #endif 
     }
//--- If the event is in the account history
   if(is_history_event)
     {
      //--- If the number of historical orders increased
      if(new_history_orders>0)
        {
         //--- Receive the list of removed pending orders only
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Sort the new list by order removal time
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Take the number of orders equal to the number of newly removed pending ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list. これがポジションIDなしの削除された未決注文の場合、 
               //--- this is an order removal - set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      //--- If the number of deals increased
      if(new_deals>0)
        {
         //--- Receive the list of deals only
         CArrayObj* list=this.GetListDeals(list_history);
         if(list!=NULL)
           {
            //--- Sort the new list by deal time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of deals equal to the number of new ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_deals;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive a deal from the list and set a trading event
               COrder* order=list.At(i);
               if(order!=NULL)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

MQL4で新しいポジションの開始を処理したり、未決注文を発動したりするためのすべてのアクションはコードのコメントに記述されており、追加の説明は不要です。

次に、新しいイベントを作成するためのCEventsCollection::CreateNewEvent()メソッドに移動し、MQL4の「ポジションを開く」イベントを作成するコードブロックを見つけます(ブロックの開始はコードのコメントで示されています)。「ポジションを開く」イベントの定義と開かれた理由を補完し、ポジションデータに適切な注文のデータおよびポジション IDを追加します。

//--- Position opened (__MQL4__)
   if(status==ORDER_STATUS_MARKET_POSITION)
     {
      //--- Set the "position opened" trading event code
      this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
      //--- Set the "request executed partially" reason
      ENUM_EVENT_REASON reason=EVENT_REASON_DONE;
      //--- If an opening order is a pending one
      if(this.m_type_first>ORDER_TYPE_SELL && this.m_type_first<ORDER_TYPE_BALANCE)
        {
         //--- set the "pending order activated" reason
         reason=EVENT_REASON_ACTIVATED_PENDING;
         //--- add a pending order activation flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
        }
      CEvent* event=new CEventPositionOpen(this.m_trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                             // Event time
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                                        // Event reason (from the ENUM_EVENT_REASON enumeration)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,this.m_type_first);                          // Event deal type
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                           // Event deal ticket
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,this.m_type_first);                         // Type of the order that triggered an event deal (the last position order)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first);                      // Type of an order that triggered a position deal (the first position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());                          // Ticket of an order, based on which a deal event is opened (the last position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                       // Ticket of an order, based on which a position event is opened (the first position order)
         event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id);                             // Position ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());                        // Opposite position ID
         event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0);                                              // Opposite position magic number
            
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder());                      // Position order type before direction changed
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket());                       // Position order ticket before direction changed
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder());                     // Current position order type
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket());                      // Current position order ticket
      
         event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen());                        // Order price before modification
         event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss());                           // StopLoss before modification
         event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit());                         // TakeProfit before modification
         event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask);                            // Ask price during an event
         event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid);                            // Bid price during an event
         
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                                  // Order/deal/position magic number
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());                    // Time of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                              // Event price
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                               // Order/deal/position open price
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                             // Order/deal/position close price
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                                  // StopLoss position price
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                                // TakeProfit position price
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume());                        // Requested order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent());                 // Remaining (unexecuted) order volume
         event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume());                    // Executed position volume
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                      // Profit
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                      // Order symbol
         event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol());                                // Opposite position symbol
         //--- Set control program chart ID, decode the event code and set the event type
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Add the event object if it is not in the list
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Send a message about the event and set the value of the last trading event
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- If the event is already present in the list, remove a new event object and display a debugging message
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list."));
            delete event;
           }
        }
     }
//--- New deal (__MQL5__)

すべての変更を行った後、ライブラリはMQL4注文のポジションの開始と発動を「確認」できる必要があります。


テスト

適用された変更を確認しましょう。TestDoEasyPart10.mq4をコンパイルし、テスターで起動し、ポジションを開始および決済し、未決注文を出して、それらのいずれかが発動するまで待機します。ストップレベルとトレーリングがアクティブになっていることを確認します(ポジションと未決注文を変更します)。ライブラリがMQL4用に「確認」したすべてのイベントは、テスターの操作ログに表示されます。


テスターの操作ログを注意深く観察すると、ライブラリはまだ終値を確認できないことがわかります。BuyLimit#3の未決注文が発動すると、操作ログのエントリは[BuyLimit #3]が発動して、Buy #3ポジションにつながったことを通知します。これで、ライブラリは未決注文の発動イベントを確認し、ポジションの発信元の注文を認識できます。また、変更機能にはわずかな欠落が見られます。トレーリングで変更されたBuyStop #1未決注文のラベルが赤になります。ただし、ライブラリはすべての注文およびポジションの変更イベントを確認します。

DELib.mqhファイルでテスターのMQL4取引関数に修正を追加します。渡された未決注文のタイプに応じて買い/売りポジションタイプを返す別の関数を作成し、注文タイプの矢印の色を選択するための文字列で注文タイプのチェックを注文タイプの方向によるチェックに置き換えます。

//+------------------------------------------------------------------+
//| Modifying a pending order by ticket                              |
//+------------------------------------------------------------------+
bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   color clr=(TypeByPendingDirection(type)==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,price_set,sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Return the type by a pending order direction                     |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE TypeByPendingDirection(const ENUM_ORDER_TYPE type)
  {
   if(type==ORDER_TYPE_BUY_LIMIT  || type==ORDER_TYPE_BUY_STOP)  return ORDER_TYPE_BUY;
   if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP) return ORDER_TYPE_SELL;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

次の段階

次の記事では、ポジション決済の追跡を実装し、現在のバージョンでMQL4の追跡イベントに発生する可能性のあるエラーを修正します。現在、発注と注文の削除はMQL5コードによって追跡されており、MQL4で作業する際に考慮すべき微妙な違いがある場合があります。

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

目次に戻る

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

第1部: 概念、データ管理
第2部: 過去の注文と取引のコレクション
第3部:注文と取引のコレクション、検索と並び替え
第4部: 取引イベント概念
第5部: 取引イベントのクラスとコレクション取引イベントのプログラムへの送信
第6部: ネッティング勘定イベント
第7部: StopLimit注文アクティブ化イベント。注文イベントと位置変更イベントの機能を準備します。
第8部: 注文とポジションの変更イベント。第9部:MQL4との互換性 - データの準備



MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/6767

添付されたファイル |
MQL5.zip (99.2 KB)
MQL4.zip (99.2 KB)
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第4部)MQL4との互換性 - ポジション決済イベント MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第4部)MQL4との互換性 - ポジション決済イベント
MetaTrader 5およびMetaTrader 4プラットフォーム用のプログラムの開発を簡素化する大規模なクロスプラットフォームライブラリの開発を継続します。第10部では、MQL4とのライブラリの互換性に関する作業を再開し、ポジションを開くイベントと未決注文の発動イベントを定義しました。本稿では、ポジション決済イベントを定義し、未使用の注文プロパティを取り除きます。
最適化管理 (パート I): GUI の作成 最適化管理 (パート I): GUI の作成
この記事では、MetaTrader ターミナルの拡張機能を作成するプロセスについて説明します。 このソリューションは、他のターミナルで最適化を実行する際、最適化プロセスを自動化するのに役立ちます。 このトピックに関する記事をいくつか書きます。 拡張機能は C# 言語とデザイン パターンを使用して開発されました。優先プログラミング言語の機能です。
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第12部): 「口座」オブジェクトクラスと口座オブジェクトのコレクション MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第12部): 「口座」オブジェクトクラスと口座オブジェクトのコレクション
前の記事では、ライブラリでMQL4ポジション決済イベントを定義し、未使用の注文プロパティを取り除きました。本稿では、口座オブジェクトの作成を検討して口座オブジェクトのコレクションを開発し、口座イベントを追跡する機能を準備します。
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第9部): MQL4との互換性 - データの準備 MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第9部): MQL4との互換性 - データの準備
前の記事では、MetaTrader 5とMetaTrader 4プラットフォーム用のプログラムの開発を単純化するための大規模なクロスプラットフォームライブラリの作成を始めました。第8部では、注文とポジションの変更イベントを追跡するためのクラスを実装しました。ここでは、MQL4と完全な互換性を備えさせることでライブラリを改善します。