Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil X): Kompatibilität mit MQL4 - Ereignisse der Positionseröffnung und der Aktivierung von Pending-Orders

9 August 2019, 08:55
Artyom Trishkin
0
177

Inhalt


Test-EA

Im vorigen Artikel haben wir Fehler in den Bibliotheksdateien entfernt, die sich auf die Unterschiede zwischen MQL4 und MQL5 beziehen, und eine Kollektion von MQL4 historischen Orders und Positionen vorgestellt. In diesem Artikel werden wir die Zusammenführung von MQL4 und MQL5 in der Bibliothek fortsetzen und die Ereignisse beim Öffnen von Positionen und beim Aktivieren von offenen Orders definieren.
Die Reihenfolge der Verbesserungsschritte wird umgekehrt. Zuvor haben wir die Funktionsweise erklärt, gefolgt von dem Test EA. Nun, um zu verstehen, was verbessert werden muss, müssen wir den Test EA starten und sehen, wo es funktioniert und wo nicht. Die Dinge, die nicht funktionieren, sind diejenigen, die verbessert werden müssen.

Um dies zu erreichen, nehmen wir den Test-EA TestDoEasyPart08.mq5 aus dem achten Teil der Bibliotheksbeschreibung aus dem Ordner \MQL5\Experts\TestDoEasy\Part08 und speichern ihn unter dem Namen TestDoEasyPart10.mq4 im MetaTrader 4 Ordner \MQL4\Experts\TestDoEasy\Part10.

Versuchen wir, ihn zu kompilieren. Dies führt erst einmal zu 34 Kompilierungsfehlern. Fast alle von ihnen stehen im Zusammenhang mit dem Fehlen der Handelsklassen in der Standardbibliothek MQL4:


Kommen wir zum ersten Fehler, der das Fehlen der Include-Datei anzeigt


und beheben ihn — die Datei wird nur unter MQL5 eingebunden:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart08.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#ifdef __MQL5__
#include <Trade\Trade.mqh>
#endif 
//--- Enumerationen

Das Kompilieren endet mit 33 Fehlern. Gehen wir erneut zum allerersten Fehler über, der bei der Deklaration des Objekts der Handelsklasse CTrade den fehlenden Typ anzeigt — er ist in MQL4 nicht vorhanden.

Verwenden wir das bedingte Kompilieren wie vorher:

//--- Globale Variablen
CEngine        engine;
#ifdef __MQL5__
CTrade         trade;
#endif 
SDataButt      butt_data[TOTAL_BUTT];

Kompilieren. Nun ist das 'Handelsobjekt' der Klasse CTrade für MQL4 unbekannt geworden. Beheben wir dies auf ähnliche Weise:

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

Ummanteln wir alle trade Objektinstanzen mit der bedingte Kompilierung im gesamten EA-Code mit der Anweisung #elseder MQL4-Code soll dort platziert werden. Verwenden wir den allerersten Fehler des unbekannten Handelstyps aus der früheren Bearbeitungen und Zusammenstellungen:

      //--- Wenn die Schaltfläche BUTT_BUY geklickt wurde: Eröffnen einer Kaufposition
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zu StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Eröffnen einer Kaufposition
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
                                            
         #endif 
        }

Nachdem wir alle Objektinstanzen von 'trade' in die Direktive zur bedingten Kompilierung aufgenommen haben, erhalten wir einen weiteren Fehler, der angibt, dass der Compiler nicht genau definieren kann, welcher Aufruf einer überladenen Funktion mangels Parameter verwendet werden soll:


Wenn wir uns den Code genau ansehen, wird der Grund für die Verwirrung des Compilers deutlich:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

In MQL5, hat die Funktion nur eine Aufrufform:

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

wobei der erste Parameter eine Chart-ID (0 - der aktuelle) ist,

während in MQL4 die Funktion seit einiger Zeit zwei Aufrufformen hat. Die erste ist die gleiche wie in MQL5:

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

die zweite ist veraltet hat nur einen Parameter:

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

In MQL5 verursacht die Übergabe von 0 als Chart ID an die Funktion (der aktuelle Chart) keine Widersprüche und Zweifel, aber in MQL4 sollte der Compiler die übergebenen Parameter verwenden, um den Aufruftyp zu definieren. In diesem Fall kann er nicht erkennen, ob wir die aktuelle Chart-ID (0) übergeben, wodurch das erste Aufrufformular verwendet werden soll (schließlich werden die beiden anderen Parameter auf ihre Standardwerte gesetzt, d.h. wir müssen sie nicht an die Funktion übergeben), oder wir einen Fensterindex (oder einen Objekttyp) übergeben haben, wodurch das zweite Aufrufformular verwendet werden sollte.

Die Lösung hier ist einfach — wir übergeben den Subfensterindex (0 = Hauptdiagrammfenster) als zweiten Parameter:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

und

//+------------------------------------------------------------------+
//| 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);
     }
  }
//+------------------------------------------------------------------+

Jetzt wird alles fehlerfrei kompiliert. Bevor wir den Test starten, müssen wir bedenken, dass die EA keine MQL4-Handelsfunktionen bietet, da wir sie durch die bedingte Kompilierung aus dem Code ausgeschlossen haben, was bedeutet, dass wir sie hinzufügen müssen.
Da wir den Code für den Tester schreiben, werden wir keine Kontrollen implementieren, die beim Handel auf einem Real/Demokonto erforderlich sind, sondern uns auf minimale Kontrollen beschränken.
Da in der Funktion Order- und Position-Tickets sowie berechnete Preisniveaus übergeben werden, sollten wir lediglich eine Order/Position über ihr Ticket auswählen und Art und Zeit des Schließens überprüfen. Wenn der Typ nicht mit einem Auftrags- oder Positionstyp übereinstimmt, zeigen wir die entsprechende Meldung an und verlassen die Funktion mit einem Fehler. Wenn ein Auftrag entfernt oder eine Position geschlossen wird, zeigen wir die Meldung an und verlassen die Funktion mit einem Fehler. Anschließend rufen wir die Funktion zum Öffnen/Schließen/Ändern auf und geben das Ausführungsergebnis zurück.

Am Ende der Datei DELib.mqh schreiben wir alle notwendigen Funktionen für den MQL4-Tester:

#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 

Diese Funktionen sind vorläufig. In Kürze werden wir die vollwertigen Handelsklassen für MQL5 und MQL4 schreiben und diese Funktionen aus der Datei entfernen.

Jetzt müssen wir den Aufruf von neu geschriebenen Funktionen hinzufügen, wo immer wir im EA-Code einen Platz für den Aufruf der MQL4-Handelsfunktionen gelassen haben. Wir drücken Strg+F und geben trade in das Suchfeld ein. So finden wir schnell den Codeteil, in denen die Aufrufe von Trading-MQL4-Funktionen gesetzt werden sollen.

Implementieren wir den Aufruf von MQL4-Handelsfunktionen, wo es notwendig ist, beginnend mit der Funktion PressButtonEvents() zur Behandlung von Tastendruckereignissen bis hinunter zum Ende der Datei. Der Code ist recht umfangreich, während die Auswahl der notwendigen Funktion eindeutig ist. Daher werde ich den Code hier nicht anzeigen. Sie finden ihn in den Dateien, die dem Artikel beigefügt sind. Wir werden uns nur mit dem Drücken von zwei Schaltflächen befassen — die Schaltfläche zum Öffnen einer Kauf-Position und der Schaltfläche zum Platzieren einer BuyLimit Pending-Order:

//+------------------------------------------------------------------+
//| Bearbeiten des Klicks auf Schaltflächen                          |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Konvertieren der Namen der Schaltflächen in die Zeichenketten-ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Falls eine Taste gedrückt wurde
   if(ButtonState(button_name))
     {
      //--- Wenn die Schaltfläche BUTT_BUY geklickt wurde: Eröffnen einer Kaufposition
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zu StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Eröffnen einer Kaufposition
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
            Buy(lot,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- Falls die Schaltfläche BUTT_BUY_LIMIT geklickt wurde: Setzen von BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Abrufen des Preises der korrekten Order-Platzierung relativ zu StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zur Level der Order-Platzierung unter Berücksichtigung von StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Setzen einer BuyLimit-Order
         #ifdef __MQL5__
            trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
         #else 
            BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- Falls die Schaltfläche BUTT_BUY_STOP geklickt wurde: Platzieren von BuyStop

Beim Testen des Bibliothekscodes bemerkte ich etwas Seltsames: Ereignisse, die MQL4 sieht, ohne den Code zu verbessern, werden erst nach einiger Zeit im Journal angezeigt. Nach einigen Recherchen wurde mir klar, dass der Grund im Zähler des Kollektion-Timers liegt, der im Timer CEngine arbeitet. Für den von uns entwickelten Zähler des Kollektion-Timer im dritten Teil der Bibliotheksbeschreibung haben wir beim Erstellen des Bibliotheks-Basisobjekts die minimale Verzögerung von 16 Millisekunden eingestellt. Da wir jedoch nicht mit dem Timer im Tester arbeiten und OnTimer() direkt aus OnTick() aufrufen, um mit Ticks zu arbeiten, wird die Verzögerung von 16 Millisekunden in die Verzögerung von 16 Ticks umgewandelt. Um dies zu beheben, habe ich die Klasse CEngine leicht modifiziert und die Methode eingeführt, die das Tester-Flag zurückgibt und die Arbeit im Tester in OnTimer() behandelt, der wiederum von OnTick() des EA aufgerufen wird, wenn er im Tester arbeitet.

Eine 'private' Klassenvariable und die Methode, die den Variablenwert zurückgibt, wurden erstellt, um Änderungen vorzunehmen:

//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Kollektion der historischen Aufträge und Deals
   CMarketCollection    m_market;                        // Kollektion der Marktorder und Deals
   CEventsCollection    m_events;                        // Kollektion der Ereignisse
   CArrayObj            m_list_counters;                 // Liste der Timerzähler
   bool                 m_first_start;                   // Flag des Erststarts
   bool                 m_is_hedge;                      // Flag des Hedging-Kontos
   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;               // Handelsereignis auf dem Konto
//--- Return counter index by id

public:
   //--- Rückgabe der Liste aller (1) Positionen, (2) Pending-Order und (3) Marktorders
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Rückgabe der Liste aller historischen (1) Aufträge, (2) gelöschten Pending-Orders, (3) Deals, (4) Positionen nach deren ID
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Rücksetzen des letzten Handelsereignisses
   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;             }
//--- Erstellen der Timerzählers
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/Destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Der Wert dieser Flag-Variablen des Testers wird im Klassenkonstruktor gesetzt:

//+------------------------------------------------------------------+
//| CEngine Konstruktor                                              |
//+------------------------------------------------------------------+
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 
  }
//+------------------------------------------------------------------+

Überprüfen wir in OnTimer() der Klasse CEngine die Arbeit im Tester und arbeiten, je nachdem, ob die Arbeit im Tester oder nicht durchgeführt wird, entweder mit dem Timerzähler oder durch mit den Ticks:

//+------------------------------------------------------------------+
//| CEngine Timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer der Kollektion der historischen Aufträge, Deals Marktorders und Positionen
   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())
           {
            //--- Wenn nicht 'in Pause', arbeite mit der Kollektion der Ereignisse
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

Kompilieren wir den EA, starten ihn im Tester und versuchen die Schaltflächen:


Die Meldungen zeigen an, dass die Bibliothek einige Ereignisse sieht: das Setzen einer ausstehenden Reihenfolge und das Ändern von Auftrags- und Positionsparametern. Er kann augenblicklich noch keine anderen Ereignisse sehen.

Kommen wir zu den Fehlern.

Verbessern der Bibliothek

Das erste, was wir uns ansehen sollten, ist, warum die Bibliothek die Entfernung einer Pending-Order nicht sieht.
Alle Ereignisse werden in der Methode der Klasse CEventsCollection::Refresh() Event Collection verfolgt. Wir sind an den Ereignissen der Kontohistorie interessiert. Gehen wir zur Methode über und werfen einen Blick auf den Code, der für Verfolgung von Änderungen in der Kollektion von MQL5 historischen Aufträgen und Geschäften verantwortlich ist:
     }
//--- Wenn das Ereignis in Kontohistorie existiert
   if(is_history_event)
     {
      //--- Wenn sich die Anzahl historischer Aufträge erhöht hat
      if(new_history_orders>0)
        {
         //--- Erhalt der Liste nur der entfernten Pending-Orders
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            Print(DFUN);
            //--- Sortieren der Liste nach der Entfernungszeit der Orders
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Nehmen der Order-Anzahl gleich der Anzahl neu entfernten vom Ende der Liste in einer Schleife (die letzten N Ereignisse)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Empfangen einer Order von der Liste. 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);
              }
           }
        }
      /--- Wenn sich die Anzahl der Deals sich erhöht hat

Die Auftragseigenschaft, die die Positions-ID angibt, ist nicht ausgefüllt (gleich Null). Nachdem wir den rechten Teil gefunden haben, können wir sehen, dass wir diese Funktion zur genauen Identifizierung einer ausstehenden Auftragslöschung (und nicht der Aktivierung) in MQL5 verwendet haben (in MQL5 wäre die Positions-ID gleich der ID der durch die Auftragsaktivierung geöffneten Position, wenn eine Order aktiviert wurde und zu einem Deal und einer Position führte). In MQL4 wird in dieses Feld sofort das Ticket des Auftrags eingetragen, was falsch ist.
Gehen wir zum Konstruktor der geschlossenen Klasse des abstrakten Auftrags und finden die Zeile mit der Auftragseigenschaft, die die Positions-ID enthält:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Sichern der ganzzahligen Eigenschaften
   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();
   
//--- Sichern der Double-Eigenschaften
   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();
   
//--- Sichern der String-Eigenschaften
   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();
   
//--- Sichern weiterer ganzzahliger Eigenschaften
   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;
   
//--- Sichern weiterer Double-Eigenschaften
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
  }
//+------------------------------------------------------------------+

Dies geschieht mit der Methode OrderPositionID(). Wie wir sehen können, wird in MQL4, das Ticket sofort als ID gesetzt:

//+------------------------------------------------------------------+
//| Rückgabe der Positions-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
  }
//+------------------------------------------------------------------+

Dort sollte zunächst 0 eingetragen werden (keine offene Position beim Entfernen der Order). Wir machen das so:

//+------------------------------------------------------------------+
//| Rückgabe der Positions-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
  }
//+------------------------------------------------------------------+

Kompilieren wir den EA, starten ihn im Tester und setzen und entfernen dann eine Pending-Order:


Nun wird das Ereignis der Entfernung einer Pending-Order verfolgt.

Wenn wir auf die Aktivierung einer Pending-Order warten, werden wir wieder sehen, dass dieses Ereignis, genau wie eine einfache Positionsöffnung, für die Bibliothek nicht sichtbar ist. Definieren wir die Gründe.

Wie wir uns erinnern, begann alles mit OnTimer() der Klasse CEngine:

//+------------------------------------------------------------------+
//| CEngine Timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer der Kollektion der historischen Aufträge, Deals Marktorders und Positionen
   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())
           {
            //--- Wenn nicht 'in Pause', arbeite mit der Kollektion der Ereignisse
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

Gemäß dem Code werden die Ereignisse in der Methode TradeEventsControl() kontrolliert. Im Falle eines Ereignisses rufen wir die Methode zum Aktualisieren der Ereignisse der Klasse Event Collection CEventsCollection::Refresh() auf:

//+------------------------------------------------------------------+
//| Prüfen der Handelsereignisse                                     |
//+------------------------------------------------------------------+
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;
//--- Aktualisieren der Liste 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Aktionen beim ersten Start
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Prüfen der Änderungen des Marktstatus' und der Kontohistorie 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Im Falle irgendeines Ereignisses, werden die Listen, Flags und die Anzahl der neuen Aufträge und Deals an die Kollektion der Ereignisse gesendet und aktualisiert
   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();
     }
  }
//+------------------------------------------------------------------+

Hier senden wir die Listen der historischen und Marktkollektionen, die Flags der Änderungen in den Kollektionen, die Anzahl der neuen historischen Aufträge und aktiven Marktorders und Positionen, sowie die Anzahl der neuen Deals an die Methode. Aber ein genauerer Blick zeigt, dass das Verfahren anstelle der Anzahl der neuen Marktpositionen die Anzahl der neuen Marktorders erhält, die wir in der Bibliothek noch nicht verwendet haben. Das ist mein Fehler. Zunächst wurde alles für MQL5 entwickelt, während die Anzahl der neuen Positionen für die MQL4-Methode gesendet werden sollte. In MQL5 werden neue Positionen durch die Anzahl der Geschäfte definiert. Der Fehler trat auf, als ich die übergebenen Daten für die MQL4-Methode eingegeben habe. Jetzt ist klar, warum die Methode die neuen Marktpositionen nicht sehen kann.
Beheben wir das und lösen ein weiteres Problem auf dem Weg dorthin:
Im Gegensatz zu MQL5 bietet MQL4 keine Möglichkeit, eine Reihenfolge zu finden, die zum Öffnen einer Position führte. Wir haben jedoch bereits eine Liste von Kontrollaufträgen zur Verfolgung von Änderungen der Auftrags- und Positionseigenschaften. Wir haben aus dieser Liste die unnötigen Daten noch nicht entfernt. Diese Liste wird uns helfen, eine Order zu verfolgen, die zur Eröffnung einer Position führte, und das Ereignis zu identifizieren — eine Marktorder oder die Aktivierung einer Pending-Order.


Fügen wir die 'public' Methode, die die Liste der Kontrollaufträge zurückgibt, zur Kollektion von Marktorders und -positionen hinzu (Klasse CMarketCollection in der Datei MarketCollection.mqh):

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;                                          }
//--- Rückgabe der Liste von Aufträgen und Positionen mit einer Eröffnungszeit vom begin_time bis end_time
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Rückgabe der Liste von Aufträgen und Positionen ausgewählt nach (1) Double-, (2) Integer- und (3) String-Eigenschaften, die eoinem Vergleichswert entsprechen
   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;                                    }
//--- Konstructor
                     CMarketCollection(void);
//--- Aktualisieren der Liste der Pending-Orders und Positionen
   void              Refresh(void);
  };
//+------------------------------------------------------------------+
Um Daten aus der Liste zu verwenden, müssen wir sie an die Methode Refresh() der Klasse CEventsCollection übergeben.

Schreiben wir dazu alle notwendigen Änderungen wie oben beschrieben:

//+------------------------------------------------------------------+
//| Prüfen der Handelsereignisse                                     |
//+------------------------------------------------------------------+
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;
//--- Aktualisieren der Liste 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Aktionen beim ersten Start
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Prüfen der Änderungen des Marktstatus' und der Kontohistorie 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Im Falle irgendeines Ereignisses, werden die Listen, Flags und die Anzahl der neuen Aufträge und Deals an die Kollektion der Ereignisse gesendet und aktualisiert
   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();
     }
  }
//+------------------------------------------------------------------+

Hier in der Methode TradeEventsControl() der Klasse CEngine, haben wir die Weitergabe einer weiteren Liste — die Liste der Kontrollaufträge zur Methode Refresh() der Klasse CEventsCollection hinzugefügt und die fehlerhafte Weitergabe einer Reihe von neuen Marktaufträgen an die Methode durch die Weitergabe einer Reihe von neuen Positionen ersetzt.

Korrigieren wir noch die Definition der Methode Refresh() im Körper der Klasse CEventsCollection:

public:
//--- Auswählen der Ereignisse aus der Kollektion mit einer Zeit im Bereich von begin_time bis end_time.

   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Rückgabe des der gesamten Kollektionsliste des Ereignisses "wie besehen"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen
   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);  }
//--- Aktualisieren der Ereignisliste
   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);
//--- Setzen der Chart-ID des Steuerprogramms
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Rückgabe des letzten Handelsereignisses auf dem Konto
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Rücksetzen des letzten Handelsereignisses
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Konstructor
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+

und in der Umsetzung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Aktualisieren der Ereignisliste                                  |
//+------------------------------------------------------------------+
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)
  {

Die Methode zum Aktualisieren der Ereignisliste der Kollektion der Ereignisklasse fehlt noch bei der Behandlung des Ereignisses zum Öffnen einer Position für MQL4. Wir werden dafür ein paar Methoden benötigen.
Um die Liste der offenen Stellen zu erhalten, sollten wir eine Methode haben, um sie abzurufen. Außerdem haben wir noch keine Methode, um mit Hilfe der Liste der Kontrollaufträge einen Typ der Order zu definieren, die zur Eröffnung einer Position führte.
Wir benötigen auch zwei 'private' Klassenmitglieder, um den Typ eines Eröffnungsauftrags in der Liste der Kontrollaufträge und eine Positions-ID zu speichern. Der Typ und die ID sind im Codeblock zur Behandlung von Marktpositionseröffnungsereignissen für MQL4 zu definieren.
Hinzufügen in den Teil 'private':

//+------------------------------------------------------------------+
//| Kollektion der Kontoereignisse                                   |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // Liste der Ereignisse
   bool              m_is_hedge;                      // Flag des Hedging-Kontos
   long              m_chart_id;                      // Chart-ID des Steuerprogramms
   int               m_trade_event_code;              // Trading event code
   ENUM_TRADE_EVENT  m_trade_event;                   // Handelsereignis auf dem Konto
   CEvent            m_event_instance;                // Ereignisobjekt für die Suche nach einer Eigenschaft
   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);
//--- Rückgabe des Gesamtvolumens aller Deals (1) IN, (2) OUT der Position nach deren 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);
//--- Rückgabe des Flags des Ereignisobjekts in der Ereignisliste
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Existing order/position change event handler
   void              OnChangeEvent(CArrayObj* list_changes,const int index);

public:

Implementieren wir das Verfahren zum Abrufen der Liste der offenen Positionen außerhalb des Klassenkörpers:.

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die vollständige Liste der Marktorders und Positionen wird an die Methode übergeben und sortiert nach dem Status "market position". Die resultierende Liste wird an das aufrufende Programm zurückgegeben.

Schreiben wir die Methode , die einen Typ der Order zurückgibt, die zum Öffnen einer Position führte:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die Liste der Kontrollaufträge und das Ticket einer neu geöffneten Position werden an die Methode übergeben. Als Nächstes holen wir uns in einer Schleife vom Anfang der Liste (vorausgesetzt, dass eine Pending-Order vor anderen offenen Positionen platziert wurde, so dass ihr Ticket schneller erscheint), die Kontrollorder aus der Liste und vergleichen ihr Ticket mit dem, das der Funktion übergeben wurde. Wird das Ticket gefunden, ist diese Order eine Eröffnungsorder für die Position, deren Ticket an die Methode übergeben wurde — Rückgabe des Typs der Order. Wenn keine Order mit einem solchen Ticket gefunden wird, wird -1 zurückgegeben.

Jetzt können wir die Behandlung von Ereignissen mit Positionen für MQL4 verbessern.

Hinzufügen der Behandlung einer Positionseröffnung für MQL4 zur Aktualisierungsmethode der Ereignisliste:

//+------------------------------------------------------------------+
//| Aktualisieren der Ereignisliste                                  |
//+------------------------------------------------------------------+
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)
  {
//--- Rückkehren, wenn die Liste leer ist
   if(list_history==NULL || list_market==NULL)
      return;
//--- Wenn das Ereignis in der Umgebung des Marktes existiert
   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);
           }
        }
      //--- wenn sich die Anzahl der platzierten Pending-Orders erhöht hat
      if(new_market_pendings>0)
        {
         //--- Empfangen der Liste der neuesten, platzierten Pending-Orders
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Sortieren der neuen Liste nach der Platzierungszeit der Orders
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Nehmen der Order-Anzahl, die gleich der Anzahl neu platzierten vom Ende der Liste ist, in einer Schleife (die letzten N Ereignisse)
            int total=list.Total(), n=new_market_pendings;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Erhalt der Order von der Liste, wenn es eine Pending-Order ist, wird das Handelsereignis gesetzt
               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 
     }
//--- Wenn das Ereignis in Kontohistorie existiert
   if(is_history_event)
     {
      //--- Wenn sich die Anzahl historischer Aufträge erhöht hat
      if(new_history_orders>0)
        {
         //--- Erhalt der Liste nur der entfernten Pending-Orders
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Sortieren der Liste nach der Entfernungszeit der Orders
            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--)
              {
               //--- Empfangen einer Order von der Liste. 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);
              }
           }
        }
      /--- Wenn sich die Anzahl der Deals sich erhöht hat
      if(new_deals>0)
        {
         //--- Empfangen der Liste nur der Deals
         CArrayObj* list=this.GetListDeals(list_history);
         if(list!=NULL)
           {
            //--- Sortieren der neuen Liste nach der Dealzeit
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Nehmen der Anzahl der Deals, die gleich der Anzahl der neuen vom Ende der Liste ist, in einer Schleife (die letzten N Ereignisse)
            int total=list.Total(), n=new_deals;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Erhalt eines Deals von der Liste und setzen des Handelsereignisses
               COrder* order=list.At(i);
               if(order!=NULL)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Alle Aktionen zum Öffnen einer neuen Position oder zum Auslösen einer ausstehenden Bestellung für MQL4 sind in den Codekommentaren beschrieben und bedürfen keiner zusätzlichen Erklärung.

Gehen wir nun zur Methode CEventsCollection::CreateNewEvent() zum Erzeugen eines neuen Ereignisses und dem Finden des Codeblocks, der für das Erzeugen eines Positionsöffnungsereignisses für MQL4 verantwortlich ist (der Anfang des Blocks ist in den Codekommentaren markiert) und ergänzen die Definition des Positionsöffnungsereignisses und die Ursache für dessen Öffnen, sowie Hinzufügen von Daten in der entsprechenden Reihenfolge und Position ID zu den Daten der offenen Position:

//--- Position eröffnet (__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
         //--- Setzen der Chart-ID des Steuerprogramms, dekodieren des Ereigniscodes und setzen des Ereignis-Typs
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Hinzufügen einer Ereignisobjekts, wenn es nicht in der Liste ist
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Senden einer Nachricht über das Ereignis und setzen des Wertes des letzten Handelsereignisses
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- Wenn es das Ereignis bereits in der Liste gibt, wird das neue Objekt entfernt und eine Debugging-Nachricht angezeigt
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list."));
            delete event;
           }
        }
     }
//--- Neuer Deal (__MQL5__)

Nachdem alle Änderungen vorgenommen wurden, sollte die Bibliothek das Öffnen der Position und die Aktivierung der Pending-Orders in MQL4 "sehen" können.


Tests

Überprüfen wir die vorgenommenen Änderungen. Kompilieren wir TestDoEasyPart10.mq4, starten es im Tester, öffnen und schließen Positionen, platzieren Pending-Orders, warten, bis eine von ihnen aktiviert ist und prüfen, ob Stopp-Loss und das Trailing aktiviert wird (Ändern von Positionen und offenen Aufträgen). Alle Ereignisse, die die Bibliothek für MQL4 "sieht", sollen im Journal des Testers angezeigt werden:


Wenn wir das Journal des Testers genau beachten, können wir feststellen, dass die Bibliothek immer noch nicht das Schließen von Positionen erkennt. Wenn die Pending-Order BuyLimit #3 ausgelöst wird, erscheint im Journal, dass [BuyLimit #3] aktiviert ist, was zur Position Buy #3 führt. Nun sieht die Bibliothek die Ereignisse der anstehenden Auftragsaktivierung und kennt einen Quellauftrag, aus dem eine Position stammt. Außerdem sehen wir ein leichtes Versäumnis in der Modifikationsfunktion — das Label der Pending-Order BuyStop #1, die durch Trailing modifiziert wurde, wird rot. Aber die Bibliothek sieht alle Ereignisse der Auftrags- und Positionsänderung.

Fügen wir die Korrekturen an den Funktionen von Trading-MQL4 des Testers in der Datei DELib.mqh hinzu. Lassen Sie uns noch eine weitere Funktion erstellen, die den Positionstyp Kaufen/Verkaufen in Abhängigkeit vom Typ der Pending-Order zurückgibt, die ihm übergeben wurde und ersetzen die Überprüfung eines Auftragstyps durch die Überprüfung eines Auftragstyps nach der Richtung in der Zeile zur Auswahl der Pfeilfarbe:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+


Was kommt als Nächstes?

Im nächsten Artikel werden wir das Verfolgen des Schließens von Positionen implementieren und weitere Fehler beheben, die in der aktuellen Version der Verfolgungsereignisse für MQL4 auftreten könnten. Derzeit werdeb das Platzieren und Entfernen von Aufträgen durch den MQL5-Code verfolgt, und es kann einige Feinheiten geben, die bei der Arbeit unter MQL4 berücksichtigt werden sollten.

Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit den Dateien der Test-EAs angehängt, die Sie herunterladen und testen können.
Stellen Sie Ihre Fragen, Kommentare und Vorschläge in den Kommentaren.

Zurück zum Inhalt

Frühere Artikel dieser Serie:

Teil 1. Konzept, Datenverwaltung.
Teil 2. Erhebung (Collection) historischer Aufträge und Deals.
Teil 3. Erhebung (Collection) von Marktorders und Positionen, Organisieren der Suche
Teil 4. Handelsereignisse. Konzept.
Teil 5. Klassen und Kollektionen von Handelsereignissen. Senden von Ereignissen an das Programm.
Teil 6. Ereignisse auf Netting-Konten.
Teil 7. Ereignis der Aktivierung einer StopLimit-Order, Vorbereiten der Funktionsweise bei Änderungen von Orders und Positionen.
Teil 8. Ereignisse von Änderungen von Orders und Positionen.
Teil 9. Kompatibilität mit MQL4 - Datenvorbereitung.



Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/6767

Beigefügte Dateien |
MQL5.zip (99.2 KB)
MQL4.zip (99.2 KB)
Organisation einer Mailing-Kampagne mit den Google-Services Organisation einer Mailing-Kampagne mit den Google-Services

Ein Händler kann eine Mailing-Kampagne organisieren, um Geschäftsbeziehungen zu anderen Händlern, Abonnenten, Kunden oder Freunden zu pflegen. Außerdem kann es notwendig sein, Screenshots, Logs oder Berichte zu versenden. Dies sind vielleicht nicht die am häufigsten auftretenden Aufgaben, aber eine solche Möglichkeit ist eindeutig von Vorteil. Der Artikel beschäftigt sich mit der gleichzeitigen Nutzung mehrerer Google-Dienste, der Entwicklung einer geeigneten Assembly auf C# und der Integration mit MQL-Tools.

Entwicklung eines plattformübergreifenden Expert Advisors zur Festlegung von StopLoss und TakeProfit basierend auf den Risikoeinstellungen. Entwicklung eines plattformübergreifenden Expert Advisors zur Festlegung von StopLoss und TakeProfit basierend auf den Risikoeinstellungen.

In diesem Artikel erstellen wir einen Expert Advisor für die automatisierte Berechnung der Losgrößen bei der Eröffnung auf Basis von Risikowerten. Auch der Expert Advisor kann TakeProfit mit dem gewählten Verhältnis zu StopLoss automatisch platzieren. Das heißt, er kann einen TakeProfit basierend auf einem beliebigen Verhältnis berechnen, z.B. 3 zu 1, 4 zu 1 oder einem anderen ausgewählten Wert.

Den Gewinn bis zum letzten Pip extrahieren Den Gewinn bis zum letzten Pip extrahieren

Der Artikel beschreibt den Versuch, Theorie und Praxis im algorithmischen Handelsbereich zu verbinden. Die meisten Diskussionen über das Erstellen von Handelssystemen stehen im Zusammenhang mit der Verwendung historischer Bars und verschiedener darauf angewandter Indikatoren. Dies ist das am besten abgedeckte Feld und deshalb werden wir es nicht berücksichtigen. Bars sind künstliches Konstrukte, versuchen wir näher an die ursprünglichen Daten zu kommen - den Preis-Ticks.

Hier sind der neue MetaTrader 5 und MQL5 Hier sind der neue MetaTrader 5 und MQL5

Dies ist nur ein kurzer Überblick über MetaTrader 5. Ich kann nicht alle neuen Funktionen des Systems in so kurzer Zeit beschreiben. Die Tests begannen am 09.09.2009. Das ist ein symbolisches Datum und ich bin sicher, dass es eine Glückszahl werden wird. Es sind ein paar Tage vergangen, seit ich die Beta-Version des MetaTrader-5-Terminals und MQL5 bekommen habe. Ich konnte noch nicht alle Funktionen ausprobieren, doch ich bin jetzt schon beeindruckt.