Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil IV).

Artyom Trishkin | 24 Mai, 2019

Inhalt

Im ersten Artikel haben wir begonnen, eine große plattformübergreifende Bibliothek zu erstellen, die die Entwicklung von Programmen für MetaTrader 5 und MetaTrader 4 Plattformen vereinfacht. In den folgenden Artikeln haben wir die Entwicklung der Bibliothek fortgesetzt und das Basisobjekt der Motorenbibliothek sowie die Sammlung von Marktorders und -positionen vervollständigt. In diesem Artikel werden wir die Entwicklung des Basisobjekts fortsetzen und ihm beibringen, Handelsereignisse auf dem Konto zu identifizieren.

Übergeben der Handelsereignisse an das Programm

Wenn wir auf den Test-EA zurückkommen, der ganz am Ende des dritten Artikels erstellt wurde, können wir sehen, dass die Bibliothek in der Lage ist, Handelsereignisse zu definieren, die auf dem Konto stattfinden. Wir sollten jedoch alle auftretenden Ereignisse genau nach ihren Typen aufteilen, um sie an ein Programm zu senden, das die Bibliothek für die Arbeit verwendet.
Dazu sollten wir die Methode schreiben, die Ereignisse definiert und diejenige, die Ereignistypen definiert.

Lassen Sie uns darüber nachdenken, welche Handelsgeschehnisse wir identifizieren müssen:

  • eine Pending-Order kann platziert worden sein
  • eine Pending-Order kann gelöscht worden sein
  • eine Pending-Order kann aktiviert worden sein, das eine Position erzeugt
  • eine Pending-Order kann teilweise aktiviert worden sein, das eine Position erzeugt
  • eine Position kann eröffnet worden sein
  • eine Position kann geschlossen worden sein
  • eine Position kann teilweise eröffnet worden sein
  • eine Position kann teilweise geschlossen worden sein
  • eine Position kann durch eine entgegengesetzte Position geschlossen worden sein
  • eine Position kann teilweise durch eine entgegengesetzte Position geschlossen worden sein
  • dem Konto wurde Geld gutgeschrieben
  • vom Konto wurde Geld abgebucht
  • eine Saldobuchung wurde auf dem Konto vorgenommen
    Ereignisse, die bis jetzt noch nicht erfasst sind:
  • eine Pending-Order kann geändert werden (Änderung des Aktivierungspreises, Hinzufügen/Entfernen/Ändern von Stop-Loss und Take-Profit).
  • Eine Position kann geändert werden (Hinzufügen/Entfernen/Ändern von Stop-Loss und Take-Profit).

Basierend auf dem oben genannten müssen Sie entscheiden, wie Sie ein Ereignis eindeutig identifizieren können. Es ist besser, die Lösung sofort nach der Kontoart zu unterteilen:

Hedging-Konten:

  1. Erhöhung der Anzahl der Pending-Order bedeutet dass Hinzufügen einer Pending-Order (Ereignis im Marktumfeld)
  2. Verminderte Anzahl der Pending-Orders:
    1. die erhöhte Anzahl der Positionen bedeutet eine ausgelöste Pending-Order (Ereignis im Markt- und historischen Umfeld)
    2. keine erhöhte Anzahl der Positionen, bedeutet das Löschen einer Pending-Order (Ereignis im Marktumfeld)
  3. keine Verminderung der Anzahl der Pending-Orders:
    1. erhöhte Anzahl von Positionen bedeutet die Eröffnung einer neuen Position (Ereignis im Markt- und historischen Umfeld).
    2. verminderte Anzahl von Positionen bedeutet Schließung einer Position (Ereignis im Markt- und historischen Umfeld)
    3. Anzahl der Positionen, die unverändert bleiben, aber mit einem abnehmenden Volumen einhergehen bedeutet teilweises Schließen einer Position (Ereignis im historischen Umfeld).

Netting-Konten:

  1. erhöhte Anzahl der Pending-Orders bedeutet dass Hinzufügen einer Pending-Order
  2. Verminderte Anzahl der Pending-Orders:
    1. erhöhte Anzahl der Positionen bedeutet eine ausgelöste Pending-Order
    2. unveränderte Anzahl von Positionen, aber eine geänderten Positionsänderungszeit und einem unveränderten Volumen begleitet werden bedeutet, eine Pending-Order wurde ausgelöst und eine Erhöhung des Positionsvolumens.
    3. verminderte Anzahl von Positionen bedeutet Schließung einer Position
  3. keine Verminderung der Anzahl der Pending-Orders:
    1. erhöhte Anzahl von Positionen bedeutet die Eröffnung einer neuen Position
    2. verminderte Anzahl von Positionen bedeutet Schließung einer Position
    3. unveränderte Anzahl von Positionen, aber eine geänderte Positionsänderungszeit und ein erhöhtes Volumen bedeutet, ein Positionsvolumen wurde erhöht.
    4. unveränderte Anzahl von Positionen, aber eine Positionsänderungszeit und ein verringertes Volumen bedeutet, ein Positionsvolumen wurde vermindert.

Um Handelsereignisse zu identifizieren, müssen wir wissen, mit welchem Kontotyp das Programm arbeitet. Fügen Sie das Flag des Typs eines Hedging-Kontos zum 'private' Bereich der Klasse CEngine hinzu, definieren Sie die Kontoart im Klassenkonstruktor und weisen Sie das Ergebnis dieser Flag-Variable zu:

//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection der historischen Aufträge und Deals
   CMarketCollection    m_market;                        // Collection der Marktorder und Deals
   CArrayObj            m_list_counters;                 // Liste der Timerzähler
   bool                 m_first_start;                   // Flag des Erststarts
   bool                 m_is_hedge;                      // Flag für Hedging-Konten
//--- Rückgabe des Zählerindex über die ID
   int                  CounterIndex(const int id) const;
//--- Rückgabe des Flags des ersten Starts
   bool                 IsFirstStart(void);
public:
//--- Erstellen der Timerzählers
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine Konstruktor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true)
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

Während des ersten Starts, während der Konstruktion des Klassenobjekts, wird der Kontentyp, mit dem das Programm gestartet wird, in seinem Konstruktor definiert. Dementsprechend werden die Methoden zur Definition von Handelsereignissen entweder einem Hedging- oder einem Netting-Konto zugeordnet.

Nachdem wir ein eingehendes Handelsereignis definiert haben, müssen wir dessen Code speichern. Es soll bis zum nächsten Ereignis konstant bleiben. Dadurch ist das Programm immer in der Lage, das letzte Ereignis eines Kontos zu definieren. Ein Ereigniscode besteht aus einem Satz von Flags. Jedes Flag beschreibt ein bestimmtes Ereignis. So kann beispielsweise das Ereignis einer Positionsschließung in bestimmte Teilmengen unterteilt werden, die es genauer charakterisieren:

  1. vollständig geschlossen
  2. teilweise geschlossen
  3. geschlossen durch eine entgegengesetzte Position
  4. geschlossen durch Stop-Loss
  5. geschlossen durch Take-Profit
  6. usw.

Alle diese Attribute sind einem Ereignis "Positionsschließung" inhärent, d.h. ein Ereigniscode sollte alle diese Daten enthalten. Um ein Ereignis mit den Flags zu konstruieren, erstellen wir zwei neue Enumerationen in der Datei Defines.mqh aus dem Stammverzeichnis der Bibliothek (Trading-Ereignisflags und mögliche Trading-Ereignisse) für das Konto, mit dem wir uns beschäftigen:

//+------------------------------------------------------------------+
//| Liste der Flags der Handelsereignisses des Kontos                |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT_FLAGS
  {
   TRADE_EVENT_FLAG_NO_EVENT        =  0,                   // Kein Ereignis
   TRADE_EVENT_FLAG_ORDER_PLASED    =  1,                   // Pending-Order platziert
   TRADE_EVENT_FLAG_ORDER_REMOVED   =  2,                   // Pending-Order entfernt
   TRADE_EVENT_FLAG_ORDER_ACTIVATED =  4,                   // Pending-Order Aktivierungspreis
   TRADE_EVENT_FLAG_POSITION_OPENED =  8,                   // Position eröffnet
   TRADE_EVENT_FLAG_POSITION_CLOSED =  16,                  // Position geschlossen
   TRADE_EVENT_FLAG_ACCOUNT_BALANCE =  32,                  // Saldobuchung (Klärung über den Dealtyp)
   TRADE_EVENT_FLAG_PARTIAL         =  64,                  // Teilweise Ausführung
   TRADE_EVENT_FLAG_BY_POS          =  128,                 // Ausführung mittels einer Gegenposition
   TRADE_EVENT_FLAG_SL              =  256,                 // Ausführung mittel Stop-Loss
   TRADE_EVENT_FLAG_TP              =  512                  // Ausführung durch Take-Profit
  };
//+------------------------------------------------------------------+
//| Liste der möglichen Handelsereignisse auf dem Konto              |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT,                                    // Kein Handelsereignis
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // Pending-Order platziert
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // Pending-Order entfernt
//--- Mitglieder der Enumeration stimmen mit den Mitgliedern der Enumeration ENUM_DEAL_TYPE überein
   TRADE_EVENT_ACCOUNT_CREDIT,                              // Gutschrift
   TRADE_EVENT_ACCOUNT_CHARGE,                              // Weitere Lastschrift
   TRADE_EVENT_ACCOUNT_CORRECTION,                          // Korrekturbuchung
   TRADE_EVENT_ACCOUNT_BONUS,                               // Bonusgutschrift
   TRADE_EVENT_ACCOUNT_COMISSION,                           // Zusätzliche Kommission
   TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                     // Kommission belastet am Tagesende
   TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                   // Kommission belastet am Monatsende
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,               // Kommission des Agenten belastet am Tagesende
   TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,             // Kommission des Agenten belastet am Monatsende
   TRADE_EVENT_ACCOUNT_INTEREST,                            // Zinsgutschrift der freien Gelder
   TRADE_EVENT_BUY_CANCELLED,                               // Stornierter Kauf-Deal
   TRADE_EVENT_SELL_CANCELLED,                              // Stornierter Verkaufs-Deal
   TRADE_EVENT_DIVIDENT,                                    // Dividenengutschrift
   TRADE_EVENT_DIVIDENT_FRANKED,                            // Gutschrift der freien Dividende
   TRADE_EVENT_TAX,                                         // Steuergutschrift
//--- Mitglieder der Enumeration, die sich auf den Dealtyp DEAL_TYPE_BALANCE der Enumeration ENUM_DEAL_TYPE beziehen.
   TRADE_EVENT_ACCOUNT_BALANCE_REFILL,                      // Überweisung auf das Konto
   TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL,                  // Kontoabbuchung
//---
   TRADE_EVENT_PENDING_ORDER_ACTIVATED,                     // Pending-Order, ausgelöst durch den Preis
   TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,             // Pending-Order, teilweise ausgelöst durch den Preis
   TRADE_EVENT_POSITION_OPENED,                             // Position eröffnet
   TRADE_EVENT_POSITION_OPENED_PARTIAL,                     // Position teilweise eröffnet
   TRADE_EVENT_POSITION_CLOSED,                             // Position geschlossen
   TRADE_EVENT_POSITION_CLOSED_PARTIAL,                     // Position teilweise geschlossen
   TRADE_EVENT_POSITION_CLOSED_BY_POS,                      // Position durch eine entgegengesetzte Position geschlossen
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,              // Position teilweise durch eine entgegengesetzte Position geschlossen
   TRADE_EVENT_POSITION_CLOSED_BY_SL,                       // Position geschlossen duch Stop-Loss
   TRADE_EVENT_POSITION_CLOSED_BY_TP,                       // Position geschlossen durch Take-Profit
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,               // Position teilweise geschlossen duch Stop-Loss
   TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,               // Position teilweise geschlossen durch Take-Profit
   TRADE_EVENT_POSITION_REVERSED,                           // Positionsumkehr (Netting)
   TRADE_EVENT_POSITION_VOLUME_ADD                          // Vergrößertes Positionsvolumen (netting)
  };
//+------------------------------------------------------------------+

Hier sollten wir einige Klarstellungen bezüglich der Enumeration ENUM_TRADE_EVENT vornehmen.
Da einige Handelsereignisse kein Programm oder menschliche Eingriffe erfordern (Gebühren, Provisionen, Gebühren, Boni, etc.), werden wir diese Daten aus der Dealtyp (der Enumeration ENUM_DEAL_TYPE) in MQL5 übernehmen. Um die spätere Bearbeitung des Ereignisses zu vereinfachen, müssen wir es so gestalten, dass unsere Ereignisse mit dem Wert der Enumeration ENUM_DEAL_TYPE übereinstimmen.
Wir werden die Saldooperation in zwei Ereignisse unterteilen: Konto-Gutschriften und Abhebungen. Andere Ereignisse aus der Enumeration der Dealtypen, beginnend mit DEAL_TYPE_CREDIT, haben die gleichen Werte wie in der Enumeration ENUM_DEAL_TYPE, mit Ausnahme von Kauf und Verkauf (DEAL_TYPE_BUY und DEAL_TYPE_SELL), die nicht mit Ausgleichsvorgängen zusammenhängen.

Lassen Sie uns die Klasse der Marktorder- und Positionsinkasso verbessern.
Wir werden eine Probenordnung zum 'private' Abschnitt der Klasse CMarketCollection hinzufügen, um eine Suche nach spezifizierten Auftragseigenschaften durchzuführen, während der 'public' Abschnitt der Klasse Methoden zum Erhalten der vollständigen Liste von Aufträgen und Positionen erhält, die Liste der Aufträge und Positionen, die durch eine spezifizierte Zeitspanne ausgewählt wurden, und Listen, die Aufträge und Positionen, die durch ein spezifiziertes Kriterium ausgewählt wurden, aus Integer-, Double- und String-Eigenschaften eines Auftrags oder einer Position zurückgeben. Dies wird es uns ermöglichen, die notwendigen Listen von Marktorders und Positionen aus der Collection zu erhalten (wie es für die Collection historischer Aufträge und Deals in Teil 2 und Teil 3 der Bibliotheksbeschreibung geschehen ist).

//+------------------------------------------------------------------+
//| Collection der Marktorders und Positionen                        |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:
   struct MqlDataCollection
     {
      long           hash_sum_acc;           // Hash-Summe aller Aufträge und Positionen
      int            total_pending;          // Anzahl aller Pending-Order auf dem Konto
      int            total_positions;        // Anzahl der Positionen auf dem Konto
      double         total_volumes;          // Gesamtvolumen der Aufträge und Positionen auf dem Konto
     };
   MqlDataCollection m_struct_curr_market;   // Aktuelle Daten der Marktorder und Positionen auf dem Konto
   MqlDataCollection m_struct_prev_market;   // Vorherige Daten der Marktorder und Positionen auf dem Konto
   CArrayObj         m_list_all_orders;      // Liste der Pending-Order und Positionen auf dem Konto
   COrder            m_order_instance;       // Auftragsobjekt für die Suche nach Eigenschaften
   bool              m_is_trade_event;       // Flag des Handelsereignisses
   bool              m_is_change_volume;     // Flag für die Änderung des Gesamtvolumens
   double            m_change_volume_value;  // Wert der Änderung des Gesamtvolumens
   int               m_new_positions;        // Number of new positions
   int               m_new_pendings;         // Anzahl der neuen Pending-Order
   //--- Sichern der aktuellen Werte des Status der Kontodaten als die vorherigen
   void              SavePrevValues(void)                                                                { this.m_struct_prev_market=this.m_struct_curr_market;                  }
public:
   //--- Rückgabe der Liste aller Pending-Order und offenen Positionen
   CArrayObj*        GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- 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);  }
   //--- Rückgabe der Anzahl von (1) neuen Pending-Orders, (2) neuen Positionen, (3) aufgetretenen Flags von Handelsereignissen, (4) verändertem Volumen
   int               NewOrders(void)                                                            const    { return this.m_new_pendings;                                           }
   int               NewPosition(void)                                                          const    { return this.m_new_positions;                                          }
   bool              IsTradeEvent(void)                                                         const    { return this.m_is_trade_event;                                         }
   double            ChangedVolumeValue(void)                                                   const    { return this.m_change_volume_value;                                    }
   //--- Konstructor
                     CMarketCollection(void);
   //--- Aktualisieren der Liste der Pending-Orders und Positionen
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Implementieren wir die Methode zur Auswahl von Aufträgen und Positionen nach Zeit über den Klassenkörper hinaus:

//+------------------------------------------------------------------------+
//| Auswahl der Marktorders oder Positionen aus der Collection nach Zeit   |
//| innerhalb der Zeitspanne von begin_time bis end_time                   |
//+------------------------------------------------------------------------+
CArrayObj* CMarketCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0)
  {
   CArrayObj* list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN,TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time);
   list.FreeMode(false);
   ListStorage.Add(list);
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,begin);
   int index_begin=m_list_all_orders.SearchGreatOrEqual(&m_order_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   m_order_instance.SetProperty(ORDER_PROP_TIME_OPEN,end);
   int index_end=m_list_all_orders.SearchLessOrEqual(&m_order_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(m_list_all_orders.At(i));
   return list;
  }
//+------------------------------------------------------------------+

Die Methode ist fast identisch mit derjenigen zur Auswahl historischer Aufträge und Geschäfte nach der Zeit, die wir im Teil 3 beschrieben haben. Lesen Sie die Beschreibung im entsprechenden Abschnitt des dritten Artikels erneut, falls erforderlich. Der Unterschied zwischen dieser Methode und derjenigen der Klasse der historischen Collection besteht darin, dass wir nicht die Aufträge nach der Zeit auswählen, nach denen selektiert werden soll. Marktorders und Positionen haben nur Eröffnungszeiten.

Ändern Sie auch die historischen Ordnungen und behandelt den Klassenkonstruktor der Sammlung. Das MQL5-Auftragssystem verfügt über kein Konzept mit den Schließzeiten — alle Aufträge und Deals sind in Listen nach ihrer Zeit der Auftragserteilung (oder Eröffnungszeit nach dem MQL4-Ordersystem) geordnet. Ändern Sie dazu die Zeile, die die Sortierrichtung in der Collection der historischen Aufträge und Deals im Klassenkonstruktor CHistoryCollection definiert:

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

Jetzt in MQL5, werden alle Aufträge und Deals in der Collection historischer Aufträge und Deals standardmäßig nach ihrer Platzierungszeit sortiert, während sie in MQL4 nach der in den Auftragseigenschaften definierten Schließzeit sortiert werden.

Kommen wir nun zu einem weiteren Merkmal des MQL5-Auftragssystem. Beim Schließen einer Position durch eine entgegengesetzte Position wird ein spezieller Schließauftrag vom Typ ORDER_TYPE_CLOSE_BY platziert, während beim Schließen einer Position durch eine Stop-Order stattdessen eine Marktorder zum Schließen einer Position platziert wird.
Um Marktorder zu berücksichtigen, mussten wir die Methoden zum Empfangen und Zurückgeben der Integer-Eigenschaften des Basisauftrags um eine weitere Eigenschaft erweitern (am Beispiel des Empfangens und Zurückgebens der Magicnummer des Auftrags):

//+------------------------------------------------------------------+
//| Rückgabe der Magicnummer                                         |
//+------------------------------------------------------------------+
long COrder::OrderMagicNumber() const
  {
#ifdef __MQL4__
   return ::OrderMagicNumber();
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_MAGIC);           break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_MAGIC);                 break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_MAGIC);   break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_MAGIC); break;
      default                             : res=0;                                              break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Ich habe solche Änderungen (oder die logisch der Methode entsprechenden) in allen Methoden zum Empfangen und Zurückgeben der ganzzahligen Eigenschaften der Basisordnung vorgenommen, bei denen dieser Status wirklich berücksichtigt werden muss. Da ein solcher Status existiert, legen wir eine neue Klasse der Marktorder CMarketOrder im Ordner Objects der Bibliothek an, um solche Auftragstypen zu speichern. Die Klasse ist völlig identisch mit den übrigen zuvor erstellten Markt- und historischen Order- und Dealobjekten, daher werde ich hier nur die Auflistung angeben:

//+------------------------------------------------------------------+
//|                                                  MarketOrder.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Marketorder                                                      |
//+------------------------------------------------------------------+
class CMarketOrder : public COrder
  {
public:
   //--- Konstructor
                     CMarketOrder(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_ORDER,ticket) {}
   //--- Unterstützte Order-Eigenschaften (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
  };
//+------------------------------------------------------------------+
//| Rückgabe von 'true', falls der Auftrag die übergebene            |
//| Integer-Eigenschaft unterstützt, sonst 'false'                   |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_EXP          || 
      property==ORDER_PROP_DEAL_ENTRY        || 
      property==ORDER_PROP_TIME_UPDATE       || 
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_TIME_CLOSE        ||
      property==ORDER_PROP_TIME_CLOSE_MSC    ||
      property==ORDER_PROP_TICKET_FROM       ||
      property==ORDER_PROP_TICKET_TO
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Rückgabe von 'true', falls der Auftrag die übergebene            |
//| Double-Eigenschaft, sonst 'false'                                |
//+------------------------------------------------------------------+
bool CMarketOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_PROFIT            || 
      property==ORDER_PROP_PROFIT_FULL       || 
      property==ORDER_PROP_SWAP              || 
      property==ORDER_PROP_COMMISSION        ||
      property==ORDER_PROP_PRICE_CLOSE       ||
      property==ORDER_PROP_SL                ||
      property==ORDER_PROP_TP                ||
      property==ORDER_PROP_PRICE_STOP_LIMIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

Schreiben wir in die Bibliotheksdatei Defines.mqh den neuen Status — Marktorder:

//+------------------------------------------------------------------+
//| Abstrakter Order-Typ (Status)                                    |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATUS
  {
   ORDER_STATUS_MARKET_PENDING,                             // Pending-Order im Markt
   ORDER_STATUS_MARKET_ORDER,                               // Marktorder
   ORDER_STATUS_MARKET_POSITION,                            // Marktposition
   ORDER_STATUS_HISTORY_ORDER,                              // Historische Marktorder
   ORDER_STATUS_HISTORY_PENDING,                            // Entfernte Pending-Order
   ORDER_STATUS_BALANCE,                                    // Saldo-Operation
   ORDER_STATUS_CREDIT,                                     // Finanz-Operation
   ORDER_STATUS_DEAL,                                       // Deal (Transaktion)
   ORDER_STATUS_UNKNOWN                                     // Status unbekannt
  };
//+------------------------------------------------------------------+

Nun, im Block zum Hinzufügen von Aufträgen zur Liste (die Methode Refresh() der Klasse CMarketCollection zum Aktualisieren der Liste der Marktorders und Positionen), implementieren wir die Prüfung des Auftragstyps. Fügen wir je nach Typ entweder ein Marktordnungsobjekt oder ein Pending-Orderobjekt zur Collection hinzu:

//+------------------------------------------------------------------+
//| Aktualisierung der Auftragsliste                                 |
//+------------------------------------------------------------------+
void CMarketCollection::Refresh(void)
  {
   ::ZeroMemory(this.m_struct_curr_market);
   this.m_is_trade_event=false;
   this.m_is_change_volume=false;
   this.m_new_pendings=0;
   this.m_new_positions=0;
   this.m_change_volume_value=0;
   m_list_all_orders.Clear();
#ifdef __MQL4__
   int total=::OrdersTotal();
   for(int i=0; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS)) continue;
      long ticket=::OrderTicket();
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketPosition *position=new CMarketPosition(ticket);
         if(position==NULL) continue;
         if(this.m_list_all_orders.InsertSort(position))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_positions++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
            delete position;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_market.hash_sum_acc+=ticket;
            this.m_struct_market.total_volumes+=::OrderLots();
            this.m_struct_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- MQ5
#else 
//--- Positionen
   int total_positions=::PositionsTotal();
   for(int i=0; i<total_positions; i++)
     {
      ulong ticket=::PositionGetTicket(i);
      if(ticket==0) continue;
      CMarketPosition *position=new CMarketPosition(ticket);
      if(position==NULL) continue;
      if(this.m_list_all_orders.InsertSort(position))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)::PositionGetInteger(POSITION_TIME_UPDATE_MSC);
         this.m_struct_curr_market.total_volumes+=::PositionGetDouble(POSITION_VOLUME);
         this.m_struct_curr_market.total_positions++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить позицию в список","Failed to add position to list"));
         delete position;
        }
     }
//--- Orders
   int total_orders=::OrdersTotal();
   for(int i=0; i<total_orders; i++)
     {
      ulong ticket=::OrderGetTicket(i);
      if(ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         CMarketOrder *order=new CMarketOrder(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
            this.m_struct_curr_market.total_market++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить маркет-ордер в список","Failed to add market order to list"));
            delete order;
           }
        }
      else
        {
         CMarketPending *order=new CMarketPending(ticket);
         if(order==NULL) continue;
         if(this.m_list_all_orders.InsertSort(order))
           {
            this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
            this.m_struct_curr_market.total_volumes+=::OrderGetDouble(ORDER_VOLUME_INITIAL);
            this.m_struct_curr_market.total_pending++;
           }
         else
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить отложенный ордер в список","Failed to add pending order to list"));
            delete order;
           }
        }
     }
#endif 
//--- Erster Start
   if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE)
     {
      this.SavePrevValues();
     }
//--- Falls die Hash-Summe aller Aufträge und Positionen sich geändert hat
   if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc)
     {
      this.m_new_market=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market;
      this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending;
      this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions;
      this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4);
      this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false);
      this.m_is_trade_event=true;
      this.SavePrevValues();
     }
  }
//+------------------------------------------------------------------+

Um Schließaufträge vom Typ ORDER_TYPE_CLOSE_BY berücksichtigen zu können, fügen wir diesen Auftragstyp zum Auftragstyp-Definitionsblock in der Methode Refresh() zur Aktualisierung der Liste der historischen Aufträge und Deals der Klasse CHistoryCollection hinzu, so dass solche Aufträge in die Collection aufgenommen werden. Ohne diese kann das Basisobjekt der Bibliothek CEngine nicht definieren, dass eine Position durch eine entgegengesetzte geschlossen wurde:

//+------------------------------------------------------------------+
//| Aktualisieren der Liste der Aufträge und Deals                   |
//+------------------------------------------------------------------+
void CHistoryCollection::Refresh(void)
  {
#ifdef __MQL4__
   int total=::OrdersHistoryTotal(),i=m_index_order;
   for(; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
      //--- Geschlossenen Positionen und Salden-/Korrekturbuchungen
      if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         //--- Entfernte Pending-Order
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//---
   int delta_order=i-m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;
   this.m_is_trade_event=(this.m_delta_order!=0 ? true : false);
//--- __MQL5__
#else 
   if(!::HistorySelect(0,END_TIME)) return;
//--- Orders
   int total_orders=::HistoryOrdersTotal(),i=m_index_order;
   for(; i<total_orders; i++)
     {
      ulong order_ticket=::HistoryOrderGetTicket(i);
      if(order_ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY)
        {
         CHistoryOrder *order=new CHistoryOrder(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         CHistoryPending *order=new CHistoryPending(order_ticket);
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//--- Sichern des Index des zuletzt ergänzten Auftrags und der Änderung im Vergleich zur vorherigen Prüfung
   int delta_order=i-this.m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;

//--- Deals
   int total_deals=::HistoryDealsTotal(),j=m_index_deal;
   for(; j<total_deals; j++)
     {
      ulong deal_ticket=::HistoryDealGetTicket(j);
      if(deal_ticket==0) continue;
      CHistoryDeal *deal=new CHistoryDeal(deal_ticket);
      if(deal==NULL) continue;
      this.m_list_all_orders.InsertSort(deal);
     }
//---Sichern des Index des zuletzt hinzugefügten Deals und der Differenz zum vorher Geprüften
   int delta_deal=j-this.m_index_deal;
   this.m_index_deal=j;
   this.m_delta_deal=delta_deal;
//--- Setzen des Flags für neue Ereignisse in der Historie
   this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
#endif 
  }
//+------------------------------------------------------------------+

Beim Testen der Klasse CEngine zur Definition auftretender Kontoereignisse habe ich einige kleinere Fehler in den Servicemethoden entdeckt und behoben. Es macht keinen Sinn, sie hier zu beschreiben, da sie die Performance nicht beeinflussen, während ihre Beschreibung die Aufmerksamkeit von der Entwicklung wichtiger Bibliotheksfunktionen ablenkt. Alle Änderungen an den Klassenauflistungen wurden bereits vorgenommen. Du kannst sie selbst in den unten angehängten Bibliotheksdateien sehen.


Setzen wir unsere Arbeit an der Definition von Ereignissen fort.

Nach dem Debuggen der Definition der Handelsereignisse werden alle aufgetretenen Ereignisse in einer einzigen Klassenvariablen gepackt, die als eine Reihe von Flags ausgeführt wird. Das Verfahren zum Lesen von Daten aus der Variablen zur Zerlegung ihres Wertes in Komponenten, die ein bestimmtes Ereignis charakterisieren, wird anschließend erstellt.

Fügen wir die Klassenvariable zum Speichern des Trading-Ereigniscodes, Methoden zum Verifizieren eines Handelsereignisses für Hedging- und Netting-Konten und Methoden zum Rückgabe der benötigten Auftragsobjekte in den 'private' Bereich der Klasse CEngine hinzu.
Im 'public' Bereich deklarieren wir die Methoden, die die Listen der Marktpositionen und Pending-Orders, der historische Marktorder und Deals, die Methode Rückgabe eines Trading-Eventcodes aus der Variable m_trade_event_code und die Methode Rückgabe des Hedge-Account-Flags.
zurückgeben.

//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection der historischen Aufträge und Deals
   CMarketCollection    m_market;                        // Collection der Marktorder und Deals
   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_market_trade_event;         // Flag eines Handelsereignisses des Kontos
   bool                 m_is_history_trade_event;        // Flag eines historischen Handelsereignisses auf dem Konto
   int                  m_trade_event_code;              // Status des Codes des Handelsereignisses auf dem konto
//--- Rückgabe des Zählerindex über die ID
   int                  CounterIndex(const int id) const;
//--- Rückgabe des Flags des ersten Starts
   bool                 IsFirstStart(void);
//--- Arbeiten mit Collection von (1) Hedging und (2) Netting
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(void);
//--- Rückgabe der letzten (1) Pending-Order, (2) Marktorder, (3) letzten Position, (4) Position nach Ticket
   COrder*              GetLastMarketPending(void);                    
   COrder*              GetLastMarketOrder(void);                      
   COrder*              GetLastPosition(void);                         
   COrder*              GetPosition(const ulong ticket);               
//--- Rückgabe der letzten (1) gelöschten Pending-Order, (2) historischen Marktorder, (3) historischen Marktorder nach Ticket
   COrder*              GetLastHistoryPending(void);                   
   COrder*              GetLastHistoryOrder(void);                     
   COrder*              GetHistoryOrder(const ulong ticket);           
//--- Rückgabe des (1) ersten und (2) des letzten historischen Marktorder aus der Liste aller Positionen, (3) des letzten Deals
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id); 
   COrder*              GetLastDeal(void);                             
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*           GetListHistoryDeals(void);                       
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Rückgabe des (1) Codes des Handelsereignisses und (2) Flag des Hedging-Kontos
   int                  TradeEventCode(void)             const { return this.m_trade_event_code;   }
   bool                 IsHedge(void)                    const { return this.m_is_hedge;           }
//--- Erstellen des Timerzählers
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/Destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Initialisieren wir den Code des Handelsereignisses in der Initialisierungsliste des Klassenkonstruktors.

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

Implementieren wir deklarierte Methoden außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Rückgabe der Liste der Marktpositionen                           |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der Liste der Pending-Orders                            |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPendings(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der Liste der Marktordersd                              |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketOrders(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der Liste der historischen Aufträge                     |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryOrders(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der Liste der entfernten Pending-Orders                 |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryPendings(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der Liste der Deals                                     |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryDeals(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//|  Rückgabe der Liste aller Positionen                             |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der letzten Position                                    |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der Position nach der Ticketnummer                      |
//+------------------------------------------------------------------+
COrder* CEngine::GetPosition(const ulong ticket)
  {
   CArrayObj* list=this.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe des letzten Deals                                       |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastDeal(void)
  {
   CArrayObj* list=this.GetListHistoryDeals();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der letzten Pending-Order im Markt                      |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketPending(void)
  {
   CArrayObj* list=this.GetListMarketPendings();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der letzten historischen Pending-Order                  |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryPending(void)
  {
   CArrayObj* list=this.GetListHistoryPendings();
   if(list==NULL) return NULL;
   list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der letzten Marktorder                                  |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketOrder(void)
  {
   CArrayObj* list=this.GetListMarketOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der letzten historischen Marktorder                     |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryOrder(void)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der historischen Marktorder nach der Ticketnummer       |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der ersten historischen Marktorder                      |
//| aus der Liste aller Positionen                                   |
//+------------------------------------------------------------------+
COrder* CEngine::GetFirstOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| Rückgabe der letzten historischen Marktorder                     |
//| aus der Liste aller Positionen                                   |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Sehen wir uns an, wie die Listen anhand des folgenden Beispiels erhalten werden:

//+------------------------------------------------------------------+
//| Rückgabe der Liste der Marktpositionen                           |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Alles ist ganz einfach und bequem: Wir erhalten die vollständige Liste der Positionen aus der Collection von Marktorders und -positionen mit der Methode der Collection GetList(). Danach wählen wir einen Auftrag mit dem Status "Position" aus , indem wir die Methode zum Auswählen von Aufträgen über eine bestimmte Eigenschaft aus der Klasse CSelect verwenden. Die Methode wurde im dritten Artikel der Bibliotheksbeschreibung beschrieben. Liefern der erhaltenen Liste.
Die Liste kann leer sein (NULL), daher sollte das von dieser Methode zurückgegebene Ergebnis im aufrufenden Programm überprüft werden.

Schauen wir anhand des folgenden Beispiels, wie wir einen benötigten Auftrag erhalten:

//+------------------------------------------------------------------+
//| Rückgabe der letzten Position                                    |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

Zunächst erhalten wir mit der oben beschriebenen Methode GetListMarketPosition() die Liste der Positionen. Wenn die Liste leer ist, erhalten wir NULL. Als Nächstes sortieren wir die Liste nach der offenen Zeit in Millisekunden (da wir die letzte offene Position erhalten werden, sollte die Liste nach der Zeit sortiert werden) und wählen wir die letzte Reihenfolge in ihr. Infolgedessen gibt einen aus der Liste erhaltenen Auftrag an das aufrufende Programm zurück.
Das Ergebnis der Suche nach einer von der Methode zurückgegebenen Bestellung kann leer sein (Reihenfolge wird nicht gefunden) und gleich NULL sein. Überprüfen wir daher das erhaltene Ergebnis auf NULL, bevor wir darauf zugreifen.

Wie man sehen kann, ist alles schnell und einfach. Wir können jede Methode zum Empfangen von Daten aus bestehenden und zukünftigen Collection erstellen. Dies gibt uns mehr Flexibilität bei der Verwendung solcher Listen.
Die Methoden zum Empfangen der Listen sind im 'public' Abschnitt der Klasse platziert, so dass wir alle Daten aus den Listen in benutzerdefinierten Programmen empfangen können, wie es mit den oben genannten Methoden geschehen ist.
Die Methoden zum Empfangen notwendiger Bestellungen sind im privaten Bereich ausgeblendet. Sie werden nur in der Klasse CEngine für den internen Bedarf benötigt — insbesondere um Daten über die letzten Aufträge, Geschäfte und Positionen zu erhalten. In nutzerdefinierten Programmen können wir nutzerdefinierte Funktionen für den Empfang bestimmter Aufträge durch bestimmte Eigenschaften erstellen (die Beispiele sind oben aufgeführt).
Um auf einfache Weise Daten aus den Collectionen zu erhalten, werden wir irgendwann eine breite Palette von Funktionen für Endbenutzer entwickeln.
Dies wird in den letzten Artikeln über die Arbeit mit der Collection der Aufträge geschehen.

Nun lassen Sie uns die Methode zur Überprüfung von Handelsgeschäften implementieren.
Derzeit funktioniert es nur bei Hedging-Konten für MQL5. Danach werde ich die Methoden zur Überprüfung von Handelsereignissen für MQL5 Netting Accounts und MQL4 entwickeln. Um die Methodenoperation zu testen, bietet sich sie die Anzeige der Prüf- und Ereignisergebnisse an. Diese Fähigkeit soll später entfernt werden, da diese Funktion durch eine andere Methode erfüllt werden soll, die nach dem Debuggen der Methode zur Überprüfung von Handelsereignissen auf dem Konto erstellt wird.

//+------------------------------------------------------------------+
//| Prüfen des Handelsereignisses (Hedging)                          |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- Initialisierung des Codes des Handelsereignisses un des Flags
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   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 Dynamik des Marktzustands und der Kontohistorie
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();
//---
#ifdef __MQL4__

#else // MQL5
//--- Wenn es nur ein Ereignis der Marktorders und Positionen ist
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
      //--- Bei einer erhöhten Anzahl der Pending-Order
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- Hinzufügen des Flags für das Installieren von Pending-Orders
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
         string text=TextByLanguage("Установлен отложенный ордер: ","Pending order placed: ");
         //--- Nehmen der letzten Pending-Order
         COrder* order=this.GetLastMarketPending();
         if(order!=NULL)
           {
            //--- Hinzufügen der Ticketnummer des Auftrags zur Nachricht
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Hinzufügen der Nachricht zum Journal
         Print(DFUN,text);
        }
      //--- Bei erhöhter Zahl von Marktorders
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- kein Ereignisflag hinzufügen
         //--- ...
         string text=TextByLanguage("Выставлен маркет-ордер: ","Market order placed: ");
         //--- Nehmen der letzten Marktorder
         COrder* order=this.GetLastMarketOrder();
         if(order!=NULL)
           {
            //--- Hinzufügen der Ticketnummer des Auftrags zur Nachricht
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Hinzufügen der Nachricht zum Journal
         Print(DFUN,text);
        }
     }
   
//--- Wenn ein Ereignis nur die historischen Aufträge und Deals betrifft
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
      //--- Wenn ein neuer Deal erscheint
      if(this.m_history.NewDeals()>0)
        {
         //--- Hinzufügen des Ereignisflags eines Kontensaldos
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
         string text=TextByLanguage("Новая сделка: ","New deal: ");
         //--- Nehmen des letzten Deals
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- Hinzufügen der Beschreibung zum Text
            text+=deal.TypeDescription();
            //--- Wenn der Deal eine Salsobuchung ist
            if((ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE)==DEAL_TYPE_BALANCE)
              {
              //--- Prüfen des Deal-Gewinns und des Ereignisses (Gelder zu- oder abbuchen) zur Nachricht
               text+=(deal.Profit()>0 ? TextByLanguage(": Пополнение счёта: ",": Account Recharge: ") : TextByLanguage(": Вывод средств: ",": Withdrawal: "))+::DoubleToString(deal.Profit(),(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS));
              }
           }
         //--- Ausdrucken der Nachricht im Journal
         Print(DFUN,text);
        }
     }
   
//--- Wenn Ereignisse sich auf Marktorders, historische Aufträge und Positionen beziehen
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      Print(DFUN,TextByLanguage("Новые торговые события на счёте и в истории счёта","New trading events on account and in account history"));
      
      //--- Bei erhöhter Anzahl der Pending-Orders und es keine neuen Deals aufgetreten sind
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- Hinzufügen des Flags zum Löschen der Pending-Order
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
         string text=TextByLanguage("Удалён отложенный ордер: ","Removed pending order: ");
         //--- Nehmen der letzten historischen Pending-Order
         COrder* order=this.GetLastHistoryPending();
         if(order!=NULL)
           {
            //--- Hinzufügen der Ticketnummer zur entsprechenden Nachricht
            text+=order.TypeDescription()+" #"+(string)order.Ticket();
           }
         //--- Ausdrucken der Nachricht im Journal
         Print(DFUN,text);
        }
      
      //--- Wenn es einen neuen Deal und einen neuen historischen Auftrag gibt
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- Nehmen des letzten Deals
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- Im Falle eines Deals eines Markteintritts
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- Hinzufügen des Flags einer Positionseröffnung
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               string text=TextByLanguage("Открыта позиция: ","Position opened: ");
               //--- Bei verminderter Anzahl der Pending-Orders
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- Hinzufügen des Aktivierungsflags der Pending-Order
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                  text=TextByLanguage("Сработал отложенный ордер: ","Pending order activated: ");
                 }
               //--- Nehmen der Ticketnummer des Auftrags
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Wenn das Volumen des aktuellen Auftrags größer Null ist
                  if(order.VolumeCurrent()>0)
                    {
                     //--- Hinzufügen des Flags einer teilweisen Ausführung
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично открыта позиция: ","Position partially open: ");
                    }
                  //--- Hinzufügen der Auftragsrichtung zur Nachricht
                  text+=order.DirectionDescription();
                 }
               //--- Hinzufügen der Ticketnummer des Deals zur Nachricht und Ausdruck der Nachricht im Journal
               text+=" #"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- Im Falle eines Deals des Verlassens des Marktes
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- Hinzufügen der Flag über das Schließen einer Position
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               string text=TextByLanguage("Закрыта позиция: ","Position closed: ");
               //--- Nehmen der Ticketnummer des Deals
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Wenn die Position des Deals noch immer im Markt existiert
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Hinzufügen des Flags einer teilweisen Ausführung
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text=TextByLanguage("Частично закрыта позиция: ","Partially closed position: ");
                    }
                  //--- Andernfalls, wenn die Position komplett geschlossen ist
                  else
                    {
                     //--- Wenn der Auftrag das Flag eines Stop-Loss hat
                     if(order.IsCloseByStopLoss())
                       {
                        //--- Hinzufügen des Flags für ein Schließen durch Stop-Loss
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                        text=TextByLanguage("Позиция закрыта по StopLoss: ","Position closed by StopLoss: ");
                       }
                     //--- Wenn der Auftrag das Flag eines Take-Profit hat
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- Hinzufügen des Flags für ein Schließen durch Take-Profit
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                        text=TextByLanguage("Позиция закрыта по TakeProfit: ","Position closed by TakeProfit: ");
                       }
                    }
                  //--- Hinzufügen der umgekehrten Auftragsrichtung zur Nachricht
                  //--- Schließen eines Kaufauftrags durch eine Verkaufsposition und das Schließen eines Verkaufsauftrag order durch eine Kaufposition,
                  //--- daher, Umkehren der Auftragsrichtung für die korrekte Beschreibung der geschlossenen Position
                  text+=(order.DirectionDescription()=="Sell" ? "Buy " : "Sell ");
                 }
               //--- Hinzufügen der Ticketnummer der Position eines Deals und Ausdruck der Nachricht im Journal
               text+="#"+(string)deal.PositionID();
               Print(DFUN,text);
              }
            
            //--- Beim Schließen durch einen Gegenposition
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- Hinzufügen der Flag über das Schließen einer Position
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Hinzufügen des Flags beim Schließen durch eine Gegenposition
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               string text=TextByLanguage("Позиция закрыта встречной: ","Position closed by opposite position: ");
               //--- Nehmen des Auftrags des Deals
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- Hinzufügen der umgekehrten Auftragsrichtung zur Nachricht
                  //--- Schließen eines Kaufauftrags durch eine Verkaufsposition und das Schließen eines Verkaufsauftrag order durch eine Kaufposition,
                  //--- daher, Umkehren der Auftragsrichtung für die korrekte Beschreibung der geschlossenen Position
                  text+=(order.DirectionDescription()=="Sell" ? "Buy" : "Sell");
                  text+=" #"+(string)order.PositionID()+TextByLanguage(" закрыта позицией #"," closed by position #")+(string)order.PositionByID();
                  //--- Wenn die Position noch immer im Markt existiert
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Hinzufügen des Flags einer teilweisen Ausführung
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                     text+=TextByLanguage(" частично"," partially");
                    }
                 }
               //--- Ausdrucken der Nachricht im Journal
               Print(DFUN,text);
              }
           //--- Ende des Blocks der Bearbeitung von Deals
           }
        }
     }
#endif 
  }
//+------------------------------------------------------------------+

Die Methode ist einfach, aber ziemlich umfangreich. Daher enthält der Code alle Prüfungen und entsprechenden Aktionen, so dass wir besser sehen können, was innerhalb der Methode passiert. Die Methode implementiert zunächst die Prüfung für das MQL5-Hedging-Konto. Tatsächlich kommt es darauf an, die Anzahl der neu erschienenen Aufträge und Deals entweder in der Kontohistorie oder am Markt zu überprüfen. Für den Ausdruck im Journal werden die Daten von der letzten Position, dem letzten Deal, dem letzten Auftrag oder der Dealorder übernommen — all dies geschieht für den Ausdruck von Daten im Journal, um die Codeausführung zu überprüfen. Diese Funktionen werden später aus dem Code entfernt und durch eine einzige Methode ersetzt, die von den mit der Bibliothek arbeitenden Programmen verwendet wird.

Prüfung der Verarbeitung von Handelsereignissen

Lassen Sie uns einen Test-EA für die Überprüfung der Methode erstellen, die Handelsereignisse auf dem Konto definiert.
Lassen Sie uns eine Reihe von Schaltflächen implementieren, um die neuen Ereignisse zu verwalten.
Die benötigten Aktionen und die entsprechenden Schaltflächen sind wie folgt:

Die Eingabeparameter sind wie folgt:

  • Magic number - Magicnummer
  • Lots - Volumen der zu eröffnenden Positionen
  • StopLoss in Punkten
  • TakeProfit in Punkten
  • Pending orders distance (Punkte)
  • StopLimit orders distance (Punkte)
    Eine StopLimit-Order wird als Stop-Order in einem Abstand (distance) vom Preis platziert, der durch den Wert Pending Orders distance festgelegt wurde.
    Sobald der Preis die angegebene Order erreicht und aktiviert hat, wird mit diesem Preisniveau eine Limit-Order in einem Abstand vom durch StopLimit-Orders distance festgelegten Preis platziert.
  • Slippage (Schlupf) in Punkten
  • Abbuchen von Geld (im Tester) - Auszahlung von Geldern vom Konto im Tester

Wir benötigen die Funktionen zur Berechnung korrekter Werte, um Orderplatzierungspreise im Verhältnis zum StopLevel, Stop-Orders und Positionsvolumen zu definieren. An dieser Stelle fügen wir die Funktionen der Bibliothek der Servicefunktionen in der Datei DELib.mqh hinzu, solange wir noch keine Handelsklassen und Symbolklassen haben:

//+------------------------------------------------------------------+
//| Rückgabe der kleinsten Losgröße des Symbols                      |
//+------------------------------------------------------------------+
double MinimumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
  }
//+------------------------------------------------------------------+
//| Rückgabe der größten Losgröße des Symbols                        |
//+------------------------------------------------------------------+
double MaximumLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
  }
//+------------------------------------------------------------------+
//| Rückgabe der Schrittweite der Losgrößenänderung des Symbols      |
//+------------------------------------------------------------------+
double StepLots(const string symbol_name) 
  { 
   return SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_STEP);
  }
//+------------------------------------------------------------------+
//| Rückgabe de normalized lot                                       |
//+------------------------------------------------------------------+
double NormalizeLot(const string symbol_name, double order_lots) 
  {
   double ml=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN);
   double mx=SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MAX);
   double ln=NormalizeDouble(order_lots,int(ceil(fabs(log(ml)/log(10)))));
   return(ln<ml ? ml : ln>mx ? mx : ln);
  }
//+------------------------------------------------------------------+
//| Rückgabe des korrekten StopLevels relativ zum StopLevel          |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ? 
      NormalizeDouble(fmin(price-lv*pt,stop_loss),dg) :
      NormalizeDouble(fmax(price+lv*pt,stop_loss),dg)
     );
  }
//+------------------------------------------------------------------+
//| Rückgabe des korrekten StopLevels relativ zum StopLevel          |
//+------------------------------------------------------------------+
double CorrectStopLoss(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int stop_loss,const int spread_multiplier=2)
  {
   if(stop_loss==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmin(price-lv*pt,price-stop_loss*pt),dg) :
      NormalizeDouble(fmax(price+lv*pt,price+stop_loss*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| Rückgabe des korrekten TakeProfit relativ zum StopLevel          |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      NormalizeDouble(fmax(price+lv*pt,take_profit),dg) :
      NormalizeDouble(fmin(price-lv*pt,take_profit),dg)
     );
  }
//+------------------------------------------------------------------+
//| Rückgabe des korrekten TakeProfit relativ zum StopLevel          |
//+------------------------------------------------------------------+
double CorrectTakeProfit(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const int take_profit,const int spread_multiplier=2)
  {
   if(take_profit==0) return 0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT);
   double price=(order_type==ORDER_TYPE_BUY ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : order_type==ORDER_TYPE_SELL ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price_set);
   return
     (order_type==ORDER_TYPE_BUY       || 
      order_type==ORDER_TYPE_BUY_LIMIT || 
      order_type==ORDER_TYPE_BUY_STOP
      #ifdef __MQL5__                  ||
      order_type==ORDER_TYPE_BUY_STOP_LIMIT
      #endif ?
      ::NormalizeDouble(::fmax(price+lv*pt,price+take_profit*pt),dg) :
      ::NormalizeDouble(::fmin(price-lv*pt,price-take_profit*pt),dg)
     );
  }
//+------------------------------------------------------------------+
//| Rückgabe des Preises der korrekten Order-Platzierung             |
//| relative zum StopLevel                                           |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Rückgabe des Preises der korrekten Order-Platzierung             |
//| relative zum StopLevel                                           |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          :  Print(DFUN,TextByLanguage("Не правильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Prüfen des Stoppreises in Punkten relative zum StopLevel         |
//+------------------------------------------------------------------+
bool CheckStopLevel(const string symbol_name,const int stop_in_points,const int spread_multiplier)
  {
   return(stop_in_points>=StopLevel(symbol_name,spread_multiplier));
  }
//+------------------------------------------------------------------+
//| Rückgabe des StopLevels in Punkten                               |
//+------------------------------------------------------------------+
int StopLevel(const string symbol_name,const int spread_multiplier)
  {
   int spread=(int)SymbolInfoInteger(symbol_name,SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(symbol_name,SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread*spread_multiplier : stop_level);
  }
//+------------------------------------------------------------------+ 

Die Funktionen berechnen einfach korrekte Werte, damit sie nicht gegen die auf dem Server festgelegten Einschränkungen verstoßen. Die Berechnungsfunktion StopLevel erhält neben dem Symbol auch den Multiplikator des Spreads. Dies geschieht, weil, wenn der StopLevel auf dem Server auf Null gesetzt ist, dies bedeutet einen Floating Level, und um StopLevel zu berechnen, sollten wir einen Spread-Wert multipliziert mit einer bestimmten Zahl verwenden (normalerweise 2, aber 3 ist auch möglich). Dieser Multiplikator wird an die Funktion übergeben, so dass wir ihn entweder in den EA-Einstellungen fest programmieren oder berechnen können.

Um Zeit zu sparen, werden wir keine nutzerdefinierten Handelsfunktionen schreiben. Stattdessen verwenden wir die vorgefertigten Handelsklassen der Standardbibliothek, nämlich die Klasse CTrade für die Durchführung von Tradingoperationen.
Um Schaltflächen für Arbeitschart zu erstellen, werden wir eine Enumeration mit ihren Mitgliedern implementieren, die Schaltflächennamen, Bezeichnungen und Werte für die Überprüfung eines bestimmten Tastendrucks festlegen.

Erstellen wir in MQL5\Experts\TestDoEasy\Part04\\ einen neuen EA namens TestDoEasy04.mqh (überprüfen Sie die OnTimer- und OnChartEvent-Ereignishandler im MQL Wizard beim Erstellen des EA):


Nachdem Sie die EA-Vorlage mit dem MQL-Assistenten erstellt haben, fügen Sie die Nutzerdefinierte Bibliothek und die Handelsklasse der Standardbibliothek hinzu. Fügen Sie auch die Eingabeparameter hinzu:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>  
//--- Enumerationen
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- Strukturen
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- Eingabeparameter
input ulong    InpMagic       =  123;  // Magicnummer
input double   InpLots        =  0.1;  // Losgröße
input uint     InpStopLoss    =  50;   // StopLoss in Punkten
input uint     InpTakeProfit  =  50;   // TakeProfit in Punkten
input uint     InpDistance    =  50;   // Abstand der Pending-Orders (Punkte)
input uint     InpDistanceSL  =  50;   // Abstand von StopLimit-Orders (Punkte)
input uint     InpSlippage    =  0;    // Slippage in Punkten
input double   InpWithdrawal  =  10;   // Abbuchung von Geldern (im Tester)
//--- Globale Variablen
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+

Hier binden wir das Hauptobjekt der CEngine Bibliothek und die CTrade Handelsklasse ein.
Erstellen wir anschließend die Enumeration unter Angabe aller erforderlichen Schaltflächen.
Die Reihenfolge der Methoden in der Enumeration ist wichtig, da sie die Reihenfolge der Tastenerstellung und deren Position in einem Diagramm festlegt.

Danach deklarieren wir die Struktur zur Speicherung des Namens eines Button-Grafikobjekts und eines Textes, der auf eine Schaltfläche geschrieben werden soll.
Wir setzen im Block der Eingabeparameter alle oben aufgeführten EA-Parametervariablen. Und wir deklarieren im globale Variablenblock des EAs das Bibliotheksobjekt, Handelsklassenobjekt, Knopfstrukturen Array und die Variablen, denen die Eingabewerte im OnInit()Handler zuzuordnen sind:

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Prüfen des Kontotyps
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- Setzen der globalen Variablen
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- Erstellen der Schaltflächen
   if(!CreateButtons())  
      return INIT_FAILED;
//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Überprüfen Sie in der Funktion OnInit() die Kontoart und informieren Sie, wenn es sich nicht um ein Hedging-Konto handelt, und beenden Sie das Programm mit einem Fehler.
Als nächstes setzen Sie das Präfix der Objektnamen (damit das EA seine Objekte erkennen kann) und füllen Sie das Array der Strukturen mit den Tasterdaten in einer Schleife um die Anzahl der Schaltflächen.
Der Objektname der Schaltfläche wird als Präfix+Zeichenkette der Enumeration ENUM_BUTTONS entsprechend dem Schleifenindex gesetzt, während der Text der Schaltfläche durch Transformation der Zeichenkette der Enumeration entsprechend dem Schleifenindex mit der Funktion EnumToButtText() kompiliert wird.

Die Losgröße der geöffneten Positionen und platzierten Aufträge wird im Folgenden berechnet. Da die Hälfte der Positionen geschlossen ist, sollte die Losgröße einer geöffneten Position mindestens doppelt so hoch sein wie die minimale Losgröße. Daher wird die maximale Losgröße als einer von zwei Werten genommen:
1) aus den Eingabeparametern, 2) minimale Losgröße multipliziert mit zwei in der Zeile fmax(InpLots,MinimumLots(Symbol())*2.0), wird der Wert der erhaltenen Losgröße normiert und der globalen Variable lot zugewiesen. Wenn also das vom Benutzer in die Eingaben eingegebene Losgröße kleiner als das doppelte Mindestlos ist, wird das doppelte Mindestlos verwendet. Andernfalls wird das von einem Benutzer eingegebene Los verwendet.

Die restlichen Eingabeparameter werden den entsprechenden globalen Variablen zugewiesen und die Funktion CreateButtons() wird zum Erstellen von Schaltflächen aus dem Struktur-Array aufgerufen, das bereits im vorherigen Schritt mit Schaltflächendaten gefüllt wurde. Wenn das Erstellen der Schaltfläche mit der Funktion ButtonCreate() fehlgeschlagen ist, wird die Fehlermeldung angezeigt und das Programm endet mit einem Initialisierungsfehler.

Abschließend wird die Klasse CTrade initialisiert:

//---
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_ERRORS);
//---

Implementieren wir in der Funktion OnDeinit() das Entfernen aller Schaltflächen mit dem Objektnamen-Präfix:

//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Lösche Objekte
   ObjectsDeleteAll(0,prefix);
  }
//+------------------------------------------------------------------+

Nun entscheiden wir uns über den Timer der Bibliothek und die Startreihenfolge der Ereignisbehandlung des EAs.

Die Funktionen des Eads von OnTick(), OnTimer() und OnChartEvent():

//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| Funktion der Chart-Events                                        |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }  
  }
//+------------------------------------------------------------------+

In OnTick()

  • Überprüfen wir, wo das EA gestartet wird. Wenn er im Tester gestartet wird, rufen wir die Funktion OnTimer() Bibliothek auf.
  • Als nächstes überprüfen wir den Objektnamen über alle aktuellen Chartobjekte in der Schleife. Wenn er mit dem Namen einer Schaltfläche übereinstimmt, wird die Ereignisbehandlung der geklickten Schaltfläche aufgerufen.

In OnTimer()

In OnChartEvent()

Die Funktion CreateButtons():

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

Bei dieser Funktion kommt es darauf an, die Koordinaten und Farben einer Schaltfläche in einer Schleife aus der Anzahl der Elemente der Enumeration ENUM_BUTTONS zu berechnen. Die Koordinaten und die Farbe werden basierend auf dem Schleifenindex berechnet, der die Nummer der Enumeration ENUM_BUTTONS angibt. Nach der Berechnung der x- und y-Koordinaten wird die Erstellungsfunktion der Schaltfläche mit in der Schleife berechneten Koordinaten und Farbwerten aufgerufen.

Die Funktion EnumToButtText()

//+------------------------------------------------------------------+
//| Konvertieren der Enumeration in den Text der Schaltflächen       |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   return txt;
  }
//+------------------------------------------------------------------+

Hier ist alles einfach: Die Funktion erhält das Element der Enumeration und wandelt es in eine Zeichenkette um, die den überflüssigen Text entfernt. Als Nächstes werden alle Zeichen der erhaltenen Zeichenkette in Kleinbuchstaben umgewandelt und alle ungeeigneten Einträge durch benötigten ersetzt.
Die Zeichenkette der übergebenen Enumeration, die in den Text umgewandelt wurde, wird schließlich zurückgegeben.

Die Funktionen zum Erstellen der Schaltfläche, zum Platzieren und Empfangen ihres Status:

//+------------------------------------------------------------------+
//| Erstellen der Schaltflächen                                      |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Rückgabe des Status der Schaltflächen                            |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Setzen des Status' der Schaltflächen                             |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+

Alles ist hier einfach unklar, es breiúcht keine weiteren Erklärungen.

Die Funktion behandlet die Klicks auf die Schaltflächen:

//+------------------------------------------------------------------+
//| Bearbeiten der 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
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_BUY_LIMIT geklickt wurde: Platzieren von BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Abrufen 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
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_BUY_STOP geklickt wurde: Platzieren von BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Abrufen des Preises der korrekten Order-Platzierung relativ zu StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,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_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Setzen einer BuyStop-Order
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_BUY_STOP_LIMIT geklickt wurde: Platzieren von BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Abrufen der korrekten BuyStop-Order relativ zu StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Berechnen des Preises der BuyLimit-Order relativ zu BuyStop unter Berücksichtigung des StopLevels
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- 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_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Setzen von BuyStopLimit-Order
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Wenn die Schaltfläche BUTT_SELL geklickt wurde: Eröffnen einer Verkaufsposition
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zu StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Eröffnen einer Verkaufsposition
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_SELL_LIMIT geklickt wurde: Setzen von SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Abrufen des Auftragspreises relativ zu StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_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_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Setzen von SellLimit-Order
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_SELL_STOP geklickt wurde: Platzieren von SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Abrufen der korrekten Order-Platzierung relativ zu StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,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_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Setzen von SellStop-Order
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_SELL_STOP_LIMIT geklickt wurde: Platzieren von SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Abrufen des Preises von SellStop relativ zu StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Berechnen des Preises der SellLimit-Order relativ zu SellStop unter Berücksichtigung des StopLevels
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- 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_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Setzen der SellStopLimit-Order
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_BUY geklickt wurde: Schließen einer Kaufposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Abrufen der Ticketnummer der Kaufposition und Schließen der Position mittels der Ticketnummer
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_BUY2 geklickt wurde: Schließen der Hälfte der Kaufposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Berechnen des zu schließenden Volumens und schließen der Hälfte der Kaufposition mittels der Ticketnummer
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_BUY_BY_SELL geklickt wurde: Schließen einer Kaufposition mit Maximalgewinn durch einen entgegengesetzten Verkauf
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Auswählen von Verkaufspositionen nur aus der Liste
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- Auswählen der Kaufposition mit Maximalgewinn
            COrder* position_buy=list_buy.At(index_buy);
            //--- Auswählen der Verkaufsposition mit Maximalgewinn
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- Schließen der Kaufposition durch eine entgegengesetzte Verkaufsposition
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_SELL geklickt wurde: Schließen einer Verkaufsposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Verkaufspositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Abrufen der Ticketnummer der Verkaufsposition und Schließen der Position mittels der Ticketnummer
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_SELL2 geklickt wurde: Schließen der Hälfte der Verkaufsposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Verkaufspositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Berechnen des zu schließenden Volumens und schließen der Hälfte der Verkaufsposition mittels der Ticketnummer
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_SELL_BY_BUY geklickt wurde: Schließen einer Verkaufsposition mit Maximalgewinn durch einen entgegengesetzten Kauf  
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Abrufen von nur Verkaufspositionen aus der Liste
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Auswählen der Verkaufsposition mit Maximalgewinn
            COrder* position_sell=list_sell.At(index_sell);
            //--- Auswählen der Kaufposition mit Maximalgewinn
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Schließen einer Verkaufsposition mit einer entgegengesetzten Kaufposition
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_ALL geklickt wurde: Schließen aller Positionen beginnend mit dem kleinsten Gewinn
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Erhalt der Liste mit allen offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- In der Schleife aller Positionen mit dem geringsten Gewinn
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- Schließen jeder Position mittels der Ticketnummer
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_PROFIT_WITHDRAWAL geklickt wurde: Gelder vom Konto abbuchen
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- Wenn das Programm im Tester gestartet wurde
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Emulieren eine Kontoabbuchung
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Warten für 1/10 einer Sekunde
      Sleep(100);
      //--- Klicken der Schaltfläche rückgängig machen und Neuzeichnen des Charts
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Die Funktion ist recht groß, aber einfach: Sie erhält den Namen des Objekts der zu konvertierenden Schaltfläche in eine Zeichenketten-ID. Der Status der Schaltfläche wird als nächstes überprüft, und wenn er auf gedrückt steht, wird die Zeichenketten-ID überprüft. Der entsprechende if-else-Zweig wird ausgeführt, indem alle Ebenen berechnet und eine notwendige Anpassung vorgenommen wird, um die StopLevel-Begrenzung nicht zu verletzen. Die entsprechende Methode der Handelsklasse wird ausgeführt.
Alle Erklärungen werden direkt als Kommentare im Code geschrieben.

Für den Test-EA haben wir die minimal notwendigen Kontrollen durchgeführt, wobei alle anderen für ein Echtgeldkonto wichtigen Prüfungen übersprungen wurden. Derzeit ist das Wichtigste für uns, den Bibliotheksverwendung zu überprüfen, anstatt einen EA zu entwickeln, der mit realen Konten arbeitet.

Der vollständige Code des Tests-EAs:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart04.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>
//--- Enumerationen
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (16)
//--- Strukturen
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- Eingabeparameter
input ulong    InpMagic       =  123;  // Magicnummer
input double   InpLots        =  0.1;  // Losgröße
input uint     InpStopLoss    =  50;   // StopLoss in Punkten
input uint     InpTakeProfit  =  50;   // TakeProfit in Punkten
input uint     InpDistance    =  50;   // Abstand der Pending-Orders (Punkte)
input uint     InpDistanceSL  =  50;   // Abstand von StopLimit-Orders (Punkte)
input uint     InpSlippage    =  0;    // Slippage in Punkten
input double   InpWithdrawal  =  10;   // Abbuchung von Geldern (im Tester)
//--- Globale Variablen
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Prüfen des Kontotyps
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- Setzen der globalen Variablen
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- Erstellen der Schaltflächen
   if(!CreateButtons())
      return INIT_FAILED;
//--- Setzender Handelsparameter
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Lösche Objekte
   ObjectsDeleteAll(0,prefix);
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
   if(engine.TradeEventCode()!=TRADE_EVENT_FLAG_NO_EVENT)
     {
      
      Print(DFUN,EnumToString((ENUM_TRADE_EVENT_FLAGS)engine.TradeEventCode()));
     }
   engine.TradeEventCode();
  }
//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| Funktion der Chart-Events                                        |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }  
  }
//+------------------------------------------------------------------+
//| Erstellen des Panels mit Schaltflächen                           |
//+------------------------------------------------------------------+
bool CreateButtons(void)
  {
   int h=18,w=84,offset=10;
   int cx=offset,cy=offset+(h+1)*(TOTAL_BUTT/2)+h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 : 0);
      if(i==TOTAL_BUTT-2) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-2 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| Erstellen der Schaltflächen                                      |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Rückgabe des Status der Schaltflächen                            |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Setzen des Status' der Schaltflächen                             |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+
//| Transformieren der Enumeration in den Text der Schaltflächen     |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   return txt;
  }
//+------------------------------------------------------------------+
//| 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
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- 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
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_BUY_STOP geklickt wurde: Platzieren von BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Abrufen des Preises der korrekten Order-Platzierung relativ zu StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,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_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- Setzen einer BuyStop-Order
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_BUY_STOP_LIMIT geklickt wurde: Platzieren von BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Abrufen des Preises von BuyStop relativ zu StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- Berechnen des Preises der BuyLimit-Order relativ zu BuyStop unter Berücksichtigung des StopLevels
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- 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_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- Setzen von BuyStopLimit-Order
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Wenn die Schaltfläche BUTT_SELL geklickt wurde: Eröffnen einer Verkaufsposition
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zu StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- Eröffnen einer Verkaufsposition
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_SELL_LIMIT geklickt wurde: Setzen von SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Abrufen des Preises der korrekten Order-Platzierung relativ zu StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_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_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- Setzen von SellLimit-Order
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_SELL_STOP geklickt wurde: Platzieren von SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Abrufen des Preises der korrekten Order-Platzierung relativ zu StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,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_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- Setzen von SellStop-Order
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- Falls die Schaltfläche BUTT_SELL_STOP_LIMIT geklickt wurde: Platzieren von SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Abrufen des Preises von SellStop relativ zu StopLevel
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- Berechnen des Preises der SellLimit-Order relativ zu SellStop unter Berücksichtigung des StopLevels
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- 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_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- Setzen der SellStopLimit-Order
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_BUY geklickt wurde: Schließen einer Kaufposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Abrufen der Ticketnummer der Kaufposition und Schließen der Position mittels der Ticketnummer
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_BUY2 geklickt wurde: Schließen der Hälfte der Kaufposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Berechnen des zu schließenden Volumens und schließen der Hälfte der Kaufposition mittels der Ticketnummer
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_BUY_BY_SELL geklickt wurde: Schließen einer Kaufposition mit Maximalgewinn durch einen entgegengesetzten Verkauf
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Auswählen von nur Verkaufspositionen aus der Liste
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- Auswählen der Kaufposition mit Maximalgewinn
            COrder* position_buy=list_buy.At(index_buy);
            //--- Auswählen der Verkaufsposition mit Maximalgewinn
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- Schließen der Kaufposition durch eine entgegengesetzte Verkaufsposition
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_SELL geklickt wurde: Schließen einer Verkaufsposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Verkaufspositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Abrufen der Ticketnummer der Verkaufsposition und Schließen der Position mittels der Ticketnummer
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_SELL2 geklickt wurde: Schließen der Hälfte der Verkaufsposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         //--- Auswählen von nur Verkaufspositionen aus der Liste
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- Berechnen des zu schließenden Volumens und schließen der Hälfte der Verkaufsposition mittels der Ticketnummer
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.VolumeCurrent()/2.0));
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_SELL_BY_BUY geklickt wurde: Schließen einer Verkaufsposition mit Maximalgewinn durch einen entgegengesetzten Kauf
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- Auswählen von nur Verkaufspositionen aus der Liste
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Verkaufsposition mit Maximalgewinn
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- Auswählen von nur Kaufpositionen aus der Liste
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- Abrufen des Index der Kaufposition mit Maximalgewinn
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- Auswählen der Verkaufsposition mit Maximalgewinn
            COrder* position_sell=list_sell.At(index_sell);
            //--- Auswählen der Kaufposition mit Maximalgewinn
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- Schließen einer Verkaufsposition mit einer entgegengesetzten Kaufposition
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_ALL geklickt wurde: Schließen aller Positionen beginnend mit dem kleinsten Gewinn
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- Abrufen der Liste aller offenen Positionen
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- In der Schleife aller Positionen mit dem geringsten Gewinn
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- Schließen jeder Position mittels der Ticketnummer
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- Wenn die Schaltfläche BUTT_PROFIT_WITHDRAWAL geklickt wurde: Gelder vom Konto abbuchen
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- Wenn das Programm im Tester gestartet wurde
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- Emulieren eine Kontoabbuchung
            TesterWithdrawal(withdrawal);
           }
        }
      //--- Warten für 1/10 einer Sekunde
      Sleep(100);
      //--- Klicken der Schaltfläche rückgängig machen und Neuzeichnen des Charts
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

Der Code sieht ziemlich lang aus... Dies ist jedoch nur zu Anfang so, da alles am Ende für den Nutzer vereinfacht werden soll. Die meisten Aktionen, die wir hier durchführen, sollen im Bibliothekscode verborgen sein, während der benutzerfreundlichere Mechanismus der Interaktion mit der Bibliothek eingeführt werden soll.

Lassen Sie uns den EA im Tester starten und die Schaltflächen ausprobieren:

Alles ist korrekt aktiviert, und das Journal erhält Meldungen über die auftretenden Ereignisse.

Derzeit ist das allerletzte Ereignis immer fixiert. Mit anderen Worten, wenn wir mehrere Positionen gleichzeitig schließen, befindet sich nur die letzte von allen geschlossenen Positionen im Ereignis. Das Schließen von allem kann anhand der Anzahl der neuen Geschäfte oder Aufträge in der Geschichte verfolgt werden. Es ist dann möglich, die Liste aller neu geschlossenen Positionen nach ihrer Anzahl zu erhalten und ihre gesamte Gruppe zu definieren. Lassen Sie uns dafür eine eigene Event-Klasse einer Collection entwickeln. Das ermöglicht es uns, einen ständigen Zugriff auf alle aufgetretenen Ereignisse im Programm.

Derzeit werden alle Ereignismeldungen im Testerjournal in der Methode CEngine::WorkWithHedgeCollections() des Basisobjekts der Bibliothek angezeigt, und wir benötigen das benutzerdefinierte Programm, um die Ereigniscodes zu kennen, um zu "verstehen", was auf dem Konto passiert ist. Dies ermöglicht es uns, die Antwortlogik des Programms in Abhängigkeit von einem Ereignis zu bilden. Um die Fähigkeit zu testen, dies zu erreichen, werden wir zwei Methoden im Basisobjekt der Bibliothek erstellen. Eine Methode besteht darin, den Code des letzten Ereignisses zu speichern, während eine andere darin besteht, diesen Code zu dekodieren, der aus einem Satz von Ereignisflags besteht.
Im nächsten Artikel werden wir eine vollwertige Klasse für die Arbeit mit Kontoereignissen erstellen.

Definieren wir im Körper der Klasse CEngine die Methode, die eine Ereigniscode dekodiert und den Handelsereigniscode eines Kontos setzt, die Methode zur Überprüfung des Vorhandenseins eines Ereignisflags im Ereigniscode, die Methode zum Empfangen des letzten Handelsereignisses vom aufrufenden Programm sowie die Methode Rücksetzen des Wertes des letzten Handelsereignisses:

//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection der historischen Aufträge und Deals
   CMarketCollection    m_market;                        // Collection der Marktorder und Deals
   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_market_trade_event;         // Flag eines Handelsereignisses des Kontos
   bool                 m_is_history_trade_event;        // Flag eines historischen Handelsereignisses auf dem Konto
   int                  m_trade_event_code;              // Status des Ereignisses auf dem Handelskonto
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Handelsereignis auf dem Konto
//--- Dekodieren des Ereigniscodes und setzen des Handelsereignisses auf dem Konto
   void                 SetTradeEvent(void);
//--- Rückgabe des Zählerindex über die ID
   int                  CounterIndex(const int id) const;
//--- Rückgabe (1) des Flag des Erststarts, (2) der Existenz des Flags eines Handelsereignisses
   bool                 IsFirstStart(void);
   bool                 IsTradeEventFlag(const int event_code)    const { return (this.m_trade_event_code&event_code)==event_code;  }
//--- Arbeit mit Collectionen für (1) Hedging, (2) Netting
   void                 WorkWithHedgeCollections(void);
   void                 WorkWithNettoCollections(void);
//--- Rückgabe der letzten (1) Pending-Order, (2) Marktorder, (3) letzten Position, (4) Position nach Ticket
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- Rückgabe der letzten (1) gelöschten Pending-Order, (2) historischen Marktorder, (3) historischen Marktorder nach Ticket
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- Rückgabe des (1) ersten und (2) des letzten historischen Marktorder aus der Liste aller Positionen, (3) des letzten Deals
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
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*           GetListHistoryDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Rückgabe des letzten Handelsereignisses
   void                 ResetLastTradeEvent(void)                       { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;  }
//--- Rückgabe des (1) letzten Handelsereignisses, (2) Codes des Handelsereignisses, (3) des Flags des Hedging-Kontos
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;                }
   int                  TradeEventCode(void)                      const { return this.m_trade_event_code;               }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;                       }
//--- Erstellen der Timerzählers
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/Destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

Jenseits des Klassenkörpers schreiben wir eine Methode zur Dekodierung eines Handelsereignisses (sie schreibt die Klarnamen direkt in den Code):

//+------------------------------------------------------------------+
//| Dekodieren des Ereigniscodes und setzen des Handelsereignisses   |
//+------------------------------------------------------------------+
void CEngine::SetTradeEvent(void)
  {
//--- Kein Handelsereignis. Beenden
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_NO_EVENT)
      return;
//--- Pending-Order wurde gesetzt (Prüfen, ob der Ereigniscode passt, da es hier nur ein Flag geben kann)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- Pending-Order wurde entfernt (Prüfen, ob der Ereigniscode passt, da es hier nur ein Flag geben kann)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_acc_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- Positionseröffnung (Prüfen mehrerer Flags im Ereigniscode)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- Wenn die Pending-Order durch den Preis aktiviert wurde
      if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
//--- Position wurde geschlossen (Prüfen mehrerer Flags im Ereigniscode)
   if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- Wenn die Position durch StopLoss geschlossen wurde
      if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- Prüfen des Flags für ein teilweises Schließen und setzen des Handelsereignisses von "Position geschlossen durch StopLoss" oder "Position teilweise geschlossen durch StopLoss"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- Wenn die Position durch TakeProfit geschlossen wurde
      else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- Prüfen des Flags für ein teilweises Schließen und setzen des Handelsereignisses von "Position geschlossen durch TakeProfit" oder "Position teilweise geschlossen durch TakeProfit"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- Wenn die Position durch eine Gegenposition geschlossen wurde
      else if(this.IsTradeEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- Prüfen des Flags für ein teilweises Schließen und setzen des Handelsereignisses von "Position geschlossen durch eine Gegenposition" oder "Position teilweise geschlossen durch eine Gegenposition"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
      //--- Wenn die Position geschlossen wurde
      else
        {
         //--- Prüfen des Flags für ein teilweises Schließen und setzen des Handelsereignisses von "Position geschlossen" oder "Position teilweise geschlossen"
         this.m_acc_trade_event=(!this.IsTradeEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
         return;
        }
     }
//--- Saldobuchung auf dem Konto (Klärung des Ereignisses durch den Dealtyp)
   if(this.m_trade_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- Initialisierung des Handelsereignisses
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      //--- Nehmen des letzten Deals
      COrder* deal=this.GetLastDeal();
      if(deal!=NULL)
        {
         ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)deal.GetProperty(ORDER_PROP_TYPE);
         //--- Wenn der Deal eine Saldobuchung ist
         if(deal_type==DEAL_TYPE_BALANCE)
           {
           //--- Prüfen des Deals-Gewinns und setzen des Ereignisses (Gelder zu- oder abbuchen)
            this.m_acc_trade_event=(deal.Profit()>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
           }
         //--- Buchungstyp des verbliebenen Saldos passt zur Enumeration ENUM_DEAL_TYPE beginnend mit DEAL_TYPE_CREDIT
         else if(deal_type>DEAL_TYPE_BALANCE)
           {
           //--- Setzen des Ereignisses
            this.m_acc_trade_event=(ENUM_TRADE_EVENT)deal_type;
           }
        }
      Print(DFUN,"Code=",this.m_trade_event_code,", m_acc_trade_event=",EnumToString(m_acc_trade_event));
      return;
     }
  }
//+------------------------------------------------------------------+

Fügen wir den Aufruf der Verschlüsselungsmethode für Handelsereignisse hinzu und entfernen die Anzeige von Ereignisbeschreibungen im Journal am Ende der Methode WorkWithHedgeCollections(), die die Überprüfung und Erstellung eines Handelsereignisses durchführt:

//+------------------------------------------------------------------+
//| Prüfen des Handelsereignisses (Hedging)                          |
//+------------------------------------------------------------------+
void CEngine::WorkWithHedgeCollections(void)
  {
//--- Initialisieren des Codes und der Flags des Handelsereignisses
   this.m_trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   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 des Marktstatus und der Änderungen der Kontohistorie
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();
//---
#ifdef __MQL4__

#else // MQL5
//--- Wenn ein Ereignis sich nur auf Marktorder und Positionen bezieht
   if(this.m_is_market_trade_event && !this.m_is_history_trade_event)
     {
      //--- Bei einer erhöhten Anzahl der Pending-Order
      if(this.m_market.NewPendingOrders()>0)
        {
         //--- Hinzufügen des Flags für die Pending-Order
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_PLASED;
        }
      //--- Bei erhöhter Zahl von Marktorders
      if(this.m_market.NewMarketOrders()>0)
        {
         //--- kein Ereignisflag hinzufügen
         //--- ...
        }
     }
   
//--- Wenn ein Ereignis nur die historischen Aufträge und Deals betrifft
   else if(this.m_is_history_trade_event && !this.m_is_market_trade_event)
     {
      //--- Wenn ein neuer Deal erscheint
      if(this.m_history.NewDeals()>0)
        {
         //--- Hinzufügen des Ereignisflags eines Kontensaldos
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
        }
     }
   
//--- Wenn Ereignisse sich auf Marktorders, historische Aufträge und Positionen beziehen
   else if(this.m_is_market_trade_event && this.m_is_history_trade_event)
     {
      //--- Bei erhöhter Anzahl der Pending-Orders und es keine neuen Deals aufgetreten sind
      if(this.m_market.NewPendingOrders()<0 && this.m_history.NewDeals()==0)
        {
         //--- Hinzufügen des Flags für das Entfernen einer Pending-Order
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_REMOVED;
        }
      
      //--- Wenn es einen neuen Deal und einen neuen historischen Auftrag gibt
      if(this.m_history.NewDeals()>0 && this.m_history.NewOrders()>0)
        {
         //--- Nehmen des letzten Deals
         COrder* deal=this.GetLastDeal();
         if(deal!=NULL)
           {
            //--- Im Falle eines Deals eines Markteintritts
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
              {
               //--- Hinzufügen des Flags einer Positionseröffnung
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_OPENED;
               //--- Bei verminderter Anzahl der Pending-Orders
               if(this.m_market.NewPendingOrders()<0)
                 {
                  //--- Hinzufügen des Flags für das Aktivieren einer Pending-Order
                  this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                 }
               //--- Nehmen der Ticketnummer des Auftrags
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Wenn das Volumen des aktuellen Auftrags größer Null ist
                  if(order.VolumeCurrent()>0)
                    {
                     //--- Hinzufügen des Flags einer teilweisen Ausführung
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
            
            //--- Im Falle eines Deals des Verlassens des Marktes
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
              {
               //--- Hinzufügen der Flag über das Schließen einer Position
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Nehmen der Ticketnummer des Deals
               ulong order_ticket=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(order_ticket);
               if(order!=NULL)
                 {
                  //--- Wenn die Position des Deals noch immer im Markt existiert
                  COrder* pos=this.GetPosition(deal.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Hinzufügen des Flags einer teilweisen Ausführung
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                  //--- Andernfalls, wenn die Position komplett geschlossen ist
                  else
                    {
                     //--- Wenn der Auftrag das Flag eines Stop-Loss hat
                     if(order.IsCloseByStopLoss())
                       {
                        //--- Hinzufügen des Flags für ein Schließen durch Stop-Loss
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
                       }
                     //--- Wenn der Auftrag das Flag eines Take-Profit hat
                     if(order.IsCloseByTakeProfit())
                       {
                        //--- Hinzufügen des Flags für ein Schließen durch Take-Profit
                        this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
                       }
                    }
                 }
              }
            
            //--- Wenn durch Gegenposition geschlossen
            if(deal.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
              {
               //--- Hinzufügen der Flag über das Schließen einer Position
               this.m_trade_event_code+=TRADE_EVENT_FLAG_POSITION_CLOSED;
               //--- Hinzufügen des Flags beim Schließen durch eine Gegenposition
               this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               //--- Nehmen des Auftrags des Deals
               ulong ticket_from=deal.GetProperty(ORDER_PROP_DEAL_ORDER);
               COrder* order=this.GetHistoryOrder(ticket_from);
               if(order!=NULL)
                 {
                  //--- Wenn die Position noch immer im Markt existiert
                  COrder* pos=this.GetPosition(order.PositionID());
                  if(pos!=NULL)
                    {
                     //--- Hinzufügen des Flags einer teilweisen Ausführung
                     this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                    }
                 }
              }
           //--- Ende des Blocks der Bearbeitung des letzten Deals
           }
        }
     }
#endif 
   this.SetTradeEvent();
  }
//+------------------------------------------------------------------+

Nachdem wir also den Ereigniscode in der Methode WorkWithHedgeCollections() erstellt haben, rufen wir die Methode zur Decodierung des Ereignisses auf. Was hält uns davon ab, das sofort zu entschlüsseln? Der Punkt ist, dass unsere aktuelle Dekodierungsmethode temporär ist. Er wird benötigt, um den Dekodierungsprozess zu überprüfen. In den kommenden Artikeln soll eine vollwertige Klasse der Handelsereignisse geschaffen werden.
Die Methode SetTradeEvent() definiert das Handelsereignis, schreibt seinen Wert in die Klassenvariable m_acc_trade_event, während die Methoden LastTradeEvent() und ResetLastTradeEvent() das Lesen des Wertes der Variablen als letztes Handelsereignis auf dem Konto und deren Zurücksetzen (ähnlich GetLastError()) im aufrufenden Programm ermöglichen.

In der Funktion OnTick() des Test-EAs von TestDoEasyPart04.mqh fügen wir die Zeilen hinzu, um das letzte Handelsereignis zu lesen und wie im Chart-Kommentar anzuzeigen:

//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
   if(MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
   int total=ObjectsTotal(0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
   if(engine.LastTradeEvent()!=last_event)                                  
     {                                                                      
      Comment("\nLast trade event: ",EnumToString(engine.LastTradeEvent()));
      last_event=engine.LastTradeEvent();                                   
     }                                                                      
  }
//+------------------------------------------------------------------+

Wenn Sie nun dieses EA im Tester ausführen und auf die Schaltflächen klicken, werden die laufenden Handelsereignisse der Methode CEngine::SetTradeEvent() im Journal angezeigt, während der Chart-Kommentar die Beschreibung des letzten Ereignisses auf dem Konto anzeigt, das der EA von der Bibliothek mit der Methode engine.LastTradeEvent() empfangen hat:


Was kommt als Nächstes?

Im nächsten Artikel werden wir Klassen von Ereignisobjekten und die Collection von Ereignisobjekten ähnlich den Collectionen von Aufträgen und Positionen entwickeln und das grundlegende Bibliotheksobjekt lehren, Ereignisse an das Programm zu senden.

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
Teil 2
Teil 3