MetaTrader 5 herunterladen

Grundlagen der Programmierung in MQL5: Listen

20 Mai 2016, 13:10
Dennis Kirichenko
0
381

Einleitung

Die neue Version der MQL5-Sprache liefert Entwicklern von automatisierten Handelssystemen effektive Werkzeuge für die Umsetzung komplexer Aufgaben. Es lässt sich nicht leugnen, dass die Programmierfunktionen der Sprache wesentlich erweitert wurden. Allein die OOP-Features von MQL5 sind schon viel wert. Auch die Standardbibliothek sollte nicht unerwähnt bleiben. Anhand des Fehlercodes 359 ist davon auszugehen, dass Klassenvorlagen bald unterstützt werden dürften.

In diesem Beitrag möchte ich auf ein Thema eingehen, das als Erweiterung oder Fortsetzung der Themen zu Datentypen und deren Datensätzen betrachtet werden kann. An dieser Stelle möchte ich mich auf einen Beitrag auf der Webseite der MQL5.community beziehen. Eine sehr detaillierte und umfassende Beschreibung der Prinzipien und der Logik der Arbeit mit Arrays wurde von Dmitri Fedoseev (Integer) in seinem Beitrag "Grundlagen der Programmierung in MQL5: Arrays" bereitgestellt.

Heute schlage ich also vor, Listen zu behandeln, genauer gesagt verkettete lineare Listen. Wir beginnen mit der Struktur, Bedeutung und Logik von Listen. Anschließend betrachten wir die damit verbundenen, bereits in der Standardbibliothek enthaltenen Werkzeuge. Abschließend werde ich Beispiele dafür liefern, wie Listen bei der Arbeit mit MQL5 angewendet werden können.

  1. Die Konzepte "Liste" und "Knoten": Theoretischer Teil
  2. Die Konzepte "Liste" und "Knoten": Programmierung
  3. Listen in der MQL5-Standardbibliothek
  4. Anwendungsbeispiele für Listen in MQL5


1. Die Konzepte "Liste" und "Knoten": Theoretischer Teil

Was ist also eine Liste aus der Sicht eines Entwicklers und was machen Sie daraus? Für eine Beispielhafte Definition dieses Begriffs stütze ich mich auf Wikipedia als öffentliche Informationsquelle:

In der Informatik handelt es sich bei der Liste um einen abstrakten Datentyp, der eine endliche, angeordnete Sammlung von Werten umsetzt, wobei ein Wert mehr als einmal vorkommen kann. Eine Instanz einer Liste ist eine Computerdarstellung des mathematischen Konzepts einer endlichen Sequenz – eines Tupels. Jede Instanz eines Werts in der Liste wird für gewöhnlich als Posten, Eintrag oder Element der Liste bezeichnet. Falls der gleiche Wert mehrfach vorkommt, wird jedes Vorkommen als einzelner Posten betrachtet.

Der Begriff 'Liste' wird auch für mehrere konkrete Datenstrukturen verwendet, die genutzt werden können, um abstrakte Listen umzusetzen, insbesondere verkettete Listen.

Ich denke, Sie werden zustimmen, dass diese Definition etwas zu akademisch ist.

Für die Zwecke dieses Beitrags interessieren wir uns mehr für den letzten Satz dieser Definition. Bauen wir also darauf auf:

In der Informatik handelt es sich bei einer verketteten Liste um eine grundlegende dynamische Datenstruktur aus Knoten, wobei jeder Knoten aus Daten und einem oder zwei Verweisen ('Links') zum nächsten und/oder vorherigen Knoten der Liste besteht.[1] Der wesentliche Vorteil einer verketteten Liste gegenüber einem herkömmlichen Array ist strukturelle Flexibilität: Die Sequenz der Posten der verketteten Liste muss nicht zwangsläufig der Sequenz der Datenelemente im Computerspeicher entsprechen und interne Verknüpfungen der Listenposten bleiben für die Überquerung der Liste immer erhalten.

Versuchen wir, dies Schritt für Schritt nachzuvollziehen.

In der Informatik ist die Liste von sich aus bereits ein bestimmter Datentyp. Damit haben wir uns bereits befasst. Dieser Datentyp ist eher synthetisch, da er andere Datentypen beinhaltet. Eine Liste ist einem Array gewissermaßen ähnlich. Wenn ein Array eines bestimmten Datentypen je als neuer Datentyp eingestuft wurde, wäre das eine Liste. Doch das ist nicht die ganze Wahrheit.

Der wesentliche Vorteil einer Liste ist, dass sie das Einfügen und Entfernen von Knoten nach Bedarf an jeder Stelle der Liste erlaubt. Hier ähnelt die Liste einem dynamischen Array, nur muss man bei einer Liste nicht die ganze Zeit die Funktion ArrayResize() nutzen.

Was die Anordnung der Speicherelemente betrifft, werden Listenknoten nicht auf die gleiche Weise gespeichert und müssen nicht gespeichert werden, wie Array-Elemente in benachbarten Speicherabschnitten gespeichert werden.

Und das ist auch in etwa alles. Machen wir weiter in der Liste.


1.1 Knoten in einer einfach verketteten Liste

In Listen können Knoten anstelle von Posten gespeichert werden. Ein Knoten ist ein aus zwei Teilen bestehender Datentyp.

Der erste Teil ist ein Datenfeld, der zweite wird für Verbindungen mit anderen Knoten genutzt (Abb. 1). Der erste Knoten in der Liste wird als 'Head' (Kopf) bezeichnet, der letzte als 'Tail' (Schwanz). Das Linkfeld des Schwanzes enthält einen NULL-Verweis. Es wird grundsätzlich dafür verwendet, das Fehlen anderer Knoten in der Liste zu signalisieren. Weitere spezielle Quellen beziehen sich auf den Rest der Liste nach dem Kopf als 'Schwanz'.

Abb. 1 Knoten in einer einfach verketteten Liste

Abb. 1 Knoten in einer einfach verketteten Liste

Abgesehen von Knoten in einer einfach verketteten Liste gibt es noch andere Arten von Knoten. Ein Knoten in einer doppelt verketteten Liste ist vielleicht die häufigste darunter.


1.2 Knoten in einer doppelt verketteten Liste

Wir werden weiterhin einen Knoten brauchen, der die Anforderungen einer doppelt verketteten Liste erfüllt. Der Unterschied zum vorherigen Typ besteht darin, dass er einen weiteren Link beinhaltet, der auf den vorherigen Knoten verweist. Und natürlich enthält der Knoten des Kopfs der Liste einen NULL-Verweis. In dem Diagramm, das die Struktur der Liste mit solchen Knoten zeigt (Abb. 2), werden Verweise zu vorherigen Knoten mit roten Pfeilen verdeutlicht.

Abb. 2 Knoten in einer doppelt verketteten Liste

Abb. 2 Knoten in einer doppelt verketteten Liste


Die Möglichkeiten eines Knotens in einer doppelt verketteten Liste sind denen in einer einfach verketteten Liste also ähnlich. Sie müssen lediglich einen weiteren Verweis zum vorherigen Knoten verarbeiten.


1.3 Knoten in einer kreisförmigen doppelt verketteten Liste

Es gibt Fälle, in denen die oben aufgeführten Knoten auch in nichtlinearen Listen verwendet werden können. Obwohl dieser Beitrag vorwiegend lineare Listen beschreibt, möchte ich auch ein Beispiel einer kreisförmigen Liste aufführen.

Abb. 3 Knoten in einer doppelt verketteten Liste

Abb. 3 Knoten in einer kreisförmigen doppelt verketteten Liste


Das Diagramm der kreisförmigen doppelt verketteten Liste (Abb. 3) zeigt, dass Knoten mit zwei Verweisfeldern einfach kreisförmig verkettet sind. Dies geschieht mithilfe der orangefarbenen und grünen Pfeile. Somit wird der Kopfknoten mit dem Schwanz verbunden (wie das vorherige Element). Und das Verweisfeld des Schwanzknotens ist nicht leer, da es zum Kopf zeigt.


1.4 Wesentliche Listenoperationen

Wie es in der Fachliteratur dargelegt wird, lassen sich alle Listenoperationen in 3 Basisgruppen unterteilen:

  1. Hinzufügung (eines neuen Knotens zur Liste);
  2. Löschung (eines Knotens aus der Liste);
  3. Prüfung (der Daten eines Knotens).

Zu den Methoden der Hinzufügung gehören:

  • Hinzufügen eines neuen Knotens zum Anfang der Liste;
  • Hinzufügen eines neuen Knotens zum Ende der Liste;
  • Hinzufügen eines Knotens zur angegebenen Position in der Liste;
  • Hinzufügen eines Knotens zu einer leeren Liste;
  • Konstruktor mit Parametern.

Was die Löschungsoperationen betrifft, spiegeln sie quasi die entsprechenden Operationen aus der Gruppe der Hinzufügungen wider:

  • Löschen des Kopfknotens;
  • Löschen des Schwanzknotens;
  • Löschen eines Knotens aus der angegebenen Position in der Liste;
  • Destruktor.

Hier möchte ich festhalten, dass der Destruktor nicht nur dem korrekten Abschließen und Beenden der Listenoperation dient, sondern auch der ordnungsgemäßen Löschung aller Listenelemente.

Die dritte Gruppe aus verschiedenen Prüfungsoperationen gewährt tatsächlichen den Zugriff auf Knoten oder Knotenwerte in der Liste:

  • Suche nach einem bestimmten Wert;
  • Prüfung auf leere Liste;
  • Abrufen des Werts des i. Knotens in der Liste;
  • Abrufen des Pointers zum i. Knoten in der Liste;
  • Abrufen der Listengröße;
  • Drucken der Werte der Listenelemente.

Zusätzlich zu den Basisgruppen möchte ich eine vierte Servicegruppe vorstellen. Sie bedient die vorherigen Gruppen:

  • Zuweisungsoperator;
  • Kopierkonstruktor;
  • Arbeit mit dynamischem Pointer;
  • Kopieren der Liste nach Werten;
  • Sortieren.

Das ist alles. Der Entwickler kann die Funktionalität und Features einer Listenklasse jederzeit nach Bedarf erweitern.


2. Die Konzepte "Liste" und "Knoten": Programmierung

In diesem Teil schlage ich vor, dass wir uns direkt der Programmierung von Knoten und Listen widmen. Illustrationen des Codes werden bereitgestellt, wo sie erforderlich sind.

2.1 Knoten in einer einfach verketteten Liste

Lassen Sie uns die Grundlage für die Knotenklasse (Abb. 4) für die Anforderungen einer einfach verketteten Liste schaffen. Im Beitrag "Wie man mit einem UML-Werkzeug einen Expert Advisor entwickelt" können Sie sich mit der Notation des Klassendiagramms (Modells) vertraut machen (siehe Abb. 5. UML-Modell der Klasse CTradeExpert).

Klassenmodell CiSingleNode

Abb. 4 Klassenmodell CiSingleNode

Versuchen wir nun, mit dem Code zu arbeiten. Er basiert auf dem Beispiel aus dem Buch von Art Friedman et al.: "C/C++ Annotated Archives".

//+------------------------------------------------------------------+
//|                     CiSingleNode class                           |
//+------------------------------------------------------------------+
class CiSingleNode
  {
protected:
   int               m_val;   // data
   CiSingleNode     *m_next;  // pointer to the next node
public:
   void              CiSingleNode(void);                             // default constructor
   void              CiSingleNode(int _node_val);                    // parameterized constructor
   void             ~CiSingleNode(void);                             // destructor
   void              SetVal(int _node_val);                          // set-method for data
   void              SetNextNode(CiSingleNode *_ptr_next);           // set-method for the next node
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev){};         // set-method for the previous node
   virtual CiSingleNode *GetPrevNode(void) const {return NULL;};     // get-method for the previous node
   CiSingleNode     *GetNextNode(void) const;                        // get-method for the next node 
   int               GetVal(void){TRACE_CALL(_t_flag) return m_val;} // get-method for data 
  };

Ich werde nicht jede Methode der Klasse CiSingleNode beschreiben. Sie können sie sich in der angehängten Datei CiSingleNode.mqh näher ansehen. Allerdings möchte ich Ihre Aufmerksamkeit auf eine wichtige Nuance lenken. Die Klasse enthält virtuelle Methoden, die mit den vorherigen Knoten arbeiten. Dabei handelt es sich tatsächlich um Dummies und ihr Vorhandensein dient eher dem Zweck des Polymorphismus für zukünftige Nachfahren..

Der Code nutzt die Präprozessordirektive TRACE_CALL(f) zum Verfolgen der Aufrufe jeder verwendeten Methode.

#define TRACE_CALL(f) if(f) Print("Calling: "+__FUNCSIG__);

Indem Sie nur über die Klasse CiSingleNode verfügen, können Sie bereits eine einfach verkettete Liste erstellen. Lassen sie mich ein Codebeispiel aufführen.

//=========== Example 1 (processing the CiSingleNode type )
 
   CiSingleNode *p_sNodes[3];                             // #1
   p_sNodes[0]=NULL;
   srand(GetTickCount()); // initialize a random number generator
//--- create nodes
   for(int i=0;i<ArraySize(p_sNodes);i++)
      p_sNodes[i]=new CiSingleNode(rand());               // #2
//--- links
   for(int j=0;j<(ArraySize(p_sNodes)-1);j++)
      p_sNodes[j].SetNextNode(p_sNodes[j+1]);             // #3
//--- check values
   for(int i=0;i<ArraySize(p_sNodes);i++)
     {
      int val=p_sNodes[i].GetVal();                       // #4
      Print("Node #"+IntegerToString(i+1)+                // #5
            " value = "+IntegerToString(val));
     }
//--- check next-nodes
   for(int j=0;j<(ArraySize(p_sNodes)-1);j++)
     {
      CiSingleNode *p_sNode_next=p_sNodes[j].GetNextNode(); // #9
      int snode_next_val=p_sNode_next.GetVal();             // #10
      Print("Next-Node #"+IntegerToString(j+1)+             // #11
            " value = "+IntegerToString(snode_next_val));
     }
//--- delete nodes
   for(int i=0;i<ArraySize(p_sNodes);i++)
      delete p_sNodes[i];                                   // #12 

In String #1 deklarieren wir ein Array von Pointern zu Objekten des Typen CiSingleNode. In String #2 wird das Array mit den erstellen Pointern gefüllt. Für die Daten jedes Knotens nehmen wir eine pseudozufällige ganze Zahl im Bereich zwischen 0 und 32767 mithilfe der Funktion rand(). Die Knoten werden mit dem nächsten Pointer in String #3 verbunden. In den Strings #4-5 prüfen wir die Werte der Knoten und in den Strings #9-11 prüfen wir die Performance der Verknüpfungen. Die Pointer werden in String #12 gelöscht.

Hier sehen Sie, was ins Protokoll gedruckt wurde.

DH      0       23:23:10        test_nodes (EURUSD,H4)  Node #1 value = 3335
KP      0       23:23:10        test_nodes (EURUSD,H4)  Node #2 value = 21584
GI      0       23:23:10        test_nodes (EURUSD,H4)  Node #3 value = 917
HQ      0       23:23:10        test_nodes (EURUSD,H4)  Next-Node #1 value = 21584
HI      0       23:23:10        test_nodes (EURUSD,H4)  Next-Node #2 value = 917

Die resultierende Knotenstruktur kann schematisch folgendermaßen dargestellt werden (Abb. 5).

Abb. 5 Verbindungen zwischen den Knoten im Array *p_sNodes[3] von CiSingleNode

Abb. 5 Verbindungen zwischen den Knoten im Array *p_sNodes[3] von CiSingleNode

Fahren wir nun mit Knoten in einer doppelt verketteten Liste fort.


2.2 Knoten in einer doppelt verketteten Liste

Als Erstes müssen wir uns daran erinnern, dass ein Knoten in einer doppelt verketteten Liste in dem Punkt anders ist, dass er zwei Pointer hat: Pointer zum nächsten Knoten und Pointer zum vorherigen Knoten. D. h., neben der Verknüpfung mit dem nächsten Knoten müssen Sie einen Pointer zum vorherigen Knoten zu einer einfach verketteten Liste hinzufügen.

Ich empfehle, dafür Vererbung als Klassenbeziehung zu nutzen. Daraufhin kann das Klassenmodell für einen Knoten in einer doppelt verketteten Liste so aussehen (Abb. 6).

Klassenmodell CDoubleNode

Abb. 6 Klassenmodell CDoubleNode

Nun ist es an der Zeit, den Code anzusehen.

//+------------------------------------------------------------------+
//|                    CDoubleNode class                             |
//+------------------------------------------------------------------+
class CDoubleNode : public CiSingleNode
  {
protected:
   CiSingleNode     *m_prev;  // pointer to the previous node 

public:
   void              CDoubleNode(void);                     // default constructor
   void              CDoubleNode(int node_val);             // parameterized constructor
   void             ~CDoubleNode(void){TRACE_CALL(_t_flag)};// destructor
   virtual void      SetPrevNode(CiSingleNode *_ptr_prev);  // set-method for the previous node
   virtual CiSingleNode *GetPrevNode(void) const;           // get-method for the previous node CDoubleNode
  };

Es gibt nur sehr wenige zusätzliche Methoden. Diese sind virtuell und hängen mit der Arbeit mit dem vorherigen Knoten zusammen. Die vollständige Beschreibung der Klasse wird in CDoubleNode.mqh bereitgestellt.

Versuchen wir, eine doppelt verkettete Liste auf Basis der Klasse CDoubleNode zu erstellen. Lassen sie mich ein Codebeispiel aufführen.

//=========== Example 2 (processing the CDoubleNode type)

   CiSingleNode *p_dNodes[3];                             // #1
   p_dNodes[0]=NULL;
   srand(GetTickCount()); // initialize a random number generator
//--- create nodes
   for(int i=0;i<ArraySize(p_dNodes);i++)
      p_dNodes[i]=new CDoubleNode(rand());                // #2
//--- links
   for(int j=0;j<(ArraySize(p_dNodes)-1);j++)
     {
      p_dNodes[j].SetNextNode(p_dNodes[j+1]);             // #3
      p_dNodes[j+1].SetPrevNode(p_dNodes[j]);             // #4
     }
//--- check values
   for(int i=0;i<ArraySize(p_dNodes);i++)
     {
      int val=p_dNodes[i].GetVal();                       // #4
      Print("Node #"+IntegerToString(i+1)+                // #5
            " value = "+IntegerToString(val));
     }
//--- check next-nodes
   for(int j=0;j<(ArraySize(p_dNodes)-1);j++)
     {
      CiSingleNode *p_sNode_next=p_dNodes[j].GetNextNode(); // #9
      int snode_next_val=p_sNode_next.GetVal();             // #10
      Print("Next-Node #"+IntegerToString(j+1)+             // #11
            " value = "+IntegerToString(snode_next_val));
     }
//--- check prev-nodes
   for(int j=0;j<(ArraySize(p_dNodes)-1);j++)
     {
      CiSingleNode *p_sNode_prev=p_dNodes[j+1].GetPrevNode(); // #12
      int snode_prev_val=p_sNode_prev.GetVal();               // #13
      Print("Prev-Node #"+IntegerToString(j+2)+               // #14
            " value = "+IntegerToString(snode_prev_val));
     }
//--- delete nodes
   for(int i=0;i<ArraySize(p_dNodes);i++)
      delete p_dNodes[i];                                     // #15 

Im Prinzip ist es ähnlich wie die Erstellung einer einfach verketteten Liste, doch es gibt ein paar Besonderheiten. Beachten Sie, dass das Pointer-Array p_dNodes[] in String #1 deklariert wird. Der Typ von Pointern kann als identisch zur Basisklasse festgelegt werden. Das Prinzip des Polymorphismus in String #2 hilft uns, sie in Zukunft zu erkennen. Vorherige Knoten werden in den Strings #12-14 geprüft.

Die folgenden Informationen wurden zum Protokoll hinzugefügt.

GJ      0       16:28:12        test_nodes (EURUSD,H4)  Node #1 value = 17543
IQ      0       16:28:12        test_nodes (EURUSD,H4)  Node #2 value = 1185
KK      0       16:28:12        test_nodes (EURUSD,H4)  Node #3 value = 23216
DS      0       16:28:12        test_nodes (EURUSD,H4)  Next-Node #1 value = 1185
NH      0       16:28:12        test_nodes (EURUSD,H4)  Next-Node #2 value = 23216
FR      0       16:28:12        test_nodes (EURUSD,H4)  Prev-Node #2 value = 17543
LI      0       16:28:12        test_nodes (EURUSD,H4)  Prev-Node #3 value = 1185

Die resultierende Knotenstruktur kann schematisch folgendermaßen dargestellt werden (Abb. 7):

Abb. 7 Verbindungen zwischen den Knoten im Array *p_sNodes[3] von CDoubleNode

Abb. 7 Verbindungen zwischen den Knoten im Array *p_sNodes[3] von CDoubleNode

Ich schlage vor, wir betrachten nun einen Knoten, der bei der Erstellung einer entrollten doppelt verketteten Liste benötigt wird.


2.3 Knoten in einer entrollten doppelt verketteten Liste

Stellen Sie sich einen Knoten mit einem Datenelement vor, der dem gesamten Array zugeordnet werden kann, anstatt eines einzelnen Werts, d. h., er enthält und beschreibt das gesamte Array. Dieser Knoten kann verwendet werden, um eine entrollte Liste zu erstellen. Ich habe beschlossen, an dieser Stelle keine Illustration anzubieten, weil dieser Knoten genau das Gleiche ist wie ein Standardknoten in einer doppelt verketteten Liste. Der einzige Unterschied ist, dass das Attribut "Daten" das gesamte Array kapselt.

Ich nutze erneut die Vererbung. Die Klasse CDoubleNode dient als Basisklasse für einen Knoten in einer entrollten doppelt verketteten Liste. Und das Klassenmodell für einen Knoten in einer entrollten doppelt verketteten Liste sieht so aus (Abb. 8).

Klassenmodell CiUnrollDoubleNode

Abb. 8 Klassenmodell CiUnrollDoubleNode

Die Klasse CiUnrollDoubleNode kann mithilfe des folgenden Codes definiert werden:

//+------------------------------------------------------------------+
//|                    CiUnrollDoubleNode class                      |
//+------------------------------------------------------------------+
class CiUnrollDoubleNode : public CDoubleNode
  {
private:
   int               m_arr_val[]; // data array

public:
   void              CiUnrollDoubleNode(void);               // default constructor 
   void              CiUnrollDoubleNode(int &_node_arr[]);   // parameterized constructor
   void             ~CiUnrollDoubleNode(void);               // destructor
   bool              GetArrVal(int &_dest_arr_val[])const;   // get-method for data array
   bool              SetArrVal(const int &_node_arr_val[]);  // set-method for data array
  };

In CiUnrollDoubleNode.mqh können Sie sich jede Methode näher ansehen.

Betrachten wir einen Konstruktor mit Parametern als Beispiel.

//+------------------------------------------------------------------+
//|                   Parameterized constructor                      |
//+------------------------------------------------------------------+
void CiUnrollDoubleNode::CiUnrollDoubleNode(int &_node_arr[])
   : CDoubleNode(ArraySize(_node_arr))
  {
   ArrayCopy(this.m_arr_val,_node_arr);
   TRACE_CALL(_t_flag)
  }

Hier geben wir mithilfe der Initialisierungsliste die Größe eines eindimensionalen Arrays im Datenelement this.m_val ein.

Anschließend erstellen wir 'manuell' eine entrollte doppelt verkettete Liste und prüfen die Verknüpfungen darin.

//=========== Example 3 (processing the CiUnrollDoubleNode type)

//--- data arrays
   int arr1[],arr2[],arr3[];                                  // #1
   int arr_size=15;
   ArrayResize(arr1,arr_size);
   ArrayResize(arr2,arr_size);
   ArrayResize(arr3,arr_size);
   srand(GetTickCount()); // initialize a random number generator
   
//--- fill the arrays with pseudorandom integers   
   for(int i=0;i<arr_size;i++)
     {
      arr1[i]=rand();                                         // #2
      arr2[i]=rand();
      arr3[i]=rand();
     }
//--- create nodes
   CiUnrollDoubleNode *p_udNodes[3];                          // #3
   p_udNodes[0]=new CiUnrollDoubleNode(arr1);
   p_udNodes[1]=new CiUnrollDoubleNode(arr2);
   p_udNodes[2]=new CiUnrollDoubleNode(arr3);
//--- links
   for(int j=0;j<(ArraySize(p_udNodes)-1);j++)
     {
      p_udNodes[j].SetNextNode(p_udNodes[j+1]);               // #4
      p_udNodes[j+1].SetPrevNode(p_udNodes[j]);               // #5
     }
//--- check values
   for(int i=0;i<ArraySize(p_udNodes);i++)
     {
      int val=p_udNodes[i].GetVal();                          // #6
      Print("Node #"+IntegerToString(i+1)+                    // #7
            " value = "+IntegerToString(val));
     }
//--- check array values
   for(int i=0;i<ArraySize(p_udNodes);i++)
     {
      int t_arr[]; // destination array 
      bool isCopied=p_udNodes[i].GetArrVal(t_arr);            // #8
      if(isCopied)
        {
         string arr_str=NULL;
         for(int n=0;n<ArraySize(t_arr);n++)
            arr_str+=IntegerToString(t_arr[n])+", ";
         int end_of_string=StringLen(arr_str);
         arr_str=StringSubstr(arr_str,0,end_of_string-2);
         Print("Node #"+IntegerToString(i+1)+                 // #9
               " array values = "+arr_str);
        }
     }
//--- check next-nodes
   for(int j=0;j<(ArraySize(p_udNodes)-1);j++)
     {
      int t_arr[]; // destination array 
      CiUnrollDoubleNode *p_udNode_next=p_udNodes[j].GetNextNode(); // #10
      bool isCopied=p_udNode_next.GetArrVal(t_arr);
      if(isCopied)
        {
         string arr_str=NULL;
         for(int n=0;n<ArraySize(t_arr);n++)
            arr_str+=IntegerToString(t_arr[n])+", ";
         int end_of_string=StringLen(arr_str);
         arr_str=StringSubstr(arr_str,0,end_of_string-2);
         Print("Next-Node #"+IntegerToString(j+1)+
               " array values = "+arr_str);
        }
     }
//--- check prev-nodes
   for(int j=0;j<(ArraySize(p_udNodes)-1);j++)
     {
      int t_arr[]; // destination array 
      CiUnrollDoubleNode *p_udNode_prev=p_udNodes[j+1].GetPrevNode(); // #11
      bool isCopied=p_udNode_prev.GetArrVal(t_arr);
      if(isCopied)
        {
         string arr_str=NULL;
         for(int n=0;n<ArraySize(t_arr);n++)
            arr_str+=IntegerToString(t_arr[n])+", ";
         int end_of_string=StringLen(arr_str);
         arr_str=StringSubstr(arr_str,0,end_of_string-2);
         Print("Prev-Node #"+IntegerToString(j+2)+
               " array values = "+arr_str);
        }
     }
//--- delete nodes
   for(int i=0;i<ArraySize(p_udNodes);i++)
      delete p_udNodes[i];                                            // #12
  }

Die Menge an Code ist etwas gestiegen. Das hat damit zu tun, dass wir für jeden Knoten ein Array erstellen und befüllen müssen.

Die Arbeit mit Daten-Arrays beginnt in String #1. Sie entspricht grundsätzlich den vorher betrachteten Knoten. Wir müssen lediglich die Datenwerte jedes Knotens für das gesamte Array drucken (z. B. String #9).

Das war mein Ergebnis:

IN      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #1 value = 15
NF      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #2 value = 15
CI      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #3 value = 15
FQ      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #1 array values = 31784, 4837, 25797, 29079, 4223, 27234, 2155, 32351, 12010, 10353, 10391, 22245, 27895, 3918, 12069
EG      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #2 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756
MK      0       00:09:13        test_nodes (EURUSD.m,H4)        Node #3 array values = 18100, 26358, 31020, 23881, 11256, 24798, 31481, 14567, 13032, 4701, 21665, 1434, 1622, 16377, 25778
RP      0       00:09:13        test_nodes (EURUSD.m,H4)        Next-Node #1 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756
JD      0       00:09:13        test_nodes (EURUSD.m,H4)        Next-Node #2 array values = 18100, 26358, 31020, 23881, 11256, 24798, 31481, 14567, 13032, 4701, 21665, 1434, 1622, 16377, 25778
EH      0       00:09:13        test_nodes (EURUSD.m,H4)        Prev-Node #2 array values = 31784, 4837, 25797, 29079, 4223, 27234, 2155, 32351, 12010, 10353, 10391, 22245, 27895, 3918, 12069
NN      0       00:09:13        test_nodes (EURUSD.m,H4)        Prev-Node #3 array values = 1809, 18553, 23224, 20208, 10191, 4833, 25959, 2761, 7291, 23254, 29865, 23938, 7585, 20880, 25756

Ich schlage vor, wir beenden die Arbeit mit Knoten und fahren direkt mit den Klassendefinitionen verschiedener Listen fort. Die Beispiele 1-3 können im Script test_nodes.mq5 gefunden werden.


2.4 Einfach verkettete Liste

Es ist an der Zeit, ein Klassenmodell einer einfach verketteten Liste aus den Gruppen der wesentlichen Listenoperationen zu erstellen (Abb. 9).

Klassenmodell CiSingleList

Abb. 9 Klassenmodell CiSingleList

Es ist unschwer zu erkennen, dass die Klasse CiSingleList den Knoten des Typen CiSingleNode nutzt. In Bezug auf Arten von Beziehungen zwischen Klassen können wir festhalten:

  1. Die Klasse CiSingleList beinhaltet die Klasse CiSingleNode (Komposition);
  2. Die Klasse CiSingleList nutzt Methoden der Klasse CiSingleNode (Abhängigkeit).

Die Illustration der oben aufgeführten Beziehungen sehen Sie in Abb. 10.

Abb. 10 Typen von Beziehungen zwischen der Klasse CiSingleList und CiSingleNode

Abb. 10 Typen von Beziehungen zwischen der Klasse CiSingleList und CiSingleNode

Erstellen wir eine neue Klasse, CiSingleList. Vorgreifend möchte ich erwähnen, dass alle anderen in diesem Beitrag verwendeten Listenklassen auf dieser Klasse basieren werden. Deshalb ist sie so 'reichhaltig'.

//+------------------------------------------------------------------+
//|                     CiSingleList class                           |
//+------------------------------------------------------------------+
class CiSingleList
  {
protected:
   CiSingleNode     *m_head;    // head
   CiSingleNode     *m_tail;    // tail
   uint              m_size;    // number of nodes in the list
public:
   //--- constructor and destructor
   void              CiSingleList();                              // default constructor 
   void              CiSingleList(int _node_val);                 // parameterized constructor 
   void             ~CiSingleList();                              // destructor                

   //--- adding nodes   
   void              AddFront(int _node_val);                         // add a new node to the beginning of the list
   void              AddRear(int _node_val);                          // add a new node to the end of the list   
   virtual void      AddFront(int &_node_arr[]){TRACE_CALL(_t_flag)}; // add a new node to the beginning of the list
   virtual void      AddRear(int &_node_arr[]){TRACE_CALL(_t_flag)};  // add a new node to the end of the list
   //--- deleting nodes     
   int               RemoveFront(void);                           // delete the head node       
   int               RemoveRear(void);                            // delete the node from the end of the list
   void              DeleteNodeByIndex(const uint _idx);          // delete the ith node from the list

   //--- checking   
   virtual bool      Find(const int _node_val) const;             // find the required value    
   bool              IsEmpty(void) const;                         // check the list for being empty
   virtual int       GetValByIndex(const uint _idx) const;        // value of the ith node in the list
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;   // get the ith node in the list
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list
   CiSingleNode     *GetHeadNode(void) const;                     // get the head node
   CiSingleNode     *GetTailNode(void) const;                     // get the tail node
   virtual uint      Size(void) const;                            // list size
   //--- service
   virtual void      PrintList(string _caption=NULL);             // print the list
   virtual bool      CopyByValue(const CiSingleList &_sList);     // copy the list by values
   virtual void      BubbleSort(void);                            // bubble sorting
   //---templates
   template<typename dPointer>
   bool              CheckDynamicPointer(dPointer &_p);           // template for checking a dynamic pointer
   template<typename dPointer>
   bool              DeleteDynamicPointer(dPointer &_p);          // template for deleting a dynamic pointer

protected:
   void              operator=(const CiSingleList &_sList) const; // assignment operator
   void              CiSingleList(const CiSingleList &_sList);    // copy constructor
   virtual bool      AddToEmpty(int _node_val);                   // add a new node to an empty list
   virtual void      addFront(int _node_val);                     // add a new "native" node to the beginning of the list
   virtual void      addRear(int _node_val);                      // add a new "native" node to the end of the list
   virtual int       removeFront(void);                           // delete the "native" head node
   virtual int       removeRear(void);                            // delete the "native" node from the end of the list
   virtual void      deleteNodeByIndex(const uint _idx);          // delete the "native" ith node from the list
   virtual CiSingleNode *newNode(int _val);                       // new "native" node
   virtual void      CalcSize(void) const;                        // calculate the list size
  };

Die komplette Definition der Klassenmethoden finden Sie in CiSingleList.mqh.

Als ich anfing, diese Klasse zu entwickeln, gab es nur 3 Datenelemente und nur wenige Methoden. Doch da diese Klasse als Basis für andere Klassen diente, musste ich etliche virtuelle Unterfunktionen hinzufügen. Ich werde diese Methoden nicht im Detail beschreiben. Ein Beispiel für den Gebrauch dieser einfach verketteten Listenklasse finden Sie im Script test_sList.mq5.

Bei Ausführung ohne das Tracing-Flag erscheinen die folgenden Einträge im Protokoll:

KG      0       12:58:32        test_sList (EURUSD,H1)  =======List #1=======
PF      0       12:58:32        test_sList (EURUSD,H1)  Node #1, val=14 
RL      0       12:58:32        test_sList (EURUSD,H1)  Node #2, val=666 
MD      0       12:58:32        test_sList (EURUSD,H1)  Node #3, val=13 
DM      0       12:58:32        test_sList (EURUSD,H1)  Node #4, val=11 
QE      0       12:58:32        test_sList (EURUSD,H1)  
KN      0       12:58:32        test_sList (EURUSD,H1)  
LR      0       12:58:32        test_sList (EURUSD,H1)  =======List #2=======
RE      0       12:58:32        test_sList (EURUSD,H1)  Node #1, val=14 
DQ      0       12:58:32        test_sList (EURUSD,H1)  Node #2, val=666 
GK      0       12:58:32        test_sList (EURUSD,H1)  Node #3, val=13 
FP      0       12:58:32        test_sList (EURUSD,H1)  Node #4, val=11 
KF      0       12:58:32        test_sList (EURUSD,H1)  
MK      0       12:58:32        test_sList (EURUSD,H1)  
PR      0       12:58:32        test_sList (EURUSD,H1)  =======renewed List #2=======
GK      0       12:58:32        test_sList (EURUSD,H1)  Node #1, val=11 
JP      0       12:58:32        test_sList (EURUSD,H1)  Node #2, val=13 
JI      0       12:58:32        test_sList (EURUSD,H1)  Node #3, val=14 
CF      0       12:58:32        test_sList (EURUSD,H1)  Node #4, val=34 
QL      0       12:58:32        test_sList (EURUSD,H1)  Node #5, val=35 
OE      0       12:58:32        test_sList (EURUSD,H1)  Node #6, val=36 
MR      0       12:58:32        test_sList (EURUSD,H1)  Node #7, val=37 
KK      0       12:58:32        test_sList (EURUSD,H1)  Node #8, val=38 
MS      0       12:58:32        test_sList (EURUSD,H1)  Node #9, val=666 
OF      0       12:58:32        test_sList (EURUSD,H1)  
QK      0       12:58:32        test_sList (EURUSD,H1)  

Das Script hat 2 einfach verkettete Listen befüllt und anschließend die zweite Liste erweitert und sortiert.


2.5 Doppelt verkettete Liste

Versuchen wir nun, eine doppelt verkettete Liste auf Basis der Liste des vorherigen Typen zu erstellen. Die Illustration des Klassenmodells einer doppelt verketteten Liste sehen sie in Abb. 11:

Klassenmodell CDoubleList

Abb. 11 Klassenmodell CDoubleList

Die Nachfahrenklasse enthält viel weniger Methoden und die Datenelemente fehlen vollständig. Nachfolgend sehen Sie die Definition der Klasse CDoubleList.

//+------------------------------------------------------------------+
//|                      CDoubleList class                           |
//+------------------------------------------------------------------+
class CDoubleList : public CiSingleList
  {
public:
   void              CDoubleList(void);                  // default constructor    
   void              CDoubleList(int _node_val);         // parameterized constructor   
   void             ~CDoubleList(void){};                // destructor                  
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list  

protected:
   virtual bool      AddToEmpty(int _node_val);          // add a node to an empty list
   virtual void      addFront(int _node_val);            // add a new "native" node to the beginning of the list
   virtual void      addRear(int _node_val);             // add a new "native" node to the end of the list 
   virtual int       removeFront(void);                  // delete the "native" head node
   virtual int       removeRear(void);                   // delete the "native" tail node
   virtual void      deleteNodeByIndex(const uint _idx); // delete the "native" ith node from the list
   virtual CiSingleNode *newNode(int _node_val);         // new "native" node
  };

Die komplette Beschreibung der Methoden der Klasse CDoubleList finden Sie in CDoubleList.mqh.

Im Allgemeinen werden virtuelle Funktionen hier nur genutzt, um die Anforderungen des Pointers zum vorherigen Knoten zu bedienen, der in einfach verketteten Listen nicht existiert.

Ein Beispiel für den Gebrauch der Liste des Typen CDoubleList finden Sie im Script test_dList.mq5. Dieses Script demonstriert alle herkömmlichen Listenoperationen, die für diesen Listentyp relevant sind. Der Scriptcode enthält einen interessanten String:

CiSingleNode *_new_node=new CDoubleNode(666);     // create a new node of CDoubleNode type

Es gibt keinen Fehler, weil eine solche Konstruktion in Fällen, in denen der Pointer der Basisklasse ein Objekt der Nachfahrenklasse beschreibt, absolut akzeptabel ist. Das ist einer der Vorteile der Vererbung.

In MQL5 sowie in C++ kann der Pointer zur Basisklasse auf das Objekt der von dieser Basisklasse abgeleiteten Unterklasse zeigen. Doch der umgekehrte Fall ist ungültig.

Wenn Sie den String auf die folgende Weise schreiben:

CDoubleNode*_new_node=new CiSingleNode(666);

gibt der Compiler keinen Fehler und keine Warnung aus, doch das Programm wird ausgeführt, bis es diesen String erreicht. In diesem Fall sehen Sie eine Nachricht über ein falsches Casting von Typen, auf die durch Pointer verwiesen wird. Da der Mechanismus der späten Anbindung erst zum Tragen kommt, wenn das Programm ausgeführt wird, müssen wir die Hierarchie der Beziehungen zwischen Klassen besonders berücksichtigen.

Nach der Ausführung des Scripts enthält das Protokoll die folgenden Einträge:

DN      0       13:10:57        test_dList (EURUSD,H1)  =======List #1=======
GO      0       13:10:57        test_dList (EURUSD,H1)  Node #1, val=14 
IE      0       13:10:57        test_dList (EURUSD,H1)  Node #2, val=666 
FM      0       13:10:57        test_dList (EURUSD,H1)  Node #3, val=13 
KD      0       13:10:57        test_dList (EURUSD,H1)  Node #4, val=11 
JL      0       13:10:57        test_dList (EURUSD,H1)  
DG      0       13:10:57        test_dList (EURUSD,H1)  
CK      0       13:10:57        test_dList (EURUSD,H1)  =======List #2=======
IL      0       13:10:57        test_dList (EURUSD,H1)  Node #1, val=14 
KH      0       13:10:57        test_dList (EURUSD,H1)  Node #2, val=666 
PR      0       13:10:57        test_dList (EURUSD,H1)  Node #3, val=13 
MI      0       13:10:57        test_dList (EURUSD,H1)  Node #4, val=11 
DO      0       13:10:57        test_dList (EURUSD,H1)  
FR      0       13:10:57        test_dList (EURUSD,H1)  
GK      0       13:10:57        test_dList (EURUSD,H1)  =======renewed List #2=======
PR      0       13:10:57        test_dList (EURUSD,H1)  Node #1, val=11 
QI      0       13:10:57        test_dList (EURUSD,H1)  Node #2, val=13 
QP      0       13:10:57        test_dList (EURUSD,H1)  Node #3, val=14 
LO      0       13:10:57        test_dList (EURUSD,H1)  Node #4, val=34 
JE      0       13:10:57        test_dList (EURUSD,H1)  Node #5, val=35 
HL      0       13:10:57        test_dList (EURUSD,H1)  Node #6, val=36 
FK      0       13:10:57        test_dList (EURUSD,H1)  Node #7, val=37 
DR      0       13:10:57        test_dList (EURUSD,H1)  Node #8, val=38 
FJ      0       13:10:57        test_dList (EURUSD,H1)  Node #9, val=666 
HO      0       13:10:57        test_dList (EURUSD,H1)  
JR      0       13:10:57        test_dList (EURUSD,H1)  

Wie bei der einfach verketteten Liste hat das Script die erste (doppelt verkettete) Liste befüllt, kopiert und an die zweite Liste übergeben. Anschließend erhöhte es die Menge der Knoten in der zweiten Liste und sortierte und druckte die Liste.


2.6 Entrollte doppelt verkettete Liste

Dieser Listentyp ist deshalb praktisch, weil er Ihnen die Speicherung nicht nur eines Werts, sondern eines kompletten Arrays ermöglicht.

Bilden wir die Grundlage für die Liste des Typen CiUnrollDoubleList (Abb. 12).

Klassenmodell CiUnrollDoubleList

Abb. 12 Klassenmodell CiUnrollDoubleList

Da wir es hier mit einem Daten-Array zu tun haben, müssen wir die in der indirekten Basisklasse CiSingleList definierten Methoden neu definieren.

Nachfolgend sehen Sie die Definition der Klasse CiUnrollDoubleList.

//+------------------------------------------------------------------+
//|                     CiUnrollDoubleList class                     |
//+------------------------------------------------------------------+
class CiUnrollDoubleList : public CDoubleList
  {
public:
   void              CiUnrollDoubleList(void);                      // default constructor
   void              CiUnrollDoubleList(int &_node_arr[]);          // parameterized constructor
   void             ~CiUnrollDoubleList(void){TRACE_CALL(_t_flag)}; // destructor
   //---
   virtual void      AddFront(int &_node_arr[]);                    // add a new node to the beginning of the list
   virtual void      AddRear(int &_node_arr[]);                     // add a new node to the end of the list
   virtual bool      CopyByValue(const CiSingleList &_udList);      // copy by values
   virtual void      PrintList(string _caption=NULL);               // print the list
   virtual void      BubbleSort(void);                              // bubble sorting

protected:
   virtual bool      AddToEmpty(int &_node_arr[]);                  // add a node to an empty list
   virtual void      addFront(int &_node_arr[]);                    // add a new "native" node to the beginning of the list
   virtual void      addRear(int &_node_arr[]);                     // add a new "native" node to the end of the list
   virtual int       removeFront(void);                             // delete the "native" node from the beginning of the list
   virtual int       removeRear(void);                              // delete the "native" node from the end of the list
   virtual void      deleteNodeByIndex(const uint _idx);            // delete the "native" ith node from the list
   virtual CiSingleNode *newNode(int &_node_arr[]);                 // new "native" node
  };

Die komplette Definition der Klassenmethoden finden Sie in CiUnrollDoubleList.mqh.

Führen wir das Script test_UdList.mq5 aus, um die Operation der Klassenmethoden zu überprüfen. Hier entsprechen die Knotenoperationen denen aus den vorherigen Scripts. Vielleicht sollten wir uns die Methoden zum Sortieren und Drucken näher ansehen. Die Sortiermethode sortiert Knoten nach der Anzahl der Elemente, sodass der Knoten, der das Array der Werte mit der geringsten Größe enthält, sich oben in der Liste befindet.

Die Druckmethode druckt einen String von Array-Werten in einem bestimmten Knoten.

Nach der Ausführung des Scripts enthält das Protokoll die folgenden Einträge:

II      0       13:22:23        test_UdList (EURUSD,H1) =======List #1=======
FN      0       13:22:23        test_UdList (EURUSD,H1) List node #1, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20
OO      0       13:22:23        test_UdList (EURUSD,H1) List node #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
GG      0       13:22:23        test_UdList (EURUSD,H1) 
GP      0       13:22:23        test_UdList (EURUSD,H1) 
GR      0       13:22:23        test_UdList (EURUSD,H1) =======List #2 before sorting=======
JO      0       13:22:23        test_UdList (EURUSD,H1) List node #1, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20
CH      0       13:22:23        test_UdList (EURUSD,H1) List node #2, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
CF      0       13:22:23        test_UdList (EURUSD,H1) List node #3, array: -89, -131, -141, -139, -129, -25, -105, -24, -122, -120, -118, -116, -114, -112, -110
GD      0       13:22:23        test_UdList (EURUSD,H1) 
GQ      0       13:22:23        test_UdList (EURUSD,H1) 
LJ      0       13:22:23        test_UdList (EURUSD,H1) =======List #2 after sorting=======
FN      0       13:22:23        test_UdList (EURUSD,H1) List node #1, array: 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
CJ      0       13:22:23        test_UdList (EURUSD,H1) List node #2, array: 55, 12, 1, 2, 11, 114, 33, 113, 14, 15, 16, 17, 18, 19, 20
II      0       13:22:23        test_UdList (EURUSD,H1) List node #3, array: -89, -131, -141, -139, -129, -25, -105, -24, -122, -120, -118, -116, -114, -112, -110
MD      0       13:22:23        test_UdList (EURUSD,H1) 
MQ      0       13:22:23        test_UdList (EURUSD,H1) 

Wie Sie sehen können, hat die Liste udList2 nach dem Sortieren ab dem Knoten mit dem kleinsten Array zum Knoten mit dem größten Array gedruckt.


2.7 Kreisförmige doppelt verkettete Liste

Obwohl nichtlineare Listen in diesem Beitrag nicht betrachtet werden, schlage ich vor, dass wir auch mit ihnen arbeiten. Wie Knoten kreisförmig verknüpft werden, wurde bereits weiter oben gezeigt (Abb. 3).

Erstellen wir ein Modell der Klasse CiCircleDoubleList (Abb. 13). Diese Klasse wird eine Nachfahrenklasse der Klasse CDoubleList sein.

Klassenmodell CiCircleDoubleList

Abb. 13 Klassenmodell CiCircleDoubleList

Da Knoten in dieser Liste einen bestimmten Charakter haben (Kopf und Schwanz sind verknüpft), müssen fast alle Methoden der Quell-Basisklasse CiSingleList virtuell gemacht werden.

//+------------------------------------------------------------------+
//|                      CiCircleDoubleList class                    |
//+------------------------------------------------------------------+
class CiCircleDoubleList : public CDoubleList
  {
public:
   void              CiCircleDoubleList(void);                       // default constructor
   void              CiCircleDoubleList(int _node_val);              // parameterized constructor
   void             ~CiCircleDoubleList(void){TRACE_CALL(_t_flag)};  // destructor
   //---
   virtual uint      Size(void) const;                                        // list size
   virtual bool      SetNodeByIndex(CiSingleNode *_new_node,const uint _idx); // insert the new ith node in the list
   virtual int       GetValByIndex(const uint _idx) const;           // value of the ith node in the list
   virtual CiSingleNode *GetNodeByIndex(const uint _idx) const;      // get the ith node in the list
   virtual bool      Find(const int _node_val) const;                // find the required value
   virtual bool      CopyByValue(const CiSingleList &_sList);        // copy the list by values

protected:
   virtual void      addFront(int _node_val);                        // add a new "native" node to the beginning of the list
   virtual void      addRear(int _node_val);                         // add a new "native" node to the end of the list
   virtual int       removeFront(void);                              // delete the "native" head node
   virtual int       removeRear(void);                               // delete the "native" tail node
   virtual void      deleteNodeByIndex(const uint _idx);             // delete the "native" ith node from the list

protected:
   void              CalcSize(void) const;                           // calculate the list size
   void              LinkHeadTail(void);                             // link head to tail
  };

Die vollständige Beschreibung der Klasse wird in CiCircleDoubleList.mqh bereitgestellt.

Betrachten wir einige Methoden der Klasse. Die Methode CiCircleDoubleList::LinkHeadTail() verknüpft den Schwanzknoten mit dem Kopfknoten. Sie muss aufgerufen werden, wenn es entweder einen neuen Schwanz oder einen neuen Kopf gibt und die vorherige Verknüpfung verlorengegangen ist.

//+------------------------------------------------------------------+
//|                  Linking head to tail                            |
//+------------------------------------------------------------------+
void CiCircleDoubleList::LinkHeadTail(void)
  {
   TRACE_CALL(_t_flag)
   this.m_head.SetPrevNode(this.m_tail);      // link head to tail
   this.m_tail.SetNextNode(this.m_head);      // link tail to head
  }

Überlegen Sie, wie diese Methode aussehen würde, wenn wir es mit einer kreisförmigen einfach verketteten Liste zu tun hätten.

Sehen Sie sich beispielsweise die Methode CiCircleDoubleList::addFront() an.

//+------------------------------------------------------------------+
//|                New "native" node to the beginning of the list    |
//+------------------------------------------------------------------+
void CiCircleDoubleList::addFront(int _node_val)
  {
   TRACE_CALL(_t_flag)
   CDoubleList::addFront(_node_val); // call a similar method of the base class
   this.LinkHeadTail();              // link head and tail
  }

Sie sehen im Körper der Methode, dass eine ähnliche Methode der Basisklasse CDoubleList aufgerufen wird. An dieser Stelle würden wir die Operation der Methode abschließen (die Methode als solche wird hier grundsätzlich nicht benötigt), gäbe es da nicht eine Besonderheit. Die Verknüpfung zwischen Kopf und Schwanz geht verloren und die Liste kann ohne sie nicht kreisförmig verkettet werden. Deshalb müssen wir die Methode zum Verknüpfen von Kopf und Schwanz aufrufen.

Die Arbeit mit der kreisförmigen doppelt verketteten Liste wird im Script test_UdList.mq5 geprüft.

Was Aufgaben und Ziele betrifft, sind die anderen verwendeten Methoden die gleichen wie in den vorherigen Beispielen.

Als Ergebnis enthält das Protokoll die folgenden Einträge:

PR      0       13:34:29        test_CdList (EURUSD,H1) =======List #1=======
QS      0       13:34:29        test_CdList (EURUSD,H1) Node #1, val=14 
QI      0       13:34:29        test_CdList (EURUSD,H1) Node #2, val=666 
LQ      0       13:34:29        test_CdList (EURUSD,H1) Node #3, val=13 
OH      0       13:34:29        test_CdList (EURUSD,H1) Node #4, val=11 
DP      0       13:34:29        test_CdList (EURUSD,H1) 
DK      0       13:34:29        test_CdList (EURUSD,H1) 
DI      0       13:34:29        test_CdList (EURUSD,H1) =======List #2 before sorting=======
MS      0       13:34:29        test_CdList (EURUSD,H1) Node #1, val=38 
IJ      0       13:34:29        test_CdList (EURUSD,H1) Node #2, val=37 
IQ      0       13:34:29        test_CdList (EURUSD,H1) Node #3, val=36 
EH      0       13:34:29        test_CdList (EURUSD,H1) Node #4, val=35 
EO      0       13:34:29        test_CdList (EURUSD,H1) Node #5, val=34 
FF      0       13:34:29        test_CdList (EURUSD,H1) Node #6, val=14 
DN      0       13:34:29        test_CdList (EURUSD,H1) Node #7, val=666 
GD      0       13:34:29        test_CdList (EURUSD,H1) Node #8, val=13 
JK      0       13:34:29        test_CdList (EURUSD,H1) Node #9, val=11 
JM      0       13:34:29        test_CdList (EURUSD,H1) 
JH      0       13:34:29        test_CdList (EURUSD,H1) 
MS      0       13:34:29        test_CdList (EURUSD,H1) =======List #2 after sorting=======
LE      0       13:34:29        test_CdList (EURUSD,H1) Node #1, val=11 
KL      0       13:34:29        test_CdList (EURUSD,H1) Node #2, val=13 
QS      0       13:34:29        test_CdList (EURUSD,H1) Node #3, val=14 
NJ      0       13:34:29        test_CdList (EURUSD,H1) Node #4, val=34 
NQ      0       13:34:29        test_CdList (EURUSD,H1) Node #5, val=35 
NH      0       13:34:29        test_CdList (EURUSD,H1) Node #6, val=36 
NO      0       13:34:29        test_CdList (EURUSD,H1) Node #7, val=37 
NF      0       13:34:29        test_CdList (EURUSD,H1) Node #8, val=38 
JN      0       13:34:29        test_CdList (EURUSD,H1) Node #9, val=666 
RJ      0       13:34:29        test_CdList (EURUSD,H1) 
RE      0       13:34:29        test_CdList (EURUSD,H1)

Somit sieht das finale Diagramm der Vererbung zwischen den vorgestellten Listenklassen so aus (Abb. 14).

Ich bin nicht sicher, ob alle Klassen unbedingt durch Vererbung verbunden sein müssen, doch ich habe mich dafür entschieden, alles so zu belassen, wie es ist.

Vererbung zwischen den Listenklassen

Abb. 14 Vererbung zwischen den Listenklassen

Als Abschluss dieses Abschnitts des Beitrags, in dem die Beschreibung benutzerdefinierter Listen behandelt wurde, möchte ich festhalten, dass wir kaum auf die Gruppe nichtlinearer Listen, mehrfach verketteter Listen und Sonstiges eingegangen sind. Während ich relevante Informationen sammle und meine Erfahrung mit der Arbeit mit solchen dynamischen Datenstrukturen ausbaue, werde ich versuchen, einen weiteren Beitrag zu schreiben.


3. Listen in der MQL5-Standardbibliothek

Sehen wir uns die Listenklasse in der Standardbibliothek an (Abb. 15).

Sie gehört zu den Datenklassen.

Klassenmodell CList

Abb. 15 Klassenmodell CList

Seltsamerweise ist CList ein Nachfahre der Klasse CObject. Das heißt, die Liste erbt Daten und Methoden der Klasse, die ein Knoten ist.

Die Listenklasse beinhaltet einen beeindruckenden Satz von Methoden. Um ehrlich zu sein, hatte ich nicht erwartet, in der Standardbibliothek auf eine so große Klasse zu stoßen.

Die Klasse CListverfügt über 8 Datenelemente. Ich möchte auf ein paar Besonderheiten hinweisen. Die Klassenattribute enthalten den Index des aktuellen Knotens (intm_curr_idx) und den Pointer zum aktuellen Knoten (CObject* m_curr_node). Die Liste kann als "intelligent" bezeichnet werden – sie kann den Ort anzeigen, an dem sich die Steuerung befindet. Ferner verfügt sie über einen Speichermanagement-Mechanismus (wir können einen Knoten physisch löschen oder ihn einfach aus der Liste ausschließen), ein Flag einer sortierten Liste und einen Sortiermodus.

Was Methoden betrifft, so sind alle Methoden der Klasse CList in die folgenden Gruppen unterteilt:

  • Attribute;
  • Methoden zum Erstellen;
  • Methoden zum Hinzufügen;
  • Methoden zum Löschen;
  • Navigation;
  • Methoden zum Anordnen;
  • Methoden zum Vergleichen;
  • Suchmethoden;
  • Ein-/Ausgabe.

Typischerweise gibt es einen Standard-Konstruktor und -Destruktor.

Der erste leert alle Pointer (NULL). Der Zustand des Flags des Speichermanagements wird auf Löschen gesetzt. Die neue Liste wird unsortiert sein.

In seinem Körper ruft der Destruktor nur die Methode Clear() zum Leeren der Liste der Knoten auf. Das Ende der Existenz der Liste bedeutet nicht zwangsläufig den "Tod" ihrer Elemente (Knoten). Das Flag des Speichermanagements, das beim Löschen der Listenelemente gesetzt wird, verändert die Klassenbeziehung von einer Komposition zu einer Aggregation.

Wir können dieses Flag mithilfe von set- und get-Methoden von FreeMode() behandeln.

Es gibt zwei Methoden in der Klasse, die es Ihnen ermöglichen, die Liste zu erweitern: Add() und Insert(). Die erste ähnelt der Methode AddRear() aus dem ersten Abschnitt des Beitrags. Die zweite ist der Methode SetNodeByIndex() ähnlich.

Beginnen wir mit einem kurzen Beispiel. Zuerst müssen wir die Knotenklasse CNodeInt erstellen, eine Nachfahrenklasse der Interface-Klasse CObject. Sie speichert einen Wert des Typen integer.

//+------------------------------------------------------------------+
//|                        CNodeInt class                            |
//+------------------------------------------------------------------+
class  CNodeInt : public CObject
  {
private:
   int               m_val;  // node data

public:
   void              CNodeInt(void){this.m_val=WRONG_VALUE;}; // default constructor
   void              CNodeInt(int _val);                      // parameterized constructor
   void             ~CNodeInt(void){};                        // destructor
   int               GetVal(void){return this.m_val;};        // get-method for node data
   void              SetVal(int _val){this.m_val=_val;};      // set-method for node data
  };
//+------------------------------------------------------------------+
//|                    Parameterized constructor                     |
//+------------------------------------------------------------------+
void CNodeInt::CNodeInt(int _val):m_val(_val)
  {

  };

Wir arbeiten mit der Liste CList im Script test_MQL5_List.mq5.

Beispiel 1 demonstriert die dynamische Erstellung der Liste und der Knoten. Anschließend wird die Liste mit Knoten gefüllt und der Wert des ersten Knotens wird vor und nach der Löschung der Liste geprüft.

//--- Example 1 (testing memory management)
   CList *myList=new CList;
// myList.FreeMode(false);  // reset flag
   bool _free_mode=myList.FreeMode();
   PrintFormat("\nList \"myList\" - memory management flag: %d",_free_mode);
   CNodeInt *p_new_nodes_int[10];
   p_new_nodes_int[0]=NULL;
   for(int i=0;i<ArraySize(p_new_nodes_int);i++)
     {
      p_new_nodes_int[i]=new CNodeInt(rand());
      myList.Add(p_new_nodes_int[i]);
     }
   PrintFormat("List \"myList\" has as many nodes as: %d",myList.Total());
   Print("=======Before deleting \"myList\"=======");
   PrintFormat("The 1st node value is: %d",p_new_nodes_int[0].GetVal());
   delete myList;
   int val_to_check=WRONG_VALUE;
   if(CheckPointer(p_new_nodes_int[0]))
      val_to_check=p_new_nodes_int[0].GetVal();
   Print("=======After deleting \"myList\"=======");
   PrintFormat("The 1st node value is: %d",val_to_check);

Wenn der String zum Zurücksetzen des Flags auskommentiert (inaktiv) bleibt, erhalten wir im Protokoll die folgenden Einträge:

GS      0       14:00:16        test_MQL5_List (EURUSD,H1)      
EO      0       14:00:16        test_MQL5_List (EURUSD,H1)      List "myList" - memory management flag: 1
FR      0       14:00:16        test_MQL5_List (EURUSD,H1)      List "myList" has as many nodes as: 10
JH      0       14:00:16        test_MQL5_List (EURUSD,H1)      =======Before deleting "myList"=======
DO      0       14:00:16        test_MQL5_List (EURUSD,H1)      The 1st node value is: 7189
KJ      0       14:00:16        test_MQL5_List (EURUSD,H1)      =======After deleting "myList"=======
QK      0       14:00:16        test_MQL5_List (EURUSD,H1)      The 1st node value is: -1

Beachten Sie, dass nach der dynamischen Löschung der Liste myList alle Knoten darin ebenfalls aus dem Speicher entfernt wurden.

Wenn wir allerdings den String zum Zurücksetzen des Flags nicht auskommentieren:

// myList.FreeMode(false); // reset flag

erhalten wir die folgende Ausgabe im Protokoll:

NS      0       14:02:11        test_MQL5_List (EURUSD,H1)      
CN      0       14:02:11        test_MQL5_List (EURUSD,H1)      List "myList" - memory management flag: 0
CS      0       14:02:11        test_MQL5_List (EURUSD,H1)      List "myList" has as many nodes as: 10
KH      0       14:02:11        test_MQL5_List (EURUSD,H1)      =======Before deleting "myList"=======
NL      0       14:02:11        test_MQL5_List (EURUSD,H1)      The 1st node value is: 20411
HJ      0       14:02:11        test_MQL5_List (EURUSD,H1)      =======After deleting "myList"=======
LI      0       14:02:11        test_MQL5_List (EURUSD,H1)      The 1st node value is: 20411
QQ      1       14:02:11        test_MQL5_List (EURUSD,H1)      10 undeleted objects left
DD      1       14:02:11        test_MQL5_List (EURUSD,H1)      10 objects of type CNodeInt left
DL      1       14:02:11        test_MQL5_List (EURUSD,H1)      400 bytes of leaked memory

Es ist unschwer zu erkennen, dass der Knoten im Kopf seinen Wert sowohl vor als auch nach der Löschung der Liste behält. In diesem Fall verbleiben auch ungelöschte Objekte, wenn das Script keinen Code beinhaltet, um sie korrekt zu löschen.

Versuchen wir nun, mit der Sortiermethode zu arbeiten.

//--- Example 2 (sorting)

   CList *myList=new CList;
   CNodeInt *p_new_nodes_int[10];
   p_new_nodes_int[0]=NULL;
   for(int i=0;i<ArraySize(p_new_nodes_int);i++)
     {
      p_new_nodes_int[i]=new CNodeInt(rand());
      myList.Add(p_new_nodes_int[i]);
     }
   PrintFormat("\nList \"myList\" has as many nodes as: %d",myList.Total());
   Print("=======List \"myList\" before sorting=======");
   for(int i=0;i<myList.Total();i++)
     {
      CNodeInt *p_node_int=myList.GetNodeAtIndex(i);
      int node_val=p_node_int.GetVal();
      PrintFormat("Node #%d is equal to: %d",i+1,node_val);
     }
   myList.Sort(0);
   Print("\n=======List \"myList\" after sorting=======");
   for(int i=0;i<myList.Total();i++)
     {
      CNodeInt *p_node_int=myList.GetNodeAtIndex(i);
      int node_val=p_node_int.GetVal();
      PrintFormat("Node #%d is equal to: %d",i+1,node_val);
     }
   delete myList;

Als Ergebnis enthält das Protokoll die folgenden Einträge:

OR      0       22:47:01        test_MQL5_List (EURUSD,H1)      
FN      0       22:47:01        test_MQL5_List (EURUSD,H1)      List "myList" has as many nodes as: 10
FH      0       22:47:01        test_MQL5_List (EURUSD,H1)      =======List "myList" before sorting=======
LG      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #1 is equal to: 30511
CO      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #2 is equal to: 17404
GF      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #3 is equal to: 12215
KQ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #4 is equal to: 31574
NJ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #5 is equal to: 7285
HP      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #6 is equal to: 23509
IH      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #7 is equal to: 26991
NS      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #8 is equal to: 414
MK      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #9 is equal to: 18824
DR      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #10 is equal to: 1560
OR      0       22:47:01        test_MQL5_List (EURUSD,H1)      
OM      0       22:47:01        test_MQL5_List (EURUSD,H1)      =======List "myList" after sorting=======
QM      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #1 is equal to: 26991
RE      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #2 is equal to: 23509
ML      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #3 is equal to: 18824
DD      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #4 is equal to: 414
LL      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #5 is equal to: 1560
IG      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #6 is equal to: 17404
PN      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #7 is equal to: 30511
II      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #8 is equal to: 31574
OQ      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #9 is equal to: 12215
JH      0       22:47:01        test_MQL5_List (EURUSD,H1)      Node #10 is equal to: 7285

Selbst wenn irgendeine Sortierung stattgefunden hat, bleibt mir die Sortiertechnik unerklärlich. Ich möchte erklären, warum das so ist. Ohne bezüglich der Reihenfolge der Aufrufe allzu sehr ins Detail zu gehen, ruft die Methode CList::Sort() die virtuelle Methode CObject::Compare() auf, die in der Basisklasse überhaupt nicht umgesetzt wird. Also muss der Programmierer sich selbst um die Umsetzung einer Sortiermethode kümmern.

Nun noch ein paar Worte zur Methode Total(). Sie gibt die Menge der Elemente (Knoten) aus, für die das Datenelement m_data_total zuständig ist. Es ist eine sehr kurze und prägnante Methode. Die Zählung der Elemente in dieser Umsetzung ist viel schneller als in der, die ich früher vorgeschlagen habe. Warum sollte man auch jedes Mal die gesamte Liste durchgehen und Knoten zählen, wenn die genaue Anzahl der Knoten in der Liste doch beim Hinzufügen oder Löschen von Knoten festgelegt werden kann?

Beispiel 3 vergleicht die Geschwindigkeit des Ausfüllens von Listen der Typen CList und CiSingleList und berechnet die Zeit zum Abrufen der Größe jeder Liste.

//--- Example 3 (nodes number)

   int iterations=1e7;   // 10 million iterations
//--- the new CList 
   CList *p_mql_List=new CList;
   uint start=GetTickCount(); // starting value
   for(int i=0;i<iterations;i++)
     {
      CNodeInt *p_node_int=new CNodeInt(rand());
      p_mql_List.Add(p_node_int);
     }
   uint time=GetTickCount()-start; // time spent, msec
   Print("\n=======the CList type list=======");
   PrintFormat("Filling the list of %.3e nodes has taken %d msec",iterations,time);
//--- get the size
   start=GetTickCount();
   int list_size=p_mql_List.Total(); 
   time=GetTickCount()-start;
   PrintFormat("Getting the size of the list has taken %d msec",time);

   delete p_mql_List;

//---  the new CiSingleList 
   CiSingleList *p_sList=new CiSingleList;

   start=GetTickCount(); // starting value
   for(int i=0;i<iterations;i++)
      p_sList.AddRear(rand());
   time=GetTickCount()-start; // time spent, msec
   Print("\n=======the CiSingleList type list=======");
   PrintFormat("Filling the list of %.3e nodes has taken %d msec",iterations,time);
//--- get the size
   start=GetTickCount();
   list_size=(int)p_sList.Size();  
   time=GetTickCount()-start;
   PrintFormat("Getting the size of the list has taken %d msec",time);
   delete p_sList;   

Folgendes habe ich im Protokoll erhalten:

KO      0       22:48:24        test_MQL5_List (EURUSD,H1)      
CK      0       22:48:24        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
JL      0       22:48:24        test_MQL5_List (EURUSD,H1)      Filling the list of 1.000e+007 nodes has taken 2606 msec
RO      0       22:48:24        test_MQL5_List (EURUSD,H1)      Getting the size of the list has taken 0 msec
LF      0       22:48:29        test_MQL5_List (EURUSD,H1)      
EL      0       22:48:29        test_MQL5_List (EURUSD,H1)      =======the CiSingleList type list=======
KK      0       22:48:29        test_MQL5_List (EURUSD,H1)      Filling the list of 1.000e+007 nodes has taken 2356 msec
NF      0       22:48:29        test_MQL5_List (EURUSD,H1)      Getting the size of the list has taken 359 msec

Die Methode zum Abrufen der Größe funktioniert in der Liste CList sofort. Im Übrigen funktioniert auch das Hinzufügen von Knoten zur Liste ziemlich schnell.

Im nächsten Block (Beispiel 4) empfehle ich, auf einen der wesentlichen Nachteile der Liste als Datencontainer zu achten: die Geschwindigkeit des Zugriffs auf Elemente. Das Problem liegt darin, dass der Zugriff auf Listenelemente linear ist. In der Klasse CList wird binär auf die Elemente zugegriffen, was die Arbeitsintensität des Algorithmus leicht senkt.

Bei der linearen Suche ist die Arbeitsintensität O(N). Eine binär umgesetzte Suche führt zu einer Arbeitsintensität von log2(N).

Das ist ein Beispiel des Codes für den Zugriff auf Elemente eines Datensatzes:

//--- Example 4 (speed of accessing the node)

   const uint Iter_arr[]={1e3,3e3,6e3,9e3,1e4,3e4,6e4,9e4,1e5,3e5,6e5};
   for(uint i=0;i<ArraySize(Iter_arr);i++)
     {
      const uint cur_iterations=Iter_arr[i]; // iterations number     
      uint randArr[];                        // array of random numbers
      uint idxArr[];                         // array of indexes
      //--- set the arrays size    
      ArrayResize(randArr,cur_iterations);
      ArrayResize(idxArr,cur_iterations);
      CRandom myRand;                        // random number generator
      //--- fill the array of random numbers
      for(uint t=0;t<cur_iterations;t++)
         randArr[t]=myRand.int32();
      //--- fill the array of indexes with random numbers (from 0 to 10 million)
      int iter_log10=(int)log10(cur_iterations);
      for(uint r=0;r<cur_iterations;r++)
        {
         uint rand_val=myRand.int32(); // random value (from 0 to 4 294 967 295)
         if(rand_val>=cur_iterations)
           {
            int val_log10=(int)log10(rand_val);
            double log10_remainder=val_log10-iter_log10;
            rand_val/=(uint)pow(10,log10_remainder+1);
           }
         //--- check the limit
         if(rand_val>=cur_iterations)
           {
            Alert("Random value error!");
            return;
           }
         idxArr[r]=rand_val;
        }
      //--- time spent for the array
      uint start=GetTickCount();
      //--- accessing the array elements 
      for(uint p=0;p<cur_iterations;p++)
         uint random_val=randArr[idxArr[p]];

      uint time=GetTickCount()-start; // time spent, msec
      Print("\n=======the uint type array=======");
      PrintFormat("Random accessing the array of elements %.1e has taken %d msec",cur_iterations,time);

      //--- the CList type list
      CList *p_mql_List=new CList;
      //--- fill the list
      for(uint q=0;q<cur_iterations;q++)
        {
         CNodeInt *p_node_int=new CNodeInt(randArr[q]);
         p_mql_List.Add(p_node_int);
        }
      start=GetTickCount();
      //--- accessing the list nodes
      for(uint w=0;w<cur_iterations;w++)
         CNodeInt *p_node_int=p_mql_List.GetNodeAtIndex(idxArr[w]);
      time=GetTickCount()-start; // time spent, msec
      Print("\n=======the CList type list=======");
      PrintFormat("Random accessing the list of nodes %.1e has taken %d msec",cur_iterations,time);

      //--- free the memory
      ArrayFree(randArr);
      ArrayFree(idxArr);
      delete p_mql_List;
     }

Basierend auf den Ergebnissen der Arbeit des Blocks wurden die folgenden Einträge ins Protokoll gedruckt: 

MR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
QL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
IG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 1.0e+003 has taken 0 msec
QF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
IQ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
JK      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 1.0e+003 has taken 0 msec
MJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
QD      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
GO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 3.0e+003 has taken 0 msec
QN      0       22:51:22        test_MQL5_List (EURUSD,H1)      
II      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
EP      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 3.0e+003 has taken 16 msec
OR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
OL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
FG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 6.0e+003 has taken 0 msec
CF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
GQ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
CH      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 6.0e+003 has taken 31 msec
QJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
MD      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
MO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 9.0e+003 has taken 0 msec
EN      0       22:51:22        test_MQL5_List (EURUSD,H1)      
MJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
CP      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 9.0e+003 has taken 47 msec
CR      0       22:51:22        test_MQL5_List (EURUSD,H1)      
KL      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
JG      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 1.0e+004 has taken 0 msec
GF      0       22:51:22        test_MQL5_List (EURUSD,H1)      
KR      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
MK      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 1.0e+004 has taken 343 msec
GJ      0       22:51:22        test_MQL5_List (EURUSD,H1)      
GG      0       22:51:22        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
LO      0       22:51:22        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 3.0e+004 has taken 0 msec
QO      0       22:51:24        test_MQL5_List (EURUSD,H1)      
MJ      0       22:51:24        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
NP      0       22:51:24        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 3.0e+004 has taken 1217 msec
OS      0       22:51:24        test_MQL5_List (EURUSD,H1)      
KO      0       22:51:24        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
CP      0       22:51:24        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 6.0e+004 has taken 0 msec
MG      0       22:51:26        test_MQL5_List (EURUSD,H1)      
ER      0       22:51:26        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
PG      0       22:51:26        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 6.0e+004 has taken 2387 msec
GK      0       22:51:26        test_MQL5_List (EURUSD,H1)      
OG      0       22:51:26        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
NH      0       22:51:26        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 9.0e+004 has taken 0 msec
JO      0       22:51:30        test_MQL5_List (EURUSD,H1)      
NK      0       22:51:30        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
KO      0       22:51:30        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 9.0e+004 has taken 3619 msec
HS      0       22:51:30        test_MQL5_List (EURUSD,H1)      
DN      0       22:51:30        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
RP      0       22:51:30        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 1.0e+005 has taken 0 msec
OD      0       22:52:05        test_MQL5_List (EURUSD,H1)      
GS      0       22:52:05        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
DE      0       22:52:05        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 1.0e+005 has taken 35631 msec
NH      0       22:52:06        test_MQL5_List (EURUSD,H1)      
RF      0       22:52:06        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
FI      0       22:52:06        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 3.0e+005 has taken 0 msec
HL      0       22:54:20        test_MQL5_List (EURUSD,H1)      
PD      0       22:54:20        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
FN      0       22:54:20        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 3.0e+005 has taken 134379 msec
RQ      0       22:54:20        test_MQL5_List (EURUSD,H1)      
JI      0       22:54:20        test_MQL5_List (EURUSD,H1)      =======the uint type array=======
MR      0       22:54:20        test_MQL5_List (EURUSD,H1)      Random accessing the array of elements 6.0e+005 has taken 15 msec
NE      0       22:58:48        test_MQL5_List (EURUSD,H1)      
FL      0       22:58:48        test_MQL5_List (EURUSD,H1)      =======the CList type list=======
GE      0       22:58:48        test_MQL5_List (EURUSD,H1)      Random accessing the list of nodes 6.0e+005 has taken 267589 msec

Sie sehen, dass der zufällige Zugriff auf Listenelemente mehr Zeit erfordert, wenn die Größe der Liste steigt (Abb. 16).

Zeitaufwand für den zufälligen Zugriff auf Array- und Listenelemente

Abb. 16 Zeitaufwand für den zufälligen Zugriff auf Array- und Listenelemente

Betrachten wir nun die Methoden zum Speichern und Laden von Daten.

Die Basis-Listenklasse CList beinhaltet solche Methoden, doch diese sind virtuell. Um also ihre Arbeit anhand eines Beispiels zu testen, müssen wir einige Vorbereitungen treffen.

Wir müssen die Möglichkeiten der Klasse CList mithilfe der Nachfahrenklasse CIntList vererben. Letztere wird nur eine Methode zum Erstellen eines neuen Elements haben, CIntList::CreateElement().

//+------------------------------------------------------------------+
//|                      CIntList class                              |
//+------------------------------------------------------------------+
class CIntList : public CList
  {

public:
   virtual CObject *CreateElement(void);
  };
//+------------------------------------------------------------------+
//|                    New element of the list                       |
//+------------------------------------------------------------------+
CObject *CIntList::CreateElement(void)
  {
   CObject *new_node=new CNodeInt();
   return new_node;
  }

Außerdem müssen wir die virtuellen Methoden CNodeInt::Save() und CNodeInt::Load() zum abgeleiteten Knotentypen CNodeInt hinzufügen. Sie werden aus den Unterfunktionen CList::Save() bzw. CList::Load() aufgerufen.

Somit erhalten wir folgendes Beispiel (Beispiel 5):

//--- Example 5 (saving list data)
//--- the CIntList type list
   CList *p_int_List=new CIntList;
   int randArr[1000];                        // array of random numbers
   ArrayInitialize(randArr,0);
//--- fill the array of random numbers 
   for(int t=0;t<1000;t++)
      randArr[t]=(int)myRand.int32();
//--- fill the list
   for(uint q=0;q<1000;q++)
     {
      CNodeInt *p_node_int=new CNodeInt(randArr[q]);
      p_int_List.Add(p_node_int);
     }
//--- save the list to the file 
   int  file_ha=FileOpen("List_data.bin",FILE_WRITE|FILE_BIN);
   p_int_List.Save(file_ha);
   FileClose(file_ha);             
   p_int_List.FreeMode(true);    
   p_int_List.Clear();           
//--- load the list from the file  
   file_ha=FileOpen("List_data.bin",FILE_READ|FILE_BIN);
   p_int_List.Load(file_ha);
   int Loaded_List_size=p_int_List.Total();
   PrintFormat("Nodes loaded from the file: %d",Loaded_List_size);
//--- free the memory     
   delete p_int_List;

Nach der Ausführung des Scripts im Diagramm wird der folgende Eintrag zum Protokoll hinzugefügt:

ND      0       11:59:35        test_MQL5_List (EURUSD,H1)      As many as 1000 nodes loaded from the file.

Auf diese Weise wurden die Eingabe-/Ausgabemethoden für ein Datenelement des Knotentypen CNodeInt umgesetzt.

Im nächsten Abschnitt werden wir Beispiele dafür sehen, wie Listen zum Lösen von Aufgaben beim Arbeiten mit MQL5 eingesetzt werden können.


4. Anwendungsbeispiele für Listen in MQL5

Bei der Betrachtung der Methoden der Klasse CList aus der Standardbibliothek im vorherigen Abschnitt habe ich einige Beispiele aufgeführt.

Nun möchte ich Fälle betrachten, in denen die Liste verwendet wird, um eine bestimmte Aufgabe zu lösen. Hier muss ich nochmals den Vorteil der Liste als Container-Datentyp betonen. Wir können die Arbeit mit dem Code effizienter machen, indem wir die Flexibilität einer Liste nutzen.


4.1 Verarbeitung von grafischen Objekten

Stellen Sie sich vor, wir müssen grafische Objekte im Diagramm programmieren. Dabei kann es sich um verschiedene Objekte handeln, die aus diversen Gründen im Diagramm erscheinen können.

Ich erinnere mich an einen Fall, in dem die Liste mir geholfen hat, mit grafischen Objekten zu arbeiten. Diese Erinnerung möchte ich gerne mit Ihnen teilen.

Meine Aufgabe war es, vertikale Linien nach Vorgabe zu erstellen. Laut Vorgabe dienten die vertikalen Linien als Grenzen für ein bestimmtes Zeitintervall, das von Fall zu Fall variierte. Dabei war das Zeitintervall nicht immer vollständig geformt.

Ich studierte das Verhalten von EMA21 und musste dafür Statistiken sammeln.

Insbesondere interessierte mich die Länge der Neigung des gleitenden Mittelwerts. Beispielsweise wurde der Ausgangspunkt bei einer Abwärtsbewegung durch die Registrierung der negativen Bewegung des gleitenden Mittelwerts identifiziert (d. h. Verringerung des Werts). Dabei wurde eine vertikale Linie gezeichnet. Abb. 17 zeigt einen solchen Punkt für EURUSD, H1 am 5. September 2013, 16:00 Uhr, beim Öffnen einer Kerze.

Abb. 17 Erster Punkt eines Abwärtsintervalls

Abb. 17 Erster Punkt eines Abwärtsintervalls 


Der zweite Punkt, der das Ende einer Abwärtsbewegung signalisiert, wurde auf Basis des umgekehrten Prinzips identifiziert – durch die Registrierung einer positiven Bewegung des gleitenden Mittelwerts, d. h. Anstieg des Werts (Abb. 18).


Abb. 18 Zweiter Punkt eines Abwärtsintervalls

Abb. 18 Zweiter Punkt eines Abwärtsintervalls


Somit lag das Zielintervall zwischen dem 5. September 2013, 16:00 Uhr und 6. September 2013, 17:00 Uhr.

Das System zur Identifizierung verschiedener Intervalle kann komplexer oder einfacher sein. Das ist hier nicht das Thema. Wichtig ist, dass diese Technik zum Arbeiten mit grafischen Objekten und dem gleichzeitigen Sammeln statistischer Daten einen der wichtigsten Vorteile der Liste beinhaltet – die Flexibilität der Zusammensetzung.

Was das vorliegende Beispiel betrifft, habe ich zuerst einen Knoten des Typen CVertLineNode erstellt, der für 2 grafische Objekte des Typen "vertikale Linie" verantwortlich ist.

Die Klasse ist folgendermaßen definiert:

//+------------------------------------------------------------------+
//|                      CVertLineNode class                         |
//+------------------------------------------------------------------+
class CVertLineNode : public CObject
  {
private:
   SVertLineProperties m_vert_lines[2];      // array of structures of vertical line properties
   uint              m_duration;             // frame duration
   bool              m_IsFrameFormed;        // flag of frame formation

public:
   void              CVertLineNode(void);
   void             ~CVertLineNode(void){};
   //--- set-methods   
   void              SetLine(const SVertLineProperties &_vert_line,bool IsFirst=true);
   void              SetDuration(const uint _duration){this.m_duration=_duration;};
   void              SetFrameFlag(const bool _frame_flag){this.m_IsFrameFormed=_frame_flag;};
   //--- get-methods   
   void              GetLine(SVertLineProperties &_vert_line_out,bool IsFirst=true) const;
   uint              GetDuration(void) const;
   bool              GetFrameFlag(void) const;
   //--- draw the line
   bool              DrawLine(bool IsFirst=true) const;
  };

Im Wesentlichen beschreibt dieser Knoten einen Frame (hier als Menge der Kerzen innerhalb von zwei vertikalen Linien interpretiert). Grenzen von Frames werden durch ein Paar von Strukturen von Eigenschaften der vertikalen Linie, die Dauer und das Flag der Entstehung repräsentiert.

Abgesehen vom Standard-Konstruktor und -Destruktor weist die Klasse mehrere set- und get-Methoden sowie eine Methode zum Zeichnen der Linie im Diagramm auf.

Ich möchte Sie daran erinnern, dass der Knoten von vertikalen Linien (Frame) in meinem Beispiel als fertig geformt betrachten kann, sobald es eine erste Linie gibt, die den Beginn der Abwärtsbewegung signalisiert, und eine zweite Linie, die den Beginn der Aufwärtsbewegung signalisiert.

Mithilfe des Scripts Stat_collector.mq5 bildete ich alle Frames im Diagramm ab und zählte, wie viele Knoten (Frames) einer bestimmten Dauer über die letzten 2000 Balken entsprechen.

Zu Illustrationszwecken habe ich 4 Listen erstellt, die jeden beliebigen Frame enthalten können. Die erste Liste enthält Frames mit bis zu 5 Kerzen, die zweite bis zu 10, die dritte bis 15 und die vierte kann Frames mit einer unbegrenzten Menge an Kerzen enthalten. 

NS      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #1=======
RF      0       15:27:32        Stat_collector (EURUSD,H1)      Duration limit: 5
ML      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 65
HK      0       15:27:32        Stat_collector (EURUSD,H1)      
OO      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #2=======
RI      0       15:27:32        Stat_collector (EURUSD,H1)      Duration limit: 10
NP      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 15
RG      0       15:27:32        Stat_collector (EURUSD,H1)      
FH      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #3=======
GN      0       15:27:32        Stat_collector (EURUSD,H1)      Duration limit: 15
FG      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 6
FR      0       15:27:32        Stat_collector (EURUSD,H1)      
CD      0       15:27:32        Stat_collector (EURUSD,H1)      =======List #4=======
PS      0       15:27:32        Stat_collector (EURUSD,H1)      Nodes number: 20

Als Ergebnis erhielt ich das folgende Diagramm (Abb. 19). Zur besseren Anschaulichkeit wird die zweite vertikale Linie des Frames in Blau angezeigt.


Abb. 19 Anzeige von Frames

Interessanterweise wurde der letzte Frame während der letzten Stunde am Freitag, dem 13. Dezember 2013 geformt. Er fiel unter die zweite Liste, da er eine Dauer von 6 Stunden hatte.


4.2 Verarbeitung von virtuellem Handel

Stellen Sie sich vor, Sie müssen einen Expert Advisor erstellen, der mehrere unabhängige Strategien in Bezug auf ein Instrument in einem Tick-Fluss umsetzen muss. Natürlich kann im realen Leben nur eine Strategie auf einmal in Bezug auf ein Instrument umgesetzt werden. Alle anderen Strategien wären virtuell. Somit lässt sich diese Idee nur zu Testzwecken und zum Optimieren einer Handelsidee umsetzen.

Hier muss ich mich auf einen grundlegenden Beitrag beziehen, der eine detaillierte Beschreibung der grundlegenden Konzepte hinter dem Handel im Allgemeinen und insbesondere auf das MetaTrader 5 Terminal liefert: "Aufträge, Positionen und Abschlüsse in MetaTrader 5".

Wenn wir für die Lösung dieser Aufgabe also das Handelskonzept, das Verwaltungssystem für Handelsobjekte und die Methoden zum Speichern von Informationen über Handelsobjekte nutzen wollen, die in der Umgebung von MetaTrader 5 üblich sind, sollten wir vermutlich eine virtuelle Datenbank erstellen.

Ich möchte Sie daran erinnern, dass ein Entwickler alle Handelsobjekte in Order, Positionen, Abschlüsse und historische Order einstuft. Dem kritischen Beobachter mag aufgefallen sein, dass der Begriff 'Handelsobjekt' an dieser Stelle durch den Verfasser selbst eingeführt wurde. Das ist auch wahr...

Ich schlage vor, eine ähnliche Herangehensweise an den virtuellen Handel zu nutzen und die folgenden virtuellen Handelsobjekte zu erhalten: virtuelle Order, virtuelle Positionen, virtuelle Abschlüsse und virtuelle historische Order.

Ich bin der Überzeugung, dass dieses Thema es wert ist, eingehender und detaillierter besprochen zu werden. Zurück zum Thema dieses Beitrags: Ich möchte erwähnen, dass Container-Datentypen, darunter auch Listen, dem Programmierer das Leben beim Umsetzen virtueller Strategien erleichtern können.

Stellen Sie sich eine neue virtuelle Position vor, die sich natürlich nicht auf der Serverseite befinden kann. Das heißt, die Informationen über sie müssen auf der Terminalseite gespeichert werden. Hier lässt sich eine Datenbank in Form einer Liste darstellen, die ihrerseits aus mehreren Listen besteht, von denen eine Knoten der virtuellen Position beinhaltet.

Mit der Herangehensweise eines Programmierers wird es die folgenden Klassen des virtuellen Handels geben:

Klasse/Gruppe

Beschreibung

CVirtualOrder

Klasse für die Arbeit mit virtuellen Pending Orders

CVirtualHistoryOrder

Klasse für die Arbeit mit virtuellen "historischen" Ordern

CVirtualPosition

Klasse für die Arbeit mit virtuellen offenen Positionen

CVirtualDeal

Klasse für die Arbeit mit virtuellen "historischen" Abschlüssen

CVirtualTrade

Klasse für die Durchführung virtueller Handelsoperationen

Tabelle 1. Klassen des virtuellen Handels


Ich werde nicht näher auf die Zusammensetzung der virtuellen Handelsklassen eingehen. Doch sie beinhalten höchstwahrscheinlich alle oder die meisten Methoden einer standardmäßigen Handelsklasse. Ich möchte nur festhalten, dass das, was der Entwickler verwendet, keine Klasse eines bestimmten Handelsobjekts ist, sondern eine Klasse seiner Eigenschaften.

Um Listen in Ihren Algorithmen nutzen zu können, werden Sie auch Knoten benötigen. Deshalb müssen wir die Klasse eines virtuellen Handelsobjekts in einem Knoten verpacken.

Nehmen wir an, der Knoten einer virtuellen offenen Positionen gehört zum Typ CVirtualPositionNode. Die Definition dieses Typen könnte anfangs so aussehen:

//+------------------------------------------------------------------+
//|                Class CVirtualPositionNode                        |
//+------------------------------------------------------------------+
class CVirtualPositionNode : public CObject
  {
protected:
   CVirtualPositionNode *m_virt_position;         // pointer to the virtual function

public:
   void              CVirtualPositionNode(void);  // default constructor
   void             ~CVirtualPositionNode(void);  // destructor
  };

Wenn die virtuelle Position nun geöffnet wird, kann sie zur Liste virtueller Positionen hinzugefügt werden.

Ich möchte auch anmerken, dass diese Herangehensweise an die Arbeit mit virtuellen Handelsobjekten nicht den Gebrauch von Cache-Speicher voraussetzt, da die Datenbank im Arbeitsspeicher gespeichert wird. Natürlich können Sie sie auch so einrichten, dass sie auf anderen Speichermedien gespeichert wird.


Fazit

In diesem Beitrag habe ich versucht, die Vorteile eines Container-Datentypen zu demonstrieren, beispielsweise einer Liste. Doch die Nachteile sollten ebenfalls nicht unerwähnt bleiben. Alles in allem hoffe ich, dass diese Informationen für alle nützlich sein werden, die sich allgemein mit OOP befassen, insbesondere mit einem ihrer Grundprinzipien, dem Polymorphismus.


Ablageort der Dateien:

Meiner Meinung nach ist es am besten, Dateien im Projektordner zu erstellen und zu speichern. Beispielsweise so: %MQL5\Projects\UserLists. Dort habe ich alle Quellcode-Dateien gespeichert. Falls Sie Standardverzeichnisse nutzen, müssen Sie die Bezeichnungsmethode im Code einiger der Dateien ändern (Anführungszeichen durch eckige Klammern ersetzen).

#DateiDateipfadBeschreibung
1 CiSingleNode.mqh  %MQL5\Projects\UserLists  Klasse eines Knotens einer einfach verketteten Liste
2 CDoubleNode.mqh  %MQL5\Projects\UserLists  Klasse eines Knotens einer doppelt verketteten Liste
3 CiUnrollDoubleNode.mqh  %MQL5\Projects\UserLists  Klasse eines Knotens einer entrollten doppelt verketteten Liste
4 test_nodes.mq5  %MQL5\Projects\UserLists  Script mit Beispielen für die Arbeit mit Knoten
5 CiSingleList.mqh  %MQL5\Projects\UserLists  Klasse einer einfach verketteten Liste
6 CDoubleList.mqh  %MQL5\Projects\UserLists  Klasse einer doppelt verketteten Liste
7 CiUnrollDoubleList.mqh  %MQL5\Projects\UserLists  Klasse einer entrollten doppelt verketteten Liste
 8 CiCircleDoublList.mqh %MQL5\Projects\UserLists Klasse einer kreisförmigen doppelt verketteten Liste
 9 test_sList.mq5 %MQL5\Projects\UserLists Script mit Beispielen für die Arbeit mit einer einfach verketteten Liste
 10 test_dList.mq5 %MQL5\Projects\UserLists Script mit Beispielen für die Arbeit mit einer doppelt verketteten Liste
 11 test_UdList.mq5 %MQL5\Projects\UserLists Script mit Beispielen für die Arbeit mit einer entrollten doppelt verketteten Liste
 12 test_CdList.mq5 %MQL5\Projects\UserLists Script mit Beispielen für die Arbeit mit einer kreisförmigen doppelt verketteten Liste
 13 test_MQL5_List.mq5 %MQL5\Projects\UserLists Script mit Beispielen für die Arbeit mit der Klasse CList
 14 CNodeInt.mqh %MQL5\Projects\UserLists Klasse eines Knotens des Typen integer
 15 CIntList.mqh %MQL5\Projects\UserLists Klasse einer Liste für CNodeInt-Knoten
 16 CRandom.mqh %MQL5\Projects\UserLists Klasse eines Zufallszahlgenerators
 17 CVertLineNode.mqh %MQL5\Projects\UserLists Klasse eines Knotens für die Verarbeitung eines Frames von vertikalen Linien
 18 Stat_collector.mq5 %MQL5\Projects\UserLists Script mit einem Beispiel einer Statistiksammlung


Literatur:

  1. A. Friedman, L. Klander, M. Michaelis, H. Schildt. C/C++ Annotated Archives. Mcgraw-Hill Osborne Media, 1999. 1008 Seiten.
  2. V.D. Daleka, A.S. Derevyanko, O.G. Kravets, L.E. Timanovskaya. Datenmodelle und -strukturen. Studienführer. Kharkov, KhGPU, 2000. 241 Seiten (russisch).


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

Beigefügte Dateien |
files.zip (22.75 KB)
Universeller Expert Advisor: Traden mit Gruppen von Strategien und deren Verwaltung (Part 4) Universeller Expert Advisor: Traden mit Gruppen von Strategien und deren Verwaltung (Part 4)

In dem letzten Abschnitt der Serie über die CStrategy Trading Engine, werden wir die parallele Ausführung von verschiedenen Handels-Algorithmen betrachten, das Laden von Strategien über ein XML-File kennenlernen und wir werden ein einfaches Bedienfeld erstellen, mit welchem es möglich ist, die Strategie(n) auszuwählen und auch die Handelsmodi.

Die Verwendung von Layout und Containern für GUI Controls: Die CGrid Klasse Die Verwendung von Layout und Containern für GUI Controls: Die CGrid Klasse

Dieser Artikel präsentiert eine alternative Methode für die Erzeugung eines GUI, basierend auf Layouts und Containern und der Verwendung eines Layout-Managers — die CGrid Klasse. Die CGrid Klasse ist ein externes Control, welches wie ein Container für andere Container und Controls agiert und ein Grid-Layout verwendet.

Das MQL5-Kochbuch: Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden Das MQL5-Kochbuch: Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden

In diesem Beitrag betrachten wir die Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden in einem bestimmten Zeitraum. Die wichtigsten Themen wurden bereits im vorhergehenden Beitrag zur Programmierung mehrwährungsfähiger Indikatoren besprochen, "Das MQL5-Kochbuch: Entwicklung eines Indikators für die Volatilität mehrerer Symbole in MQL5". Diesmal gehen wir also nur auf jene neuen Features und Funktionen ein, an denen wesentliche Änderungen vorgenommen wurden. Wenn Sie ein Neuling in der Programmierung von mehrwährungsfähigen Indikatoren sind, empfehle ich Ihnen, zuerst den vorherigen Beitrag zu lesen.

Das MQL5-Kochbuch – Mehrwährungsfähiger Expert Advisor und die Arbeit mit Pending Orders in MQL5 Das MQL5-Kochbuch – Mehrwährungsfähiger Expert Advisor und die Arbeit mit Pending Orders in MQL5

Diesmal werden wir einen mehrwährungsfähigen Expert Advisor mit einem Handelsalgorithmus erstellen, der auf der Arbeit mit den Pending Orders Buy Stop und Sell Stop basiert. Folgende Themen werden in diesem Beitrag erörtert: der Handel in einem festgelegten Zeitbereich, Platzieren/Modifizieren/Löschen von Pending Orders, die Prüfung, ob die letzte Position bei Take Profit oder Stop Loss geschlossen wurde, und die Kontrolle der Historie der Abschlüsse für jedes Symbol.