English Русский 中文 Español 日本語 Português
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXVI): Arbeiten mit schwebenden Handelsanfragen - erste Implementation (Positionseröffnung)

Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXVI): Arbeiten mit schwebenden Handelsanfragen - erste Implementation (Positionseröffnung)

MetaTrader 5Beispiele | 10 Februar 2020, 10:12
721 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Ich habe das Konzept einer schwebenden Anfrage (pending request) bereits in einer Reihe von früheren Artikeln erwähnt.
In diesem Artikel werden wir herausfinden, was es ist und warum wir es brauchen, und wir werden mit der Umsetzung der schwebenden Anträge beginnen.

Wenn wir einen Handelsserver-Fehler erhalten und behandeln, müssen wir manchmal warten und die Anfrage wiederholen. Im einfachsten Fall wird das Warten dadurch erreicht, dass die Funktion Sleep() mit der erforderlichen Anzahl von Millisekunden ausgeführt und die Anfrage wiederholt wird. Dies ist in vielen Programmen ausreichend. Während des Wartens hält das Programm jedoch an und wartet auf die Beendigung der Pause, bevor es seine Logik wieder aufnimmt. All dies geschieht in der Handelsmethode, und alle anderen Programmfunktionen sind nicht zugänglich.
Um diesen Nachteil zu vermeiden, können wir eine Kopie einer Handelsanfrage erstellen, die den Fehler verursacht, der eine wiederholte Anfrage nach dem Warten erfordert, diese Anfrage auf die Liste der Handelsanfragen setzen und die Handelsmethode beenden. Dadurch können wir das Programm von der Notwendigkeit befreien, innerhalb der Handelsmethode zu "hängen zu bleiben", und es in die Lage versetzen, die Arbeit gemäß seiner eingebauten Logik weiter fortzusetzen. Die Handelsklasse sieht ständig die Liste der schwebenden Anfragen. Wenn es Zeit ist, eine Anfrage auszuführen (die Wartezeit ist vorbei), ruft die Bibliothek die erforderliche Handelsmethode mit der entsprechenden Handelsanfrage auf. Dann geschieht alles in der gleichen Reihenfolge - wenn wir wieder einen Fehler erhalten, verlassen wir die Handelsmethode vor der nächsten Handelsanfrage. Wenn die Anfrage ohne Fehler ausgeführt und auf dem Server in die Warteschlange gestellt wird, wird die ausstehende Anfrage aus der Liste der Handelsanfragen entfernt.

Dies ist eine der möglichen Arten, ausstehende Anträge zu stellen, die es uns ermöglichen, die Programmausführung während des Wartens nicht zu unterbrechen, insbesondere wenn das Warten eine Weile dauert.
Eine weitere Möglichkeit, schwebende Anträge anzuwenden, ist die Implementierung von StopLimit-Orders in MQL4. Was ist eine StopLimit-Order? Es handelt sich um eine doppelte Pending Order, die Preise von Stop- und Limit-Orders enthält. Eine Pending-Limit-Order wird gesetzt, nachdem der Preis das für eine Stop-Order festgelegte Niveau erreicht hat. Eine Pending-Order erlaubt es uns also, auf einfache Weise die Operationslogik einer StopLimit-Order zu implementieren: Wir erstellen einfach eine Pending Order, um eine Limit-Order zu platzieren, während der Moment der Platzierung einer Limit-Order (der Preis, zu dem die Order gestellt werden soll) in die Parameter der Pending-Order geschrieben wird. Sobald der Preis einen bestimmten Wert erreicht, wird eine Anfrage zur Platzierung einer Limit-Order an den Server gesendet. Eine solche Logik wiederholt vollständig die Operationslogik von StopLimit-Orders.

Eine andere Möglichkeit, Pending-Orders zu verwenden, besteht darin, automatisch Handelsanfrageb an offene Marktpositionen zu senden, wenn der Preis ein bestimmtes Niveau oder eine bestimmte Zeit oder beides erreicht.
Generell gibt es zahlreiche Möglichkeiten, den Prozess der Versendung von Handelsanfragen entsprechend den gegebenen Bedingungen zu automatisieren.

Die Magicnummer als Datenspeicher verwenden

Wenn wir eine neue schwebende Anfrage erstellen, müssen wir sie irgendwie markieren, damit das Programm weiß, dass dies genau die Order ist, die gemäß dieser schwebenden Anfrage erteilt wurde. Mit anderen Worten, wir müssen den Auftrag oder die Position genau identifizieren und mit einer bestimmten schwebenden Anfrage verknüpfen. Darüber hinaus sollte diese Zuordnung auch in Notsituationen bestehen bleiben.
Ich habe lange über verschiedene Möglichkeiten nachgedacht, eine solche Vereinigung zu organisieren, und beschlossen, dass die schwebende Antrags-ID in der Reihenfolge/Positionsmagiezahl festgelegt werden sollte. Dies ist der einzige Parameter, der unverändert bleibt und in der ursprünglichen Reihenfolge vorhanden ist. Alle anderen Methoden sind entweder unzuverlässig (in einem Kommentar zur Order/Position gespeichert) oder erfordern umfangreiche Ressourcen für die Erstellung und Pflege (Speicherung in Dateien). Ich betrachte die globalen Terminalvariablen nicht, da sie im Notfall möglicherweise noch nicht genügend Zeit zum Schreiben haben und daher doch kein vollständiges Vertrauen in die Datenrelevanz bieten.

Ich sehe nur eine Lösung — die Speicherung von Daten in der Magicnummer. Vorher haben wir die Gruppen-ID hinzugefügt, um Objekte zu ordnen. Diese ID ermöglicht die Gruppierung von Orders/Positionen in eine gemeinsame Gruppe, was für verschiedene EA (z.B. Gitter) nützlich sein kann. Wir können eine solche ID dem Auftragsobjekt erst dann hinzufügen, wenn es physisch auf dem Server erscheint. In Notfallsituationen geht sie verloren. Natürlich sollen alle Sammlungen zusammen mit ihren Objekten auf der Festplatte gespeichert werden, aber das wird erst viel später geschehen. Im Moment können wir der Magicnummer der Order die Gruppen-ID zusammen mit der ID des schwebenden Auftrags hinzufügen.
Wir sind in der Lage, mehrere IDs im Wert der Magicnummer zu speichern:

  • ID der Magicnummer selbst (festgelegt in den EA-Eingaben)
  • erste Gruppen-ID (mit Untergruppennummern von 0 bis 15, Null — gehört nicht zur Gruppe)
  • zweite Gruppen-ID (mit Untergruppennummern von 0 bis 15, Null — gehört nicht zur Gruppe)
  • schwebende Anfrage-ID (mit möglichen Zahlen von 0 bis 255, Null — keine ID)

Auf diese Weise können wir die Anzahl der Gruppen der ersten und zweiten Orders festlegen. Jede Ordergruppe kann bis zu fünfzehn Untergruppen enthalten. Wie kann das für uns von Nutzen sein? Angenommen, wir haben 20 Orders/Positionen, die wir nach einem bestimmten Kriterium zu einer einzigen Untergruppe zusammenfassen wollen. In der ersten Gruppe legen wir für sie die Untergruppennummer 1 fest. Außerdem haben wir weitere 20 Orders/Positionen, die wir nach einem anderen Kriterium zu einer anderen Untergruppe derselben ersten Gruppe gruppieren wollen. Wir setzen für sie die Untergruppennummer 2 fest. Die Untergruppe 3 wird weiteren zwanzig Orders/Positionen zugeordnet. Nun haben wir drei Gruppen von Orders/Positionen, die wir mit Hilfe des Handlers für jede der Untergruppen der ersten Gruppe leicht gemeinsam bearbeiten können. Wenn wir zwei von drei Gruppen auf andere Weise mit einem anderen Handler behandeln/analysieren müssen, ohne ihre Zugehörigkeit zu den bereits etablierten Gruppen zu verlieren, können wir sie der zweiten Gruppe (bis zu fünfzehn Untergruppen) zuweisen. Dies gibt uns mehr Flexibilität bei der Kombination von Orders/Positionen in verschiedenen Gruppen im Vergleich zu einer einzelnen Gruppe, obwohl wir eine größere Menge von Untergruppennummern haben.

Wie wir sehen, haben wir viel geplant, aber wo ist der Haken dabei? Der Haken liegt darin, dass die Größe des ganzzahligen Wertes, in dem die Magicnummer für MQL4 gespeichert ist, nur 4 Bytes (int) beträgt. Deshalb müssen wir den Wert der Magicnummer, den wir in benutzerdefinierten Programmen einstellen können, opfern. Bei MQL5 wird die Größe der Magicnummer durch den ulong-Typ festgelegt. Wir können dort größere Werte einstellen und viel mehr Daten speichern. Aber es gibt auch eine Frage der Kompatibilität, was bedeutet, dass wir etwas opfern müssen, nämlich die Größe der Magicnummer — sie wird nur zwei Bytes (ushort) betragen, während die freigegebenen zwei Bytes für die IDs von zwei Gruppen (uchar) und eine schwebende Auftrags-ID (uchar) zugewiesen werden sollen.

Die Tabelle zeigt die Struktur der Magicnummer und die Datenposition im uint-Wert der Magicnummer an:

-------------------------------------------------------------------------
| bit   |31           24|23   20|19   16|15            8|7             0|
-------------------------------------------------------------------------
| byte  |       3       |       2       |       1       |       0       |
-------------------------------------------------------------------------
| type  |     uchar     |     uchar     |            ushort             |
-------------------------------------------------------------------------
| descr |  request id   |  id2  |  id1  |             magic             |
-------------------------------------------------------------------------

Wie man aus der Tabelle erkennen kann,

  • der Wert der Magicnummer hat die Größe von zwei Bytes und wird in den beiden unteren Bytes 0 und 1 des Typs uint (Bits 0 — 15) entsprechend dem Typ ushort gespeichert. Wir werden diese Art der Magicnummer in unseren Programmen mit den möglichen Werten von 0 bis 65535 verwenden müssen.
  • Die Nächste in der Hierarchie ist die Byte 2 der uint-Zahl, die für das Speichern von zwei Gruppen-IDs verwendet wird und die Größe uchar hat (Bits 16 — 23).
    Die erste Gruppen-ID wird in den unteren vier Bits der uchar-Zahl gespeichert (Bits 16 — 19), während die zweite Gruppen-ID in den oberen vier Bits dieser uchar-Zahl gespeichert wird (Bits 20 — 23).
    Auf diese Weise können wir zwei Gruppen in einer uchar-Zahl mit eine Byte speichern, wobei die Anzahl der einzelnen Gruppen von Null (keine Gruppe) bis 15 (maximaler Wert, den wir in vier Bits speichern können) variieren kann. 
  • Im dritten und letzten uint-Zahlenbyte speichern wir den uchar-Wert einer schwebenden Anfrage-ID (Bits 24 — 31), deren Werte von Null (keine ID) bis 255 variieren können. Das bedeutet, dass wir gleichzeitig bis zu 255 aktive schwebende Anfragen haben können.

Lassen Sie uns die abstrakte Orderklasse und die Ereignisklassen verbessern, um Daten im Eigenschaftswert "Magicnummer" zu speichern.
Aber zuerst werden wir in Defines.mqh die neue ganzzahlige Eigenschaften des Objekts der abstrakten Order hinzufügen:

//+------------------------------------------------------------------+
//| Order, Deal, Position, Integer-Eigenschaften                     |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order-Ticket
   ORDER_PROP_MAGIC,                                        // Real order magic number
   ORDER_PROP_TIME_OPEN,                                    // Open time in milliseconds (MQL5 Deal time)
   ORDER_PROP_TIME_CLOSE,                                   // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE)
   ORDER_PROP_TIME_EXP,                                     // Verfallszeit der Order (für Pending-Orders)
   ORDER_PROP_STATUS,                                       // Order-Status (aus der Enumeration ENUM_ORDER_STATUS)
   ORDER_PROP_TYPE,                                         // Order/deal type
   ORDER_PROP_REASON,                                       // Deal/Order/Position Ursache oder Quelle
   ORDER_PROP_STATE,                                        // Auftragsstatus (aus der Enumeration ENUM_ORDER_STATE)
   ORDER_PROP_POSITION_ID,                                  // Positions-ID
   ORDER_PROP_POSITION_BY_ID,                               // Entgegengesetzte Positions-ID
   ORDER_PROP_DEAL_ORDER_TICKET,                            // Ticket of the order that triggered a deal
   ORDER_PROP_DEAL_ENTRY,                                   // Deal-Richtung – IN, OUT oder IN/OUT
   ORDER_PROP_TIME_UPDATE,                                  // Position change time in milliseconds
   ORDER_PROP_TICKET_FROM,                                  // Ticket der Ober-Order
   ORDER_PROP_TICKET_TO,                                    // Ticket der abgeleiteten Order
   ORDER_PROP_PROFIT_PT,                                    // Gewinn in Points
   ORDER_PROP_CLOSE_BY_SL,                                  // Flag für das Schließen durch StopLoss
   ORDER_PROP_CLOSE_BY_TP,                                  // Flag für das Schließen mit TakeProfit
   ORDER_PROP_MAGIC_ID,                                     // Order's "magic number" ID
   ORDER_PROP_GROUP_ID1,                                    // First order/position group ID
   ORDER_PROP_GROUP_ID2,                                    // Second order/position group ID
   ORDER_PROP_PEND_REQ_ID,                                  // Pending request ID
   ORDER_PROP_DIRECTION,                                    // Type by direction (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (24)                    // Total number of integer properties
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Number of order properties not used in sorting
//+------------------------------------------------------------------+


Diese Eigenschaften sollen zuvor beschriebene IDs speichern. Die IDs sollen in dem Wert der Magicnummer gespeichert werden. Da wir drei neue Eigenschaften hinzugefügt und eine geändert haben, ändern Sie die Gesamtzahl der ganzzahligen Eigenschaften der Order von 21 auf 24 in der entsprechenden Makro-Substitution.

Außerdem fügen Sie die Sortierung nach diesen Eigenschaften zur Enumeration der möglichen Order- und Dealkriterien : hinzu.

//+------------------------------------------------------------------+
//| Possible criteria of sorting orders and deals                    |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP)
enum ENUM_SORT_ORDERS_MODE
  {
//--- Sortieren nach den Integer-Eigenschaften
   SORT_BY_ORDER_TICKET = 0,                                // Sort by an order ticket
   SORT_BY_ORDER_MAGIC,                                     // Sort by an order magic number
   SORT_BY_ORDER_TIME_OPEN,                                 // Sort by an order open time in milliseconds
   SORT_BY_ORDER_TIME_CLOSE,                                // Sort by an order close time in milliseconds
   SORT_BY_ORDER_TIME_EXP,                                  // Sort by an order expiration date
   SORT_BY_ORDER_STATUS,                                    // Sort by an order status (market order/pending order/deal/balance and credit operation)
   SORT_BY_ORDER_TYPE,                                      // Sort by an order type
   SORT_BY_ORDER_REASON,                                    // Sort by a deal/order/position reason/source
   SORT_BY_ORDER_STATE,                                     // Sort by an order status
   SORT_BY_ORDER_POSITION_ID,                               // Sort by a position ID
   SORT_BY_ORDER_POSITION_BY_ID,                            // Sort by an opposite position ID
   SORT_BY_ORDER_DEAL_ORDER,                                // Sort by the order a deal is based on
   SORT_BY_ORDER_DEAL_ENTRY,                                // Sort by a deal direction – IN, OUT or IN/OUT
   SORT_BY_ORDER_TIME_UPDATE,                               // Sort by position change time in seconds
   SORT_BY_ORDER_TICKET_FROM,                               // Sort by a parent order ticket
   SORT_BY_ORDER_TICKET_TO,                                 // Sort by a derived order ticket
   SORT_BY_ORDER_PROFIT_PT,                                 // Sort by order profit in points
   SORT_BY_ORDER_CLOSE_BY_SL,                               // Sort by the flag of closing an order by StopLoss
   SORT_BY_ORDER_CLOSE_BY_TP,                               // Sort by the flag of closing an order by TakeProfit
   SORT_BY_ORDER_MAGIC_ID,                                  // Sort by an order/position "magic number" ID
   SORT_BY_ORDER_GROUP_ID1,                                 // Sort by the first order/position group ID
   SORT_BY_ORDER_GROUP_ID2,                                 // Sort by the second order/position group ID
   SORT_BY_ORDER_PEND_REQ_ID,                               // Sort by a pending request ID
   SORT_BY_ORDER_DIRECTION,                                 // Sort by direction (Buy, Sell)
//--- Sortieren nach den Double-Eigenschaften
   SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP,           // Sort by open price
   SORT_BY_ORDER_PRICE_CLOSE,                               // Sort by close price
   SORT_BY_ORDER_SL,                                        // Sort by StopLoss price
   SORT_BY_ORDER_TP,                                        // Sort by TakeProfit price
   SORT_BY_ORDER_PROFIT,                                    // Sort by profit
   SORT_BY_ORDER_COMMISSION,                                // Sort by commission
   SORT_BY_ORDER_SWAP,                                      // Sort by swap
   SORT_BY_ORDER_VOLUME,                                    // Sort by volume
   SORT_BY_ORDER_VOLUME_CURRENT,                            // Sort by unexecuted volume
   SORT_BY_ORDER_PROFIT_FULL,                               // Sort by profit+commission+swap
   SORT_BY_ORDER_PRICE_STOP_LIMIT,                          // Sort by Limit order when StopLimit order is activated
//--- Sortieren nach den String-Eigenschaften
   SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP,               // Sort by symbol
   SORT_BY_ORDER_COMMENT,                                   // Sort by comment
   SORT_BY_ORDER_COMMENT_EXT,                               // Sort by custom comment
   SORT_BY_ORDER_EXT_ID                                     // Sort by order ID in an external trading system
  };
//+------------------------------------------------------------------+


Um die Ordereigenschaften im Journal korrekt anzuzeigen, ergänzen die Indizes der neuen Eigenschaften und die entsprechenden Nachrichten in der Datei Datas.mqh:

   MSG_ORD_PROFIT_PT,                                 // Profit in points
   MSG_ORD_MAGIC_ID,                                  // Magic number ID
   MSG_ORD_GROUP_ID1,                                 // First group ID
   MSG_ORD_GROUP_ID2,                                 // Second group ID
   MSG_ORD_PEND_REQ_ID,                               // Pending request ID
   MSG_ORD_PRICE_OPEN,                                // Open price

   {"Прибыль в пунктах","Profit in points"},
   {"Идентификатор магического номера","Magic number's identifier"},
   {"Идентификатор первой группы","First group's identifier"},
   {"Идентификатор второй группы","Second group's identifier"},
   {"Идентификатор отложенного запроса","Pending request's identifier"},
   {"Цена открытия","Price open"},


Fügen Sie die erforderlichen Änderungen an der abstrakten Orderklasse in der Datei Order.mqh hinzu.

Fügen Sie im 'private' Teil der Klasse vier Methoden hinzu, die die "Magicnummer" aus dem Wert der Ordereigenschaft abrufen und die ID der Magicnummer (die in den Programmeinstellungen festgelegte Magicnummer), Gruppe 1 und 2 IDs und die ID der schwebenden Anfrage zurückgeben:

//+------------------------------------------------------------------+
//| Abstract order class                                             |
//+------------------------------------------------------------------+
class COrder : public CObject
  {
private:
   ulong             m_ticket;                                    // Ticket der ausgewählten Order/Deal (MQL5)
   long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];       // Ganzzahlige Eigenschaften
   double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];      // Double-Eigenschaften
   string            m_string_prop[ORDER_PROP_STRING_TOTAL];      // String-Eigenschaften

//--- Return the index of the array the order's (1) double and (2) string properties are located at
   int               IndexProp(ENUM_ORDER_PROP_DOUBLE property)      const { return(int)property-ORDER_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_ORDER_PROP_STRING property)      const { return(int)property-ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_DOUBLE_TOTAL; }

//--- Data location in the magic number int value
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(void)                                const { return ushort(this.Magic() & 0xFFFF);                                 }
   uchar             GetGroupID1(void)                               const { return uchar(this.Magic()>>16) & 0x0F;                                }
   uchar             GetGroupID2(void)                               const { return uchar((this.Magic()>>16) & 0xF0)>>4;                           }
   uchar             GetPendReqID(void)                              const { return uchar(this.Magic()>>24) & 0xFF;                                }

public:
//--- Standardmäßiger Konstruktor
                     COrder(void){;}


Um den Wert der ushort-Magicnummer aus dem uint-Wert der Magicnummer der Order zurückzugeben, wenden Sie die Maske (0xFFFF) an, wobei nur zwei untere Bytes in der uint-Zahl unverändert bleiben, während zwei höhere Bytes der uint-Zahl mit Nullen aufgefüllt werden. Bei der Umwandlung von uint in ushort werden die oberen zwei Bytes automatisch verworfen.

Um die erste Gruppen-ID zu erhalten, müssen wir zunächst die Eigenschaft der Magicnummer um 16 Bit nach rechts verschieben (so dass der uchar-Wert der Gruppen-IDs auf das Byte uint-Zahl Null wird). Dann wird die Maske 0x0F auf die erhaltene Zahl angewendet. Die Maske lässt nur die unteren vier Bits des während der Verschiebung erhaltenen Wertes übrig. Die Umwandlung von uint in uchar verwirft alle oberen Bytes der Zahl und lässt ein unteres Byte übrig, auf das die Maske angewendet wird. Auf diese Weise erhalten wir einen Vier-Bit-Wert von 0 bis 15.

Das Abrufen der zweiten Gruppen-ID ist anders, da sich der erforderliche Wert in den oberen vier Bits des uchar-Wertes befindet. Deshalb machen wir zunächst dasselbe wie beim Abrufen der ersten Gruppen-ID - wir verschieben den Wert der Eigenschaft der Magicnummer um 16 Bit nach rechts (so dass der uchar-Wert der Gruppen-IDs auf das Null-Byte der uint-Zahl kommt) und wenden die Maske von 0xF0 auf die erhaltene Zahl an. Die Maske lässt nur die vier obere Bits des während der Verschiebung erhaltenen Wertes übrig. Als Nächstes wird der erhaltene Wert zusätzlich um vier Bits nach rechts verschoben, so dass die oberen Bits, die die ID-Nummer speichern, auf 0 und 15 korrigiert werden.

Um die ID der schwebenden Anfrage abzurufen, schieben Sie das letzte Byte der uint-Zahl um 24 Bit nach rechts, so dass dieser Ein-Byte-uchar-Wert auf die uint-Zahl Null kommt, und wenden Sie die 0xFF-Maske darauf an (was in der Tat nicht notwendig ist, da bei der Umwandlung einer uint-Zahl in den uchar-Typ ohnehin ein einziges unteres Byte übrig bleibt).

Fügen wir den Methoden, die die vier neuen Eigenschaften zurückgeben, zum Block der Methoden für einen vereinfachten Zugriff auf die Eigenschaften des abstrakten Orderobjekts hinzu:

//+------------------------------------------------------------------+
//| Methods of a simplified access to the order object properties    |
//+------------------------------------------------------------------+
//--- Return (1) ticket, (2) parent order ticket, (3) derived order ticket, (4) magic number, (5) order reason,
//--- (6) position ID, (7) opposite position ID, (8) first group ID, (9) second group ID,
//--- (10) pending request ID, (11) magic number ID, (12) type, (13) flag of closing by StopLoss,
//--- (14) flag of closing by TakeProfit (15) open time, (16) close time,
//--- (17) order expiration date, (18) state, (19) status, (20) type by direction
   long              Ticket(void)                                       const { return this.GetProperty(ORDER_PROP_TICKET);                     }
   long              TicketFrom(void)                                   const { return this.GetProperty(ORDER_PROP_TICKET_FROM);                }
   long              TicketTo(void)                                     const { return this.GetProperty(ORDER_PROP_TICKET_TO);                  }
   long              Magic(void)                                        const { return this.GetProperty(ORDER_PROP_MAGIC);                      }
   long              Reason(void)                                       const { return this.GetProperty(ORDER_PROP_REASON);                     }
   long              PositionID(void)                                   const { return this.GetProperty(ORDER_PROP_POSITION_ID);                }
   long              PositionByID(void)                                 const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID);             }
   long              MagicID(void)                                      const { return this.GetProperty(ORDER_PROP_MAGIC_ID);                   }
   long              GroupID1(void)                                     const { return this.GetProperty(ORDER_PROP_GROUP_ID1);                  }
   long              GroupID2(void)                                     const { return this.GetProperty(ORDER_PROP_GROUP_ID2);                  }
   long              PendReqID(void)                                    const { return this.GetProperty(ORDER_PROP_PEND_REQ_ID);                }
   long              TypeOrder(void)                                    const { return this.GetProperty(ORDER_PROP_TYPE);                       }
   bool              IsCloseByStopLoss(void)                            const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL);          }
   bool              IsCloseByTakeProfit(void)                          const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP);          }
   long              TimeOpen(void)                                     const { return this.GetProperty(ORDER_PROP_TIME_OPEN);                  }
   long              TimeClose(void)                                    const { return this.GetProperty(ORDER_PROP_TIME_CLOSE);                 }
   datetime          TimeExpiration(void)                               const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP);         }
   ENUM_ORDER_STATE  State(void)                                        const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE);    }
   ENUM_ORDER_STATUS Status(void)                                       const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS);  }
   ENUM_ORDER_TYPE   TypeByDirection(void)                              const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }


Außerdem fügen wir die drei Methoden zur Einstellung der neuen Eigenschaften für die abstrakten Orders hinzu:

//--- Gesamter Gewinn der Order
   double            ProfitFull(void)                                   const { return this.Profit()+this.Comission()+this.Swap();              }
//--- Gesamter Gewinn der Order in Points
   int               ProfitInPoints(void) const;
//--- Set (1) the first group ID, (2) the second group ID, (3) the pending request ID, (4) custom comment
   void              SetGroupID1(const long group_id)                         { this.SetProperty(ORDER_PROP_GROUP_ID1,group_id);                }
   void              SetGroupID2(const long group_id)                         { this.SetProperty(ORDER_PROP_GROUP_ID2,group_id);                }
   void              SetPendReqID(const long req_id)                          { this.SetProperty(ORDER_PROP_PEND_REQ_ID,req_id);                }
   void              SetCommentExt(const string comment_ext)                  { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext);           }
   
//+------------------------------------------------------------------+


Im geschlossenen Klassenkonstruktor tragen wir die neuen Eigenschaftsfelder mit ID-Werten unter Verwendung der obigen Methoden ein:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Sichern der ganzzahligen Eigenschaften
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                               = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                                = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                               = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                                 = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                                = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                            = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                          = this.OrderPositionID();
   this.m_long_prop[ORDER_PROP_REASON]                               = this.OrderReason();
   this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]                    = this.DealOrderTicket();
   this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                           = this.DealEntry();
   this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                       = this.OrderPositionByID();
   this.m_long_prop[ORDER_PROP_TIME_OPEN]                            = this.OrderOpenTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = this.OrderCloseTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = this.PositionTimeUpdateMSC();
   
//--- Sichern der Double-Eigenschaften
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]         = this.OrderOpenPrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]        = this.OrderClosePrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]             = this.OrderProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]         = this.OrderCommission();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]               = this.OrderSwap();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]             = this.OrderVolume();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]                 = this.OrderStopLoss();
   this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]                 = this.OrderTakeProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]     = this.OrderVolumeCurrent();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]   = this.OrderPriceStopLimit();
   
//--- Sichern der String-Eigenschaften
   this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]             = this.OrderSymbol();
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]            = this.OrderComment();
   this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]             = this.OrderExternalID();
   
//--- Sichern weiterer ganzzahliger Eigenschaften
   this.m_long_prop[ORDER_PROP_PROFIT_PT]                            = this.ProfitInPoints();
   this.m_long_prop[ORDER_PROP_TICKET_FROM]                          = this.OrderTicketFrom();
   this.m_long_prop[ORDER_PROP_TICKET_TO]                            = this.OrderTicketTo();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_SL]                          = this.OrderCloseByStopLoss();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_TP]                          = this.OrderCloseByTakeProfit();
   this.m_long_prop[ORDER_PROP_MAGIC_ID]                             = this.GetMagicID();
   this.m_long_prop[ORDER_PROP_GROUP_ID1]                            = this.GetGroupID1();
   this.m_long_prop[ORDER_PROP_GROUP_ID2]                            = this.GetGroupID2();
   this.m_long_prop[ORDER_PROP_PEND_REQ_ID]                          = this.GetPendReqID();
   
//--- Sichern weiterer Double-Eigenschaften
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
   
//--- Save additional string properties
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)]        = "";
  }
//+------------------------------------------------------------------+


Hinzufügen der Darstellung der Beschreibung aller neuen Eigenschaften einer abstrakten Order zur Methode, die die Beschreibungen von ganzzahligen Eigenschaften zurückgibt:

//+------------------------------------------------------------------+
//| Rückgabe der Beschreibung der Integer-Eigenschaft des Auftrags   |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property)
  {
   return
     (
   //--- Allgemeine Eigenschaften
      property==ORDER_PROP_MAGIC             ?  CMessage::Text(MSG_ORD_MAGIC)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET            ?  CMessage::Text(MSG_ORD_TICKET)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_FROM       ?  CMessage::Text(MSG_ORD_TICKET_FROM)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_TO         ?  CMessage::Text(MSG_ORD_TICKET_TO)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_EXP          ?  CMessage::Text(MSG_ORD_TIME_EXP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          (this.GetProperty(property)==0     ?  CMessage::Text(MSG_LIB_PROP_NOT_SET)+": "+CMessage::Text(MSG_LIB_PROP_NOT_SET)  :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
         )  :
      property==ORDER_PROP_TYPE              ?  CMessage::Text(MSG_ORD_TYPE)+": "+this.TypeDescription()   :
      property==ORDER_PROP_DIRECTION         ?  CMessage::Text(MSG_ORD_TYPE_BY_DIRECTION)+": "+this.DirectionDescription() :
      
      property==ORDER_PROP_REASON            ?  CMessage::Text(MSG_ORD_REASON)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetReasonDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_ID       ?  CMessage::Text(MSG_ORD_POSITION_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ORDER_TICKET ?  CMessage::Text(MSG_ORD_DEAL_ORDER_TICKET)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ENTRY        ?  CMessage::Text(MSG_ORD_DEAL_ENTRY)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetEntryDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_BY_ID    ?  CMessage::Text(MSG_ORD_POSITION_BY_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN         ?  CMessage::Text(MSG_ORD_TIME_OPEN)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_CLOSE        ?  CMessage::Text(MSG_ORD_TIME_CLOSE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_UPDATE       ?  CMessage::Text(MSG_ORD_TIME_UPDATE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0")
         )  :
      property==ORDER_PROP_STATE             ?  CMessage::Text(MSG_ORD_STATE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": \""+this.StateDescription()+"\""
         )  :
   //--- Zusätzliche Eigenschaften
      property==ORDER_PROP_STATUS            ?  CMessage::Text(MSG_ORD_STATUS)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": \""+this.StatusDescription()+"\""
         )  :
      property==ORDER_PROP_PROFIT_PT         ?  (
                                                 this.Status()==ORDER_STATUS_MARKET_PENDING ? 
                                                 CMessage::Text(MSG_ORD_DISTANCE_PT)  : 
                                                 CMessage::Text(MSG_ORD_PROFIT_PT)
                                                )+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_CLOSE_BY_SL       ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_SL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==ORDER_PROP_CLOSE_BY_TP       ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_TP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==ORDER_PROP_MAGIC_ID          ?  CMessage::Text(MSG_ORD_MAGIC_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_GROUP_ID1         ?  CMessage::Text(MSG_ORD_GROUP_ID1)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_GROUP_ID2         ?  CMessage::Text(MSG_ORD_GROUP_ID2)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_PEND_REQ_ID       ?  CMessage::Text(MSG_ORD_PEND_REQ_ID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


Die abstrakte Orderklasse ist fertig. Jetzt müssen wir noch Änderungen an den Ereignisklassen vornehmen.

In der abstrakten Ereignisklasse in der Datei Event.mqh fügen wir die Methoden, die die neuen IDs zurückgeben, dem Abschnitt der geschützten Klasse hinzu:

protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // Handelsereignis
   bool              m_is_hedge;                                     // Hedge account flag
   long              m_chart_id;                                     // Chart-ID des Steuerprogramms
   int               m_digits;                                       // Symbol's Digits()
   int               m_digits_acc;                                   // Dezimalstellen der Kontowährung
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // Integer-Eigenschaften des Ereignisses
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // Double-Eigenschaften des Ereignisses
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // String-Eigenschaften des Ereignisses
//--- Rückgabe des Vorhandenseins des Flags des Handelsereignisses
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(void)                          const { return ushort(this.Magic() & 0xFFFF);                           }
   uchar             GetGroupID1(void)                         const { return uchar(this.Magic()>>16) & 0x0F;                          }
   uchar             GetGroupID2(void)                         const { return uchar((this.Magic()>>16) & 0xF0)>>4;                     }
   uchar             GetPendReqID(void)                        const { return uchar(this.Magic()>>24) & 0xFF;                          }
//--- 'Protected' Konstruktor
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);


Die Methoden ähneln den oben beschriebenen Methoden der abstrakten Orderklasse.

Jetzt gibt es in den fünf von der abstrakten Ereignisklasse abgeleiteten Klassen (in den Dateien EventModify.mqh, EventOrderPlaced.mqh, EventOrderRemoved.mqh, EventPositionClose.mqh und EventPositionOpen.mqh), insbesondere in den Methoden die Ereignis-Kurzbeschreibung an Stelle der einzelnen Zeichenketten

//+------------------------------------------------------------------+
//| Create and return a short event message                          |
//+------------------------------------------------------------------+
string CEventModify::EventsMessage(void)
  {

//--- (1) header, (2) magic number
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic() : "");
   string text="";


fügen wir für jede Klasse die folgenden Zeichenketten hinzu:

//+------------------------------------------------------------------+
//| Create and return a short event message                          |
//+------------------------------------------------------------------+
string CEventModify::EventsMessage(void)
  {
//--- (1) header, (2) magic number
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string magic_id=((this.GetPendReqID()>0 || this.GetGroupID1()>0 || this.GetGroupID2()>0) ? " ("+(string)this.GetMagicID()+")" : "");
   string group_id1=(this.GetGroupID1()>0 ? ", G1: "+(string)this.GetGroupID1() : "");
   string group_id2=(this.GetGroupID2()>0 ? ", G2: "+(string)this.GetGroupID2() : "");
   string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2 : "");
   string text="";



Wenn mehrere Daten in einer einzigen Magicnummer gespeichert werden, wird bei der Darstellung der Zahl im Journal ein völlig anderer Wert (nicht der in den Programmeinstellungen eingestellte) angezeigt, da die Magicnummer nur in zwei unteren Bytes gespeichert wird, während Gruppen und schwebenden Anfrage-IDs in zwei oberen Bytes gespeichert werden. Wenn also IDs (oder mindestens eine von ihnen) im Wert der Magicnummer hinzugefügt werden, werden Beschreibungen jeder einzelnen ID zu den im Journal angezeigten Werten hinzugefügt.

Wir haben alle notwendigen Änderungen für die Speicherung von Daten im Wert der Magicnummer implementiert. Kommen wir nun zur Klasse der schwebenden Anfragen und zur ersten Implementierung der Generierung von schwebenden Anfragen im Falle von Fehlern beim Öffnen einer Position.

Die Klasse Pending, einer erste Implementierung der Anfragen

Fügen wir in der Datei der Handelsklasse Trading.mqh vor dem Hauptteil der Handelsklasse CTrading die neue Klasse hinzu, die das ausstehende Antragsobjekt beschreibt:

//+------------------------------------------------------------------+
//| Pending request object class                                     |
//+------------------------------------------------------------------+
class CPendingReq : public CObject
  {
private:
   MqlTradeRequest      m_request;                       // Trade request structure
   uchar                m_id;                            // Trading request ID
   int                  m_retcode;                       // Result a request is based on
   double               m_price_create;                  // Price at the moment of a request generation
   ulong                m_time_create;                   // Request generation time
   ulong                m_time_activate;                 // Next attempt activation time
   ulong                m_waiting_msc;                   // Waiting time between requests
   uchar                m_current_attempt;               // Current attempt index
   uchar                m_total_attempts;                // Number of attempts
//--- Copy trading request data
   void                 CopyRequest(const MqlTradeRequest &request)  { this.m_request=request;        }
//--- Compare CPendingReq objects by IDs
   virtual int          Compare(const CObject *node,const int mode=0) const;

public:
//--- Return (1) the request structure, (2) the price at the moemnt of the request generation,
//--- (3) request generation time, (4) current attempt time,
//--- (5) waiting time between requests, (6) current attempt index,
//--- (7) number of attempts, (8) request ID
   MqlTradeRequest      MqlRequest(void)                    const { return this.m_request;            }
   double               PriceCreate(void)                   const { return this.m_price_create;       }
   ulong                TimeCreate(void)                    const { return this.m_time_create;        }
   ulong                TimeActivate(void)                  const { return this.m_time_activate;      }
   ulong                WaitingMSC(void)                    const { return this.m_waiting_msc;        }
   uchar                CurrentAttempt(void)                const { return this.m_current_attempt;    }
   uchar                TotalAttempts(void)                 const { return this.m_total_attempts;     }
   uchar                ID(void)                            const { return this.m_id;                 }
//--- Set (1) the price when creating a request, (2) request creation time,
//--- (3) current attempt time, (4) waiting time between requests,
//--- (5) current attempt index, (6) number of attempts, (7) request ID
   void                 SetPriceCreate(const double price)        { this.m_price_create=price;        }
   void                 SetTimeCreate(const ulong time)           { this.m_time_create=time;          }
   void                 SetTimeActivate(const ulong time)         { this.m_time_activate=time;        }
   void                 SetWaitingMSC(const ulong miliseconds)    { this.m_waiting_msc=miliseconds;   }
   void                 SetCurrentAttempt(const uchar number)     { this.m_current_attempt=number;    }
   void                 SetTotalAttempts(const uchar number)      { this.m_total_attempts=number;     }
   void                 SetID(const uchar id)                     { this.m_id=id;                     }
//--- Constructors
                        CPendingReq(void){;}
                        CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode);
  };
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CPendingReq::CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode) : m_price_create(price),
                                                                                                                                m_time_create(time),
                                                                                                                                m_id(id),
                                                                                                                                m_retcode(retcode)
  {
   this.CopyRequest(request);
  }
//+------------------------------------------------------------------+
//| Compare CPendingReq objects by IDs                               |
//+------------------------------------------------------------------+
int CPendingReq::Compare(const CObject *node,const int mode=0) const
  {
   const CPendingReq *compared_req=node;
   return(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0);
   return 0;
  }
//+------------------------------------------------------------------+


Ich glaube, diese Klasse ist ganz einfach. Es ist nicht notwendig, die darin auskommentierten Zeichenfolgen zu beschreiben. Alles ist durch die Methodennamen und die Klassenvariablen klar. Ich denke jedoch, ich sollte erklären, wie dieses Objekt, die zugehörigen Methoden und die Funktionalität der Handelsklasse funktionieren sollten.

Wenn wir einen Fehler vom Server erhalten, wollen wir eine weitere Serveranfrage erstellen und die Handelsmethode beenden. Wenn die Wartezeit der neu erstellten Anfrage abgelaufen ist, wird sie erneut an den Server gesendet. Wenn wieder ein Fehler empfangen wird, müssen wir anscheinend eine schwebende Anfrage erstellen. Aber sie wurde bereits erzeugt, als der Serverfehler zum ersten Mal empfangen wurde. Daher wird das Vorhandensein der ID der schwebenden Anfrage in der Magicnummer der empfangenen Handelsanfrage geprüft. Wenn die Anfrage bereits vorhanden ist, wurde die Anfrage bereits erstellt und ein weiterer Versuch wird im Moment an den Server gesendet, so dass keine neue Anfrage erforderlich ist. Wenn die Magicnummer der Handelsanfrage keine ID hat, wird eine neue schwebende Anfrage mit der erstniedrigsten ID erzeugt und der Ausstieg aus der Handelsmethode durchgeführt, um das Programm für andere Aktionen freizugeben. 
Die Liste der Handelsanfragen ist immer im Timer der Handelsklasse zu sehen. Wenn die Wartezeit der nächsten Anfrage abgelaufen ist, wird die entsprechende Handelsmethode aus dem Timer aufgerufen. Bei der Überprüfung jeder Anfrage aus der Liste der schwebenden Anfragen wird das Vorhandensein einer entsprechenden Position oder eines Auftrags in der Liste der Marktaufträge und Positionen geprüft. Wenn ein Auftrag oder eine Position mit der aktuellen Kennung vorhanden ist, hat die Pending-Order seine Funktion erfüllt und sollte aus der Liste der Anfragen entfernt werden.

Beginnen wir mit der Umsetzung.

Wir haben die Klasse bereits der Datei Trading.mqh hinzugefügt.
Nun deklarieren wir die Methode für die Suche und die Rückgabe der ersten unbenutzten, schwebenden Anfrage ID in ihrem 'private' Abschnitt:

//--- Look for the first free pending request ID
   int                  GetFreeID(void);
                                    
public:
//--- Konstructor
                        CTrading();


Schreiben wir seine Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Look for the first free pending request ID                       |
//+------------------------------------------------------------------+
int CTrading::GetFreeID(void)
  {
   int id=WRONG_VALUE;
   CPendingReq *element=new CPendingReq();
   if(element==NULL)
      return 0;
   for(int i=1;i<256;i++)
     {
      element.SetID((uchar)i);
      this.m_list_request.Sort();
      if(this.m_list_request.Search(element)==WRONG_VALUE)
        {
         id=i;
         break;
        }
     }
   delete element;
   return id;
  }
//+------------------------------------------------------------------+


Wir haben möglicherweise insgesamt 255 unabhängige, schwebenden Anfragen. Jede der Anfragen hat ihre eigenen Eigenschaften, ihre eigene Wartezeit zwischen den Handelsversuchen und folglich ihre eigene Lebensdauer der schwebenden Anfrageobjekte. In dieser Hinsicht kann es eine Situation geben, in der die Anzahl von beispielsweise 255 bereits von der Antrags-ID verwendet wird, während die ID mit der Nummer 0 oder 1 oder eine der niedrigeren bereits freigegeben ist und für neue Handelsanträge verwendet werden kann. Die Methode wird verwendet, um die niedrigste freigegebene ID-Nummer zu finden.

Ein temporäres Klassenobjekt einer schwebenden Anfrage und die ID mit dem Wert -1 werden zuerst erstellt. Dieser Wert informiert darüber, dass es keine freien IDs gibt — alle 255 davon sind belegt, während der Wert 0 einen Fehler bei der Erstellung eines temporären Objekts bedeutet. Weiter, in einer Schleife durch mögliche ID-Indexwerte von 1 bis 255, prüfen wir das Vorhandensein eines Anfrageobjekts mit einer ID, die dem aktuellen Schleifenindex entspricht, in der Liste der schwebenden Anfragen. Dazu setzen wir zunächst die ID gleich der Schleifenindexnummer für das temporäre Objekt, das Flag für die sortierte Liste für die Liste und suchen einfach nach dem Anfrageobjekt mit der gleichen ID in der Liste. Mit anderen Worten, wir suchen nach dem Anfrageobjekt, das dem temporären Objekt entspricht, auf das ein Schleifenindex als ID gesetzt ist. Wenn ein solches Objekt nicht in der Liste gefunden wird, setzen wir den Schleifenindex für den von der Methode zurückgegebenen Wert und brechen die Schleife ab.
Nach Abschluss der Schleife entfernen wir das temporäre Anfrageobjekt und geben den ID-Wert zurück, der entweder -1 oder zwischen 1 und 255 sein kann.

Im 'public' Klassenabschnitt deklarieren wir die Methode zum Erstellen einer schwebenden Anfrage und fügen Sie die Methoden für Setzen/Rückgabe der ID-Werte zu/vom Order-/Positionseigenschaftswert der "Magicnummer" hinzu:

//--- Create a pending request
   bool                 CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj);

//--- Data location in the magic number int value
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------

//--- Set the ID of the (1) first group, (2) second group, (3) pending request to the magic number value
   void              SetGroupID1(const uchar group,uint &magic)            { magic &=0xFFF0FFFF; magic |= uint(ConvToXX(group,0)<<16);    }
   void              SetGroupID2(const uchar group,uint &magic)            { magic &=0xFF0FFFFF; magic |= uint(ConvToXX(group,1)<<16);    }
   void              SetPendReqID(const uchar id,uint &magic)              { magic &=0x00FFFFFF; magic |= (uint)id<<24;                   }
//--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones)
   uchar             ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));}
//--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value
   ushort            GetMagicID(const uint magic)                    const { return ushort(magic & 0xFFFF);                               }
   uchar             GetGroupID1(const uint magic)                   const { return uchar(magic>>16) & 0x0F;                              }
   uchar             GetGroupID2(const uint magic)                   const { return uchar((magic>>16) & 0xF0)>>4;                         }
   uchar             GetPendReqID(const uint magic)                  const { return uchar(magic>>24) & 0xFF;                              }


Wir haben bereits die Methoden der Werterückgabe besprochen. Hier werden die gleichen verwendet. Betrachten wir die Methoden zum Setzen unterschiedlicher IDs.

Da zwei Gruppen-IDs in einem einzigen Byte gespeichert sind und der numerische ID-Wert nur Werte von 0 bis 15 (4 Bytes) annehmen darf, müssen wir seine Werte um 4 Bits nach links verschieben, um die zweite Gruppen-ID zu setzen. Dadurch können wir sie in den vier oberen Bits einer Ein-Byte-Zahl speichern. Dies geschieht durch die Methode ConvToXX(). Je nach Gruppenindex (0 oder 1) verschiebt sie eine ihr übergebene Zahl (0-15) um 4 Bits nach links (die zweite Gruppe, Index 1) oder sie verschiebt sie nicht (die erste Gruppe, Index 0).

Um den ersten Wert der Gruppen-ID einzustellen, müssen wir zunächst die vier unteren Bits des Bytes, in dem wir den ID-Wert speichern wollen, zurücksetzen. Dies kann durch Anwenden der Maske auf die Magicnummer geschehen. Der F-Wert wird für jedes Halbbyte (4 Bits) in der Maske verwendet.
Mit anderen Worten, der hexadezimale Wert der Dezimalzahl 15 (F) wird auf die Bits angewendet, wobei die Werte unverändert bleiben sollten, während Null auf die zu löschenden Bits angewendet wird. Die Maske, die auf den Wert der Magicnummer angewendet wird, soll also wie folgt aussehen: 0x FFF0FFFF.
wobei:

  • FFFF — belassen wir die ID der Magicnummer wie sie ist (die in den EA-Einstellungen eingestellte Magicnummer),
  • F0 — löschen wir (0) die unteren vier Bits im Byte, das die Gruppen-IDs speichert, während die oberen auf (F) gesetzt bleiben — die zweite Gruppen-ID wird dort gespeichert,
  • FF — wir lassen den ID-Wert der schwebenden Anfrage stehen

Als Nächstes wird die aus der ConvToXX()-Methode erhaltene Gruppennummer mit dem Index 0 platziert und um 16 Bit nach links auf das für die Speicherung von Gruppen-IDs vorbereitete Byte verschoben, so dass die erhaltene Nummer auf das erforderliche Byte kommt, in dem die Gruppen-IDs gespeichert werden.

Um den zweiten Gruppen-ID-Wert einzustellen, setzen Sie die vier oberen Bits des Bytes zurück, in dem wir den ID-Wert speichern werden. Wir tun dies, indem wir die Maske 0x FF0FFFFFFF auf die Magicnummer anwenden.
wobei:

  • FFFF — belassen wir die ID der Magicnummer wie sie ist (die in den EA-Einstellungen eingestellte Magicnummer),
  • 0F — wir löschen (0) die oberen vier Bits im Byte, das die Gruppen-IDs speichert, während die unteren auf (F) gesetzt bleiben — die erste Gruppen-ID wird dort gespeichert,
  • FF — wir lassen den ID-Wert der schwebenden Anfrage stehen

Als Nächstes wird die aus der ConvToXX()-Methode erhaltene Gruppennummer mit dem Index 1 platziert und um 16 Bit nach links auf das für die Speicherung von Gruppen-IDs vorbereitete Byte verschoben, so dass die erhaltene Nummer auf das erforderliche Byte kommt, in dem die Gruppen-IDs gespeichert werden.

Um den ID-Wert der schwebenden Anfrage zu setzen, setzen Sie den Byte-Wert zurück, in dem wir den ID-Wert speichern werden. Dies geschieht durch Anwendung der Maske 0x 00FFFFFF auf die Magicnummer.
wobei:

  • FFFF — belassen wir die ID der Magicnummer wie sie ist (die in den EA-Einstellungen eingestellte Magicnummer),
  • FF — lassen wir die ID-Werte der Gruppe unverändert,
  • 00 — wir löschen den Wert der schwebenden Anfrage

Als Nächstes wird der ID uchar-Wert um 24 Bit nach links zum Byte verschoben, das für die Speicherung einer schwebenden Anfrage-ID vorbereitet ist, so dass die erhaltene Zahl auf das erforderliche Byte kommt, in dem die schwebenden Anfrage-ID gespeichert ist.

Implementieren wir die Methode zur Erzeugung eines schwebenden Anfrageobjekts außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Create a pending request                                         |
//+------------------------------------------------------------------+
bool CTrading::CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj)
  {
   //--- Create a new pending request object
   CPendingReq *req_obj=new CPendingReq(id,symbol_obj.Bid(),symbol_obj.Time(),request,retcode);
   if(req_obj==NULL)
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
      return false;
     }
   //--- If failed to add the request to the list, display the appropriate message,
   //--- remove the created object and return 'false'
   if(!this.m_list_request.Add(req_obj))
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ));
      delete req_obj;
      return false;
     }
   //--- Filled in the fields of a successfully created object by the values passed to the method
   req_obj.SetTimeActivate(symbol_obj.Time()+wait);
   req_obj.SetWaitingMSC(wait);
   req_obj.SetCurrentAttempt(0);
   req_obj.SetTotalAttempts(attempts);
   return true;
  }
//+------------------------------------------------------------------+


Die Methode ist einfach — es wird ein neues Anfrageobjekt erstellt und der Liste der schwebenden Anfragen hinzugefügt. Die an die Methode übergebenen Werte werden den Objektfeldern hinzugefügt (die Aktivierungszeit der Anfrage wird als Anfrageerzeugungszeit + Wartezeit berechnet) und es wird true zurückgegeben. Andernfalls gibt die Methode false zurück.

In der Handelsklassenzeitschaltuhr haben wir im vorigen Artikel die Logik der Arbeit mit schwebenden Anfragen hinzugefügt:

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CTrading::OnTimer(void)
  {
   //--- In a loop by the list of pending requests
   int total=this.m_list_request.Total();
   for(int i=total-1;i>WRONG_VALUE;i--)
     {
      //--- receive the next request object
      CPendingReq *req_obj=this.m_list_request.At(i);
      if(req_obj==NULL)
         continue;
      //--- if the current attempt exceeds the defined number of trading attempts,
      //--- remove the current request object and move on to the next one
      if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX)
        {
         this.m_list_request.Delete(i);
         continue;
        }
      //--- get the request structure and the symbol object a trading operation should be performed for
      MqlTradeRequest request=req_obj.MqlRequest();
      CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol);
      if(symbol_obj==NULL || !symbol_obj.RefreshRates())
         continue;
      
      //--- Set the request activation time in the request object
      req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1));
      
      //--- If the current time is less than the request activation time,
      //--- this is not the request time - move on to the next request in the list
      if(symbol_obj.Time()<req_obj.TimeActivate())
         continue;
      
      //--- Set the attempt number in the request object
      req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1));
      
      //--- Get the pending request ID
      uchar id=this.GetPendReqID((uint)request.magic);
      
      //--- Get the list of orders/positions containing the order/position with the pending request ID
      CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL);
      if(::CheckPointer(list)==POINTER_INVALID)
         continue;
      //--- Depending on the type of action performed in the trading request 
      switch(request.action)
        {
         //--- Positionseröffnung
         case TRADE_ACTION_DEAL :
            //--- if there is no position/order with the obtained pending request ID (the list is empty), send a trading request
            if(list.Total()==0)
              {
               this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation);
              }
            //--- if a position/order with the obtained pending request ID is already present (the list is empty), the request has been handled and should be removed 
            else
               this.m_list_request.Delete(i);
            break;
         //---
         default:
            break;
        }
     }
  }
//+------------------------------------------------------------------+


Die Operationslogik wird in den Code-Kommentaren ausführlich beschrieben und bedarf keiner Erklärungen. Bemerkenswert ist nur die Berechnung der nächsten Aktivierungszeit für die Handelsanfrage. Die Zeit wird berechnet als "Erstellungszeit des Anfrageobjekts" + Wartezeit in Millisekunden * Index des nächsten Versuchs. Somit ist die Anfragezeit an die Erstellungszeit der ersten Anfrage und den Index für den nächsten Versuch gebunden. Je höher die Versuchszahl, desto mehr Zeit sollte von der Objekterzeugung bis zu seiner Aktivierung vergehen. Die Zeit wird diskret erhöht: wenn wir 10 Sekunden warten, sollte der erste Versuch in 10 Sekunden erfolgen, der zweite — in 20 Sekunden, der dritte — in 30 Sekunden usw. Die Zeitspanne zwischen den Handelsversuchen wird also immer nicht kürzer sein als die angegebene Wartezeit zwischen ihnen.

In der Methode, die die Art und Weise der Fehlerbehandlung zurückgibt, verschieben Sie den Code des Fehlers der Abwesenheit der Handelsserververbindung in den Block, der den Behandlungstyp "Warten" zurückgibt. Zuvor wurde der Code als "Erstellen einer schwebenden Handelsanfrage" behandelt:

//+------------------------------------------------------------------+
//| Return the error handling method                                 |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(const uint result_code)
  {
   switch(result_code)
     {
   #ifdef __MQL4__
      //--- Malfunctional trade operation
      case 9   :
      //--- Account disabled
      case 64  :
      //--- Invalid account number
      case 65  :  return ERROR_CODE_PROCESSING_METHOD_DISABLE;
      
      //--- No error but result is unknown
      case 1   :
      //--- General error
      case 2   :
      //--- Old client terminal version
      case 5   :
      //--- Not enough rights
      case 7   :
      //--- Market closed
      case 132 :
      //--- Trading disabled
      case 133 :
      //--- Order is locked and being processed
      case 139 :
      //--- Buy only
      case 140 :
      //--- The number of open and pending orders has reached the limit set by the broker
      case 148 :
      //--- Attempt to open an opposite order if hedging is disabled
      case 149 :
      //--- Attempt to close a position on a symbol contradicts the FIFO rule
      case 150 :  return ERROR_CODE_PROCESSING_METHOD_EXIT;
      
      //--- Invalid trading request parameters
      case 3   :
      //--- Invalid price
      case 129 :
      //--- Invalid stop levels
      case 130 :
      //--- Invalid volume
      case 131 :
      //--- Not enough money to perform the operation
      case 134 :
      //--- Expirations are denied by broker
      case 147 :  return ERROR_CODE_PROCESSING_METHOD_CORRECT;
      
      //--- Trade server is busy
      case 4   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- No connection to the trade server
      case 6   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Too frequent requests
      case 8   :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- No price
      case 136 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Broker is busy
      case 137 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Too many requests
      case 141 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Modification denied because the order is too close to market
      case 145 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- Trade context is busy
      case 146 :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)1000;    // ERROR_CODE_PROCESSING_METHOD_WAIT
      
      //--- Trade timeout
      case 128 :
      //--- Price has changed
      case 135 :
      //--- New prices
      case 138 :  return ERROR_CODE_PROCESSING_METHOD_REFRESH;

   //--- MQL5
   #else
      //--- Auto trading disabled by the server
      case 10026  :  return ERROR_CODE_PROCESSING_METHOD_DISABLE;
      
      //--- Request canceled by a trader
      case 10007  :
      //--- Request expired
      case 10012  :
      //--- Trading disabled
      case 10017 :
      //--- Market closed
      case 10018  :
      //--- Order status changed
      case 10023  :
      //--- Request unchanged
      case 10025  :
      //--- Request blocked for handling
      case 10028  :
      //--- Transaction is allowed for live accounts only
      case 10032  :
      //--- The maximum number of pending orders is reached
      case 10033  :
      //--- Reached the maximum order and position volume for this symbol
      case 10034  :
      //--- Invalid or prohibited order type
      case 10035  :
      //--- Position with the specified ID already closed
      case 10036  :
      //--- A close order is already present for a specified position
      case 10039  :
      //--- The maximum number of open positions is reached
      case 10040  :
      //--- Request to activate a pending order is rejected, the order is canceled
      case 10041  :
      //--- Request is rejected, because the rule "Only long positions are allowed" is set for the symbol
      case 10042  :
      //--- Request is rejected, because the rule "Only short positions are allowed" is set for the symbol
      case 10043  :
      //--- Request is rejected, because the rule "Only closing of existing positions is allowed" is set for the symbol
      case 10044  :
      //--- Request is rejected, because the rule "Only closing of existing positions by FIFO rule is allowed" is set for the symbol
      case 10045  :  return ERROR_CODE_PROCESSING_METHOD_EXIT;

      //--- Requote
      case 10004  :
      //--- Request rejected
      case 10006  :
      //--- Prices changed
      case 10020  :  return ERROR_CODE_PROCESSING_METHOD_REFRESH;

      //--- Invalid request
      case 10013  :
      //--- Invalid request volume
      case 10014  :
      //--- Invalid request price
      case 10015  :
      //--- Invalid request stop levels
      case 10016  :
      //--- Insufficient funds for request execution
      case 10019  :
      //--- Invalid order expiration in a request
      case 10022  :
      //--- The specified type of order execution by balance is not supported
      case 10030  :
      //--- Closed volume exceeds the current position volume
      case 10038  :  return ERROR_CODE_PROCESSING_METHOD_CORRECT;

      //--- No quotes to handle the request
      case 10021  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000;    // ERROR_CODE_PROCESSING_METHOD_WAIT;
      //--- Too frequent requests
      case 10024  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT
      //--- An order or a position is frozen
      case 10029  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000;   // ERROR_CODE_PROCESSING_METHOD_WAIT;
      //--- No connection to the trade server
      case 10031  :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)20000;   // ERROR_CODE_PROCESSING_METHOD_WAIT;

      //--- Request handling error
      case 10011  :
      //--- Auto trading disabled by the client terminal
      case 10027  :  return ERROR_CODE_PROCESSING_METHOD_PENDING;

      //--- Order placed
      case 10008  :
      //--- Request executed
      case 10009  :
      //--- Request executed partially
      case 10010  :
   #endif 
      //--- "OK"
      default:
        break;
     }
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+


Warum wohl? Erstens, um das Erstellen von schwebenden Anfragen mit Warten zu testen, eine Wartezeit von 20 Sekunden zwischen den Anfragen zurückgeben. Außerdem wird dadurch die Durchführung mehrerer Handelsversuche während des Wartens auf die Wiederherstellung der Verbindung mit dem Handelsserver bequemer. Wie auch immer, dies ist die erste Testversion der Behandlung von schwebenden Anfragen und sie wird verbessert und geändert werden.

Da wir das Konzept hier testen, werden wir eine schwebende Anfrage nur zur Eröffnung einer Position und nur zur Erlangung eines Handelsserverfehlers erstellen. Bei der Überprüfung der Gültigkeit von Handelsanfragen werden wir keine schwebenden Anfragen erstellen, die innerhalb der Positionseröffnungsmethode warten.

Fügen Sie den Block mit den schwebenden Anfragen zur Positionseröffnungsmethode hinzu:

//+------------------------------------------------------------------+
//| Open a position                                                  |
//+------------------------------------------------------------------+
template<typename SL,typename TP> 
bool CTrading::OpenPosition(const ENUM_POSITION_TYPE type,
                            const double volume,
                            const string symbol,
                            const ulong magic=ULONG_MAX,
                            const SL sl=0,
                            const TP tp=0,
                            const string comment=NULL,
                            const ulong deviation=ULONG_MAX)
  {
//--- Set the trading request result as 'true' and the error flag as "no errors"
   bool res=true;
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type;
   ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type;
//--- Get a symbol object by a symbol name. If failed to get
   CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
//--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false'
   if(symbol_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return false;
     }
//--- get a trading object from a symbol object
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
//--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false'
   if(trade_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Set the prices
//--- If failed to set - write the "internal error" flag, set the error code in the return structure,
//--- display the message in the journal and return 'false'
   if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj))
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      trade_obj.SetResultRetcode(10021);
      trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(10021));   // No quotes to process the request
      return false;
     }

//--- Write the volume to the request structure
   this.m_request.volume=volume;
//--- Get the method of handling errors from the CheckErrors() method while checking for errors in the request parameters
   double pr=(type==POSITION_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.Bid());
   ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,pr,action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp);
//--- In case of trading limitations, funds insufficiency,
//--- if there are limitations by StopLevel or FreezeLevel ...
   if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
     {
      //--- If trading is completely disabled, set the error code to the return structure,
      //--- display a journal message, play the error sound and exit
      if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
        {
         trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
         trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- If the check result is "abort trading operation" - set the last error code to the return structure,
      //--- display a journal message, play the error sound and exit
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- If the check result is "waiting" - set the last error code to the return structure and display the message in the journal
      if(method==ERROR_CODE_PROCESSING_METHOD_WAIT)
        {
         int code=this.m_list_errors.At(this.m_list_errors.Total()-1);
         if(code!=NULL)
           {
            trade_obj.SetResultRetcode(code);
            trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
           }
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
         //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned)
         ::Sleep(method);
         //--- after waiting, update all data
         symbol_obj.Refresh();
        }
      //--- If the check result is "create a pending request", do nothing temporarily
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
        }
     }
   
//--- In the loop by the number of attempts
   for(int i=0;i<this.m_total_try;i++)
     {                
      //--- Send the request
      res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation);
      //--- If the request is executed successfully or the asynchronous order sending mode is set, play the success sound
      //--- set for a symbol trading object for this type of trading operation and return 'true'
      if(res || trade_obj.IsAsyncMode())
        {
         if(this.IsUseSounds())
            trade_obj.PlaySoundSuccess(action,order_type);
         return true;
        }
      //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation
      else
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         
         //--- Get the error handling method
         method=this.ResultProccessingMethod(trade_obj.GetResultRetcode());
         //--- If "Disable trading for the EA" is received as a result of sending a request, enable the disabling flag and end the attempt loop
         if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
           {
            this.SetTradingDisableFlag(true);
            break;
           }
         //--- If "Exit the trading method" is received as a result of sending a request, end the attempt loop
         if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
           {
            break;
           }
         //--- If "Correct the parameters and repeat" is received as a result of sending a request -
         //--- correct the parameters and start the next iteration
         if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT)
           {
            this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj);
            continue;
           }
         //--- If "Update data and repeat" is received as a result of sending a request -
         //--- update data and start the next iteration
         if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH)
           {
            symbol_obj.Refresh();
            continue;
           }
         //--- If "Wait and repeat" or "Create a pending request" is received as a result of sending a request,
         //--- create a pending request and end the loop
         if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH)
           {
            //--- If the trading request magic number, has no pending request ID
            if(this.GetPendReqID((uint)magic)==0)
              {
               //--- Waiting time in milliseconds:
               //--- for the "Wait and repeat" handling method, the waiting value corresponds to the 'method' value,
               //--- for the "Create a pending request" handling method - till there is a zero waiting time
               ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : 0);
               //--- Look for the least of the possible IDs. If failed to find
               //--- or in case of an error while updating the current symbol data, return 'false'
               int id=this.GetFreeID();
               if(id<1 || !symbol_obj.RefreshRates())
                  return false;
               //--- Write the request ID to the magic number, while a symbol name is set in the request structure
               //--- Set position and trading operation types (the remaining structure fields are already filled in)
               uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic);
               this.SetPendReqID((uchar)id,mn);
               this.m_request.magic=mn;
               this.m_request.symbol=symbol_obj.Name();
               this.m_request.action=TRADE_ACTION_DEAL;
               this.m_request.type=order_type;
               //--- Pass the number of trading attempts minus one to the pending request,
               //--- since there already has been one failed attempt
               uchar attempts=(this.m_total_try-1 < 1 ? 1 : this.m_total_try-1);
               this.CreatePendingRequest((uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj);
               break;
              }
           }
        }
     }
//--- Return the result of sending a trading request in a symbol trading object
   return res;
  }
//+------------------------------------------------------------------+


Die Code-Kommentare enthalten alle Details. Sie können jedoch gerne den Kommentarbereich verwenden, wenn Sie Fragen haben.

Lassen Sie uns noch leichte Verbesserungen in der Basisobjektklasse CEngine in der Datei Engine.mqh vornehmen.

Fügen Sie die Methode zur Einstellung der Anzahl der Handelsversuche in den Methodenblock zur Platzierung von Handelsobjekteigenschaften ein:

//--- Set the following for the trading classes:
//--- (1) correct filling policy, (2) filling policy,
//--- (3) correct order expiration type, (4) order expiration type,
//--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date,
//--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts
   void                 TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL);
   void                 TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL);
   void                 TradingSetMagic(const uint magic,const string symbol_name=NULL);
   void                 TradingSetComment(const string comment,const string symbol_name=NULL);
   void                 TradingSetDeviation(const ulong deviation,const string symbol_name=NULL);
   void                 TradingSetVolume(const double volume=0,const string symbol_name=NULL);
   void                 TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL);
   void                 TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL);
   void                 TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL);
   void                 TradingSetTotalTry(const uchar attempts)                          { this.m_trading.SetTotalTry(attempts);               }
   
//--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols


Die Methode ruft einfach die gleichnamige Handelsklassenmethode auf.

Fügen Sie im 'private' Klassenabschnitt die Methode zur Konvertierung von Gruppen-ID-Werten in einen uchar-Wert hinzu, während Sie im 'public' Abschnitt die Methode zur Erstellung und Rückgabe der zusammengesetzten Magicnummer deklarieren:

//--- Constructor/Destructor
                        CEngine();
                       ~CEngine();

private:
//--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones)
   uchar                ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));   }

public:
//--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID
   uint                 SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0);

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


Wir haben die obige Konvertierungsmethode bereits berücksichtigt. Die Methode zur Erstellung der zusammengesetzten Magicnummer wird verwendet, um die Werte der Magicnummer, der ersten und zweiten Gruppe und der schwebenden Auftrags-ID zu einer einzigen Magicnummer zu kombinieren, die für einen Auftrag festgelegt wird, wenn dieser an den Server gesendet wird.
Hier ist die Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Create and return the composite magic number                     |
//| from the specified magic number value,                           |
//| first and seconf group IDs and                                   |
//| the pending request ID                                           |
//+------------------------------------------------------------------+
uint CEngine::SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0)
  {
   uint magic=magic_id;
   this.m_trading.SetGroupID1(group_id1,magic);
   this.m_trading.SetGroupID2(group_id2,magic);
   this.m_trading.SetPendReqID(pending_req_id,magic);
   return magic;
  }
//+------------------------------------------------------------------+

Die Methode empfängt alle IDs, die alle zum Wert der Magicnummer (die von der Methode zurückgegeben werden soll) unter Verwendung der zuvor in Betracht gezogenen Handelsklassenmethoden zum Setzen von IDs addiert werden.

Das ist alles, was wir tun müssen, um die vorgestellte Idee zu überprüfen.

Um die Generierung und Bearbeitung einer schwebender Anfrage zu testen, müssen wir einen Fehler simulieren, der nach dem Warten eine zweite Anfrage erfordert. Wie Sie sich vielleicht erinnern, haben wir die Behandlung des Fehlers "keine Verbindung zum Handelsserver" auf 20 Sekunden Wartezeit festgelegt. Wir haben standardmäßig fünf Handelsversuche. Das bedeutet, dass wir einfach den EA starten, das Internet deaktivieren (Verbindung zum Handelsserver trennen) und versuchen müssen, eine Position zu eröffnen (Schaltfläche Kaufen/Verkaufen auf dem Test-AA-Handelspanel). Nachdem wir einen Fehler erhalten haben, haben wir 20 * 5 = 100 Sekunden Zeit, um das Internet wieder zu aktivieren und zu beobachten, wie der EA die erstellte Pending-Anfrage behandelt. Nach 100 Sekunden (notwendig, um fünf wiederholte Versuche abzuschließen), sollte die schwebende Anfrage automatisch aus der Anfrageliste entfernt werden (nachdem die Verbindung zum Server wiederhergestellt wurde, da die Zeit nur bei aktiver Verbindung erhalten werden kann). Diese Funktion ist noch nicht implementiert, da wir derzeit die Operation der schwebenden Anfrage testen. Außerdem befindet sich die Funktionalität noch in der Entwicklung und erfordert Änderungen, damit alle übrigen Funktionen implementiert werden können. Dies bedeutet, dass der EA nach Wiederherstellung der Verbindung zum Handelsserver in jedem Fall mit dem Senden von Handelsanfragen beginnt, die im Anfrageobjekt festgelegt sind. Nach dem ersten der wiederholten Versuche sollte eine Position eröffnet und das schwebende Anfrageobjekt aus der Anfrageliste entfernt werden.

Wir haben die Speicherung mehrerer IDs im Wert der Magicnummer zusammen mit der schwebenden Anfrage implementiert. Um das Hinzufügen dieser IDs zu der gesendeten Magicnummer der Anfrage zu testen, nehmen wir eine zufällige Auswahl der ersten und zweiten Untergruppennummern in den Gruppen 1 und 2 vor und schreiben sie in die Ordereigenschaft "Magicnummer". Beim Öffnen einer Position zeigt das Journal sowohl eine echte Magicnummer der offenen Position/der platzierten Orders als auch eine in den Einstellungen festgelegte ID der Magicnummer (in Klammern nach dem wahren Wert der Magicnummer) sowie Untergruppen-IDs in der ersten und zweiten Gruppe (angegeben als G1 und G2) an.

Tests

Um schwebende Anfragen zu testen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\Part26\ unter dem Namen TestDoEasyPart26.mq5.

In den EA-Eingaben ändern wir den Typ der Magicnummer von ulong in ushort jetzt darf die maximale Größe der Magicnummer zwei Bytes (65535) nicht überschreiten. Fügen wir außerdem noch eine weitere Variable hinzu — die Anzahl der Handelsversuche:

//--- Eingabeparameter
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
sinput   uint              InpButtShiftX        =  40;   // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 
input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;   // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds
//--- Globale Variablen


In den globalen Variablen ändern wir den Variablentyp von magic_number ulong in ushort und fügen die zwei Variablen zur Speicherung der Gruppenwerte hinzu:

//--- Globale Variablen
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
bool           trailing_on;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         used_symbols;
string         array_used_symbols[];
bool           testing;
uchar          group1;
uchar          group2;
//+------------------------------------------------------------------+


Im OnInit() initialisieren wir die Gruppenvariablen und setzen Sie den Anfangsstatus, um Pseudozufallszahlen zu erzeugen:

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal 
//--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
   
//--- Initialize random group numbers
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Initialize DoEasy library
   OnInitDoEasy();


In der Bibliotheksinitialisierungsfunktion setzen wir die standardmäßige Magicnummer für alle Handelsobjekte und die Anzahl der Handelsversuche:

//+------------------------------------------------------------------+
//| Initializing DoEasy library                                      |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Check if working with the full list is selected
   used_symbols_mode=InpModeUsedSymbols;
   if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
     {
      int total=SymbolsTotal(false);
      string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
      string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
      string caption=TextByLanguage("Внимание!","Attention!");
      string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
      string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
      string message=TextByLanguage(ru,en);
      int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
      int mb_res=MessageBox(message,caption,flags);
      switch(mb_res)
        {
         case IDNO : 
           used_symbols_mode=SYMBOLS_MODE_CURRENT; 
           break;
         default:
           break;
        }
     }
//--- Fill in the array of used symbols
   used_symbols=InpUsedSymbols;
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols);

//--- Set the type of the used symbol list in the symbol collection
   engine.SetUsedSymbols(array_used_symbols);
//--- Displaying the selected mode of working with the symbol object collection
   Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Number of used symbols: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal());
   
//--- Create resource text files
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);

//--- Pass all existing collections to the trading class
   engine.TradingOnInit();

//--- Set the default magic number for all used symbols
   engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
//--- Set synchronous passing of orders for all used symbols
   engine.TradingSetAsyncMode(false);
//--- Set the number of trading attempts in case of an error
   engine.TradingSetTotalTry(InpTotalAttempts);
//--- Set standard sounds for trading objects of all used symbols
   engine.SetSoundsStandart();
//--- Set the general flag of using sounds
   engine.SetUseSounds(InpUseSounds);
//--- Set the spread multiplier for symbol trading objects in the symbol collection
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Set controlled values for symbols
   //--- Get the list of all collection symbols
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- In a loop by the list, set the necessary values for tracked symbol properties
      //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" 
      //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program
      /*
      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- Set control of the symbol price increase by 100 points
         symbol.SetControlBidInc(100000*symbol.Point());
         //--- Set control of the symbol price decrease by 100 points
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Set control of the symbol spread increase by 40 points
         symbol.SetControlSpreadInc(400);
         //--- Set control of the symbol spread decrease by 40 points
         symbol.SetControlSpreadDec(400);
         //--- Set control of the current spread by the value of 40 points
         symbol.SetControlSpreadLevel(400);
        }
      */
     }
//--- Set controlled values for the current account
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Set control of the profit increase to 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Set control of the funds increase to 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Set profit control level to 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
  }
//+------------------------------------------------------------------+


Um die Magicnummer mit zufälligen Gruppen-ID-Werten zu testen, führen wir die boolesche Variable comp_magic ein, die gleich true ist und die Verwendung der zusammengesetzten Magicnummer in den Funktionen zum Eröffnen/Platzieren von Positionen und Pending-Orders angibt. Anstatt die Variable magic_number zu verwenden, führen Sie die neue Variable magic ein, die die Magicnummer in Abhängigkeit von der Variablen comp_magic speichert.
Wenn wir den Wert für magic (die in den Einstellungen definierte permanente Magicnummer oder die zusammengesetzte Magicnummer, die aus einer bestimmten Magicnummer + zufälligen ID-Werten der Gruppe 1 und 2 besteht) einstellen, werden wir den Wert comp_magic überprüfen. Wenn true, verwenden wir die zusammengesetzte Magicnummer. Wenn false
, verwenden wir die in den Einstellungen definierte.

Nehmen wir Änderungen in der Funktion PressButtonEvents() vor, die das Drücken der Tasten des EA-Handelspanels behandelt:

//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   bool comp_magic=true;   // Temporary variable selecting the composite magic number with random group IDs
   string comment="";
   //--- Konvertieren der Namen der Schaltflächen in die Zeichenketten-ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- Random group 1 and 2 numbers within the range of 0 - 15
   group1=(uchar)Rand();
   group2=(uchar)Rand();
   uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number);
   //--- Falls eine Taste gedrückt wurde
   if(ButtonState(button_name))
     {
      //--- Wenn die Schaltfläche BUTT_BUY geklickt wurde: Eröffnen einer Kaufposition
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Eröffnen einer Kaufposition
         engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit);   // No comment - the default comment is to be set
        }
      //--- Falls die Schaltfläche BUTT_BUY_LIMIT geklickt wurde: Platzieren von BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Setzen einer BuyLimit-Order
         engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order"));
        }
      //--- Falls die Schaltfläche BUTT_BUY_STOP geklickt wurde: Platzieren von BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- Setzen einer BuyStop-Order
         engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order"));
        }
      //--- Falls die Schaltfläche BUTT_BUY_STOP_LIMIT geklickt wurde: Platzieren von BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- Setzen von BuyStopLimit-Order
         engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order"));
        }
      //--- Wenn die Schaltfläche BUTT_SELL geklickt wurde: Eröffnen einer Verkaufsposition
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- Eröffnen einer Verkaufsposition
         engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit);  // No comment - the default comment is to be set
        }
      //--- Falls die Schaltfläche BUTT_SELL_LIMIT geklickt wurde: Setzen von SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- Setzen von SellLimit-Order
         engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order"));
        }
      //--- Falls die Schaltfläche BUTT_SELL_STOP geklickt wurde: Platzieren von SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- Setzen von SellStop-Order
         engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order"));
        }
      //--- Falls die Schaltfläche BUTT_SELL_STOP_LIMIT geklickt wurde: Platzieren von SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- Setzen der SellStopLimit-Order
         engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order"));
        }
      //--- Wenn die Schaltfläche BUTT_CLOSE_BUY geklickt wurde: Schließen einer Kaufposition mit Maximalgewinn
      else if(button==EnumToString(BUTT_CLOSE_BUY))


Ersetzen wir in allen Zeichenketten, die die Bibliotheks-Handelsmethoden aufrufen, die Variable Magicnummer durch magic.

Um Zufallswerte für Gruppen-IDs zu setzen, addieren Sie den von der Funktion Rand() zurückgegebenen Wert, der bereits minimale und maximale Werte des Bereichs aufweist, innerhalb dessen die Funktion einen pseudozufälligen Wert zurückgibt:

//+------------------------------------------------------------------+
//| A random value within the range                                  |
//+------------------------------------------------------------------+
uint Rand(const uint min=0, const uint max=15)
  {
   return (rand() % (max+1-min))+min;
  }
//+------------------------------------------------------------------+

Kompilieren und starten wir den EA. Trennen wir die Internetverbindung und warten, bis das folgende Bild in der unteren rechten Ecke des Terminals erscheint:



Nachdem dem Unterbrechen der Internetverbindung klicken wir auf "Verkaufen", gibt es die folgenden Einträge werden im Journal:

2019.11.26 15:34:48.661 CTrading::OpenPosition<uint,uint>: Invalid request:
2019.11.26 15:34:48.661 No connection with the trade server
2019.11.26 15:34:48.661 Correction of trade request parameters ...
2019.11.26 15:34:48.661 Trading attempt #1. Error: No connection with the trade server


Nach Erhalt des Fehlers erstellt die Bibliothek eine schwebende Anfrage mit den Parametern, die während des erfolglosen Versuchs, eine Short-Position zu eröffnen, festgelegt wurden.
Die schwebende Anfrage enthält auch die Anzahl der Versuche und die Wartezeit von 20 Sekunden.

Stellen wir nun die Internetverbindung und die Verbindung zum Handelsserver wieder her:


Sobald die Verbindung wiederhergestellt ist, beginnt die Bibliothek mit der Bearbeitung einer schwebenden Anfrage, die an den Server gesendet wird. Infolgedessen haben wir eine offene Position und folgende Journaleinträge:

2019.11.26 15:35:00.853 CTrading::OpenPosition<double,double>: Invalid request:
2019.11.26 15:35:00.853 Trading is prohibited for the current account
2019.11.26 15:35:00.853 Correction of trade request parameters ...
2019.11.26 15:35:00.853 Trading operation aborted
2019.11.26 15:35:01.192 CTrading::OpenPosition<double,double>: Invalid request:
2019.11.26 15:35:01.192 Trading is prohibited for the current account
2019.11.26 15:35:01.192 Correction of trade request parameters ...
2019.11.26 15:35:01.192 Trading operation aborted
2019.11.26 15:35:01.942 - Position is open: 2019.11.26 10:35:01.660 -
2019.11.26 15:35:01.942 EURUSD Opened 0.10 Sell #486405595 [0.10 Market-order Sell #486405595] at price 1.10126, sl 1.10285, tp 1.09985, Magic number 17629307 (123), G1: 13
2019.11.26 15:35:01.942 OnDoEasyEvent: Position is open


Wie wir sehen können, wurde nach der Wiederherstellung der Verbindung zum Handelsserver der Handel für das Girokonto mit einer Verzögerung aktiviert.
Aber die schwebende Anfrage hat es trotzdem geschafft
...

Außerdem sehen wir die wirkliche Magicnummer 17629307 im Journal, gefolgt von der in den EA-Einstellungen definierten Magicnummer in Klammern (123) sowie einen weiteren Eintrag G1: 13, der darüber informiert, dass die erste Gruppen-ID gleich 13 ist, während die zweite Gruppen-ID fehlt — ihr Wert ist Null, daher gab es keinen zweiten Eintrag mit der G2: XX zweite Gruppen-ID.

Bitte beachten Sie:

Verwenden Sie die Ergebnisse der Handelsklasse mit den im Artikel beschriebenen schwebenden Anträgen und dem beigefügten Test EA nicht im realen Handel!
Der Artikel, die Begleitmaterialien und die Ergebnisse sind nur als Test für das Konzept der schwebenden Anträge gedacht. In seinem gegenwärtigen Zustand ist er kein fertiges Produkt und in keiner Weise für den realen Handel bestimmt.
Stattdessen ist er nur für den Demo-Modus oder den Tester bestimmt.

Was kommt als Nächstes?

In den folgenden Artikeln werden wir die Entwicklung der schwebenden Anfrageklasse fortsetzen.

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

Zurück zum Inhalt

Frühere Artikel dieser Serie:

Teil 1. Konzept, Datenverwaltung.
Teil 2. Erhebung (Collection) historischer Aufträge und Deals.
Teil 3. Erhebung (Collection) von Marktorders und Positionen, Organisieren der Suche
Teil 4. Handelsereignisse. Konzept
Teil 5. Klassen und Kollektionen von Handelsereignissen. Senden von Ereignissen an das Programm
Teil 6. Ereignisse auf Netting-Konten
Teil 7. Ereignis der Aktivierung einer StopLimit-Order, Vorbereiten der Funktionsweise bei Änderungen von Orders und Positionen
Teil 8. Ereignisse von Änderungen von Orders und Positionen
Teil 9. Kompatibilität mit MQL4 — Datenvorbereitung
Teil 10. Kompatibilität mit MQL4 - Ereignisse der Positionseröffnung und Aktivierung von Pending-Orders
Teil 11. Kompatibilität mit MQL4 - Ereignisse des Schließens von Positionen
Teil 12. Objektklasse "Account" und die Kollektion von Konto-Objekten
Teil 13. Das Objekt der Kontoereignisse
Teil 14. Das Symbolobjekt
Teil 15. Die Kollektion der Symbolobjekte
Teil 16. Ereignisse der Kollektionssymbole
Teil 17. Interaktivität von Bibliotheksobjekten
Teil 18. Interaktivität des Kontos und aller anderen Bibliotheksobjekt
Teil 19. Klassenbibliothek für Nachrichten
Teil 20. Erstellen und Speichern von Programmressourcen
Teil 21. Handelsklassen - Plattformübergreifendes Basis-Handelsobjekt
Teil 22. Handelsklassen - Basisklasse des Handels, Verifikation der Einschränkungen
Teil 23. Handelsklasse - Basisklasse des Handels, Verifikation der Parameter
Teil 24. Trading classes - Handelsklassen, automatische Korrektur ungültiger Parametern
Teil 25. Handelsklassen - Basisklasse des Handels, Behandlung der Fehlermeldungen vom Server

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

Beigefügte Dateien |
MQL5.zip (3609.19 KB)
MQL4.zip (3609.19 KB)
Kontinuierliche Rolloptimierung (Teil 2): Mechanismus zur Erstellung eines Optimierungsberichts für einen beliebigen Roboter Kontinuierliche Rolloptimierung (Teil 2): Mechanismus zur Erstellung eines Optimierungsberichts für einen beliebigen Roboter
Der erste Artikel innerhalb der rollenden Optimierungsreihe beschrieb die Erstellung einer DLL, die in unserem Autooptimierer verwendet werden soll. Diese Fortsetzung ist vollständig der Sprache MQL5 gewidmet.
Mit Boxplot saisonale Muster von Finanzzeitreihen erforschen Mit Boxplot saisonale Muster von Finanzzeitreihen erforschen
In diesem Artikel werden wir die saisonalen Charakteristika von Finanzzeitreihen mit Hilfe von Boxplot-Diagrammen betrachten. Jedes separate Boxplot (oder Box-and-Whiskey-Diagramm) bietet eine gute Visualisierung der Verteilung von Werten entlang des Datensatzes. Boxplots sollten nicht mit den Kerzencharts verwechselt werden, obwohl sie visuell ähnlich aussehen.
SQLite: Natives Arbeiten mit SQL-Datenbanken in MQL5 SQLite: Natives Arbeiten mit SQL-Datenbanken in MQL5
Die Entwicklung von Handelsstrategien ist mit dem Umgang mit großen Datenmengen verbunden. Jetzt können Sie mit Datenbanken mit SQL-Abfragen auf der Basis von SQLite direkt in MQL5 arbeiten. Ein wichtiges Merkmal dieser Engine ist, dass die gesamte Datenbank in einer einzigen Datei auf dem PC des Benutzers abgelegt wird.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXV): Behandlung der Fehlermeldungen von Server Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXV): Behandlung der Fehlermeldungen von Server
Nachdem wir einen Handelsauftrag an den Server gesendet haben, müssen wir die Fehlercodes oder das Fehlen von Fehlern überprüfen. In diesem Artikel werden wir die Behandlung von Fehlern, die vom Handelsserver zurückgegeben werden, besprechen und die Erstellung von ausstehenden Handelsanfragen vorbereiten.