Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XI). Kompatibilität mit MQL4 - Ereignisse des Schließens von Positionen

26 August 2019, 09:19
Artyom Trishkin
0
163

Inhalt


Entfernen unbenutzter Eigenschaften

Bei der Arbeit an der Definition von Ereignissen fiel mir auf, dass in MQL5 alle Zeitparameter in Millisekunden eingestellt sind. MQL4 verfügt über keine derartigen Auftrags- und Positionseigenschaften, aber nichts hindert uns daran, die Zeit in Sekunden, ausgedrückt in Millisekunden, für MQL4 zu verwenden. Mit anderen Worten, jede Zeit in Sekunden wird einfach durch die Zeit in Millisekunden dupliziert aber nirgendwo verwendet. Die in Sekunden einzustellende Empfangs- und Anzeigezeit ist mit dem Empfang in Millisekunden identisch, abgesehen von einem dreistelligen "Rest", das die Anzahl der Millisekunden im Format der angezeigten Zeit angibt.

Deshalb habe ich mich entschieden, alle in Sekunden eingestellten Zeiteigenschaften aus den Auftragseigenschaften zu entfernen, falls ein Auftrag die gleiche Eigenschaft in Millisekunden hat.
Da wir uns entschieden haben, etwas zu entfernen, wäre es schön, auch etwas hinzuzufügen. Fügen wir also jedem Auftrag die neue Eigenschaft "custom comment" hinzu. Sie kann jederzeit für jeden Auftrag oder Position (sowohl offene als auch geschlossene/entfernte) eingestellt werden. Warum brauchen wir das? Dies kann beispielsweise bei einer textlichen Kennung für Aufträge, die bestimmte Bedingungen erfüllen, oder bei der visuellen Darstellung (die Bibliothek soll später eine eigene grafische Oberfläche haben) erforderlich sein, damit ein mit einer Textkennung markierter Auftrag mit verschiedenen grafischen Konstruktionen leicht dargestellt werden kann.

Öffnen Sie die Datei Defines.mqh, drücken Sie Ctrl+F, um alle Auftragseigenschaften zu finden, die Zeit in Sekunden und eine ähnliche Eigenschaft mit der Endung "_MSC" enthalten (diese Eigenschaft wird in Millisekunden gesetzt). Entfernen Sie die "Millisekunden"-Auftragseigenschaften und belassen die "zweite" Eigenschaften wie sie ist und setzen die Anzahl der ganzzahligen Eigenschaften von 24 auf 21:

//+------------------------------------------------------------------+
//| Order, Deal, Position, Integer-Eigenschaften                     |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order-Ticket
   ORDER_PROP_MAGIC,                                        // Magicnummer des Auftrags
   ORDER_PROP_TIME_OPEN,                                    // Open time (MQL5 Deal time)
   ORDER_PROP_TIME_CLOSE,                                   // Close time (MQL5 Execution or removal time - ORDER_TIME_DONE)
   ORDER_PROP_TIME_OPEN_MSC,                                // Open time in milliseconds (MQL5 Deal time in msc)
   ORDER_PROP_TIME_CLOSE_MSC,                               // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE_MSC)
   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 seconds
   ORDER_PROP_TIME_UPDATE_MSC,                              // 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_GROUP_ID,                                     // Order/position group ID
   ORDER_PROP_DIRECTION,                                    // Direction type (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
//+------------------------------------------------------------------+

Nach den Änderungen sieht die Liste der ganzzahligen Eigenschaften der Reihenfolge wie folgt aus:

//+------------------------------------------------------------------+
//| Order, Deal, Position, Integer-Eigenschaften                     |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = 0,                                   // Order-Ticket
   ORDER_PROP_MAGIC,                                        // Magicnummer des Auftrags
   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_GROUP_ID,                                     // Order/position group ID
   ORDER_PROP_DIRECTION,                                    // Direction type (Buy, Sell)
  }; 
#define ORDER_PROP_INTEGER_TOTAL    (21)                    // Total number of integer properties
#define ORDER_PROP_INTEGER_SKIP     (0)                     // Number of order properties not used in sorting
//+------------------------------------------------------------------+

Lassen Sie uns die Enumeration der möglichen Auswahloptionen nach der Zeit suchen und Auswahlkonstanten in Millisekunden entfernen:

//+------------------------------------------------------------------+
//| Possible selection options by time                               |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // Nach Eröffnungszeit
   SELECT_BY_TIME_CLOSE,                                    // Nach Schlusszeit
   SELECT_BY_TIME_OPEN_MSC,                                 // By open time in milliseconds
   SELECT_BY_TIME_CLOSE_MSC,                                // By close time in milliseconds
  };
//+------------------------------------------------------------------+

Die Enumeration wird nur zwei Konstanten enthalten:

//+------------------------------------------------------------------+
//| Possible selection options by time                               |
//+------------------------------------------------------------------+
enum ENUM_SELECT_BY_TIME
  {
   SELECT_BY_TIME_OPEN,                                     // By open time (in milliseconds)
   SELECT_BY_TIME_CLOSE,                                    // By close time (in milliseconds)
  };
//+------------------------------------------------------------------+

Wenn Sie nun die Auswahl nach Zeit einstellen, erfolgt die Auswahl nach Zeit in Millisekunden für MQL5 und in Sekunden für MQL4.

Fügen Sie die neue Eigenschaft "custom comment" zur den Eigenschaften in Form von Zeichenketten hinzu und erhöhen Sie die Gesamtzahl der Zeichenketteneigenschaften auf 4:.

//+------------------------------------------------------------------+
//| Order, Deal, Position, String-Eigenschaften                      |
//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_STRING
  {
   ORDER_PROP_SYMBOL = (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL), // Order-Symbol
   ORDER_PROP_COMMENT,                                      // Order-Kommentar
   ORDER_PROP_COMMENT_EXT,                                  // Order custom comment
   ORDER_PROP_EXT_ID                                        // Order ID in the external trading system
  };
#define ORDER_PROP_STRING_TOTAL     (4)                     // Total number of string properties
//+------------------------------------------------------------------+

Entfernen wir alle Verweise auf Millisekunden in der Enumeration der möglichen Sortierkriterien (jetzt werden sie standardmäßig beim Sortieren nach Zeit verwendet) und fügen wir das Kriterium für das Sortieren durch einen benutzerdefinierten Kommentar hinzu:

//+------------------------------------------------------------------+
//| Mögliche Sortierkriterien von Orders und 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,                      // Sortieren nach Auftrags-Ticket
   SORT_BY_ORDER_MAGIC           =  1,                      // Sortieren nach der Magicnummer
   SORT_BY_ORDER_TIME_OPEN       =  2,                      // Sort by order open time in milliseconds
   SORT_BY_ORDER_TIME_CLOSE      =  3,                      // Sort by order close time in milliseconds
   SORT_BY_ORDER_TIME_EXP        =  4,                      // Sort by order expiration date
   SORT_BY_ORDER_STATUS          =  5,                      // Sort by order status (market order/pending order/deal/balance, credit operation)
   SORT_BY_ORDER_TYPE            =  6,                      // Sort by order type
   SORT_BY_ORDER_REASON          =  7,                      // Sort by order/position reason/source
   SORT_BY_ORDER_STATE           =  8,                     // Sort by order status
   SORT_BY_ORDER_POSITION_ID     =  9,                     // Sort by position ID
   SORT_BY_ORDER_POSITION_BY_ID  =  10,                     // Sort by opposite position ID
   SORT_BY_ORDER_DEAL_ORDER      =  11,                     // Sort by order a deal is based on
   SORT_BY_ORDER_DEAL_ENTRY      =  12,                     // Sort by deal direction – IN, OUT or IN/OUT
   SORT_BY_ORDER_TIME_UPDATE     =  13,                     // Sort by position change time in seconds
   SORT_BY_ORDER_TICKET_FROM     =  14,                     // Sort by parent order ticket
   SORT_BY_ORDER_TICKET_TO       =  15,                     // Sort by derived order ticket
   SORT_BY_ORDER_PROFIT_PT       =  16,                     // Sort by order profit in points
   SORT_BY_ORDER_CLOSE_BY_SL     =  17,                     // Sort by order closing by StopLoss flag
   SORT_BY_ORDER_CLOSE_BY_TP     =  18,                     // Sort by order closing by TakeProfit flag
   SORT_BY_ORDER_GROUP_ID        =  19,                     // Sort by order/position group ID
   SORT_BY_ORDER_DIRECTION       =  20,                     // 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     =  FIRST_ORD_DBL_PROP+1,   // Sort by close price
   SORT_BY_ORDER_SL              =  FIRST_ORD_DBL_PROP+2,   // Sort by StopLoss price
   SORT_BY_ORDER_TP              =  FIRST_ORD_DBL_PROP+3,   // Sort by TakeProfit price
   SORT_BY_ORDER_PROFIT          =  FIRST_ORD_DBL_PROP+4,   // Sort by profit
   SORT_BY_ORDER_COMMISSION      =  FIRST_ORD_DBL_PROP+5,   // Sort by commission
   SORT_BY_ORDER_SWAP            =  FIRST_ORD_DBL_PROP+6,   // Sort by swap
   SORT_BY_ORDER_VOLUME          =  FIRST_ORD_DBL_PROP+7,   // Sort by volume
   SORT_BY_ORDER_VOLUME_CURRENT  =  FIRST_ORD_DBL_PROP+8,   // Sort by unexecuted volume
   SORT_BY_ORDER_PROFIT_FULL     =  FIRST_ORD_DBL_PROP+9,   // Sort by profit+commission+swap criterion
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  FIRST_ORD_DBL_PROP+10,  // 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         =  FIRST_ORD_STR_PROP+1,   // Sort by comment
   SORT_BY_ORDER_COMMENT_EXT     =  FIRST_ORD_STR_PROP+2,   // Sort by custom comment
   SORT_BY_ORDER_EXT_ID          =  FIRST_ORD_STR_PROP+3    // Sort by order ID in an external trading system
  };
//+------------------------------------------------------------------+

Damit sind die Änderungen in Defines.mqh abgeschlossen. Nun müssen wir alle Verweise auf gelöschte Auftragseigenschaften in den Bibliotheksdateien entfernen:

Ersetzen aller Instanzen der Sortiermodi in allen Bibliotheksdateien.

SORT_BY_ORDER_TIME_OPEN_MSC

und

SORT_BY_ORDER_TIME_CLOSE_MSC

mit

SORT_BY_ORDER_TIME_OPEN

und

SORT_BY_ORDER_TIME_CLOSE

in der HistoryDeal.mqh, HistoryOrder.mqh, HistoryPending.mqh, MarketOrder.mqh, MarketPending.mqh und Marktposition.mqh Dateien der abstrakten, abgeleiteten Klassen der Aufträge, Entfernen aller Referenzen auf die Millisekunden-Eigenschaften der Aufträge (sie sind nun standardmäßig Millisekunden):

ORDER_PROP_TIME_CLOSE_MSC

und
ORDER_PROP_TIME_UPDATE_MSC

in der Datei Order.mqh der abstrakten Auftragsklasse COrder, Entfernen der Methoden, die Zeit in Sekunden zurückgeben, aus dem 'private' Teil:

   datetime          OrderOpenTime(void)           const;
   datetime          OrderCloseTime(void)          const;
   datetime          OrderExpiration(void)         const;
   datetime          PositionTimeUpdate(void)      const;
   datetime          PositionTimeUpdateMSC(void)   const;

Entfernen der vereinfachten Zugriffsmethoden, die die zurückgegebene Zeit in Millisekunden aus dem 'public' Bereich der Klasse zurückgeben. Sie werden mit den Methoden Rückkehrzeit in Sekunden ersetzt:

//+------------------------------------------------------------------+
//| 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) group ID, (9) type, (10) flag of closing by StopLoss,
   //--- (11) flag of closing by TakeProfit (12) open time, (13) close time, (14) open time in milliseconds,
   //--- (15) close time in milliseconds, (16) expiration date, (17) state, (18) status, (19) order 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              GroupID(void)                                      const { return this.GetProperty(ORDER_PROP_GROUP_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);          }
   datetime          TimeOpen(void)                                     const { return (datetime)this.GetProperty(ORDER_PROP_TIME_OPEN);        }
   datetime          TimeClose(void)                                    const { return (datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE);       }
   datetime          TimeOpenMSC(void                                 const { return (datetime)this.GetProperty(ORDER_PROP_TIME_OPEN_MSC);    }
   datetime          TimeCloseMSC(void)                                 const { return (datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE_MSC);   }
   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); }
   
   //--- Rückgabe von (1) Eröffnungspreis, (2) Schlusskurs, (3) Gewinn, (4) Kommission, (5) Swap, (6) Volumen, 

Fügen Sie auch die Methoden zur Rückgabe und zum Eintragen eines benutzerdefinierten Kommentars hinzu:

   //--- Rückgabe von (1) Symbol, (2) Kommentar, (3) ID des Börsenplatzes
   string            Symbol(void)                                       const { return this.GetProperty(ORDER_PROP_SYMBOL);                     }
   string            Comment(void)                                      const { return this.GetProperty(ORDER_PROP_COMMENT);                    }
   string            CommentExt(void)                                   const { return this.GetProperty(ORDER_PROP_COMMENT_EXT);                }
   string            ExternalID(void)                                   const { return this.GetProperty(ORDER_PROP_EXT_ID);                     }

   //--- 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) group ID and (2) custom comment
   void              SetGroupID(const long group_id)                          { this.SetProperty(ORDER_PROP_GROUP_ID,group_id);                 }
   void              SetCommentExt(const string comment_ext)                  { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext);           }
   

Im geschlossenen Konstruktor der Klasse COrder entfernen Sie das Speichern der zweiten Zeiteigenschaften, ersetzen Sie die Eigenschaft der Millisekunden durch Sekunden und speichern Sie Zeit in Millisekunden dort. Fügen Sie das Speichern eines benutzerdefinierten Kommentars als leere Zeichenkette hinzu:

//+------------------------------------------------------------------+
//| 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_GROUP_ID]                             = 0;
   
//--- 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)]        = "";
  }
//+------------------------------------------------------------------+

In der Methode, die die Positions-ID für MQL4 zurückgibt, gehen Sie wie folgt vor: Wenn dies eine Marktposition ist, soll das ihr Ticket zurückgegeben werden, andernfalls Null. Ein Ticket zur Positionsöffnung dient als Positions-ID in MQL5. Diese bleibt während der gesamten Lebensdauer der Position unverändert.

Somit kann in MQL4 nur ein Positionsticket als Positions-ID dienen. Eine Pending-Order in MQL4 hat keine solche ID. Wenn eine Order gelöscht wird, wurde keine Position durch sie eröffnet. Wenn eine Order aktiviert wurde, gibt es keine solche Order in der MQL4-Orderhistorie, aber die Position erhält ihr Ticket, so dass das Ticket als Positions-ID dient.
//+------------------------------------------------------------------+
//| Return the position ID                                           |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return(this.Status()==ORDER_STATUS_MARKET_POSITION ? this.Ticket() : 0);
#else
   long id=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : id=::PositionGetInteger(POSITION_IDENTIFIER);             break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : id=::OrderGetInteger(ORDER_POSITION_ID);                  break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : id=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID);  break;
      case ORDER_STATUS_DEAL              : id=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);    break;
      default                             : id=0;                                                     break;
     }
   return id;
#endif
  }
//+------------------------------------------------------------------+

Ergänzen Sie die Methode, die eine entgegengesetzte Positions-ID für MQL4 zurückgibt:

//+------------------------------------------------------------------+
//| Rückgabe des der entgegengesetzten Positions-ID                  |
//+------------------------------------------------------------------+
long COrder::OrderPositionByID(void) const
  {
   long ticket=0;
#ifdef __MQL4__
   string order_comment=::OrderComment();
   if(::StringFind(order_comment,"close hedge by #")>WRONG_VALUE) ticket=::StringToInteger(::StringSubstr(order_comment,16));
#else
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : ticket=::OrderGetInteger(ORDER_POSITION_BY_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : ticket=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_BY_ID); break;
      default                             : ticket=0;                                                       break;
     }
#endif
   return ticket;
  }
//+------------------------------------------------------------------+

Hier, wenn dies MQL4 ist und wenn der Auftragskommentar die Zeile "Close Hedge by #" enthält, berechnen Sie den Index der entgegengesetzten Auftragsnummer, mit der der Kommentar beginnt und weisen Sie ihn dem Wert zu, der von der Methode zurückgegeben wird.

Entfernen Sie die Implementierung der beiden Methoden, die nicht mehr benötigt werden aus der Klassenauflistung, da wir keine Zeit in Sekunden mehr erhalten wollen:

//+------------------------------------------------------------------+
//| Return open time                                                 |
//+------------------------------------------------------------------+
datetime COrder::OrderOpenTime(void) const
  {
#ifdef __MQL4__
   return ::OrderOpenTime();
#else 
   datetime res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=(datetime)::PositionGetInteger(POSITION_TIME);                 break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=(datetime)::OrderGetInteger(ORDER_TIME_SETUP);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=(datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_SETUP); break;
      case ORDER_STATUS_DEAL              : res=(datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);         break;
      default                             : res=0;                                                             break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+
//| Return close time                                                |
//+------------------------------------------------------------------+
datetime COrder::OrderCloseTime(void) const
  {
#ifdef __MQL4__
   return ::OrderCloseTime();
#else 
   datetime res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=(datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_DONE);  break;
      case ORDER_STATUS_DEAL              : res=(datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);         break;
      default                             : res=0;                                                             break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+

Für eine aussagekräftigere Darstellung eines Auftragsstatus in MQL4, nehmen Sie kleine Änderungen an der Methode vor, die die Auftragsstatusbeschreibung zurückgibt:

//+------------------------------------------------------------------+
//| Rückgabe des Namens des Order-Status                             |
//+------------------------------------------------------------------+
string COrder::StatusDescription(void) const
  {
   ENUM_ORDER_STATUS status=this.Status();
   ENUM_ORDER_TYPE   type=(ENUM_ORDER_TYPE)this.TypeOrder();
   return
     (
      status==ORDER_STATUS_BALANCE           ?  TextByLanguage("Балансовая операция","Balance operation") :
      #ifdef __MQL5__
      status==ORDER_STATUS_MARKET_ORDER || status==ORDER_STATUS_HISTORY_ORDER ?  
         (
          type==ORDER_TYPE_CLOSE_BY ? TextByLanguage("Закрывающий ордер","Order for closing by")         :
          TextByLanguage("Ордер на ","The order to ")+(type==ORDER_TYPE_BUY ? TextByLanguage("покупку","buy") : TextByLanguage("продажу","sell"))
         ) :
      #else 
      status==ORDER_STATUS_HISTORY_ORDER     ?  TextByLanguage("Исторический ордер","History order")     :
      #endif 
      status==ORDER_STATUS_DEAL              ?  TextByLanguage("Сделка","Deal")                          :
      status==ORDER_STATUS_MARKET_POSITION   ?  TextByLanguage("Позиция","Active position")              :
      status==ORDER_STATUS_MARKET_PENDING    ?  TextByLanguage("Установленный отложенный ордер","Active pending order") :
      status==ORDER_STATUS_HISTORY_PENDING   ?  TextByLanguage("Отложенный ордер","Pending order") :
      EnumToString(status)
     );
  }
//+------------------------------------------------------------------+

Hier, für eine entfernte Pending-Order und eine geschlossene Position in MQL4, werden wir die Statusbeschreibung als "Historische Order" zurückgeben.

In der Methode, die die Beschreibung der ganzzahligen Auftragseigenschaft zurückgibt, ändern Sie die Zeichenketten, die die Beschreibungen der Eigenschaften ORDER_PROP_TIME_OPEN, ORDER_PROP_TIME_CLOSE und ORDER_PROP_TIME_UPDATE enthalten, so dass sie die Millisekunden-Eigenschaften zurückgegeben:

//+------------------------------------------------------------------+
//| Rückgabe der Beschreibung der Integer-Eigenschaft des Auftrags   |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property)
  {
   return
     (
   //--- Allgemeine Eigenschaften
      property==ORDER_PROP_MAGIC             ?  TextByLanguage("Магик","Magic number")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET            ?  TextByLanguage("Тикет","Ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_FROM       ?  TextByLanguage("Тикет родительского ордера","Ticket of parent order")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TICKET_TO         ?  TextByLanguage("Тикет наследуемого ордера","Inherited order ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          " #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_EXP          ?  TextByLanguage("Дата экспирации","Date of expiration")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          (this.GetProperty(property)==0     ?  TextByLanguage(": Не задана",": Not set") :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
         )  :
      property==ORDER_PROP_TYPE              ?  TextByLanguage("Тип","Type")+": "+this.TypeDescription()                   :
      property==ORDER_PROP_DIRECTION         ?  TextByLanguage("Тип по направлению","Type by direction")+": "+this.DirectionDescription() :
      
      property==ORDER_PROP_REASON            ?  TextByLanguage("Причина","Reason")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+this.GetReasonDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_ID       ?  TextByLanguage("Идентификатор позиции","Position identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ORDER_TICKET ?  TextByLanguage("Сделка на основании ордера с тикетом","Deal by order ticket")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": #"+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_DEAL_ENTRY        ?  TextByLanguage("Направление сделки","Deal entry")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+this.GetEntryDescription(this.GetProperty(property))
         )  :
      property==ORDER_PROP_POSITION_BY_ID    ?  TextByLanguage("Идентификатор встречной позиции","Opposite position identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_TIME_OPEN         ?  TextByLanguage("Время открытия в милисекундах","Opening time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_CLOSE        ?  TextByLanguage("Время закрытия в милисекундах","Closing time in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")"
         )  :
      property==ORDER_PROP_TIME_UPDATE       ?  TextByLanguage("Время изменения позиции в милисекундах","Time to change the position in milliseconds")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0")
         )  :
      property==ORDER_PROP_STATE             ?  TextByLanguage("Состояние","Statе")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": \""+this.StateDescription()+"\""
         )  :
   //--- Zusätzliche Eigenschaften
      property==ORDER_PROP_STATUS            ?  TextByLanguage("Статус","Status")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": \""+this.StatusDescription()+"\""
         )  :
      property==ORDER_PROP_PROFIT_PT         ?  (
                                                 this.Status()==ORDER_STATUS_MARKET_PENDING ? 
                                                 TextByLanguage("Дистанция от цены в пунктах","Distance from price in points") : 
                                                 TextByLanguage("Прибыль в пунктах","Profit in points")
                                                )+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      property==ORDER_PROP_CLOSE_BY_SL       ?  TextByLanguage("Закрытие по StopLoss","Close by StopLoss")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)   ?  TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      property==ORDER_PROP_CLOSE_BY_TP       ?  TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(this.GetProperty(property)   ?  TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
         )  :
      property==ORDER_PROP_GROUP_ID          ?  TextByLanguage("Идентификатор группы","Group identifier")+
         (!this.SupportProperty(property)    ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

und ergänzen Sie die Rückgabe einer benutzerdefinierten Kommentarbeschreibung an die Methode, die eine String-Eigenschaftsbeschreibung zurückgibt:

//+------------------------------------------------------------------+
//| Rückgabe der Beschreibung der String-Eigenschaft der Order       |
//+------------------------------------------------------------------+
string COrder::GetPropertyDescription(ENUM_ORDER_PROP_STRING property)
  {
   return
     (
      property==ORDER_PROP_SYMBOL         ?  TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\""            :
      property==ORDER_PROP_COMMENT        ?  TextByLanguage("Комментарий","Comment")+
         (this.GetProperty(property)==""  ?  TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(property)+"\"") :
      property==ORDER_PROP_COMMENT_EXT    ?  TextByLanguage("Пользовательский комментарий","Custom comment")+
         (this.GetProperty(property)==""  ?  TextByLanguage(": Не задан",": Not set"):": \""+this.GetProperty(property)+"\"") :
      property==ORDER_PROP_EXT_ID         ?  TextByLanguage("Идентификатор на бирже","Exchange identifier")+
         (!this.SupportProperty(property) ?  TextByLanguage(": Свойство не поддерживается",": Property not supported") :
         (this.GetProperty(property)==""  ?  TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(property)+"\"")):
      ""
     );
  }
//+------------------------------------------------------------------+

Damit sind die Änderungen in der abstrakten Auftragsklasse COrder abgeschlossen.

Nehmen Sie in der Datei mit den Servicefunktionen DELib.mqh eine kleine Verbesserung der Funktion vor, die einen Auftrags-/Positionsnamen nach Auftragsart zurückgibt:.

//+------------------------------------------------------------------+
//| Return order name                                                |
//+------------------------------------------------------------------+
string OrderTypeDescription(const ENUM_ORDER_TYPE type,bool as_order=true)
  {
   string pref=(#ifdef __MQL5__ "Market order" #else (as_order ? "Market order" : "Position") #endif );
   return
     (
      type==ORDER_TYPE_BUY_LIMIT       ?  "Buy Limit"                                                 :
      type==ORDER_TYPE_BUY_STOP        ?  "Buy Stop"                                                  :
      type==ORDER_TYPE_SELL_LIMIT      ?  "Sell Limit"                                                :
      type==ORDER_TYPE_SELL_STOP       ?  "Sell Stop"                                                 :
   #ifdef __MQL5__
      type==ORDER_TYPE_BUY_STOP_LIMIT  ?  "Buy Stop Limit"                                            :
      type==ORDER_TYPE_SELL_STOP_LIMIT ?  "Sell Stop Limit"                                           :
      type==ORDER_TYPE_CLOSE_BY        ?  TextByLanguage("Закрывающий ордер","Order for closing by")  :  
   #else 
      type==ORDER_TYPE_BALANCE         ?  TextByLanguage("Балансовая операция","Balance operation")   :
      type==ORDER_TYPE_CREDIT          ?  TextByLanguage("Кредитная операция","Credit operation")     :
   #endif 
      type==ORDER_TYPE_BUY             ?  pref+" Buy"                                                 :
      type==ORDER_TYPE_SELL            ?  pref+" Sell"                                                :  
      TextByLanguage("Неизвестный тип ордера","Unknown order type")
     );
  }
//+------------------------------------------------------------------+

Hier haben wir das Flag hinzugefügt, das die Darstellung eines Auftragsnamens für MQL4 entweder als Auftrag oder als Position verwaltet. Die Darstellung für MQL4 "als Auftrag" ist standardmäßig eingestellt. Warum das? Angenommen, eine Order, die das Öffnen einer Position verursacht hat, wird in eckigen Klammern angezeigt, wenn das Ereignis Positionseröffnung an das Journal gesendet wird. In diesem Fall wird die Meldung [Position Sell #123] für die Verkaufsposition, die durch eine Marktorder (nicht ausstehend) mit dem Ticket 123 als Order eröffnet wurde, die die Positionseröffnung verursacht hat, durch den aussagekräftigeren Eintrag [Market Order Sell #123] ersetzt.

Lassen Sie uns die Methode AddToListMarket() der Marktorder- und Positionserfassungsklasse verbessern. Anstelle der Positions-Aktualisierungszeit in Millisekunden ORDER_PROP_TIME_UPDATE_MSC verwenden wir nun die Positions-Aktualisierungszeit ORDER_PROP_TIME_UPDATE (sie ist standardmäßig in Millisekunden eingestellt):

//+--------------------------------------------------------------------------------+
//| Add an order or a position to the list of orders and positions on the account  |
//+--------------------------------------------------------------------------------+
bool CMarketCollection::AddToListMarket(COrder *order)
  {
   if(order==NULL)
      return false;
   ENUM_ORDER_STATUS status=order.Status();
   if(this.m_list_all_orders.InsertSort(order))
     {
      if(status==ORDER_STATUS_MARKET_POSITION)
        {
         this.m_struct_curr_market.hash_sum_acc+=order.GetProperty(ORDER_PROP_TIME_UPDATE)+this.ConvertToHS(order);
         this.m_struct_curr_market.total_volumes+=order.Volume();
         this.m_struct_curr_market.total_positions++;
         return true;
        }
      if(status==ORDER_STATUS_MARKET_PENDING)
        {
         this.m_struct_curr_market.hash_sum_acc+=this.ConvertToHS(order);
         this.m_struct_curr_market.total_volumes+=order.Volume();
         this.m_struct_curr_market.total_pending++;
         return true;
        }
     }
   else
     {
      ::Print(DFUN,order.TypeDescription()," #",order.Ticket()," ",TextByLanguage("не удалось добавить в список","failed to add to the list"));
      delete order;
     }
   return false;
  }
//+------------------------------------------------------------------+

Ersetzen Sie die Auftragszeit in Millisekunden durch die Auftragszeit in der Methode zum Erstellen von Kontrollorders und Hinzufügen zur Liste (aus dem gleichen Grund):

//+------------------------------------------------------------------+
//| Create and add an order to the list of control orders            |
//+------------------------------------------------------------------+
bool CMarketCollection::AddToListControl(COrder *order)
  {
   if(order==NULL)
      return false;
   COrderControl* order_control=new COrderControl(order.PositionID(),order.Ticket(),order.Magic(),order.Symbol());
   if(order_control==NULL)
      return false;
   order_control.SetTime(order.TimeOpen());
   order_control.SetTimePrev(order.TimeOpen());
   order_control.SetVolume(order.Volume());
   order_control.SetTime(order.TimeOpen());
   order_control.SetTypeOrder(order.TypeOrder());
   order_control.SetTypeOrderPrev(order.TypeOrder());
   order_control.SetPrice(order.PriceOpen());
   order_control.SetPricePrev(order.PriceOpen());
   order_control.SetStopLoss(order.StopLoss());
   order_control.SetStopLossPrev(order.StopLoss());
   order_control.SetTakeProfit(order.TakeProfit());
   order_control.SetTakeProfitPrev(order.TakeProfit());
   if(!this.m_list_control.Add(order_control))
     {
      delete order_control;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

In der Datei HistoryCollection.mqh der Methode zur Auswahl von Aufträgen nach der Zeit der Kollektionsklasse CHistoryCollection historischer Aufträge und Deals, verbessern wir die Auswahl der verglichenen Eigenschaft.
Da wir bereits eine Auswahl von vier Eigenschaften hatten (Öffnungszeit in Millisekunden, Schließzeit in Millisekunden, Öffnungszeit in Sekunden und Schließzeit in Sekunden) und wir nun zwei davon entfernt haben, ist die Auswahl nun vereinfacht:

//+------------------------------------------------------------------+
//| Select orders from the collection with time                      |
//| von begin_time bis end_time                                      |
//+------------------------------------------------------------------+
CArrayObj *CHistoryCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0,
                                             const ENUM_SELECT_BY_TIME select_time_mode=SELECT_BY_TIME_CLOSE)
  {
   ENUM_ORDER_PROP_INTEGER property=(select_time_mode==SELECT_BY_TIME_CLOSE ? ORDER_PROP_TIME_CLOSE : ORDER_PROP_TIME_OPEN);

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

Ersetzen Sie in der Datei CEngine.mqh alle Instanzen der Zeitkonstante SORT_BY_ORDER_TIME_OPEN_MSC in Millisekunden durch die Zeitkonstante SORT_BY_ORDER_TIME_OPEN. Nun wird standardmäßig die Zeit in Millisekunden durch diese Konstante verwendet.
Damit sind die Verbesserungen der Bibliotheksdateien bezüglich der Entfernungszeit in Sekunden abgeschlossen.

Implementieren der Ereignisse des Schließens von Positionen

Verschiedene Experimente und Tests, die auf der Suche nach möglichen Optionen zur Identifizierung des Auftretens von Ereignissen des Schließens von Positionen und dem Entfernen Aufträgen in MQL4 durchgeführt wurden, haben sich als abschreckend erwiesen. Im Gegensatz zu MQL5 verfügt MQL4 über wesentlich weniger Daten, die für die entscheidende Ereigniserkennung verwendet werden können.
In MQL5 können wir auf einfache Weise Daten über Aufträge, die zu einer bestimmten Position gehören, verwenden und ein Ereignis definieren, während in MQL4 sowohl eine Position als auch eine Pending-Order als Aufträge betrachtet werden. Wenn wir eine Pending-Order in MetaTrader 4 hatten, dann haben wir nach ihrer Entfernung:

  • eine erhöhte Anzahl der historischen Aufträge
  • einen Rückgang des Gesamtvolumens
  • eine unveränderte Anzahl von Marktorders.

Um sicherzustellen, dass ein Ereignis zum Entfernen einer Pending-Order gehört (Position in MQL4 ist auch eine Order), überprüfen Sie die Anzahl der offenen Positionen. Wenn sie sich nicht geändert hat, wurde die Aktion mit einer Pending-Order ausgeführt. Alles scheint hier normal und logisch zu sein, bis wir eine der offenen Positionen (eine Order) im MetaTrader 4 teilweise schließen. Wenn wir eine Position teilweise schließen, haben wir das Gleiche wie beim Entfernen einer Pending-Order:

  • eine erhöhte Anzahl der historischen Orders (die teilweise geschlossene Position (ihre Order) ist Teil der Historie),
  • einen Rückgang des Kontovolumens, da wir die Position teilweise geschlossen haben,
  • eine unveränderte Anzahl von offenen Positionen — sie bleibt unverändert, wenn eine Position teilweise geschlossen wird.

Dies ist derselbe Zustand wie beim Entfernen einer Pending-Order. Wir können dies beobachten, wenn wir einen Test-EA aus dem vorherigen Artikel im Tester starten. Öffnen Sie eine Position, schließen Sie sie teilweise, setzen Sie eine Pending-Order und entfernen Sie sie. Während der letzten Aktion erscheinen gleichzeitig zwei Einträge im Journal: teilweises Schließen eine Position und Entfernen einer Pending-Order. Ich habe bereits die Übereinstimmung der Kriterien für die Definition dieser beiden Ereignisse erwähnt. Das Programm definiert einfach zwei Ereignisse auf einmal, und eines davon ist falsch.
Hier können wir die Überprüfung der geänderten Anzahl der Pending-Orders nutzen — bei der Definition des teilweisen Schließens einer Position werden wir auch die Änderung der Anzahl der Pending-Orders im Markt überprüfen. Wenn sie sich nicht geändert hat, handelt es sich bei dem Ereignis um das teilweise Schließen einer Position.

Alles erscheint logisch, aber dieser Ansatz schränkt die erlaubte Reihenfolge der Handelsoperationen in MetaTrader 4 ein. Mit anderen Worten, wir können eine Pending-Order nicht entfernen und eine Position in einer einzigen Schleife teilweise schließen, da dies gegen die oben genannte ereignisdefinierende Logik verstößt. Wir können eine Lösung implementieren, um diese Einschränkung zu umgehen, aber es bedarf einer Überarbeitung der Klassen der Kollektionen der Aufträge und Positionen im Markt und der Historie. Um eine Veränderung des Marktumfelds zu definieren, müssen wir temporäre Listen von Aufträgen und Positionen verwenden, die auf dem Konto geändert wurden, anstatt die Höhe der aufgetretenen Änderungen zu verwalten. Die Ereignisse sollten entsprechend den Daten dieser Listen behandelt werden. In diesem Fall hat jeder Ereignistyp seine eigene Liste und das Anlegen von Ereignissen zum Senden an das Programm soll entsprechend den Listen der geänderten Aufträge und Positionen erfolgen.

Vielleicht werde ich nach Abschluss dieser Artikelserie eine solche Suche und Ereignisbehandlung veranlassen. Aber im Moment nutzen wir die Kontrolle über die Anzahl der Pending-Orders auf dem Markt. Beachten Sie eine solche Einschränkung für MetaTrader 4 und entwickeln Sie die Funktionen zum Schließen/Entfernen von Orders unter Berücksichtigung derselben. Für Endanwender bleibt diese Einschränkung verborgen. Sie sollen damit nicht belastet werden, da die Funktionen für die Arbeit mit der Bibliothek unter Berücksichtigung der Einschränkung für MQL4 eingeführt werden sollen.

Um ein teilweises Schließen zu definieren, müssen wir das Gesamtvolumen auf dem Konto verwenden. Das bedeutet, dass wir dessen Änderungswert an die Methode Refresh() der Klasse CEventsCollection übergeben müssen. Wie üblich beginnt alles mit dem Basisobjekt der Bibliothek. Lassen Sie uns die notwendige Ergänzung zum Aufruf der Methode zum Aktualisieren der Klasse der Ereigniskollektion vornehmen.

Fügen Sie der Methode Refresh der Klasse CEventsCollection in der Klasse CEngine::TradeEventsControl() einen zusätzlichen übertragbaren Parameter hinzu:

//+------------------------------------------------------------------+
//| Prüfen der Handelsereignisse                                     |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the trading events code and flags
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Aktualisieren der Liste 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- Aktionen beim ersten Start
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Prüfen der Änderungen des Marktstatus' und der Kontohistorie 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- Im Falle irgendeines Ereignisses, werden die Listen, Flags und die Anzahl der neuen Aufträge und Deals an die Kollektion der Ereignisse gesendet und aktualisiert
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewPositions(),this.m_history.NewDeals(),
                            this.m_market.ChangedVolumeValue());
      //--- Get the account's last trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Nun müssen wir die Methode Refresh() der Klasse CEventsCollection selbst ändern, indem wir einen weiteren Parameter (volume change value) implementieren.
Fügen Sie die neuen Parameter zur Definition der Methode Refresh() in der Datei EventsCollection.mqh hinzu:.

public:
//--- Auswählen der Ereignisse aus der Kollektion mit einer Zeit im Bereich von begin_time bis end_time.

   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Rückgabe des der gesamten Kollektionsliste des Ereignisses "wie besehen"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Aktualisieren der Ereignisliste
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             CArrayObj* list_changes,
                             CArrayObj* list_control,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals,
                             const double changed_volume);
//--- Setzen der Chart-ID des Steuerprogramms
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Rückgabe des letzten Handelsereignisses auf dem Konto
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Rücksetzen des letzten Handelsereignisses
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Konstructor
                     CEventsCollection(void);
  };

Wie müssen weitere Ergänzungen bei der Implementationen der Methode Refresh() vornehmen:

//+------------------------------------------------------------------+
//| Aktualisieren der Ereignisliste                                  |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals,
                                const double changed_volume)
  {

Jetzt müssen wir die Ereignisbehandlung vom teilweisen Schließen und dem Schließen ganzer Positionen erstellen und das Problem der fehlerhaften Definition eines Ereignisses von gelöschten Pending-Orders während eines Teilpositionsschließens lösen.

Ändern Sie im 'private' Abschnitt der Klasse die erste Form des Aufrufs der Methode für neue Ereignisse, indem Sie die Liste der Steuerbefehle an die Methode übergeben. Wir werden es brauchen, um Aufträge zu identifizieren, die an der Schließung einer Position durch eine andere teilnehmen. Fügen Sie auch die Methode hinzu, die die Liste der historischen (geschlossenen) Positionen und die Methode , die den Zeiger auf die Kontrollorder durch das Positionsticket zurückgibt:

class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // Liste der Ereignisse
   bool              m_is_hedge;                      // Flag des Hedging-Kontos
   long              m_chart_id;                      // Chart-ID des Steuerprogramms
   int               m_trade_event_code;              // Trading event code
   ENUM_TRADE_EVENT  m_trade_event;                   // Handelsereignis auf dem Konto
   CEvent            m_event_instance;                // Ereignisobjekt für die Suche nach einer Eigenschaft
   MqlTick           m_tick;                          // Last tick structure
   ulong             m_position_id;                   // Position ID (MQL4)
   ENUM_ORDER_TYPE   m_type_first;                    // Opening order type (MQL4)
   
//--- Create a trading event depending on the order (1) status and (2) change type
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market,CArrayObj* list_control);
   void              CreateNewEvent(COrderControl* order);
//--- Create an event for a (1) hedging account, (2) netting account
   void              NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
   void              NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
//--- Select from the list and return the list of (1) market pending orders, (2) open positions
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
   CArrayObj*        GetListPositions(CArrayObj* list);
//--- Select from the list and return the list of historical (1) closed orders,
//--- (2) removed pending orders, (3) deals, (4) all closing orders 
   CArrayObj*        GetListHistoryPositions(CArrayObj* list);
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Return the list of (1) all position orders by its ID, (2) all position deals by its ID 
//--- (3) all market entry deals by position ID, (4) all market exit deals by position ID,
//--- (5) all position reversal deals by position ID
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id);
//--- Rückgabe des Gesamtvolumens aller Deals (1) IN, (2) OUT der Position nach deren ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the (1) first, (2) last and (3) closing order from the list of all position orders,
//--- (4) an order by ticket, (5) market position by ID,
//--- (6) the last and (7) penultimate InOut deal by position ID
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket);
   COrder*           GetPositionByID(CArrayObj* list,const ulong position_id);
//--- Return the (1) control order by ticket, (2) opening order type by position ticket (MQL4)
   COrderControl*    GetOrderControlByTicket(CArrayObj* list,const ulong ticket);
   ENUM_ORDER_TYPE   GetTypeFirst(CArrayObj* list,const ulong ticket);
//--- Rückgabe des Flags des Ereignisobjekts in der Ereignisliste
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Existing order/position change event handler
   void              OnChangeEvent(CArrayObj* list_changes,const int index);

public:

Implementieren Sie die Methode, die die Liste der geschlossenen Positionen außerhalb des Klassenkörpers zurückgibt:

//+------------------------------------------------------------------+
//| Select only closed positions from the list                       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListHistoryPositions(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

Die Methode enthält für uns nichts Neues. Ich habe die ähnlichen Methoden bereits in den vorherigen Teilen der Bibliotheksbeschreibung beschrieben.

Implementieren wir die Methode, die eine Kontrollorder durch ein Ticket zurückgibt und ändern wir die Methode, die den Typ einer Kontrollorder nach dem Ticket zurückgibt:

//+------------------------------------------------------------------+
//| Return a control order by a position ticket (MQL4)               |
//+------------------------------------------------------------------+
COrderControl* CEventsCollection::GetOrderControlByTicket(CArrayObj *list,const ulong ticket)
  {
   if(list==NULL)
      return NULL;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      COrderControl* ctrl=list.At(i);
      if(ctrl==NULL)
         continue;
      if(ctrl.Ticket()==ticket)
         return ctrl;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
//| Return an opening order type by a position ticket (MQL4)         |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket)
  {
   if(list==NULL)
      return WRONG_VALUE;
   COrderControl* ctrl=this.GetOrderControlByTicket(list,ticket);
   if(ctrl==NULL)        
      return WRONG_VALUE;
   return (ENUM_ORDER_TYPE)ctrl.TypeOrder();
  }
//+------------------------------------------------------------------+

Wie bei den zuvor betrachteten, ähnlichen Methoden ist das Verfahren, das eine Kontrollorder nach einem Ticket zurückgibt, einfach. Sie erhält die Liste der Kontrollorders und das Ticket der Position, deren Kontrollorder wir erhalten möchten.
Nehmen Sie die Order aus der Liste und vergleichen Sie sie mit dem Ticket, das in einer Schleife über die Liste an das Verfahren übergeben wird. Wenn die Tickets gleich sind, geben Sie den Zeiger auf die Kontrollorder zurück, andernfalls NULL.

Die Methode GetTypeFirst(), die den Steuerungsauftragstyp zurückgibt, bestand zuvor aus einer Schleife durch die Liste der Kontrollorders mit der Suche nach einer Order mit dem Ticket, das gleich dem der Methode übergebenen ist. Wenn eine solche Order erkannt wurde, wurde ihr Typ zurückgegeben.
Nun, da wir die Methode haben, die eine Kontrollorder gemäß dem Positionsticket zurückgibt, können wir die Suchschleife aus der Methode GetTypeFirst() löschen. Das ist genau das, was ich getan habe. Jetzt erhalten wir in der Methode eine Kontrollorder gemäß dem Positionsticket unter Verwendung der Methode GetOrderControlByTicket(). Wenn sie erfolgreich erhalten wurde (nicht NULL), geben Sie einen Typ einer erhaltenen Order zurück, andernfalls -1.

Nun können wir die Behandlung des Schließens von Positionen in MQL4 zur Aktualisierungsmethode der Ereignissammlung hinzufügen:

//+------------------------------------------------------------------+
//| Aktualisieren der Ereignisliste                                  |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals,
                                const double changed_volume)
  {
//--- Rückkehren, wenn die Liste leer ist
   if(list_history==NULL || list_market==NULL)
      return;
//--- Wenn das Ereignis in der Umgebung des Marktes existiert
   if(is_market_event)
     {
      //--- if the order properties were changed
      int total_changes=list_changes.Total();
      if(total_changes>0)
        {
         for(int i=total_changes-1;i>=0;i--)
           {
            this.OnChangeEvent(list_changes,i);
           }
        }
      //--- if the number of placed pending orders increased (MQL5, MQL4)
      if(new_market_pendings>0)
        {
         //--- Empfangen der Liste der neuesten, platzierten Pending-Orders
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Sortieren der neuen Liste nach der Platzierungszeit der Orders
            list.Sort(SORT_BY_ORDER_TIME_OPEN);
            //--- Nehmen der Order-Anzahl, die gleich der Anzahl neu platzierten vom Ende der Liste ist, in einer Schleife (die letzten N Ereignisse)
            int total=list.Total(), n=new_market_pendings;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Erhalt der Order von der Liste, wenn es eine Pending-Order ist, wird das Handelsereignis gesetzt
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                  this.CreateNewEvent(order,list_history,list_market,list_control);
              }
           }
        }
      #ifdef __MQL4__
         //--- If the number of positions increased (MQL4)
         if(new_market_positions>0)
           {
            //--- Get the list of open positions
            CArrayObj* list=this.GetListPositions(list_market);
            if(list!=NULL)
              {
               //--- Sort the new list by a position open time
               list.Sort(SORT_BY_ORDER_TIME_OPEN);
               //--- Take the number of positions equal to the number of newly placed open positions from the end of the list in a loop (the last N events)
               int total=list.Total(), n=new_market_positions;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Receive a position from the list. If this is a position, search for opening order data and set a trading event
                  COrder* position=list.At(i);
                  if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION)
                    {
                     //--- Find an order and set (1) a type of an order that led to opening a position and a (2) position ID 
                     this.m_type_first=this.GetTypeFirst(list_control,position.Ticket());
                     this.m_position_id=position.Ticket();
                     this.CreateNewEvent(position,list_history,list_market,list_control);
                    }
                 }
              }
           }
         //--- If the number of positions decreased or a position is closed partially (MQL4)
         else if(new_market_positions<0 || (new_market_positions==0 && changed_volume<0 && new_history_orders>0 && new_market_pendings>WRONG_VALUE))
           {
            //--- Get the list of closed positions
            CArrayObj* list=this.GetListHistoryPositions(list_history);
            if(list!=NULL)
              {
               //--- Sort the new list by position close time
               list.Sort(SORT_BY_ORDER_TIME_CLOSE);
               //--- Take the number of positions equal to the number of newly closed positions from the end of the list in a loop (the last N events)
               int total=list.Total(), n=new_history_orders;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Empfangen einer Order von der Liste. If this is a position, look for data of an opening order and set a trading event
                  COrder* position=list.At(i);
                  if(position!=NULL && position.Status()==ORDER_STATUS_HISTORY_ORDER)
                    {
                     //--- If there is a control order of a closed position
                     COrderControl* ctrl=this.GetOrderControlByTicket(list_control,position.Ticket());
                     if(ctrl!=NULL)
                       {
                        //--- Set an (1) order type that led to a position opening, (2) position ID and create a position closure event
                        this.m_type_first=(ENUM_ORDER_TYPE)ctrl.TypeOrder();
                        this.m_position_id=position.Ticket();
                        this.CreateNewEvent(position,list_history,list_market,list_control);
                       }
                    }
                 }
              }
           }
      #endif 
     }
//--- If an event in an account history
   if(is_history_event)
     {
      //--- If the number of historical orders increased (MQL5, MQL4)
      if(new_history_orders>0)
        {
         //--- Get the list of newly removed pending orders
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Sortieren der Liste nach der Entfernungszeit der Orders
            list.Sort(SORT_BY_ORDER_TIME_CLOSE);
            //--- Nehmen der Order-Anzahl gleich der Anzahl neu entfernten vom Ende der Liste in einer Schleife (die letzten N Ereignisse)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Empfangen einer Order von der Liste. If this is a removed pending order without a position ID, 
               //--- this is an order removal - set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market,list_control);
              }
           }
        }
      //--- If the number of deals increased (MQL5)
      #ifdef __MQL5__
         if(new_deals>0)
           {
            //--- Empfangen der Liste nur der Deals
            CArrayObj* list=this.GetListDeals(list_history);
            if(list!=NULL)
              {
               //--- Sortieren der neuen Liste nach der Dealzeit
               list.Sort(SORT_BY_ORDER_TIME_OPEN);
               //--- Nehmen der Anzahl der Deals, die gleich der Anzahl der neuen vom Ende der Liste ist, in einer Schleife (die letzten N Ereignisse)
               int total=list.Total(), n=new_deals;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Erhalt eines Deals von der Liste und setzen des Handelsereignisses
                  COrder* order=list.At(i);
                  if(order!=NULL)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
      #endif 
     }
  }
//+------------------------------------------------------------------+

Das Behandeln des Schließens von Positionen ist klar und transparent. Die Beschreibung wird in der Auflistung kommentiert, und es macht keinen Sinn, auf seine Logik einzugehen, da die Codekommentare ausführlich genug sind.

Da nun eine weitere Liste an die erste Form des Aufrufs der Methode zum Erzeugen eines neuen Ereignisses übergeben wird, wird die Liste der Steuerbefehle an den Aufruf der Methode für MQL5 in der Methode Refresh() der Klasse der Ereigniskollektion CEventsCollection hinzugefügt:

      //--- If the number of deals increased (MQL5)
      #ifdef __MQL5__
         if(new_deals>0)
           {
            //--- Empfangen der Liste nur der Deals
            CArrayObj* list=this.GetListDeals(list_history);
            if(list!=NULL)
              {
               //--- Sortieren der neuen Liste nach der Dealzeit
               list.Sort(SORT_BY_ORDER_TIME_OPEN);
               //--- Nehmen der Anzahl der Deals, die gleich der Anzahl der neuen vom Ende der Liste ist, in einer Schleife (die letzten N Ereignisse)
               int total=list.Total(), n=new_deals;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- Erhalt eines Deals von der Liste und setzen des Handelsereignisses
                  COrder* order=list.At(i);
                  if(order!=NULL)
                     this.CreateNewEvent(order,list_history,list_market,list_control);
                 }
              }
           }
      #endif 

Fügen wir nun den Code zum Erstellen eines Ereignisses des Schließens von Positionen zur ersten Form eines Ereignisaufrufs in der Ereigniserzeugungsmethode hinzu:

CEventsCollection::CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market,CArrayObj* list_control).

Da die Methode recht umfangreich ist, werden wir uns nur den Code zum Erstellen eines Ereignisses des Schließens von Positionen für MQL4 ansehen:

//--- Position closed (__MQL4__)
   if(status==ORDER_STATUS_HISTORY_ORDER)
     {
      //--- Set the "position closed" trading event code
      this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
      //--- Set the "request executed in full" reason
      ENUM_EVENT_REASON reason=EVENT_REASON_DONE;
      //--- If the closure by StopLoss flag is set for an order, a position is closed by StopLoss
      if(order.IsCloseByStopLoss())
        {
         //--- set the "closure by StopLoss" reason
         reason=EVENT_REASON_DONE_SL;
         //--- add the StopLoss closure flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_SL;
        }
      //--- If the closure by TakeProfit flag is set for an order, a position is closed by TakeProfit
      if(order.IsCloseByTakeProfit())
        {
         //--- set the "closure by TakeProfit" reason
         reason=EVENT_REASON_DONE_TP;
         //--- add the TakeProfit closure flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_TP;
        }
      //--- If an order has the property with an inherited order filled, a position is closed partially
      if(order.TicketTo()>0)
        {
         //--- set the "partial closure" reason
         reason=EVENT_REASON_DONE_PARTIALLY;
         //--- add the partial closure flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
        }
      //--- Check closure by an opposite position
      COrder* order_close_by=this.GetCloseByOrderFromList(list_history,order.Ticket());
      //--- Declare the variables of the opposite order properties and initialize them with the values of the current closed position 
      ENUM_ORDER_TYPE close_by_type=this.m_type_first;
      double close_by_volume=order.Volume();
      ulong  close_by_ticket=order.Ticket();
      long   close_by_magic=order.Magic();
      string close_by_symbol=order.Symbol();
      //--- If the list of historical orders features an order with a closed position ID, the position is closed by an opposite one
      if(order_close_by!=NULL)
        {
         //--- Fill in the properties of an opposite closing order using data on the opposite position properties
         close_by_type=(ENUM_ORDER_TYPE)order_close_by.TypeOrder();
         close_by_ticket=order_close_by.Ticket();
         close_by_magic=order_close_by.Magic();
         close_by_symbol=order_close_by.Symbol();
         close_by_volume=order_close_by.Volume();
         //--- set the "close by" reason
         reason=EVENT_REASON_DONE_BY_POS;
         //--- add the close by flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
         //--- Take data on (1) closed and (2) opposite positions from the list of control orders
         //--- (in this list, the properties of two opposite positions remain the same as before the closure)
         COrderControl* ctrl_closed=this.GetOrderControlByTicket(list_control,order.Ticket());
         COrderControl* ctrl_close_by=this.GetOrderControlByTicket(list_control,close_by_ticket);
         double vol_closed=0;
         double vol_close_by=0;
         //--- If no errors detected when receiving these two opposite orders
         if(ctrl_closed!=NULL && ctrl_close_by!=NULL)
           {
            //--- Calculate closed volumes of a (1) closed and (2) an opposite positions
            vol_closed=ctrl_closed.Volume()-order.Volume();
            vol_close_by=vol_closed-close_by_volume;
            //--- If a position is closed partially (the previous volume exceeds the currently closed one)
            if(ctrl_closed.Volume()>order.Volume())
              {
               //--- add the partial closure flag to an event code
               this.m_trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
               //--- set the "partial closure" reason
               reason=EVENT_REASON_DONE_PARTIALLY_BY_POS;
              }
           }
        }
      //--- Create the position closure event
      CEvent* event=new CEventPositionClose(this.m_trade_event_code,order.Ticket());
      if(event!=NULL && order.PositionByID()==0)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeClose());                               // Event time
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                                        // Event reason (from the ENUM_EVENT_REASON enumeration)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,close_by_type);                              // Event deal type
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                           // Event deal ticket
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,close_by_type);                             // Type of the order that triggered an event deal (the last position order)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first);                      // Type of an order that triggered a position deal (the first position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,close_by_ticket);                         // Ticket of an order, based on which an event deal is opened (the last position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                       // Ticket of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id);                             // Position ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,close_by_ticket);                             // Opposite position ID
         event.SetProperty(EVENT_PROP_MAGIC_BY_ID,close_by_magic);                                 // Opposite position magic number
            
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder());                      // Position order type before direction changed
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket());                       // Position order ticket before direction changed
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder());                     // Current position order type
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket());                      // Current position order ticket
      
         event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen());                        // Order price before modification<
         event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss());                           // StopLoss before modification
         event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit());                         // TakeProfit before modification
         event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask);                            // Ask price during an event
         event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid);                            // Bid price during an event
         
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                                  // Order/deal/position magic number
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpen());                       // Time of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                              // Event price
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                               // Order/deal/position open price
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                             // Order/deal/position close price
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                                  // StopLoss position price
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                                // TakeProfit position price
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume());                        // Requested order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent());                 // Remaining (unexecuted) order volume
         event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume());                    // Executed position volume
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                      // Profit
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                      // Order symbol
         event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,close_by_symbol);                               // Opposite position symbol
         //--- Setzen der Chart-ID des Steuerprogramms, dekodieren des Ereigniscodes und setzen des Ereignis-Typs
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Hinzufügen einer Ereignisobjekts, wenn es nicht in der Liste ist
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Senden einer Nachricht über das Ereignis und setzen des Wertes des letzten Handelsereignisses
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- Wenn es das Ereignis bereits in der Liste gibt, wird das neue Objekt entfernt und eine Debugging-Nachricht angezeigt
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list."));
            delete event;
           }
        }
     }
#endif 

Da wir die Logik des Erstellens von Ereignissen bereits in den vorangegangenen Artikeln beschrieben haben, lassen Sie es uns kurz erwähnen: Setzen Sie zunächst einen Ereigniscode als "Position schließen" und eine Ereignisursache als "Anforderung vollständig ausgeführt". Als Nächstes sehen Sie sich die verschiedenen Eigenschaften einer geschlossenen Bestellung an, fügen dem auf diesen Eigenschaften basierenden Ereigniscode die notwendigen Kennzeichen hinzu und ändern ggf. die Ereignisursache. Bei der Definition eines entgegengesetzten Positionsschließens verwenden wir Daten von Kontrollorders für geschlossene Positionen. Diese Daten liefern uns die gesamten Informationen über entgegengesetzte Aufträge vor deren gemeinsamen Abschluss, so dass wir Auftragsarten und deren Volumen definieren und herausfinden können, welcher von ihnen ein Schließen-Durch eingeleitet hat.
Dann werden alle gesammelten Daten in den Ereigniseigenschaften aufgezeichnet und ein neues Positionsschließereignis erzeugt.

Ich habe auch die Methode verbessert, die die Liste aller Positions-Schließaufträge für MQL4: zurückgibt.

//+------------------------------------------------------------------+
//|  Rückgabe der Liste aller Aufträge CloseBy aus der Liste         |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListCloseByOrders(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of history collection"));
      return NULL;
     }
#ifdef __MQL5__
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
#else 
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_BY_ID,0,NO_EQUAL);
#endif 
   return list_orders;
  }
//+------------------------------------------------------------------+

Für MQL5 selektieren wir nur Aufträge vom Typ ORDER_TYPE_CLOSE_BY aus der Liste der historischen Aufträge. Da es keine solchen Aufträge in MQL4 gibt, selektieren wir nur die Aufträge mit der entgegengesetzten Positions-ID Eigenschaft, genauer gesagt, Aufträge, bei den diese Eigenschaft ungleich gleich Null ist.

Die Methode , die die letzte Reihenfolge der Schließposition für MQL4 zurückgibt, wurde ebenfalls verbessert:

//+------------------------------------------------------------------+
//| Return the last closing order                                    |
//| aus der Liste aller Positionen                                   |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetCloseByOrderFromList(CArrayObj *list,const ulong position_id)
  {
#ifdef __MQL5__
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
#else 
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_BY_ID,position_id,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_CLOSE);
#endif 
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

In MQL5 können wir jederzeit die letzte zu einer Position gehörende Order abrufen. Dies ist bei MQL4 jedoch nicht möglich. Daher habe ich mich im Falle von MQL4 entschieden, eine gegenläufige Positionsreihenfolge falls vorhanden oder NULL zurückzugeben, wenn eine Position nicht durch eine entgegengesetzte geschlossen wurde. Dies ermöglicht es uns, den Erhalt eines Schließauftrags teilweise zu realisieren, wenn eine Position durch eine gegenüberliegende geschlossen wird.

Dies sind alle Änderungen und Verbesserungen, die für die Definition des Schließens einer Position für MQL4 notwendig sind.

Die vollständigen Auflistungen aller Klassen finden Sie in den Dateien der Anlage.

Tests

Um den Test durchzuführen, verwenden wir den Test-EA aus dem vorherigen Artikel TestDoEasyPart10.mq4 aus \MQL4\Experts\TestDoEasy\Part10 und speichern ihn im neuen Ordner \MQL4\Experts\TestDoEasy\Part11 unter dem Namen TestDoEasyPart11.mq4.

Da wir Zeitkonstanten in Millisekunden aus der ENUM_SORT_ORDERS_MODE Aufzählung der möglichen Sortierkriterien für Reihenfolge und Transaktion entfernt haben, müssen wir die Sortierung nach der Installationszeit korrigieren, wobei die gelöschte Konstante

list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);

mit Sortieren nach Zeit im EA bei der Behandlung des Drückens der Schaltfläche zum Löschen von Pending-Orders:

      //--- Wenn die Schaltfläche BUTT_DELETE_PENDING geklickt wurde:: Entfernen der ersten Pending-Order
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- Abrufen der Liste aller Aufträge
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- Sortieren der neuen Liste nach der Platzierungszeit
            list.Sort(SORT_BY_ORDER_TIME_OPEN);
            int total=list.Total();
            //--- In der Schleife aller Positionen mit der größten Zeitspanne
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- Löschen der Order mach dem Ticket
               #ifdef __MQL5__
                  trade.OrderDelete(order.Ticket());
               #else 
                  PendingOrderDelete(order.Ticket());
               #endif 
              }
           }
        }

Kompilieren Sie den EA und setzen Sie die Eingabeparameter StopLoss in Punkten und TakeProfit in Punkten im Tester auf Null, so dass Positionen ohne Stop-Orders geschlossen werden. Dann starten Sie den EA im Tester, öffnen eine Position und schließen sie teilweise.
Danach platzieren und entfernen Sie eine Pending-Order:


Nun werden die Ereignisse teilweises Schließen und Entfernen eine Pending-Order als separate Ereignisse definiert.

Starten Sie den EA noch einmal und klicken Sie auf die Schaltflächen zur Beobachtung der Definition von Ereignissen:


Wie wir sehen können, werden die Ereignisse korrekt erfasst. Das Ereignis von Schließen-Durch ist definiert, Änderungen der Stop-Level und die Preise der Pending-Orders werden ebenfalls verfolgt.

Was kommt als Nächstes?

In diesem Artikel haben wir die Umwandlung der bestehenden Bibliotheksfunktionalität aus Gründen der Kompatibilität mit MQL4 abgeschlossen. In den kommenden Artikeln werden wir neue Objekte "Konto" und "Symbol", deren Kollektion und Ereignisse erstellen.

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

Zurück zum Inhalt

Frühere Artikel dieser Serie:

Teil 1. 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.


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

Beigefügte Dateien |
MQL5.zip (100.78 KB)
MQL4.zip (100.79 KB)
Optimierungsmanagement (Teil I): Erstellen einer GUI Optimierungsmanagement (Teil I): Erstellen einer GUI

Dieser Artikel beschreibt den Prozess der Erstellung einer Erweiterung für das MetaTrader-Terminal. Die vorgestellte Lösung hilft, den Optimierungsprozess zu automatisieren, indem Optimierungen in anderen Terminals durchgeführt werden. Es werden noch einige weitere Artikel zu diesem Thema geschrieben. Die Erweiterung wurde unter Verwendung der Sprache C# und der Designmuster entwickelt, was zusätzlich die Fähigkeit demonstriert, die Terminalfunktionen durch die Entwicklung benutzerdefinierter Module zu erweitern, sowie die Fähigkeit, benutzerdefinierte grafische Benutzeroberflächen mit der Funktionsvielfalt einer bevorzugten Programmiersprache zu erstellen.

Den Gewinn bis zum letzten Pip extrahieren Den Gewinn bis zum letzten Pip extrahieren

Der Artikel beschreibt den Versuch, Theorie und Praxis im algorithmischen Handelsbereich zu verbinden. Die meisten Diskussionen über das Erstellen von Handelssystemen stehen im Zusammenhang mit der Verwendung historischer Bars und verschiedener darauf angewandter Indikatoren. Dies ist das am besten abgedeckte Feld und deshalb werden wir es nicht berücksichtigen. Bars sind künstliches Konstrukte, versuchen wir näher an die ursprünglichen Daten zu kommen - den Preis-Ticks.

Entwicklung eines plattformübergreifenden Grider-EAs (Teil III): Korrekturbasiertes Raster mit Martingal Entwicklung eines plattformübergreifenden Grider-EAs (Teil III): Korrekturbasiertes Raster mit Martingal

In diesem Artikel werden wir versuchen, den bestmögliche, rasterbasierten EA zu entwickeln. Wie üblich wird dies ein plattformübergreifender EA sein, der sowohl mit MetaTrader 4 als auch mit MetaTrader 5 arbeiten kann. Der erste EA war gut genug, außer dass er über einen langen Zeitraum keinen Gewinn erzielen konnte. Der zweite EA konnte in Zeiträumen von mehr als einigen Jahren arbeiten. Leider konnte er nicht mehr als 50% Gewinn pro Jahr bei einem maximalen Drawdown von weniger als 50% erzielen.

Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XIII): Objektklasse "Account" und die Kollektion von Konto-Objekten Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XIII): Objektklasse "Account" und die Kollektion von Konto-Objekten

Im vorherigen Artikel haben wir für MQL4 in der Bibliothek der Ereignisse des Positionsschließens definiert und die ungenutzten Auftragseigenschaften beseitigt. Hier werden wir die Erstellung des Konto-Objekts betrachten, die Kollektion von Konto-Objekten entwickeln und die Funktionsweise zur Verfolgung von Konto-Ereignissen vorbereiten.