Handelsereignisse in MetaTrader 5

MetaQuotes | 15 Januar, 2016

Einleitung

Alle Befehle, Handels-Operationen auszuführen, werden vom MetaTrader 5 Client-Terminal mittels Anfrage senden an den Handels-Server weitergeleitet. Jede Anfrage sollte gemäß der angeforderten Operation korrekt befüllt sein, da sie sonst nicht die erste Prüfstufe passieren und vom Server nicht für eine weitere Bearbeitung akzeptiert wird.

Vom Handels-Server akzeptierte Anfragen werden in Form von Orders gespeichert, die entweder offen sind oder sofort durch zum Marktkurs ausgeführt werden. Orders werden solange auf dem Server gespeichert, bis sie befüllt oder storniert werden. Das Ergebnis einer Order-Ausführung ist ein Abschluss.

Ein Abschluss ändert die Handelsposition durch ein gegebenes Symbol, das die Position öffnen, schließen, erhöhen, verringern oder umkehren kann. Daher ist eine offene Position immer das Ergebnis der Ausführung eines oder mehrerer Abschlüsse. Detaillierte Informationen finden Sie im Beitrag Orders, Positions und Abschlüsse in MetaTrader 5.

Der vorliegende Beitrag beschreibt das Konzept, die Begriffe und Vorgänge, die innerhalb des Zeitraums zwischen dem Absenden einer Anfrage und ihrem Verschieben in die Handels-History, nachdem sie bearbeitet wurde, vor sich gehen.


Absenden einer Anfrage vom Client-Terminal zum Handels-Server

Um eine Handels-Operation durchzuführen, sollte man eine Order an das Handelssystem senden. Eine Anfrage wird stets an den Handels-Server durch Abschicken einer Order vom Client-Terminal gesendet. Die Struktur der Anfrage muss, ungeachtet des Handels, stets korrekt befüllt sein - entweder manuell oder via eines MQL5 Programms.

Um eine Handels-Operation manuell auszuführen, müssen Sie da Dialogfenster zum Befüllen einer Handelsanfrage öffnen oder F9 drücken. Bei automatisch via MQL5 durchgeführtem Handel, werden entsprechende Anfragen mittels der OrderSend() Funktion gesendet. Da eine große Anzahl an nicht korrekten Anfragen das unerwünschte Überladen des Handels-Servers verursachen kann, muss jede Anfrage vor dem Absenden mit Hilfe der OrderCheck() Funktion überprüft werden. Das Überprüfungsergebnis einer Anfrage wird einer Variable übertragen, die durch die MqlTradeCheckresult Struktur beschrieben ist.

Wichtig: Jede Anfrage wird im Client-Terminal auf Exaktheit überprüft und dann erst an den Handels-Server gesendet. Absichtlich nicht korrekte Anfragen (Kauf einer Million Posten oder Kauf durch einen Negativkurs) werden niemals vom Client-Terminal weiter übertragen. Dies dient dem Schutz der Handels-Server vor einer Masse inkorrekter Anfragen, die durch einen Fehler in einem MQL5 Programm verursacht werden.

Sobald eine Anfrage beim Handels-Server ankommt, durchläuft sie ihre primäre Überprüfung:

Eine inkorrekte Anfrage, die diese primäre Überprüfung auf dem Server nicht besteht, wird abgelehnt. Der Client-Terminal wird stets über das Prüfungsergebnis einer Anfrage via Übermittlung einer Antwort informiert. Die Antwort des Handels-Servers kann von einer Variabel des Typs MqlTradeResult angenommen werden, die als der zweite Parameter in der OrderSend() Funktion beim Senden einer Anfrage übertragen wird.

Senden von Handels-Anfragen vom Client-Terminal zum Handels-Server

Sobald eine Anfrage die primäre Überprüfung auf Exaktheit besteht, wird sie zur Anfrage, die auf auf ihre Bearbeitung wartet, platziert. Als Ergebnis der Bearbeitung einer Anfrage wird eine Order (Befehl eine Handels-Operation durchzuführen) in der Basis des Handels-Servers erzeugt. Es gibt jedoch zwei Arten von Anfragen, die nicht zur Erzeugung einer Order führen:

  1. die Anfrage eine Position zu verändern (ihren Stop Loss und/oder Take Profit zu ändern);
  2. die Anfrage eine offene Order zu modifizieren (ihre Kurslevels und ihr Ablaufdatum).

Der Client-Terminal erhält die Meldung, dass die Anfrage akzeptiert und in das Handels-Subsystem der MetaTrader 5 Plattform platziert ist. Der Server platziert die akzeptierte Anfrage in die Anfrage-Warteschlange zur weiteren Bearbeitung, mit den folgenden Ergebnissen:

Die 'Lebensdauer' einer Anfrage in der Warteschlange des Servers ist auf 180 Sekunden begrenzt. Sobald diese Zeit überschritten ist, wird die Anfrage aus der Warteschlange entfernt


Absenden von Handelsereignissen vom Handels-Server zum Client-Terminal

Das Ereignis-Modell und die Funktionen der Behandlung von Ereignissen sind in der MQL5 Sprache implementiert. Das heißt, dass bei einer Antwort auf jedes vorab festgelegte Ereignis die MQL5 Ausführungsumgebung die entsprechende Funktion aufruft - den Event-Anwender. Zur Bearbeitung von Handelsereignissen gibt es eine vorab festgelegte Funktion: OnTrade(). Der Code zur Arbeit mit Order, Positions und Abschlüssen muss in diese Funktion gesetzt werden. Diese Funktion wird nur für Expert Advisors aufgerufen und nicht in Indikatoren und Scripts verwendet, selbst wenn Sie dort eine Funktion mit demselben Namen und Typ hinzufügen.

Die Handelsereignisse werden vom Server in folgenden Fällen erzeugt:

Bitte beachten Sie, dass eine Operation durchaus das Auftreten mehrerer Ereignisse auslösen kann. So führt z.B. das Auslösen einer offenen Order zu folgenden zwei Ereignissen:

  1. Auftauchen eines Abschlusses, der in die Handels-History geschrieben wird;
  2. Verschiebung der ausstehenden Order von der Liste der aktiven in die Liste der History-Orders (die Order wird also in die History verschoben).

Ein weiteres Beispiel für mehrere Ereignisse ist die Durchführung von mehreren Abschlüssen auf Grundlage einer einzigen Order, falls das erforderliche Volumen nicht aus einem einzigen Gegenangebot erhalten werden kann. Der Handels-Server erzeugt und sendet die Meldungen über jedes Ereignis an den Client-Terminal. Deshalb kann die OnTrade() Funktion mehrere Male für ein anscheinend nur einziges Ereignis aufgerufen werden. Dies ist ein einfaches Beispiel des Bearbeitungsvorgangs einer Order im Handels-Subsystem der MetaTrader 5 Plattform.

Hier kommt das Beispiel: Während eine ausstehende Order über den Kauf von 10 Posten von EURUSD auf ihre Ausführung wartet, erscheinen Gegenangebote zum Verkauf von 1, 4 und 5 Posten. Zusammen ergeben diese drei Anfragen das erforderliche Volumen von 10 Posten, also werden sie nacheinander durchgeführt, wenn die Befüllungs-Direktive die teilweise Durchführung von Handels-Operationen erlaubt.

Als Ergebnis der Ausführung der 4 Orders, führt der Server nun 3 Abschlüsse von 1, 4 und 5 Posten auf Grundlage der bestehenden Gegen-Anfragen aus. Wie viele Handelsereignisse werden in diesem Fall erzeugt? Die erste Gegenanfrage für den Verkauf eines Postens führt zur Ausführung des Abschlusses von 1 Posten. Das ist das erste Handelsereignis (Abschluss 1 Postens). Doch die ausstehende Order des Kaufs von 10 Posten ist ebenfalls geändert, und zwar jetzt in eine Order zum Kauf von 9 Posten EURUSD. Die Änderung des Volumens der ausstehenden Order ist das zweite Handelsereignis (Volumenänderung einer ausstehenden Order).

Erzeugung von Handelsereignissen

Die die anderen zwei Handelsereignisse werden für den zweiten Abschluss von 4 Posten erzeugt, und eine Meldung über jedes Ereignis wird an den Client-Terminal geschickt, der die ursprünglich ausstehende Order für den Kauf von 10 Posten EURUSD initiiert hat.

Der letzte Abschluss von 5 Posten führt seinerseits zum Auftreten von drei Handelsereignissen:

  1. den Abschluss von 5 Posten,
  2. die Veränderung des Volumens,
  3. das Verschieben der Order in die Handels-History.

Als Ergebnis der Ausführung des Abschlusses erhält der Client-Terminal 7 Handelsereignisse, Trade, nacheinander (unter der Voraussetzung, dass die Verbindung zwischen Client-Terminal und Trade-Server stabil ist und keine Meldung verlorengehen). Diese Meldungen müssen in einem Expert Advisor mit Hilfe der OnTrade() Funktion verarbeitet werden.

Wichtig: Als Ergebnis einer oder mehrerer Anfragen kann jede Meldung über einen Handel, ja sogar Handel selbst, auftauchen. Jede Anfrage kann zum Auftauchen mehrerer Handelsereignisse führen. Sie dürfen sich daher nicht auf die Aussage "Eine Anfrage - Ein Handelsereignis" verlassen, da die Ereignisse in verschiedenen Phasen bearbeitet werden können und jede Operation den Status von Orders, Positions und der Handels-History verändern kann.


Bearbeitung von Orders durch den Handels-Server

Alle Order, die auf ihre Ausführung warten, werden am Ende in die History verschoben - entweder wird die Bedingung für ihre Ausführung erfüllt oder sie werden storniert. Die Stornierung einer Order kann auf unterschiedliche Weise ablaufen:

Egal aus welchem Grund eine aktive Order in die History verschoben wird, der Client-Terminal erhält eine Meldung über die Veränderung. Meldungen über das Handelsereignis gehen nicht an alle Client-Terminals, sondern nur an die mit dem entsprechenden Account verbundenen Terminals.



Wichtig: Die Tatsache, dass der Handels-Server eine Anfrage akzeptiert führt nicht notwendigerweise immer zur Ausführung der angefragten Operation. Das bedeutet zunächst nur, dass die Anfrage die Prüfung bestanden hat, nachdem sie beim Handels-Server angekommen ist.

Und deshalb sagt die Dokumentation der OrderSend() Funktion ja auch:

Gelieferter Wert

Im Falle einer erfolgreichen Basisprüfung einer Anfrage liefert  die OrderSend() Funktion true - . . Dies ist kein Anzeichen für eine erfolgreiche Ausführung einer Handels-Operation . Für eine detailliertere Beschreibung des Ausführungsergebnisses der Funktion, analysieren Sie die Felder der MqlTradeResult Struktur.


Aktualisierung von Handel und History im Client-Terminal

Meldungen über Handelsereignisse und Veränderungen in der Handels-History werden durch separate Kanäle geschickt. Beim Senden einer Kauf-Anfrage mit Hilfe der OrderSend() Funktion, bekommen Sie das Order-Ticket, das als Ergebnis der erfolgreichen Prüfung der Anfrage erstellt wird. Gleichzeitig kann die Order selbst u.U. nicht im Client-Terminal erscheinen, und ein Versuch, sie mittels OrderSelect() auszuwählen, kann scheitern. 


Alle Meldungen vom Handels-Server zum Client-Terminal kommen unabhängig voneinander

In der obigen Abbildung erkennen Sie, wie der Handels-Server das Order-Ticket an das MQL5 Programm schickt. Doch die Meldung über das Handelsereignis Handel (Auftauchen einer neuen Order) ist noch nicht angekommen. Die Meldung über die Veränderung in der Liste der aktiven Order ist ebenfalls noch nicht angekommen.

Es kann auch durchaus die Fall eintreten, dass die Meldung Handel über das Auftauchen einer neuen Order im Programm ankommt, wenn ein Abschluss auf ihrer Basis bereits durchgeführt worden ist und die Order somit bereits nicht mehr in der Liste der aktiven Order ist, sondern sich bereits in History befindet. Dies ist eine reale Situation, da die Verarbeitungsgeschwindigkeit von Anfragen viel höher ist als die aktuelle Geschwindigkeit der Kommunikation von Meldungen durch ein Netzwerk.


Umgang mit Handelsereignissen in MQL5

Alle Operationen auf dem Handels-Server und das Versenden von Meldungen zu Handelsereignissen werden asymmetrisch ausgeführt. Es gibt nur eine Methode, um herauszufinden, welche Veränderungen tatsächlich im Handels-Account stattgefunden haben. Nämlich sich den Handels-Status und die Handels-History zu merken und sie mit dem neuen Status abzugleichen.

Mit folgendem Algorithmus können Sie Handelsereignisse im Expert Advisor nachverfolgen:

  1. Deklarierung der Order-, Positions- und Abschlusszähler in globalem Umfang;
  2. Festlegung der Intensität der Handels-History, die für den MQL5 Programm-Cache angefragt werden wird. Je mehr History wir in den Cache laden, umso mehr Ressourcen des Terminals und Computers werden aufgebraucht;
  3. Initialisierung der Order-, Positions- und Abschlusszähler in der OnInit Funktion;
  4. Festlegung der Anwender-Funktionen, in denen die Handels-History für den Cache angefordert wird;
  5. Nach dem Laden der Handels-History finden wir hier auch heraus, was mit dem Handels-Account geschehen ist, und zwar durch einen Vergleich des gemerkten und aktuellen Status.

Dies ist der einfachste Algorithmus, da er uns erlaubt, herauszufinden, ob die Zahl der offenen Positions (Order, Abschlüsse) verändert wurde und in welche Richtung diese Veränderung stattfand. Gibt es Veränderungen, dann können wir weitere detaillierte Informationen erhalten. Hat sich die Zahl der Order nicht verändert, wurden die Order jedoch an sich modifiziert, ist hierfür ein andere Vorgehensweise nötig. Sie wird in diesem Beitrag jedoch nicht behandelt.

Veränderungen des Zählers können in den OnTrade() und OnTick() Funktionen eines Expert Advisors nachgeprüft werden. 

Schreiben wir Schritt für Schritt ein Programmbeispiel.

1. Der Order-, Abschluss- und Positionszähler im globalen Umfang.

int          orders;            // number of active orders
int          positions;         // number of open positions
int          deals;             // number of deals in the trade history cache
int          history_orders;    // number of orders in the trade history cache
bool         started=false;     // flag of initialization of the counters


2. Die Intensität der Handels-History, die in den Cache geladen wird, wird in der Eingabevariable days festgelegt (lädt die Handels-History für die in dieser Variable bestimmte Anzahl an Tagen).

input    int days=7;            // depth of the trade history in days

//--- set the limit of the trade history on the global scope
datetime     start;             // start date of the trade history in cache
datetime     end;               // end date of the trade history in cache


3. Initialisierung der Zähler und die Begrenzungen der Handels-History. Wenn Sie die Initialisierung der Zähler in der InitCounters() Funktion ausführen, können Sie den Code besser lesen:

int OnInit()
  {
//---
   end=TimeCurrent();
   start=end-days*PeriodSeconds(PERIOD_D1);
   PrintFormat("Limits of the trade history to be loaded: start - %s, end - %s",
               TimeToString(start),TimeToString(end));
   InitCounters();
//---
   return(0);
  }

Die InitCounters() Funktion versucht die Handels-History in den Cache zu laden. Wenn dies erfolgreich war, initialisiert sie alle Zähler. Wenn die History erfolgreich geladen ist, wird der Wert der globalen 'gestartet' Variable auf 'true' gesetzt, was anzeigt, dass die Zähler erfolgreich initialisiert wurden.

//+------------------------------------------------------------------+
//|  initialization of the counters of positions, orders and deals   |
//+------------------------------------------------------------------+
void InitCounters()
  {
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %s to %s to the cache. Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }
//--- get current values
   orders=OrdersTotal();
   positions=PositionsTotal();
   deals=HistoryDealsTotal();
   history_orders=HistoryOrdersTotal();
   started=true;
   Print("The counters of orders, positions and deals are successfully initialized");
  }


4. Die Suche nach Veränderungen im Status des Handels-Account wird in den OnTick() und OnTrade() Anwendern vollzogen. Als erstes wird die Variable 'gestartet' geprüft: ist ihr Wert 'true', wird die SimpleTradeProcessor() Funktion aufgerufen. Ansonsten wird die Initialisierungsfunktion der Zähler InitCounters() aufgerufen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }
//+------------------------------------------------------------------+
//| called when the Trade event occurs                               |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }

5. Die Funktion SimpleTradeProcessor() prüft, ob sich die Anzahl der Order, Abschlüsse und Positions, verändert hat. Nachdem alle Prüfungen durchgeführt sind, rufen wir die CheckStartDateInTradeHistory() Funktion auf, die den 'Start' Wert ggf. näher an den aktuellen Augenblick heran schiebt.

//+------------------------------------------------------------------+
//| simple example of processing changes in trade and history        |
//+------------------------------------------------------------------+
void SimpleTradeProcessor()
  {
   end=TimeCurrent();
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %s to %s to the cache. Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }

//--- get current values
   int curr_orders=OrdersTotal();
   int curr_positions=PositionsTotal();
   int curr_deals=HistoryDealsTotal();
   int curr_history_orders=HistoryOrdersTotal();

//--- check whether the number of active orders has been changed
   if(curr_orders!=orders)
     {
      //--- number of active orders is changed
      PrintFormat("Number of orders has been changed. Previous number is %d, current number is %d",
                  orders,curr_orders);
     /*
       other actions connected with changes of orders
     */
      //--- update value
      orders=curr_orders;
     }

//--- change in the number of open positions
   if(curr_positions!=positions)
     {
      //--- number of open positions has been changed
      PrintFormat("Number of positions has been changed. Previous number is %d, current number is %d",
                  positions,curr_positions);
      /*
      other actions connected with changes of positions
      */
      //--- update value
      positions=curr_positions;
     }

//--- change in the number of deals in the trade history cache
   if(curr_deals!=deals)
     {
      //--- number of deals in the trade history cache has been changed
      PrintFormat("Number of deals has been changed. Previous number is %d, current number is %d",
                  deals,curr_deals);
      /*
       other actions connected with change of the number of deals
       */
      //--- update value
      deals=curr_deals;
     }

//--- change in the number of history orders in the trade history cache
   if(curr_history_orders!=history_orders)
     {
      //--- the number of history orders in the trade history cache has been changed
      PrintFormat("Number of orders in the history has been changed. Previous number is %d, current number is %d",
                  history_orders,curr_history_orders);
     /*
       other actions connected with change of the number of order in the trade history cache
      */
     //--- update value
     history_orders=curr_history_orders;
     }
//--- check whether it is necessary to change the limits of trade history to be requested in cache
   CheckStartDateInTradeHistory();
  }

Die CheckStartDateInTradeHistory() Funktion berechnet das Startdatum der Anfrage der Handels-History für den aktuellen Moment (curr_start) und vergleicht ihn mit der 'Start' Variable. Ist die Differenz zwischen beiden > 1 Tag, wird 'Start' korrigiert, und die Zähler der Order- und Abschluss-History werden aktualisiert.

//+------------------------------------------------------------------+
//|  Changing start date for the request of trade history            |
//+------------------------------------------------------------------+
void CheckStartDateInTradeHistory()
  {
//--- initial interval, as if we started working right now
   datetime curr_start=TimeCurrent()-days*PeriodSeconds(PERIOD_D1);
//--- make sure that the start limit of the trade history period has not gone 
//--- more than 1 day over intended date
   if(curr_start-start>PeriodSeconds(PERIOD_D1))
     {
      //--- we need to correct the date of start of history loaded in the cache
      start=curr_start;
      PrintFormat("New start limit of the trade history to be loaded: start => %s",
                  TimeToString(start));

      //--- now load the trade history for the corrected interval again
      HistorySelect(start,end);

      //--- correct the counters of deals and orders in the history for further comparison
      history_orders=HistoryOrdersTotal();
      deals=HistoryDealsTotal();
     }
  }

Den kompletten Code des Expert Advisor DemoTradeEventProcessing.mq5 finden Sie im Anhang dieses Beitrags.


Fazit

Alle Operationen der Online Handels-Plattform MetaTrader 5 werden asynchron ausgeführt. Die Meldungen über alle Veränderungen in einem Handels-Account werden unabhängig voneinander übermittelt. Es macht daher keinen Sinn, einzelne Ereignisse nach der Regel "Eine Anfrage - Ein Handelsereignis" nach verfolgen zu wollen. Wenn Sie wirklich exakt feststellen müssen, was sich genau verändert hat, wenn ein Handel kommen sollte, sollten Sie all Ihre Abschlüsse, Positions und Order bei jedem Aufruf des OnTrade-Anwenders analysieren und ihren aktuellen Status mit dem vorigen vergleichen.