English Русский 中文 Español 日本語 Português
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil III). Erhebung (Collection) von Marktorders und Positionen

Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil III). Erhebung (Collection) von Marktorders und Positionen

MetaTrader 5Beispiele | 13 Mai 2019, 08:57
852 4
Artyom Trishkin
Artyom Trishkin

Inhalt

Einrichten der Suche
Das Basisobjekt Engine ist der Bibliothekskern
Objekte aktiver Marktorders und Positionen
Erfassung aktiver Marktorders und Positionen
Was kommt als nächstes

Im ersten Teil der Artikelserie haben wir begonnen, eine große plattformübergreifende Bibliothek zu erstellen, die die Entwicklung von Programmen für die Plattformen MetaTrader 5 und MetaTrader 4 vereinfacht.
Im zweiten Teil haben wir die Entwicklung der Bibliothek wieder aufgenommen und die Collection (Sammlung) historischer Aufträge und Deals (Transaktionen) implementiert.


Hier werden wir eine Klasse für eine komfortable Auswahl und Sortierung von Orders, Deals und Positionen in der Collection erstellen, das Basisobjekt Engine der Bibliothek implementieren und die Collection von Marktorders und Positionen zur Bibliothek hinzufügen.

Im Moment entsteht bereits eine bestimmte Datenspeicherstruktur. Wir werden uns daran halten, wenn wir Collections verschiedener Objekttypen erstellen:


Ein einziges Engine-Objekt wird für das Speicheren und Verwalten von Collections sowie für den Datenaustausch zwischen dem Programm und der Bibliothek erstellt. Engine soll zum Basisobjekt der gesamten Bibliothek werden. Programme auf Basis der Bibliothek sollen sich darauf beziehen, um Daten abzurufen. Dabei soll die gesamte Bibliotheksautomatisierung zusammengeführt werden.

Einrichten der Suche

Um die Daten aus den Bibliotheksbeständen einfach und bequem zu nutzen, werden wir auf Wunsch eine komfortable Datensuche, -sortierung und -anzeige implementieren. Um dies zu erreichen, erstellen wir eine spezielle Klasse und benennen sie CSelect.
Alle Datenanforderungen müssen sie durchlaufen.

Wir erstellen im Bibliotheksordner Collections die neue Klasse CSelect. Es ist nicht notwendig, die Basisklasse einzustellen. Nach Abschluss des MQL-Wizards wird die neue Datei Select.mqh im Ordner Collections erstellt:

//+------------------------------------------------------------------+
//|                                                       Select.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"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSelect
  {
private:

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

Um die Suche durchzuführen, stellen wir alle ihre Modi ein. Dazu erstellen wir die Enumeration, die die Objektvergleichsmodi während der Suche beschreibt. Die Enumeration ist in der Datei Defines.mqh zu erstellen:

//+------------------------------------------------------------------+
//|                                                      Defines.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"
//+------------------------------------------------------------------+
//| Macro-Substitution                                               |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // Landessprache
#define DFUN           (__FUNCTION__+": ")      // "Funktionsberechnung"
#define END_TIME       (D'31.12.3000 23:59:59') // Final data for requesting account history data
//+------------------------------------------------------------------+
//| Suche                                                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Datensuche                                                       |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // Gleich
   MORE,                                                    // Größer
   LESS,                                                    // Kleiner
   NO_EQUAL,                                                // Ungleich
   EQUAL_OR_MORE,                                           // Größer oder gleich
   EQUAL_OR_LESS                                            // Kleiner oder gleich
  };
//+------------------------------------------------------------------+

Verbinden Sie in der Klasse CSelect aus der Standardbibliothek die Klasse der Liste der dynamischen Zeiger mit Objektinstanzen, Klasse und der Bibliothek der DELib.mqh-Servicefunktionen (zusammen mit der Defines.mqh-Datei). Deklarieren Sie außerdem ein spezielles Speicherobjekt, das der gesamten Bibliothek auf globaler Ebene zur Verfügung steht. Es soll die Kopien der beim Sortieren erstellten Listen speichern. Wenn neu erstellte Listen nicht an ein Speicherobjekt angehängt sind, sollten sie gelöscht werden, nachdem der Bedarf an ihnen verschwunden ist. Das bedeutet, dass wir zusätzliche Ressourcen für die Kontrolle von wo, wann und warum wir eine bestimmte Liste benötigten, die einen Verlust einer Logikkette und Speicherlecks aufgrund nicht gelöschter Objekte verursachen kann. Wenn wir sie an die Liste anhängen, wird die Verfolgung durch das Terminal-Subsystem durchgeführt, das immer sowohl die Speicherliste als auch deren Inhalt rechtzeitig löscht.

Um zu vergleichen, müssen wir eine solche Methode erstellen. Lassen Sie uns eine statische Template-Methode zum Vergleichen von zwei Werten deklarieren.
//+------------------------------------------------------------------+
//|                                                       Select.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 <Arrays\ArrayObj.mqh>
#include "..\Objects\Order.mqh"
//+------------------------------------------------------------------+
//| Speicherliste                                                    |
//+------------------------------------------------------------------+
CArrayObj   ListStorage; // Objekt zum Speichern der sortierten Collection
//+------------------------------------------------------------------+
//| Klasse zum Sortieren der Objekte, die dem Kriterium entsprechen  |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //| Vergleichsmethode von zwei Werten
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:

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

Implementation der Vergleichsmethode:

//+------------------------------------------------------------------+
//| Vergleichsmethode von zwei Werten                                |
//+------------------------------------------------------------------+
template<typename T>
bool CSelect::CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode)
  {
   return
     (
      mode==EQUAL && value1==value2          ?  true  :
      mode==NO_EQUAL && value1!=value2       ?  true  :
      mode==MORE && value1>value2            ?  true  :
      mode==LESS && value1<value2            ?  true  :
      mode==EQUAL_OR_MORE && value1>=value2  ?  true  :
      mode==EQUAL_OR_LESS && value1<=value2  ?  true  :  false
     );
  }
//+------------------------------------------------------------------+

Zwei Werte gleichen Typs und der Vergleichs-Modus werden der Methode übergeben.
Als Nächstes wird ein einfacher Vergleich in Abhängigkeit von der angewandten Methode durchgeführt (gleich/nicht gleich, größer/kleiner, kleiner/größer oder gleich, größer/kleiner oder gleich) und das Ergebnis zurückgegeben.

Lassen Sie uns nun mehrere Methoden zum Durchsuchen der Liste erstellen. Deklarieren Sie im 'public' Bereich der Klasse CSelect drei statische Methoden, um nach einem bestimmten Kriterium nach einer Bestellung zu suchen:

//+------------------------------------------------------------------+
//| Klasse zum Sortieren der Objekte, die dem Kriterium entsprechen  |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Vergleichsmethode zweier Werte
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- Rückgabe der Auftragsliste mit der (1) Integer-, (2) Double- und (3) String-Eigenschaft, die dem angegebenen Kriterium entspricht
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
  };
//+------------------------------------------------------------------+

Implementieren wir sie sofort außerhalb des Körpers der Klasse:

//+------------------------------------------------------------------+
//| Rückgabe der Auftragsliste mit einer Integer-                    |
//| Eigenschaft, die dem Kriterium entspricht                        |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      long order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der Auftragsliste mit einer Double-                     |
//| Eigenschaft, die dem Kriterium entspricht                        |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      double order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Rückgabe der Auftragsliste mit einer String-                     |
//| Eigenschaft, die dem Kriterium entspricht                        |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      COrder *order=list_source.At(i);
      if(!order.SupportProperty(property)) continue;
      string order_prop=order.GetProperty(property);
      if(CompareValues(order_prop,value,mode)) list.Add(order);
     }
   return list;
  }
//+------------------------------------------------------------------+

Werfen wir beispielsweise einen genaueren Blick auf die Suche nach Zeichenkettenkriterien:

  • Das Verfahren erhält den Zeiger auf die Collection, eine Eigenschaft, auf der die neue Liste basieren soll und den Vergleichsmodus. Die Eigenschaft sollte das Suchkriterium erfüllen.
  • Danach wird die Liste auf Gültigkeit überprüft, ist sie ungültig wird NULL zurückgegeben.
  • Wenn die Prüfung bestanden ist, erstellen Sie ein neues Listenobjekt und setzen Sie dafür das Flag für die manuelle Speicherverwaltung. Wenn dies nicht geschieht, werden beim Löschen dieses Listenobjekts auch alle Verweise auf die in der Collection gespeicherten Auftragsobjekte gelöscht, was inakzeptabel ist. Die Details finden Sie in der Hilfe für dynamische Listen der Pointer, insbesondere diese Methode.
  • Als Nächstes fügen Sie die neu erstellte Liste der Speicherliste hinzu und starten Sie das Sortieren von Objekten in einer Schleife:
    • Liefert einen Auftrag aus der Liste. Wenn es eine bei der Suche angegebene Eigenschaft nicht unterstützt, überspringen Sie sie und wählen Sie die nächste aus.
    • Als nächstes wird die Auftragseigenschaft mitder Eigenschaft verglichen, die zum Vergleich gemäß dem angegebenen Modus (gleich/nicht gleich, größer/kleiner etc.) übergeben wurde. Wenn das Vergleichskriterium erfüllt ist, wird diese Reihenfolge zu einer neu erstellten Liste hinzugefügt.
    • Am Ende der Schleife wird die Liste an das aufrufende Programm zurückgegeben.

Fügen Sie weitere sechs Methoden zum Suchen und Zurückgeben eines Auftragsindex mit dem Wert Maximum und Minimum der angegebenen Eigenschaft hinzu:

//+------------------------------------------------------------------+
//| Klasse zum Sortieren der Objekte, die dem Kriterium entsprechen  |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Vergleichsmethode zweier Werte
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
   //--- Rückgabe der Auftragsliste mit der (1) Integer-, (2) Double- und (3) String-Eigenschaft, die dem angegebenen Kriterium entspricht
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Rückgabe des Auftragsindex mit dem Maximum der (1) Integer-, (2) Double- und (3) String-Eigenschaft
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property); 
   static int        FindOrderMax(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property); 
   //--- Rückgabe des Auftragsindex mit dem Minimum der (1) Integer-, (2) Double- und (3) String-Eigenschaft
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property); 
   static int        FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property); 
  };
//+------------------------------------------------------------------+

und deren Umsetzung:

//+------------------------------------------------------------------+
//| Rückgabe des Index der Liste                                     |
//| mit dem Maximum der Integer-Eigenschaft                          |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);
      long order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);
      long order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Rückgabe des Index der Auftragsliste                             |
//| mit dem Maximum der Double-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);
      double order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);
      double order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Rückgabe des Index der Auftragsliste                             |
//| mit dem Maximum der String-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   COrder *max_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      COrder *order=list_source.At(i);               
      string order1_prop=order.GetProperty(property);
      max_order=list_source.At(index);                   
      string order2_prop=max_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

Lassen Sie uns einen genaueren Blick auf die Suche nach dem Auftragsindex mit dem maximalen Zeichenkettenwert werfen:

  • Das Verfahren erhält den Zeiger auf die Collection und die Eigenschaft zur Suche nach einem Auftrag mit dem Maximalwert dieser Eigenschaft.
  • Danach wird die Liste auf Gültigkeit überprüft, und WRONG_VALUE (-1) wird zurückgegeben, wenn sie ungültig ist.
  • Deklarieren Sie den Auftragsindex mit dem Maximalwert, initialisieren Sie ihn mit Null und erzeugen Sie ein leeres Auftragsobjekt, d.h. speichern Sie Vergleichswerte.
  • Als Nächstes bewegen Sie sich in einer Schleife durch die Collection zum zweiten Auftrag:
    • Gehen Sie den Zielwert mit dem Schleifenindex aus dem Auftrag, Gehen Sie den Zielwert mit dem Index 'index' aus dem Auftrag und Vergleichen Sie die beiden erhaltene Werte. Wenn der Zielwert des ersten Auftrags (mit dem Schleifenindex) denjenigen des zweiten Auftrags (mit dem Index 'index') überschreitet, weisen Sie den Index des Auftrags mit dem größeren Wert der Variablen 'index' zu.
    • Nach dem Ende der Schleife hat die Variable 'index' den Index des Auftrags mit dem höchsten Zielwert. Rückgabe an das aufrufende Programm.

Methoden, die den Ordnungsindex mit dem niedrigsten Wert der angegebenen Eigenschaft zurückgeben, sind ähnlich aufgebaut:

//+------------------------------------------------------------------+
//| Rückgabe des Index der Auftragsliste                             |
//| mit dem Minimum der Integer-Eigenschaft                          |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_INTEGER property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      long order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      long order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+
//| Rückgabe des Index der Auftragsliste                             |
//| mit dem Minimum der Double-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_DOUBLE property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      double order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      double order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+
//| Rückgabe des Index der Auftragsliste                             |
//| mit dem Minimum der String-Eigenschaft                           |
//+------------------------------------------------------------------+
int CSelect::FindOrderMin(CArrayObj* list_source,ENUM_ORDER_PROP_STRING property)
  {
   int index=0;
   COrder* min_order=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++){
      COrder* order=list_source.At(i);
      string order1_prop=order.GetProperty(property);
      min_order=list_source.At(index);
      string order2_prop=min_order.GetProperty(property);
      if(CompareValues(order1_prop,order2_prop,LESS)) index=i;
      }
   return index;
  }
//+------------------------------------------------------------------+

Now we can add sorting the collection list by time and specified criteria to the collection class of historical orders.

Jetzt können wir der Klasse Collection mit den historischen Aufträgen eine Sortierung der Collection nach Zeit und angegebenen Kriterien hinzufügen.

//+------------------------------------------------------------------+
//|                                                      Defines.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"

//+------------------------------------------------------------------+
//| Macro-Substitution                                               |
//+------------------------------------------------------------------+
#define COUNTRY_LANG   ("Russian")              // Landessprache
#define DFUN           (__FUNCTION__+": ")      // "Funktionsberechnung"
#define END_TIME       (D'31.12.3000 23:59:59') // Final data for requesting account history data
//+------------------------------------------------------------------+
//| Suche                                                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Suchen und Sortieren der Daten                                   |
//+------------------------------------------------------------------+
enum ENUM_COMPARER_TYPE
  {
   EQUAL,                                                   // Gleich
   MORE,                                                    // Größer
   LESS,                                                    // Kleiner
   NO_EQUAL,                                                // Ungleich
   EQUAL_OR_MORE,                                           // Größer oder gleich
   EQUAL_OR_LESS                                            // Kleiner oder gleich
  };
//+------------------------------------------------------------------+
//| Mögliche Optionen zur Zeitauswahl                                |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // Nach Eröffnungszeit
   SELECT_BY_TIME_CLOSE,                                    // Nach Schlusszeit
   SELECT_BY_TIME_OPEN_MSC,                                 // Nach Eröffnungszeit in Millisekunden
   SELECT_BY_TIME_CLOSE_MSC,                                // Nach Schlusszeit in Millisekunden
  };
//+------------------------------------------------------------------+

Binden Sie die Klasse CSelect in die Datei HistoryCollection.mqh ein. Ersetzen Sie dazu die Zeile, die die Servicefunktionen einbindet, durch das Einbinden der Klassendatei CSelect:

//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\DELib.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"
//+------------------------------------------------------------------+

Nun haben wir die Klassendatei CSelect anstelle der Servicefunktionen eingebunden. Wir haben Order.mqh in Select.mqh eingebunden, während die Servicefunktionsdatei bereits in Order.mqh enthalten ist.

Deklarieren Sie im 'public' Teil der Klasse CHistoryCollection die Methode zur Auswahl von Aufträgen aus der Collection bis zum angegebenen Zeitpunkt im Datumsbereich, während Sie im 'private' Teil die abstrakte Klasse Order hinzufügen, die als Muster der Aufträge für die Suche nach Werten dienen soll:
//+------------------------------------------------------------------+
//| Collection der historischen Aufträge und Deals                   |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // Liste aller historischen Aufträge und Deals
   COrder            m_order_instance;       // Auftragsobjekt für die Suche nach einer Eigenschaft
   bool              m_is_trade_event;       // Flag des Handelsereignisses
   int               m_index_order;          // Index des letzten zur Collection hinzugefügten Auftrags aus der Liste der Terminalhistorie (MQL4, MQL5)
   int               m_index_deal;           // Index des letzten zur Collection hinzugefügten Deals aus der Liste der Terminalhistorie (MQL5)
   int               m_delta_order;          // Differenz der Auftragsanzahl im Vergleich zur vorherigen Prüfung
   int               m_delta_deal;           // Differenz der Anzahl der Deals im Vergleich zur vorherigen Prüfung
public:
   //--- Rückgabe der ganzen Collection 'wie besehen'
   CArrayObj        *GetList(void) { return &m_list_all_orders;  }
   //--- Auftragsauswahl aus der Collection innerhalb der Zeitspanne von begin_time bis end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- Konstructor
                     CHistoryCollection();
   //--- Aktualisieren der Auftragsliste, eintragen der Werte der neuen und setzten des Flags des Handelsereignisses
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Bevor Sie die Methode zur Auswahl von Aufträgen aus der Collection im Datumsbereich implementieren, fügen Sie die Methoden zum Platzieren von Integer-, Double- und String-Eigenschaften zur abstrakten Auftragsklasse COrder in der Datei Order.mqh hinzu (in diesem Fall sind die Methoden zum Schreiben der Ganzzahleigenschaft erforderlich, um Parameter zur Bestellung der Probezeitdaten hinzuzufügen):

public:
   //--- Setzen der (1) Integer-, (2) Double- und (3) String-Eigenschaften
   void              SetProperty(ENUM_ORDER_PROP_INTEGER property,long value) { m_long_prop[property]=value;                     }
   void              SetProperty(ENUM_ORDER_PROP_DOUBLE property,long value)  { m_long_prop[property]=value                    }
   void              SetProperty(ENUM_ORDER_PROP_STRING property,long value)  { m_long_prop[property]=value                    }
   //--- Rückgabe von (1) Integer-, (2) Double- und (3) String-Eigenschaften aus dem Array der Eigenschaften
   long              GetProperty(ENUM_ORDER_PROP_INTEGER property)      const { return m_long_prop[property];                    }
   double            GetProperty(ENUM_ORDER_PROP_DOUBLE property)       const { return m_double_prop[this.IndexProp(property)];  }
   string            GetProperty(ENUM_ORDER_PROP_STRING property)       const { return m_string_prop[this.IndexProp(property)];  }

   //--- Rückgabe des Flags der Order mit den Eigenschaften
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property)         { return true; }

   //--- Vergleich der Objekte von COrder mit einer von allen möglichen Eigenschaften
   virtual int       Compare(const CObject *node,const int mode=0) const;

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

Implementieren Sie in der Datei HistoryCollection.mqh die Methode zur Auswahl von Aufträgen aus der Collection im Bereich von Daten:

//+------------------------------------------------------------------+
//| Auswahl der Aufträge aus der Collection                          |
//| von begin_time bis end_time                                      |
//+------------------------------------------------------------------+
CArrayObj *CHistoryCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                             const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE)
  {
   ENUM_ORDER_PROP_INTEGER property=
     (
      select_time_mode==SELECT_BY_TIME_CLOSE       ?  ORDER_PROP_TIME_CLOSE      : 
      select_time_mode==SELECT_BY_TIME_OPEN        ?  ORDER_PROP_TIME_OPEN       :
      select_time_mode==SELECT_BY_TIME_CLOSE_MSC   ?  ORDER_PROP_TIME_CLOSE_MSC  : 
      ORDER_PROP_TIME_OPEN_MSC
     );

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

Also, was haben wir hier?

  • Das Verfahren erhält die gewünschte Startzeit der Historie, deren Endezeit und den Modus zum Auswählen der Daten der Zeitspanne aus der Enumeration ENUM_SELECT_BY_TIME.
  • Die benötigten Eigenschaft zum Suchen und Vergleichen in den Auftragseigenschaften wird abhängig von der Zeit, nach der die Liste sortiert wurde, eingestellt. Wenn es nach der offenen Zeit sortiert wurde, ist die Sucheigenschaft die offene Zeit. Wenn nach der Schlusszeit sortiert wurde, wird die Schlusszeit der Aufträge verglichen etc..
  • Dann wird die neue Liste erstellt. Sie enthält die Aufträge, die der Zeitspanne entsprechen und schlussendlich an das aufrufende Programm zurückgegeben werden.
  • Anschließend werden die Anfangs- und Enddaten der Zeitspanne überprüft.
  • Wenn als Datum der Zeitspanne Null übergeben wird, tragen Sie das maximale zukünftige Datum ein und überprüfen Sie dann das Startdatum der Zeitspanne. Wenn es das Datum der Zeitspanne überschreitet, wird das früheste Datum als Beginn der Zeitspanne auf Eins gesetzt. In diesem Fall wird bei falscher Datumseinstellung die Liste vom Beginn der Kontenhistorie bis zu ihrem Ende angezeigt. Ein früheres Datum wird als Anfang der Zeitspanne betrachtet, während ein Datum, das näher an der aktuellen Zeit liegt, als deren Ende betrachtet wird.
  • Das Flag der manuellen Speicherverwaltung (siehe oben) wird dann für die neu erstellte Liste gesetzt, und die Liste wird an das Speicherobjekt angehängt.
  • Als nächstes wird das Anfangsdatum des Musterauftrags für die Suche in der Collection gesetzt und die Suche nach dem Auftragsindex mit dem Anfangsdatum wird mit der Methode SearchGreatOrEqual() durchgeführt, die von der Klasse COrder aus dem übergeordneten CObject übernommen wird.
  • Wenn der Index nicht gefunden wird, bedeutet das, dass es keine Aufträge gibt, die später als das angegebene Datum liegen, und es wird eine leere Liste zurückgegeben.
  • Als Nächstes wird das Gleiche bei der Suche nach dem Enddatum getan: Das Enddatum, das bei der Suche verwendet werden soll, wird in das Muster eingetragen, und die Suche nach dem Auftragsindex mit dem Enddatum über die Methode SearchLessOrEqual() wird durchgeführt. Wenn der Index nicht gefunden wird, bedeutet dies, dass es keine Aufträge vor dem Zieldatum gibt, und es wird eine leere Liste zurückgegeben.
  • Als Nächstes werden alle Aufträge, die sich inerhalb der Zeitspanne befinden, zur Liste hinzugefügt in der Schleife vom Auftragsindex des Startdatums bis zum Auftrag des Enddatums, und die ausgefüllte Liste wird an das aufrufende Programm zurückgegeben.

Deklarieren Sie im 'public' Teil die Methoden, die die Liste durch die ausgewählten Integer-, Double- und String-Eigenschaften zurückgeben, die dem Vergleichskriterium entsprechen:

//+------------------------------------------------------------------+
//| Collection der historischen Aufträge und Deals                   |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // Liste aller historischen Aufträge und Deals
   COrder            m_order_instance;       // Auftragsobjekt für die Suche nach einer Eigenschaft
   bool              m_is_trade_event;       // Flag des Handelsereignisses
   int               m_index_order;          // Index des letzten zur Collection hinzugefügten Auftrags aus der Liste der Terminalhistorie (MQL4, MQL5)
   int               m_index_deal;           // Index des letzten zur Collection hinzugefügten Deals aus der Liste der Terminalhistorie (MQL5)
   int               m_delta_order;          // Differenz der Auftragsanzahl im Vergleich zur vorherigen Prüfung
   int               m_delta_deal;           // Differenz der Anzahl der Deals im Vergleich zur vorherigen Prüfung
public:
   //--- Rückgabe der ganzen Collection 'wie besehen'
   CArrayObj        *GetList(void) { return &m_list_all_orders;  }
   //--- Auftragsauswahl aus der Collection innerhalb der Zeitspanne von begin_time bis end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); }
   //--- Konstructor
                     CHistoryCollection();
   //--- Aktualisieren der Auftragsliste, eintragen der Werte der neuen und setzten des Flags des Handelsereignisses
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Das Verfahren erhält die Zieleigenschaft des Auftrags, den zu vergleichenden Wert und den Vergleichs-Modus (gleich/nicht gleich, größer/kleiner, kleiner/größer oder gleich, größer/kleiner oder gleich). Die nach den gewünschten Eigenschaften, Werten und Vergleichsmethoden sortierte Liste wird mit Hilfe der zuvor beschriebenen Klassenmethoden CSelect zurückgegeben.

Lassen Sie uns testen, ob wir die benötigten Listen mit verschiedenen Methoden erhalten.

Verwenden Sie den Test EA TestDoEasyPart02.mq5 aus dem zweiten Teil und speichern Sie ihn im neuen Unterordner Part03 von MQL5\Experts\TestDoEasy unter dem Namen TestDoEasyPart03_1.mq5. Tragen Sie Anfang und Ende der Zeitspanne zu seinen Eingabeparametern ein und ändern Sie den Code in der Funktion OnInit(), wo wir zur Anforderungshistorie in der Zeitspanne gehen:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_1.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\Collections\HistoryCollection.mqh>
//--- Enumerationen
enum ENUM_TYPE_ORDERS
  {
   TYPE_ORDER_MARKET,   // Marktorder
   TYPE_ORDER_PENDING,  // Pending-Order
   TYPE_ORDER_DEAL      // Deals
  };
//--- Eingabeparameter
input ENUM_TYPE_ORDERS  InpOrderType   =  TYPE_ORDER_DEAL;  // Anzeige des Typs:
input datetime          InpTimeBegin   =  0;                // Anfangsdatum der benötigten Zeitspanne
input datetime          InpTimeEnd     =  END_TIME;         // Enddatum der benötigten Zeitspanne
//--- Globale Variablen
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Aktualisieren der Historie
   history.Refresh();
//--- Abrufen der Collection innerhalb der Zeitspanne
   CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- Abrufen des Auftrags aus der Liste
      COrder* order=list.At(i);
      if(order==NULL) continue;
      //--- Falls es ein Deal ist
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
      //--- Falls es eine historische Marktorder ist
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
      //--- Falles es eine entfernte Pending-Order ist
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Jetzt erhalten wir anstelle der vollständigen Liste die Liste, die nach dem angegebenen Datumsbereich ausgewählt wurde, mit der Methode GetListByTime(). Kompilieren und starten Sie den EA mit den Standardeinstellungen. Alle Transaktionen für die gesamte Kontohistorie werden im Journal angezeigt:


Drücken Sie F7 und stellen Sie das Enddatum des gewünschten Bereichs in den Einstellungen ein. Persönlich habe ich die Kontohistorie eingegeben, die bestimmt wurde, wann ein Nachschub stattgefunden hat, das Datum des nächsten Deals definiert (erster Deal, der nach der Gutschrift auf das Konto stattgefunden hat)


und den Bereich so gewählt, dass der erste Deal außerhalb des Bereichs lag: 2018.01.22 - 2018.02.01.01.
Infolgedessen wurde nur ein Deal (eine Gutschift) im Journal angezeigt:


Speichern wir nun den EA TestDoEasyPart03_1.mq5 unter dem Namen TestDoEasyPart03_2mq5. Entfernen Sie die Eingaben und ändern Sie die Art und Weise, wie die Daten über die Deals abgerufen werden:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_2.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\Collections\HistoryCollection.mqh>
//--- Globale Variablen
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Aktualisieren der Historie
   history.Refresh();
//--- Erhalt nur der Deals aus der Collection
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
//--- Sortieren der erhaltenen Liste nach den Saldenoperationen
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,DEAL_TYPE_BALANCE,EQUAL);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- Abrufen des Auftrags aus der Liste
      COrder* order=list.At(i);
      if(order==NULL) continue;
      order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Zuerst gehen Sie die Liste aller Deals (markieren Sie die Auftragstatus vom Typ Deal in der Liste) und sortieren Sie die erhaltene Liste nach dem Typ "Saldooperation" ('balance'). In beiden Fällen wird der Vergleichsmodus Equal verwendet.
Dadurch wird im Journal nur der Vorgang "Gutschrift" ('balance') angezeigt:


Während wir im vorherigen Beispiel den Aktionsradius in der Registerkarte Kontohistorie des Terminals betrachten mussten, um den Saldovorgang anzuzeigen, haben wir ihn hier unmittelbar nach dem Sortieren der Liste nach den erforderlichen Kriterien erhalten.

Alle anderen Möglichkeiten, die erforderlichen Daten zu erhalten, folgen dem gleichen Prinzip. Um zum Beispiel die gleiche "Gutschrift" zu erhalten, können wir die Indizes der am meisten und am wenigsten profitablen Deals finden.
Beispiel im TestDoEasyPart03_3.mq5 Test EA:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_3.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\Collections\HistoryCollection.mqh>
//--- Globale Variablen
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Aktualisieren der Historie
   history.Refresh();
//--- Erhalt nur der Deals aus der Collection
   CArrayObj* list=history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   if(list==NULL)
     {
      Print(TextByLanguage("Не удалось получить список","Could not get list"));
      return INIT_FAILED;
     }
//--- Abrufen des Index des profitabelsten Deals (erste Gutschrift)
   int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- Abrufen des Deals aus der Liste gemäß dem Index
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с максимальным значением профита","Order index with maximum profit value not found"));
//--- Abrufen des Index des Deals mit dem kleinsten Gewinn<50/63/68% >
   index=CSelect::FindOrderMin(list,ORDER_PROP_PROFIT);
   if(index!=WRONG_VALUE)
     {
      //--- Abrufen des Deals aus der Liste gemäß dem Index
      COrder* order=list.At(index);
      if(order!=NULL)
         order.Print();
     }
   else
      Print(TextByLanguage("Не найден индекс ордера с минимальным значением профита","Order index with minimum profit value not found"));
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Nach Abschluss werden zwei Deals im Journal angezeigt — die mit dem höchsten Gewinn (Gutschrift) und das mit dem geringsten Gewinn.



Das Basisobjekt Engine ist der Bibliothekskern.

Ein benutzerdefiniertes Programm, das in Verbindung mit der Bibliothek arbeitet, sollte Daten an die Bibliothek senden und Daten von ihr empfangen. Um dies zu erreichen, ist es bequemer, eine einzige Klasse zu haben, die sich mit dem Programm verbindet und alle möglichen Aktionen für die Kommunikation zwischen der Bibliothek und dem Programm sammelt. Natürlich sollte die Klasse alle möglichen Servicefunktionen übernehmen, die im Hintergrund funktionieren und keine kostspieligen Aktionen eines Benutzers erfordern. Daher werden wir eine Basisklasse als Bibliotheksbasis erstellen und sie Engine benennen.

Erstellen Sie im Stammordner der Bibliothek die neue Klasse CEngine basierend auf dem Basisobjekt CObject und binden Sie die Klasse der historischen Collection ein:

//+------------------------------------------------------------------+
//|                                                       Engine.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 "Collections\HistoryCollection.mqh"
//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Collection der historischen Aufträge und Deals
   CHistoryCollection   m_history;
public:
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine Konstruktor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngine Destruktor                                               |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+

Alle Aktionen, die wir während der Arbeit an der Collection der historischen Aufträge in Test EAs durchgeführt haben, wurden in der Funktion OnInit() durchgeführt. Mit anderen Worten, sie werden nur einmal ausgeführt, wenn der EA gestartet, neu kompiliert oder seine Parameter geändert wurden. Dies reicht für eine schnelle Überprüfung aus, ist aber im Arbeitsprogramm nicht zulässig. Also fangen wir an, alles in Ordnung zu bringen.

Erstellen Sie zunächst die Funktion OnTimer() im 'public' Bereich der Klasse, sowie ihre Implementierung außerhalb des Klassenkörpers, um alle Collections zu aktualisieren:
//+------------------------------------------------------------------+
//|                                                       Engine.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 "Collections\HistoryCollection.mqh"
//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Collection der historischen Aufträge und Deals
   CHistoryCollection   m_history;
public:
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine Konstruktor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngine Destruktor                                               |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
  }
//+------------------------------------------------------------------+
//| CEngine Timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   
  }
//+------------------------------------------------------------------+

Lassen Sie uns die Serviceklasse (Timerzähler) festlegen, da es sehr wahrscheinlich ist, dass für verschiedene Ereignisse unterschiedliche Timer-Verzögerungen erforderlich sind. Die Klasse soll die erforderliche Verzögerungszeit zählen, und für jeden der Timerzähler ist eine eigene Zählerinstanz zu deklarieren.

Fügen Sie zunächst neue Makro-Substitutionen zu den Dateien Defines.mqh hinzu. Geben Sie die Frequenz des Bibliothekstimers und die Pause des Timerzähler der Collection in Millisekunden an, die Inkrementierung des Timers der Collection und die ID der historischen Aufträge und Deals Aktualisierungszeitzähler in ihnen (bei der Entwicklung der Bibliothek benötigen Sie möglicherweise mehrere Zähler, die individuelle IDs benötigen).
//+------------------------------------------------------------------+
//| Macro-Substitution                                               |
//+------------------------------------------------------------------+
#define COUNTRY_LANG             ("Russian")                // Landessprache
#define DFUN                     (__FUNCTION__+": ")        // "Funktionsbeschreibung"
#define END_TIME                 (D'31.12.3000 23:59:59')   // Enddatum der abgefragten Kontohistorie
#define TIMER_FREQUENCY          (16                      // Minimalfrequenz des Timers der Bibliothek in Millisekunden
#define COLLECTION_PAUSE         (250)                      // Pause des Timers der Collection der Aufträge und Deals in Millisekunden
#define COLLECTION_COUNTER_STEP  (16                      // Increment des Timerzählers der Collection der Aufträge und Deals
#define COLLECTION_COUNTER_ID    (1)                        // ID des Timerzählers der Collection von Aufträgen und Deals
//+------------------------------------------------------------------+

Erstellen Sie im Stammordner der Bibliothek den neuen Ordner Services sowie die neue Klasse CTimerCounter darin. Verschieben Sie die Datei DELib.mqh mit den Servicefunktionen sofort in diesen Ordner. Hier ist der richtige Ort dafür.

Nachdem Sie DELib.mqh in den neuen Ordner verschoben haben, ändern Sie die Adresse der Servicefunktionsdatei in der Datei Order.mqh:

Ersetzen Sie die Adresse.

//+------------------------------------------------------------------+
//|                                                        Order.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"
#property strict    // Notwendig für mql4
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\DELib.mqh"
//+------------------------------------------------------------------+

mit dieser Adresse:

//+------------------------------------------------------------------+
//|                                                        Order.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"
#property strict    // Notwendig für mql4
//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "..\Services\DELib.mqh"
//+------------------------------------------------------------------+

Jetzt betrachten wir die Klasse des Timerzählers. Die Klasse ist einfach. Lassen Sie uns also einen Blick auf die Auflistung werfen und auf die Funktionsweise eingehen:

//+------------------------------------------------------------------+
//|                                                 TimerCounter.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 <Object.mqh>
#include "DELib.mqh"
//+------------------------------------------------------------------+
//| Klasse des Timerzählers                                          |
//+------------------------------------------------------------------+
class CTimerCounter : public CObject
  {
private:  
   int               m_counter_id;   
   ulong             m_counter;      
   ulong             m_counter_step; 
   ulong             m_counter_pause;
public:
   //--- Rückgabe des Flags für das Ende der Wartezeit
   bool              IsTimeDone(void);
   //--- Setzen des Parameters des Zählers
   void              SetParams(const ulong step,const ulong pause)         { this.m_counter_step=step; this.m_counter_pause=pause;  }
   //--- Rückgabe der ID des Zählers
   virtual  int      Type(void)                                      const { return this.m_counter_id;                              }
   //--- Vergleich von zwei Zählerobjekte
   virtual int       Compare(const CObject *node,const int mode=0)   const;
   //--- Konstructor
                     CTimerCounter(const int id);
  };
//+------------------------------------------------------------------+
//| CTimerCounter Konstruktor                                        |
//+------------------------------------------------------------------+
CTimerCounter::CTimerCounter(const int id) : m_counter(0),m_counter_step(16),m_counter_pause(16)
  {
   this.m_counter_id=id;
  }
//+------------------------------------------------------------------+
//| CTimerCounter gibt das Flag des Pausenendes zurück               |
//+------------------------------------------------------------------+
bool CTimerCounter::IsTimeDone(void)
  {
   if(this.m_counter>=ULONG_MAX)
      this.m_counter=0;
   if(this.m_counter<this.m_counter_pause)
     {
      this.m_counter+=this.m_counter_step;
      return false;
     }
   this.m_counter=0;
   return true;
  }
//+------------------------------------------------------------------+
//| Vergleich der Objekte CTimerCounter nach deren id                |
//+------------------------------------------------------------------+
int CTimerCounter::Compare(const CObject *node,const int mode=0) const
  {
   const CTimerCounter *counter_compared=node;
   int value_compared=counter_compared.Type();
   int value_current=this.Type();
   return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
   return 0;
  }
  
//+------------------------------------------------------------------+

Da wir DELib.mqh in den gleichen Ordner verschoben haben, in dem sich die Zählerklasse befindet, sollten wir es direkt aus dem gleichen Ordner einbinden. Defines.mqh ist in DELib.mqh enthalten, was bedeutet, dass die Klasse alle Makro-Substitutionen sieht.

  • Im privaten Bereich werden vier Variablen der Klasse deklariert: timer-ID, timer counter, timer increment und pause.
  • Die Methode zum Einstellen der erforderlichen Zählerparameter befindet sich im 'public' Teil. Das Verfahren übergibt dem Timer step und pau. Die übergebenen Werte werden sofort den Variablen der Klasse zugewiesen.
  • Im Konstruktor der Klasse werden in der Initialisierungsliste die Standardparameter des Timer für den Zähler (0), Schrittweite (16) und die Pause (16) angegeben. Die Parameter Schrittweite und Pause ermöglichen es dem Timer, ohne Verzögerung zu arbeiten, während er darauf wartet, dass die Pausendauer erreicht wird.
  • Der übergebene Wert des Eingabeparameters wird der Zähler-ID im Konstruktor der Klasse zugewiesen.

Die Methode, die das Flag für das Ende der Pause zurückgibt, ist auf einfache Weise eingerichtet:

  • Zuerst prüft das System auf den Überlauf des Variablenzählers. Überschreitet der Wert den maximal möglichen Wert, wird der Zähler auf Null gesetzt.
  • Als Nächstes vergleicht das System Timerzähler und Pausenwerte. Wenn der Zählerwert kleiner als derjenige der Pause ist, wird das Inkrement zum Zähler addiert und "false" zurückgegeben.
  • Wenn der Zählerwert die Pause überschreitet oder gleich ist, wird der Zähler zurückgesetzt und das Ereignis der Zählerwartzeit wird zurückgegeben.

Die Methode, die die Type() Zähler-ID zurückgibt, wird virtuell gemacht. In der Klassenauslieferung des CObjects gibt es eine virtuelle Methode, die den Objekttyp zurückgibt:

   //--- Methode zur Identifizierung des Objekts
   virtual int       Type(void)                                    const { return(0);      }

Es wird davon ausgegangen, dass diese Methode in den Kindklassen neu definiert werden soll und die ID des Objekts der Kindklasse CObject zurückgeben soll (der Typ=0 wird für CObject selbst zurückgegeben). Lassen Sie uns diese Gelegenheit nutzen und die Zähler-ID zurückgeben, indem wir die virtuelle Methode neu definieren:

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

Die virtuelle Methode zum Vergleichen zweier Zählerobjekte ist einfach:

//+------------------------------------------------------------------+
//| Vergleich der Objekte CTimerCounter nach deren id                |
//+------------------------------------------------------------------+
int CTimerCounter::Compare(const CObject *node,const int mode=0) const
  {
   const CTimerCounter *counter_compared=node;
   int value_compared=counter_compared.Type();
   int value_current=this.Type();
   return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
   return 0;
  }
//+------------------------------------------------------------------+

Holen eines Links zum Quellobjekt, dessen ID und der ID des aktuellen Zählers. Als Nächstes gibt das Ergebnis eines einfachen Vergleichs mit größer/kleiner/gleich zurück.


Lassen Sie uns weiterhin die Klasse CEngine ausfüllen. Binden Sie die Timerzählerklasse in die Engine.mqh-Datei ein:

//+------------------------------------------------------------------+
//| Include-Dateien                                                  |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+

Fügen Sie das Listenobjekt der Timerzähler, sowie die Methode, die den Zählerindex in der Liste durch ihre ID zurückgibt, dem 'private' Teil und die Zählererstellungsmethode dem 'public' hinzu (so dass die Methode von außen zugänglich ist und es möglich ist, benutzerdefinierte Zähler in Programmen zu erstellen).

Im Klassenkonstruktor initialisieren Sie den Millisekunden-Timer, setzen Sie das Sortierlisten-Flag und erzeugen Sie den Zähler der historischen Aufträge und Deals Collection Update Timer. Eintragen des Löschen des Timers im Destruktor der Klasse:
//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
//--- Liste der Timerzählers
   CArrayObj            m_list_counters;           
//--- Collection der historischen Aufträge und Deals
   CHistoryCollection   m_history;
//--- Rückgabe des Zählerindex über die ID
   int                  CounterIndex(const int id) const;
public:
//--- Erstellen der Timerzählers
   void                 CreateCounter(const int counter_id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine Konstruktor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine()
  {
   ::EventSetMillisecondTimer(TIMER_FREQUENCY);
   this.m_list_counters.Sort();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_MIN_PAUSE);
  }
//+------------------------------------------------------------------+
//| CEngine Destruktor                                               |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
   ::EventKillTimer();
  }
//+------------------------------------------------------------------+

Implementierung der Methode, die den Zählerindex anhand seiner ID zurückgibt:

//+------------------------------------------------------------------+
//| Rückgabe des Zählerindex der Liste über die ID                   |
//+------------------------------------------------------------------+
int CEngine::CounterIndex(const int id) const
  {
   int total=this.m_list_counters.Total();
   for(int i=0;i<total;i++)
     {
      CTimerCounter* counter=this.m_list_counters.At(i);
      if(counter==NULL) continue;
      if(counter.Type()==id) 
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Da es kaum mehrere Zähler geben kann, habe ich die einfachste Enumeration mit den Werten Suche und Vergleich zusammengestellt. Wenn der Zähler mit dieser ID in der Liste gefunden wird, gibt das System seinen Index in der Liste zurück, ansonsten gibt das System -1 zurück.

Betrachten wir die Methode zum Erstellen des Timerzählers:

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

Überprüfen Sie zunächst die an die Methode übergebene Zähler-ID. Wenn eine solche ID bereits vorhanden ist, zeigen Sie die entsprechende Meldung im Journal an und verlassen Sie das Verfahren - der Zähler mit dieser ID ist bereits vorhanden.
Da die Suche nur in einer sortierten Liste durchgeführt werden kann, wird das Sortier-Flag für die Liste gesetzt, ein neues Zählerobjekt wird erstellt, dessen erfolgreiche Erstellung wird überprüft und die erforderlichen Eigenschaften des Zählers werden gesetzt.

Danach wird die Suche nach dem gleichen Zähler in der Liste durchgeführt. Wenn er nicht gefunden wird, wird der neue Zähler zur Liste hinzugefügt.

Andernfalls wird die Meldung mit allen Parametern gebildet und im Journal angezeigt. Dann wird das Zählerobjekt entfernt, da wir bereits ein ähnliches haben.

Die letzte Prüfung für alle Parameter des neu erstellten Zählers, die mit dem bereits vorhandenen übereinstimmen, ist derzeit redundant — die ID-Prüfung ganz am Anfang der Methode verhindert das Anlegen eines Objekts mit einer bereits in der Liste vorhandenen ID. Ich habe es für mögliche zukünftige Änderungen reserviert.

Um der Klasse CEngine mitzuteilen, wann sie mit der Handelssituation umgehen soll, sollte sie die aufgetretenen Veränderungen in der Anzahl der historischen Aufträge und Deals kennen. Dazu fügen Sie die Methoden, die die Anzahl der neu erschienenen Orders und deals in der Liste zurückgeben, der Klasse CHistoryCollection hinzu:

//+------------------------------------------------------------------+
//| Collection der historischen Aufträge und Deals                   |
//+------------------------------------------------------------------+
class CHistoryCollection
  {
private:
   CArrayObj         m_list_all_orders;      // Liste aller historischen Aufträge und Deals
   COrder            m_order_instance;       // Auftragsobjekt für die Suche nach einer Eigenschaft
   bool              m_is_trade_event;       // Flag des Handelsereignisses
   int               m_index_order;          // Index des letzten zur Collection hinzugefügten Auftrags aus der Liste der Terminalhistorie (MQL4, MQL5)
   int               m_index_deal;           // Index des letzten zur Collection hinzugefügten Deals aus der Liste der Terminalhistorie (MQL5)
   int               m_delta_order;          // Differenz der Auftragsanzahl im Vergleich zur vorherigen Prüfung
   int               m_delta_deal;           // Differenz der Anzahl der Deals im Vergleich zur vorherigen Prüfung
public:
   //--- Auftragsauswahl aus der Collection innerhalb der Zeitspanne von begin_time bis end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                   const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE);
   //--- Rückgabe der ganzen Collection 'wie besehen'
   CArrayObj        *GetList(void)                                                                       { return &m_list_all_orders;                                            }
   //--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen
   CArrayObj        *GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   //--- Rückgabe der Anzahl (1) neuer Aufträge und (2) neuer Deals
   int               NewOrders(void)                                                                     { return m_delta_order; }
   int               NewDeals(void                                                                     { return m_delta_deal;  }
   
   //--- Konstructor
                     CHistoryCollection();
   //--- Aktualisieren der Auftragsliste, eintragen der Werte der neuen und setzten des Flags des Handelsereignisses
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Die Methoden geben einfach die Werte der entsprechenden Klassenmitgliedervariablen zurück.

Jetzt können wir in CEngine ihren Status im Timer der Klasse überprüfen:

//+------------------------------------------------------------------+
//| CEngine Timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- Timer der Collection der historischen Aufträge und Deals
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter.IsTimeDone())
        {
         this.m_history.Refresh();
         if(this.m_history.NewOrders()>0)
           {
            Print(DFUN,TextByLanguage("Изменилось количество исторических ордеров: NewOrders=","Number of historical orders changed: NewOrders="),this.m_history.NewOrders());
           }
         if(this.m_history.NewDeals()>0)
           {
            Print(DFUN,TextByLanguage("Изменилось количество сделок: NewDeals=","Number of deals changed: NewDeals="),this.m_history.NewOrders());
           }
        }
     }
  }
//+------------------------------------------------------------------+

Abrufen des Index des Timerzählers der Collection der historischen Aufträge und Deals, Abrufen des Zeigers auf den Timerzähler mit seinem Index, prüfen der Fertigstellung der Timerverzögerungszeit und aktualisieren der Collection (nur die zuletzt hinzugefügten Aufträge oder Deals, wenn vorhanden).

Wenn sich die Anzahl der historischen Aufträge geändert hat, drucken Sie die Meldung im Journal. Das Gleiche gilt für die Deals.

Lassen Sie uns einen einfachen EA für eine Überprüfung machen. Erstellen Sie in Experts\TestDoEasy\Part3 den EA mit dem Timer TestDoEasyPart03_4.mq5. Um die Vorlage des EA mit dem Timer zu erstellen, markieren Sie OnTimer auf der zweiten Seite des MQL Wizard:


Klicken Sie auf Weiter, bis der Vorgang des Wizards abgeschlossen ist. Dadurch wird eine leere EA-Vorlage erstellt. Binden Sie die Hauptbibliotheksdatei ein, erzeugen Sie das Bibliotheksklassenobjekt und rufen Sie den Bibliothekstimer im EA-Timer auf.

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_4.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>
//--- Globale Variablen
CEngine        engine;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

Dies ist alles, was im EA getan werden muss, um Daten über Änderungen in der Kontenhistorie zu erhalten.

Wenn wir den EA jetzt starten, eine Pending-Order platzieren und löschen, erscheint ein Eintrag über die Änderung der Anzahl der historischen Aufträge im Journal.
Wenn wir eine Position eröffnen, erscheinen zwei Einträge im Journal:

  1. über die Änderung der Anzahl der Aufträge (eine offene Marktorder wurde versandt) und
  2. über die Änderung der Anzahl der Deals (die Marktorder wurde aktiviert und hat den Deal "Markteintritt" generiert).

Wenn wir eine Position schließen, erscheinen wieder zwei Einträge im Journal:

  1. über das Auftreten des Schließens der Marktorder
  2. bezüglich des Auftretens des neuen Deals (die schließende Marktorder wurde aktiviert und hat den Deal "Market Exit" generiert).

Beim ersten Start werden Meldungen über die Änderungen der Aufträge und Deals in der Kontohistorie im Journal angezeigt. Dies geschieht, weil die Bibliothek die gesamte Historie während des ersten Starts liest, so dass die Differenz zwischen der Nullzahl der Aufträge und Deals (die Bibliothek weiß nichts über sie während des ersten Starts) und der Anzahl aller Aufträge und Deals, die während des gesamten Kontoverlaufs berechnet wurden, gleich ihrer vollen Anzahl ist. Das ist unangenehm und unnötig. Daher müssen wir solche falschen Nummernänderungsnachrichten beseitigen. Wir können das auf zwei Arten tun:

  1. Nichts im Journal während des ersten Starts anzeigen
  2. Die Kontostatusmeldungen während des ersten Starts anzeigen
Da wir die Collection und Anzeige der notwendigen Statistiken in den nachfolgenden Teilen der Beschreibung implementieren werden, verwenden wir vorerst die erste Option (Nichts im Journal während des ersten Starts anzeigen) und machen einfach einen Stummel für den ersten Start:

Fügen Sie im privaten Bereich der Klasse CEngine das Flag des ersten Starts und die Methode hinzu, die das Flag prüft und zurücksetzt:

//+------------------------------------------------------------------+
//| Bibliothek der Basisklasse                                       |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection der historischen Aufträge und Deals
   CArrayObj            m_list_counters;                 // Liste der Timerzähler
   bool                 m_first_start;                   // Flag für den ersten Start
//--- 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();
  };
//+------------------------------------------------------------------+

und fügen Sie die Implementierung der Prüfmethode hinzu und setzen Sie das Flag beim ersten Start über den Klassenkörper hinaus zurück:

//+------------------------------------------------------------------+
//| Rückgabe des Flags des ersten Starts, rücksetzen des Flags       |
//+------------------------------------------------------------------+
bool CEngine::IsFirstStart(void)
  {
   if(this.m_first_start)
     {
      this.m_first_start=false;
      return true;             
     }
   return false;
  }
//+------------------------------------------------------------------+

Hier ist alles sehr einfach: wenn das Flag gesetzt ist, setzt sie es zurück und gibt 'true' zurück, ansonsten 'false'.
Jetzt müssen wir das Flag in der Initialisierungsliste so setzen, dass es immer aktivierbar bleibt.

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

Alles ist eingestellt. Von nun an erscheint beim ersten Programmstart kein unnötiges Ereignis der Kontenhistorie mehr, das sich auf die allererste Berechnung der Historie bezieht.

Wir können dies überprüfen, indem wir den Test-EA von MQL5\Experts\TestDoEasy\Part2\TestDoEasyPart03_4.mq5 starten und sicherstellen, dass beim ersten Start keine Nachrichten über das Hinzufügen von Aufträgen und Deals zur Kontohistorie im Expertenjournal erscheinen.

Objekte aktiver Marktorders und Positionen

Ich glaube, jetzt ist es an der Zeit, die Erweiterung der Klasse CEngine vorübergehend einzustellen und mit der Implementierung von Objekten und der Collection von Marktaufträgen und Positionen zu beginnen. Nach Abschluss der Implementierung werden wir die Funktionalität des Basisobjekts Engine weiter entwickeln, da diese Funktionen sowohl die Historie des Kontos als auch seinen aktuellen Status beeinflusst.

Legen Sie im Ordner Objekte der Bibliothek die neue Klasse CMarketPosition basierend auf der abstrakten Ordnung der Bibliothek COrder an - dies soll ein Objekt einer Marktposition sein:


Nach dem Klicken auf Fertig wird eine Vorlage der Klasse mit dem Namen MarketPosition.mqh erstellt. Binden wir gleich die Klasse COrder ein:

//+------------------------------------------------------------------+
//|                                               MarketPosition.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"
//+------------------------------------------------------------------+
//| Marktposition                                                    |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
private:

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

Ändern Sie den Konstruktor so, dass das Positionsticket ihm übergeben wird, setzen Sie den Status "Marktposition" für die übergeordnete Klasse (COrder) in ihrer Initialisierungsliste und senden Sie ihm das Ticket. Deklarieren Sie drei virtuelle Methoden, die das Flag der Unterstützung von Integer-, Double- und String-Eigenschaften an der Position zurückgeben:

//+------------------------------------------------------------------+
//| Marktposition                                                    |
//+------------------------------------------------------------------+
class CMarketPosition : public COrder
  {
public:
   //--- Konstructor
                     CMarketPosition(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_POSITION,ticket) {}
   //--- Unterstützte Positionseigenschaften (1) Double, (2) Integer
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property);
  };
//+------------------------------------------------------------------+

und fügen Sie die Implementierung dieser Methoden außerhalb des Klassenkörpers hinzu:

//+------------------------------------------------------------------+
//| Rückgabe von 'true' falls die Position die übergebene            |
//| Integer-Eigenschaft unterstützt, sonst 'false'                   |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TIME_CLOSE     || 
      property==ORDER_PROP_TIME_CLOSE_MSC ||
      property==ORDER_PROP_TIME_EXP       ||
      property==ORDER_PROP_POSITION_BY_ID ||
      property==ORDER_PROP_DEAL_ORDER     ||
      property==ORDER_PROP_DEAL_ENTRY     ||
      property==ORDER_PROP_CLOSE_BY_SL    ||
      property==ORDER_PROP_CLOSE_BY_TP
     #ifdef __MQL5__                      ||
      property==ORDER_PROP_TICKET_FROM    ||
      property==ORDER_PROP_TICKET_TO
     #endif 
     ) return false;
   return true;
}
//+------------------------------------------------------------------+
//| Rückgabe von 'true' falls die Position die übergebene            |
//| Double-Eigenschaft, sonst 'false'                                |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_PRICE_CLOSE || property==ORDER_PROP_PRICE_STOP_LIMIT) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Rückgabe von 'true' falls die Position die übergebene            |
//| String-Eigenschaft, sonst 'false'                                |
//+------------------------------------------------------------------+
bool CMarketPosition::SupportProperty(ENUM_ORDER_PROP_STRING property)
  {
   if(property==ORDER_PROP_EXT_ID) return false;
   return true;
  }
//+------------------------------------------------------------------+

Hier ist alles ähnlich wie beim Erstellen von Objekten historischer Aufträge und Deals im zweiten Teil der Bibliotheksbeschreibung.

Lassen Sie uns nun in ähnlicher Weise ein Objekt für die Pending-Orders erstellen. Wir erstellen eine neue Klasse CMarketPending basierend auf der abstrakten Order der COrder-Bibliothek im Ordner Objects und geben die bereits bekannten Änderungen der vom MQL Wizard erstellten Klassenvorlage ein:

//+------------------------------------------------------------------+
//|                                                MarketPending.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"
//+------------------------------------------------------------------+
//| Markt-Pending-Order                                              |
//+------------------------------------------------------------------+
class CMarketPending : public COrder
  {
public:
   //--- Konstructor
                     CMarketPending(const ulong ticket=0) : COrder(ORDER_STATUS_MARKET_PENDING,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 CMarketPending::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_PROFIT_PT         ||
      property==ORDER_PROP_DEAL_ORDER        ||
      property==ORDER_PROP_DEAL_ENTRY        ||
      property==ORDER_PROP_TIME_UPDATE       ||
      property==ORDER_PROP_TIME_CLOSE        ||
      property==ORDER_PROP_TIME_CLOSE_MSC    ||
      property==ORDER_PROP_TIME_UPDATE_MSC   ||
      property==ORDER_PROP_TICKET_FROM       ||
      property==ORDER_PROP_TICKET_TO         ||
      property==ORDER_PROP_CLOSE_BY_SL       ||
      property==ORDER_PROP_CLOSE_BY_TP
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Rückgabe von 'true', falls der Auftrag die übergebene            |
//| Double-Eigenschaft, sonst 'false'                                |
//+------------------------------------------------------------------+
bool CMarketPending::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(property==ORDER_PROP_COMMISSION  ||
      property==ORDER_PROP_SWAP        ||
      property==ORDER_PROP_PROFIT      ||
      property==ORDER_PROP_PROFIT_FULL ||
      property==ORDER_PROP_PRICE_CLOSE
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

Übergeben Sie den Status "pending order" an den Basis-COrder in der Initialisierungsliste des Klassenkonstruktors.

Wir haben die Entwicklung der Objekte abgeschlossen, die für die Erstellung der Collection von Marktorders und Positionen notwendig sind.

Die Collection aktiver Marktorders und Positionen

Beim Erstellen der Collection historischer Aufträge und Deals haben wir uns an die Regel gehalten, dass es keinen Sinn macht, die gesamte Historie ständig zu überprüfen. Also haben wir neue Aufträge und Deals nur dann in die zuvor erstellte Liste aufgenommen, wenn sich ihre Nummern geändert haben. Bei der Verwendung der Liste der Marktpositionen sollte eine ganz andere Regel beachtet werden — die Liste auf jedem Tick sollte relevant sein.

Um dies zu erreichen:

  1. Kontrollieren Sie die Änderungen der Anzahl der Pending-Orders, über der Anzahl der aktiven Positionen für Hedge-Konten (da es nur eine Position auf Netting-Konten gibt) und der Anzahl der Positionen (Erhöhung oder Verringerung des Volumens in der Netting-Position, Teilabschluss einer der Hedging-Positionen).
  2. Stellen Sie sicher, dass die Daten zu jeder bestehenden Sicherungsposition oder ein einzelnes Netting bei jedem Tick aktualisiert werden, um immer relevante Statusdaten der Positionen zu haben.

Sichern Sie die Werte, die beim letzten Tick angegeben wurden, um sie mit den gleichen, aktuellen Daten zu vergleichen und die Position zu aktualisieren oder die gesamte Liste bei Änderungen erneut zu erstellen. Glücklicherweise ist die Liste nicht groß, und ihre Neuerstellung braucht nicht viel Zeit.

Fangen wir an. Erstellen Sie im Bibliotheksordner Collections die neue Klasse CMarketCollection. Klicken Sie dazu mit der rechten Maustaste auf den Sammelordner und wählen Sie "Neue Datei". Wählen Sie im neu geöffneten MQL-Assistenten "Neue Klasse" und klicken Sie auf Weiter.


Geben Sie den Namen der Klasse CMarketCollection ein und klicken Sie auf Fertig stellen. Die Klassenvorlage MarketCollection.mqh wird erstellt:

//+------------------------------------------------------------------+
//|                                             MarketCollection.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"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CMarketCollection
  {
private:

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

Tragen wir das jetzt ein.

Zuerst werden alle vorbereiteten Klassen einbezogen, sowie diejenigen, die für die Implementierung der Collection von Marktaufträgen und Positionen und für die Suche nach ihr benötigt werden:

//+------------------------------------------------------------------+
//|                                             MarketCollection.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 <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+

Erstellen Sie im 'private' Teil der Klasse die Struktur, tragen Sie die Variablen zum Speichern aller zuvor genannten zu kontrollierenden Werte darin an (Anzahl der Aufträge und Positionen, etc.) und erstellen Sie zwei Variablen der Klasse mit diesem Strukturtyp zum Speichern von aktuellen und vorherigen Daten:

//+------------------------------------------------------------------+
//|                                             MarketCollection.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 <Arrays\ArrayObj.mqh>
#include "Select.mqh"
#include "..\Objects\MarketPending.mqh" 
#include "..\Objects\MarketPosition.mqh"
//+------------------------------------------------------------------+
//| Collection der historischen Aufträge und Deals                   |
//+------------------------------------------------------------------+
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
public:
                     CMarketCollection();
                    ~CMarketCollection();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CMarketCollection::~CMarketCollection()
  {
  }
//+------------------------------------------------------------------+

Lassen Sie uns auf die Hash-Summe in der Struktur eingehen.
Die Anzahl der Aufträge und Positionen ist unzureichend, wenn wir ein aufgetretenes Kontoereignis genau definieren wollen. Eine Pending-Order kann entfernt werden, wodurch sich die Gesamtzahl der Aufträge und Positionen auf dem Konto ändert. Andererseits kann eine Pending-Order aktiviert werden und zu einer Position werden. In diesem Fall bleibt die Gesamtsumme der Aufträge und Positionen unverändert (für Hedging Accounts und MQL4) — die Anzahl der Positionen steigt, aber die Anzahl der Aufträge sinkt. Dadurch bleibt die Gesamtzahl gleich. Dies ist für uns nicht geeignet.

Betrachten wir das Ticket. Das Hinzufügen/Entfernen einer Pending-Order ändert die Gesamtansumme der Tickets auf dem Konto, während eine Aktivierung der Pending-Order die Gesamtsumme der Tickets auf dem Konto nicht ändert.

Betrachten wir das Gesamtvolumen. Platzieren/Entfernen einer Pending-Order — das Gesamtvolumen auf dem Konto hat sich geändert, eine Position eröffnet, geschlossen oder geändert — das Gesamtvolumen auf dem Konto hat sich verändert. Diese Option scheint zu passen, aber die Aktivierung einer Pending-Order ändert nichts am Gesamtvolumen.

Werfen wir also einen Blick auf noch eine weitere Positionseigenschaft — Zeit ihrer Änderung in Millisekunden: Das Öffnen einer neuen Position ändert die gesamte Positionsänderungszeit, das Teilschließen ändert die Positionsänderungszeit und das Hinzufügen des Volumens auf einem Netting-Konto ändert die gesamte Positionsänderungszeit.

Welche sind die am besten geeigneten Optionen zur genauen Definition einer aufgetretenen Kontoänderung? Ticket+Positionsänderungszeit. Prüfen wir es

  • Öffnen einer Position — Summe der Tickets hat sich geändert + Summe der Positionsänderungszeit hat sich geändert Es gibt eine Änderung.
  • Das Schließen einer Position — Summe der Tickets hat sich geändert + Summe der Positionsänderungszeit hat sich geändert Es gibt eine Änderung.
  • Erteilte Pending-Order — Summe der Tickets hat sich geändert + Summe der Positionsänderungszeit hat sich nicht geändert - Es gibt eine Änderung.
  • Eine Pending-Order wurde entfernt — Summe der Tickets hat sich geändert + Summe der Positionsänderungszeit hat sich nicht geändert - Es gibt eine Änderung.
  • Aktivierung einer Pending-Order — Summe der Tickets hat sich nicht geändert + Summe der Positionsänderungszeit hat sich geändert Es gibt eine Änderung.
  • Eine Position wurde teilweise geschlossen — Summe der Tickets hat sich geändert + Summe der Positionsänderungszeit hat sich geändert Es gibt eine Änderung.
  • Das Hinzufügen eines Volumens zu einer Position — Summe der Tickets hat sich nicht geändert + Summe der Positionsänderungszeit hat sich geändert Es gibt eine Änderung.
So werden wir die Ticket + Positionswechselzeit in Millisekunden für die Hash-Summe verwenden.

Erstellen Sie im 'private' Teil der Klasse die dynamische Liste der Zeiger auf Objekte, die als Collection der Pending-Order und Positionen verwendet werden soll. Erstellen Sie auch zwei Flags: Handelsereignis-Flag auf dem Konto und dem Flag, das eine aufgetretene Positionsvolumenänderung zur vereinfachten Identifizierung eines Handelsereignisses in der Klasse CEngine anzeigt, sowie drei Variablen der Klasse zum Setzen des Volumenänderungswertes, Anzahl der neuen Positionen und Pending-Orders.
Deklarieren Sie im 'public' Teil die Methode zur Aktualisierung der Collection und schreiben Sie die Implementation des Konstruktors der Klasse außerhalb des Klassenkörpers:
.

//+------------------------------------------------------------------+
//| 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
   bool              m_is_trade_event;       // Flag der Handelsereignisse
   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;        // Anzahl der neuen Positionen
   int               m_new_pendings;         // Anzahl der neuen Pending-Order
public:
   //--- Konstructor
                     CMarketCollection(void);
   //--- Aktualisieren der Liste der Pending-Orders und Positionen
   void              Refresh(void);
  };
//+------------------------------------------------------------------+
//| Konstruktor                                                      |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
  {
   m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
   ::ZeroMemory(this.m_struct_prev_market);
   this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Setzen Sie die Flags von Handelsereignis und Änderung des Positionsvolumens zurück und setzen Sie den Wert des Positionsvolumens in der Initialisierungsliste des Klassenkonstruktors zurück.
Stellen Sie im Körper des Konstruktors die Sortierung der Marktordnungsliste und Positionsliste nach der Eröffnungszeit ein, setzen Sie alle variablen Strukturen des vorherigen Status des Kontos zurück, mit Ausnahme der vorherigen Hash-Summe setzen -1 dafür (zur Identifizierung des ersten Starts).

Fügen Sie die Methode zum Speichern der aktuell gesammelten Kontodaten in der vorherigen Datenstruktur in den 'private' Teil der Klasse ein, um später Änderungen in der Anzahl der Aufträge und Positionen auf dem Konto zu überprüfen. Fügen Sie die drei Methoden hinzu, die die Anzahl der neuen Pending-Order, die Anzahl der neuen Positionen und die Methode, die das Flag eines Handelsereignis zurückgibt, das auf dem Konto stattgefunden hat, in den 'public' Teil der Klasse zurückgeben:

//+------------------------------------------------------------------+
//| 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
   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 Anzahl (1) neuer Pending-Orders, (2) neuer Positionen, (3) das aufgetretene Flag des Handelsereignisses
   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;                          }
   //--- Konstructor
                     CMarketCollection(void);
   //--- Aktualisieren der Liste der Pending-Orders und Positionen
   void              Refresh(void);
  };
//+------------------------------------------------------------------+

Lassen Sie uns die Methode zur Aktualisierung des aktuellen Marktstatus implementieren:

//+------------------------------------------------------------------+
//| 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;
      CMarketPending *order=new CMarketPending(ticket);
      if(order==NULL) continue;
      if(this.m_list_all_orders.InsertSort(order))
        {
         this.m_struct_curr_market.hash_sum_acc+=(long)ticket;
         this.m_struct_curr_market.total_volumes+=::OrderGetDouble(ORDER_VOLUME_INITIAL);
         this.m_struct_curr_market.total_pending++;
        }
      else
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
         delete order;
        }
     }
#endif 
//--- 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_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();
     }
  }
//+------------------------------------------------------------------+

Bevor wir die Methode analysieren, machen wir einen kleinen Exkurs: Da wir ständig relevante Daten zu allen Marktorders und Positionen benötigen, können wir die Liste bei jedem Tick löschen und mit Daten aus dem Marktumfeld füllen. Alternativ können wir die Liste auch einmal ausfüllen und nur die Daten ändern, die sich ändern könnten. Auf den ersten Blick scheint es, dass es schneller wäre, nur geänderte Daten zu ändern. Aber um das zu tun, müssten wir Folgendes tun:

  1. Durch die Liste der Marktorder und Terminalpositionen gehen und die Bibliotheksliste damit ausfüllen.
  2. Bei jedem Tick, gehen Sie durch die Liste der Marktorder des Terminals und Positionen, nehmen Sie die sich ändernden Daten, suchen Sie nach Aufträgen und Positionen mit dem gleichen Ticket in der Bibliotheksliste und aktualisieren Sie die vorhandenen Daten.
  3. Wenn ein Auftrag entfernt oder eine Position geschlossen wird, entfernen Sie ihn aus der Bibliotheksliste.

Dies scheint aufwendiger zu sein, als die Bibliotheksliste einfach zu löschen und mit Marktorders und Terminalpositionen in einer Schleife zu befüllen.

Gehen wir daher einen einfacheren Weg: Löschen Sie die Liste und füllen Sie sie erneut aus. Natürlich hindert uns nichts daran, die Methode der Datensuche und -aktualisierung in der bereits vorhandenen Bibliotheksliste auszuprobieren. Wir werden es versuchen, wenn wir mit der Bibliothek arbeiten und ihre Arbeitsgeschwindigkeit verbessern ("einfach-zu-komplex" Basis).

Nun wollen wir sehen, wie die Methode zur Aktualisierung der Collection der Marktorder und Positionen aufgebaut ist.

Zu Beginn der Methode werden die Struktur der aktuellen Marktdaten, Ereignisflags, Volumenänderungswert sowie alle Variablen bezüglich der Anzahl der Aufträge und Positionen zurückgesetzt und die Collection wird gelöscht.

Dann wird die Prüfung auf Zugehörigkeit zu MQL4 oder MQL5 durchgeführt.
Da wir in diesem Stadium den MQL5-Code erstellen, lassen Sie uns einen Blick auf die MQL5-Version werfen:

Im Gegensatz zu MQL4 werden in MQL5 Aufträge und Positionen in verschiedenen Listen gespeichert.
Daher erhalten Sie die Gesamtzahl der Positionen auf dem Konto, bewegen Sie sich dann durch alle Terminalpositionen in einer Schleife, wählen Sie das Ticket der nächsten Position, erzeugen Sie ein Positionsobjekt und fügen Sie es zur Collection der aktiven Aufträge und Bibliothekspositionen hinzu.

Auf die gleiche Weise fügen Sie alle Pending-Order hinzu, die derzeit auf dem Konto vorhanden sind. Abrufen der Gesamtzahl der Aufträge auf dem Konto nur für die Pending-Order und in einer Schleife durch die Liste der Aufträge des Terminals für den Erhalt eines Auftragstickets und Hinzufügen eines Auftragsobjekts zur Bestandsliste der aktiven Aufträge und Positionen der Bibliothek.

Nachdem beide Schleifen durchlaufen wurden, enthält die Collection der aktiven Aufträge und Positionen der Bibliothek die Objekte der Aufträge und Positionen, die derzeit auf dem Konto vorhanden sind. Als Nächstes überprüfen Sie das Flag des ersten Starts (der Wert der "vorherigen" Hash-Summe gleich -1 wird hier als Flag verwendet). Wenn dies der erste Start ist, werden die Werte aller "vergangenen" Werte mit der Methode SavePrevValues() in die Struktur kopiert, die diese Werte speichert. Wenn dies nicht der erste Lauf ist, wird der Wert der vergangenen Hash-Summe mit dem Wert der aktuellen Hash-Summe verglichen, die beim Übergeben der Schleifen der Sammelkontodaten an die Collection berechnet wird. Wenn die vorherige Hash-Summe nicht gleich der aktuellen ist, dann ist eine Änderung auf dem Konto eingetreten.

In diesem Fall wird die Differenz zwischen der aktuellen und der vorherigen Anzahl von Aufträgen in der Variablen gesetzt, die die neue Anzahl von Kontoaufträgen speichert, während die Differenz zwischen der aktuellen und der vorherigen Anzahl von Positionen in der Variablen, die die neue Anzahl von Kontopositionen speichert, eingestellt wird. Speichern Sie den Wert, um den sich das Gesamtkontovolumen geändert hat, setzen Sie das Kennzeichen für die Volumenänderung, sowie das Flag eines aufgetretenen Trading-Ereignisses, und fügen Sie schließlich neue Werte zur Struktur der "vorherigen" Werte hinzu, indem Sie die Methode SavePrevValues() zur späteren Überprüfung verwenden.

Die Methode SavePrevValues() kopiert einfach die Struktur mit den aktuellen Werten in die Struktur mit den vorherigen.

Um die Funktionsweise der Aktualisierungsmethode für Marktorders und Positionslisten und ihre gemeinsame Arbeit mit der Aktualisierungsmethode für historische Orders und Deals-Listen zu überprüfen, verwenden Sie den letzten Test EA aus dem Ordner Part03 namens TestDoEasyPart03_4.mq5:

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_4.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>
//--- Globale Variablen
CEngine        engine;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 
   
  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Timer Funktion                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   engine.OnTimer();
  }
//+------------------------------------------------------------------+

Um zu sehen, welche Änderungen beim Hinzufügen und Verfolgen von Collectionen von Marktorders und Positionen vorgenommen wurden, fügen Sie die folgenden Zeichenketten dem Timer-Ereignishandler der Klasse CEngine hinzu:

//+------------------------------------------------------------------+
//| CEngine Timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
   //--- Timer der Collection der historischen Aufträge und Deals, und aus der Marktorders und Positionen
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL && counter.IsTimeDone())
        {
         //--- Aktualisieren der Liste 
         this.m_market.Refresh(); 
         this.m_history.Refresh();
         //--- Aktionen beim ersten Start
         if(this.IsFirstStart())
           {
            return;
           }
         //--- Prüfen der Änderung des Marktstatus
         if(this.m_market.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие на счёте","New trading event on account"));
           }
         //--- Prüfen der Änderung des Kontohistorie
         if(this.m_history.IsTradeEvent())
           {
            Print(DFUN,TextByLanguage("Новое торговое событие в истории счёта","New trading event in account history"));
           }
        }
     }
  }
//+------------------------------------------------------------------+

Alles ist einfach hier. Die Beendigung des Wartens im Sammeltimerzähler wird zuerst überprüft. Wenn die Pause vorbei ist, werden die Listen der Marktorders und Positionen, sowie der historischen Aufträge und Deals aktualisiert. Während des ersten Starts sind noch keine Aktionen erforderlich. Wenn Sie ein Flag eines aufgetretenen Kontoereignisses erhalten , zeigen Sie die entsprechende Methode im Journal an. Dasselbe geschieht, wenn Empfang des Ereignis-Flags in der Kontohistorie.

Kompilieren Sie den Test-EA und starten Sie ihn. Wenn wir nun eine Position eröffnen, erscheinen zwei Einträge im Journal:

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

Die erste zeigt an, dass ein Trading-Ereignis auf einem Konto stattgefunden hat, während die zweite darüber informiert, dass ein neues Ereignis in die Kontohistorie aufgenommen wurde. In diesem Fall besteht ein Handelsereignis auf dem Konto aus Erhöhung der Anzahl der Positionen um 1, während ein neues Ereignis in der Kontohistorie das Erscheinen einer einzigen neuen Eröffnungsmarktordnung und eines neuen Deals bedeutet — "Markteintritt".

Wenn wir jetzt eine Position schließen, erscheinen die gleichen beiden Einträge im Journal, aber jetzt sind die Werte unterschiedlich: Ein Handelsereignis auf dem Konto verringert die Anzahl der Positionen um 1, während ein neues Ereignis in der Kontoverlaufhistorie eine einzige neue schließende Marktorder und ein einziges neues Deal hinzufügt — "Market Exit".

Was kommt als Nächstes?

Im nächsten Artikel werden wir die Entwicklung des Hauptbibliothekselements (die Klasse CEngine) fortsetzen und Ereignisse der Handhabung aus der Collection implementieren und an das Programm 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.
Hinterlassen Sie Ihre Fragen, Kommentare und Anregungen in den Kommentaren.

Zurück zum Inhalt


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/5687

Beigefügte Dateien |
MQL5.zip (36.13 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (4)
Christian
Christian | 13 Mai 2019 in 17:26

Wieder eine gute Serie.

Weiter so !


Schön wäre es noch das die Artikel untereinander verlinkt wären.

Momentan kommt man nur vom 3. zum 1. Also immer vom neuesten zu alten.

Vom 1. aber nicht zum 3.




Oder habe ich was übersehen ?

Könnt ihr Artikel nachträglich ändern ?

Grüße

Carl Schreiber
Carl Schreiber | 13 Mai 2019 in 18:31
Christian:

Wieder eine gute Serie.

Weiter so !


Schön wäre es noch das die Artikel untereinander verlinkt wären.

Momentan kommt man nur vom 3. zum 1. Also immer vom neuesten zu alten.

Vom 1. aber nicht zum 3.

...

Oder habe ich was übersehen ?

Könnt ihr Artikel nachträglich ändern ?

Grüße

Im Artikel steht, dass die Reihe ein Work-in-Progress ist, was bedeutet, dass als der erste Artikel veröffentlicht wurde es den dritten noch nicht gab, den man, nach Dir, hätte verlinken sollen.

Sind doch nur ein paar mehr Klicks - sooo faul ;)

Christian
Christian | 13 Mai 2019 in 18:43
Carl Schreiber:

Sind doch nur ein paar mehr Klicks - sooo faul ;)

Nein Carl , ich finde was ich suche.

Ich tuhe das nur für das Forum um denen die diese Fähigkeiten nicht besitzen das finden des nächsten Artikels zu erleichtern.

Artyom Trishkin
Artyom Trishkin | 13 Mai 2019 in 19:41
Christian:

Нет, Карл, я найду то, что ищу.

Я делаю это только для форума, чтобы тем, кто не обладает этими навыками, было легче найти следующую статью.

Начиная со статьи №4 будут линки на предыдущие части. Готово уже 8 статей, в ожидании проверки находятся две статьи. Но будет больше. Много больше. Ждите переводов. Ну или читайте на русском ;)

EN (Google):

Starting from article number 4 there will be links to the previous parts. Already published 8 articles in the Russian segment of the forum. Pending publication - two articles are under review. But there will be more. Much more. Wait for translations. Well, or read in Russian ;)

Die rechnerische Fähigkeiten von MATLAB 2018 im MetaTrader 5 nutzen Die rechnerische Fähigkeiten von MATLAB 2018 im MetaTrader 5 nutzen
Nach dem Upgrade des MATLAB-Pakets im Jahr 2015 ist es notwendig, auf eine moderne Art der Erstellung von DLL-Bibliotheken umzustellen. Der Artikel veranschaulicht anhand eines exemplarischen prädiktiven Indikators die Besonderheiten der Verknüpfung von MetaTrader 5 und MATLAB mit modernen 64-Bit-Versionen der Plattformen, die heute genutzt werden. Mit der gesamten Verbindungssequenz von MATLAB können MQL5-Entwickler Anwendungen mit erweiterten Rechenfunktionen viel schneller erstellen und so "Fallstricke" vermeiden.
Optimale Farben für Handelsstrategien Optimale Farben für Handelsstrategien
In diesem Artikel werden wir ein Experiment durchführen: Wir werden die Optimierungsergebnisse einfärben. Die Farbe wird durch drei Parameter bestimmt: die Werte für Rot, Grün und Blau (RGB). Es gibt noch andere Methoden der Farbcodierung, die ebenfalls drei Parameter verwenden. So können drei Prüfparameter in eine Farbe umgewandelt werden, die die Werte visuell darstellt. Lesen Sie diesen Artikel, um herauszufinden, ob eine solche Darstellung nützlich sein kann.
Entwicklung eines plattformübergreifenden Grid-EAs Entwicklung eines plattformübergreifenden Grid-EAs
In diesem Artikel werden wir lernen, wie man Expert Advisors (EAs) erstellt, die sowohl in MetaTrader 4 als auch in MetaTrader 5 arbeiten. Zu diesem Zweck werden wir ein EA entwickeln, der Auftragsraster (grids) erstellt. Raster-EAs oder Grider sind EAs, die mehrere Limit-Orders über dem aktuellen Preis und gleichzeitig die gleiche Anzahl von Limit-Orders unter ihm platzieren.
Integration von MetaTrader 5 und Python: Daten senden und empfangen Integration von MetaTrader 5 und Python: Daten senden und empfangen
Eine umfassende Datenverarbeitung erfordert umfangreiche Werkzeuge und geht oft über den Sandkasten (Sandbox) einer einzigen Anwendung hinaus. Für die Verarbeitung und Analyse von Daten, Statistiken und maschinellem Lernen werden spezielle Programmiersprachen verwendet. Eine der führenden Programmiersprachen für die Datenverarbeitung ist Python. Der Artikel enthält eine Beschreibung, wie man MetaTrader 5 und Python über Sockets verbindet und wie man Kurse über die Terminal-API erhält.