Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXVI): Arbeiten mit schwebenden Handelsanfragen - erste Implementation (Positionseröffnung)
Inhalt
- Konzept
- Die Magicnummer als Datenspeicher verwenden
- Die Klasse Pending, einer erste Implementierung der Anfragen
- Tests
- Was kommt als Nächstes?
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.
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
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.