MetaTrader 5 herunterladen

Handels-Ereignisse im Expert Advisor mit Hilfe der OnTrade() Funktion bearbeiten

11 Januar 2016, 13:52
KlimMalgin
0
433

Einleitung

Jeder Händler, der auf MQL Experts schreibt, muss über kurz oder lang berichten, wie sein Expert arbeitet. Oder er muss vielleicht SMS oder E-Mail Benachrichtigungen über die Aktionen des Expert implementieren. Auf jeden Fall müssen bestimmte Ereignisse, die im Markt passieren, oder von einem Expert ausgeführte Handlungen, "festgehalten" werden und Benutzer darüber in Kenntnis gesetzt werden.

In diesem Beitrag möchte ich Ihnen erklären, wie Sie die Bearbeitung von Handels-Ereignissen implementieren können und Ihnen meine Implementierung anbieten.

Es geht also um die Bearbeitung der folgenden Ereignisse:

  • Positionen
    1. Eröffnung
    2. Hinzufügen
    3. Modifizieren (Stop Loss und Take Profit verändern)
    4. Umkehren
    5. Gesamte Position schließen
    6. Teil der Position schließen
  • Offene Order
    1. Platzieren
    2. Modifizieren

1. Wie funktioniert das? 

Bevor wir beginnen, beschreibe ich zunächst ganz allgemein wie Handels-Ereignisse funktionieren sowie kurz alle dafür notwendigen Details.

In MQL5 gibt es vorgegebene und benutzerdefinierte Ereignisse. Uns interessieren die vorgegebenen Ereignisse, insb. das Handels-Ereignis.

Jedes Mal wenn eine Handels-Operation abgeschlossen ist, wir das Handels-Ereignis erzeugt. Und ebenfalls jedes Mal nach einem erzeugten Handels-Ereignis wird die OnTrade() Funktion aufgerufen. Die Bearbeitung von Aufträgen und Positionen erfolgt ganz exakt innerhalb der OnTrade() Funktion.

2. Expert Template

Legen wir also zunächst einen neuen Expert Advisor an. Klicken Sie dazu im MetaEditor auf Datei -> Neu, um den MQL5 Assistenten zu starten. Wählen Sie Expert Advisor und klicken auf Weiter. Geben Sie im Dialog "Allgemeine Eigenschaften des Expert Advisors" den Namen des Expert Advisors und ggf. Ihre eigenen Daten ein. Ich habe meinen Expert Advisor "TradeControl" genannt. Entweder Sie übernehmen diesen Namen oder wählen einen eigenen. Es spielt keine Rolle. Als nächstes legen wir alle Parameter fest, so wie sie beim Schreiben eines Experten spontan erzeugt werden.

Fertig! Das Expert Advisor Template ist angelegt und wir müssen ihm nun die OnTrade() Funktion hinzufügen.

Als Ergebnis sollten Sie folgenden Code erhalten:

//+------------------------------------------------------------------+
//|                                              TradeControl_en.mq5 |
//|                                             Copyright KlimMalgin |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "KlimMalgin"
#property link      ""
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

3. Mit Positionen arbeiten

Beginnen wir mit dem einfachsten Handels-Ereignis - der Eröffnung und Abschluss von Positionen. Dazu sollte man zunächst wissen, welche Vorgänge ablaufen, nachdem man auf "Verkaufen" und "Kaufen" gedrückt hat.

Wenn wir einen Aufruf in der OnTrade() Funktion platzieren:

Alert("The Trade event occurred");

sehen wir, dass nach der Eröffnung per Marktfunktion OnTrade() und gemeinsam mit ihr, unsere Warnung vier Mal ausgeführt wurde:

Abb. 1 Warnungen

Abb. 1 Warnungen

Warum wird OnTrade() viermal aufgerufen und wie können wir auf diese Warnungen reagieren? Um das herauszufinden, genügt ein kurzer Blick in die Dokumentation :

 OnTrade

Diese Funktion wird aufgerufen, wenn das Handels-Ereignis eintritt. Dies geschieht, sobald die Liste der platzierten Order, geöffneten Positionen, Auftrags-History und Abschluss-History verändert wird.

Lassen Sie mich hier einen Punkt erwähnen:

Während der Abfassung dieses Beitrags und meinen Gesprächen mit Entwicklern, habe ich herausgefunden, dass Veränderungen in der History nicht zum Aufruf von OnTrade() führen! Tatsächlich wird die OnTrade() Funktion nur dann aufgerufen, wenn die Liste der platzierten Order und geöffneten Positionen verändert wird! Bei der Entwicklung von Steuerungsprogrammen für Handels-Ereignisse werden Sie evtl. bemerken, dass ausgeführte Order und Abschlüsse in der History mit zeitlicher Verzögerung erscheinen und Sie sie nicht bearbeiten können, solange die OnTrade() Funktion läuft.  

Aber zurück zu den Ereignissen. Wie wir gesehen haben, taucht das Handels-Ereignis vier Mal auf, wenn Sie per Markt öffnen:

  1. Erzeugung einer Order, der per Markt geöffnet werden soll.
  2. Ausführung die Order.
  3. Übergabe der kompletten Order in die History.
  4. Eröffnung der Position.

  Um diesen Prozess im Terminal verfolgen zu können, müssen Sie die Order-Liste im "Handel"-Tab des MetaTrader Fenster beachten: 

Abb. 2 Order-Liste im "Handel"-Tab

Abb. 2 Order-Liste im "Handel"-Tab

Sobald Sie eine Position (er)öffnen (z.B. unten), erscheint in die Order-Liste eine Order, die den Status gestartet hat (Fig. 2). Dies verändert die Liste der platzierten Orders, und das Handels-Ereignis wird aufgerufen. Die OnTrade() Funktion wird nun zum ersten Mal aktiviert. Danach wird ein Abschluss per erzeugter Order ausgeführt. Die OnTrade() Funktion wird zum zweiten Mal ausgeführt. Sobald der Abschluss ausgeführt ist, werden die abgeschlossene Order und ihr ausgeführter Abschluss an History geschickt, und die OnTrade() Funktion ein drittes Mal aufgerufen. Im letzten Schritt wird eine Position per ausgeführten Abschluss geöffnet und die OnTrade() Funktion ein viertes Mal aufgerufen.

Um den Moment der Eröffnung der Position "festzuhalten", müssen Sie, jedes Mal wenn Sie OnTrade() aufrufen, die Order-Liste sowie die Order- und Abschluss-History analysieren. Und das machen wir jetzt einmal gemeinsam!

Also, die OnTrade() Funktion ist aufgerufen. Wir müssen nun wissen, ob sich die Anzahl der Orders im "Handel"-Tab verändert hat. Dazu müssen wir die Anzahl der Orders in der Liste zum Zeitpunkt des vorherigen OnTrade() Aufrufs mit der Anzahl der Orders jetzt vergleichen. Um herauszufinden, wie viele Orders die Liste augenblicklich enthält, verwenden wir die OrdersTotal() Funktion. Und um zu erfahren, wie viele Orders beim vorherigen Aufruf gelistet waren, brauchen wir den Wert von OrdersTotal() in jedem OnTrade() Aufruf. Dazu erzeugen wir eine spezielle Variable:

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call

Der OrdersPrev Variable wird am Ende der OnTrade() Funktion der Wert von OrdersTotal() zugewiesen.

Hier sollten Sie auch Situationen berücksichtigen, in denen der Expert Advisor läuft, und die Liste offene Orders enthält. Expert muss sie erkennen können, daher muss in der OnInit() Funktion der OrdersPrev Variable ebenfalls der Wert von OrdersTotal() zugewiesen werden. Diese Veränderungen, die wir gerade in Expert gemacht haben, sehen folgendermaßen aus: 

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   OrdersPrev = OrdersTotal();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

OrdersPrev = OrdersTotal();
//---
  }

Jetzt kennen wir also die Anzahl der Orders für die aktuellen und zurückliegenden Aufrufe und können daher herausfinden, wann die Order in der Liste auftauchte und wann sie, aus welchen Gründen auch immer, wieder verschwand. Dazu verwenden wir folgende Bedingung:

if (OrdersPrev < OrdersTotal())
{
  // Order appeared
}
else if(OrdersPrev > OrdersTotal())
{
  // Order disappeared
}

Hier zeigt sich dann, dass die Order in der Liste auftaucht (mehrere Orders können nicht gleichzeitig angezeigt werden), falls wir im vorherigen Aufruf weniger Orders haben als aktuell. Ist jedoch das Gegenteil der Fall, also derzeit weniger Orders als im vorigen OnTrade() Aufruf, wird die Order aus irgendeinem Grund entweder ausgeführt oder storniert. Fast die gesamte Arbeit mit Positionen fängt mit diesen zwei Bedingungen an.

Nur Stop Loss und Take Profit verlangen ein anderes Vorgehen. Ich füge nun der OnTrade() Funktion den Code hinzu, der mit den Positionen funktioniert. Sehen wir ihn uns an:

void OnTrade()
  {
//---
Alert("Trade event occurred");

HistorySelect(start_date,TimeCurrent());

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
}
else if(OrdersPrev > OrdersTotal())
{
   state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE);

   // If order is not found, generate an error
   _GetLastError=GetLastError();
   if (_GetLastError != 0){Alert("Error #",_GetLastError," Order is not found!");LastOrderTicket = 0;}
   Print("Error #",_GetLastError," state: ",state);ResetLastError();


   // If order is fully executed
   if (state == ORDER_STATE_FILLED)
   {
      // Then analyze the last deal
      // --
      Alert(LastOrderTicket, "Order executed, going to deal");
      switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
      {
         
         // Entering the market
         case DEAL_ENTRY_IN:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
               break;
               
               case 1:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Exiting the market
         case DEAL_ENTRY_OUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Sell position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Sell position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
               break;
               
               case 1:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Buy position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Buy position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Reverse
         case DEAL_ENTRY_INOUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
                  Alert("Sell is reversed to Buy on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               case 1:
                  Alert("Buy is reversed to Sell on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Indicates the state record
         case DEAL_ENTRY_STATE:
            Alert("Indicates the state record. Unprocessed code of type: ", 
            HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
         break;
      }
      // --
   }
}

OrdersPrev = OrdersTotal();

//---
  }

Achten Sie darauf, dass Sie zu Beginn des Programms die folgenden Variablen deklariert haben:

datetime start_date = 0;   // Date, from which we begin to read history

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call
int PositionsPrev = 0;     // Number of positions at the time of previous OnTrade() call
ulong LastOrderTicket = 0; // Ticket of the last processed order

int _GetLastError=0;       // Error code
long state=0;              // Order state

Doch zurück zu den Inhalten von OnTrade().

Sie können die Warnung am Anfang auskommentieren, doch ich lasse das. Als Nächstes geht es um die HistorySelect() Funktion. Sie generiert eine Liste der Abschluss- und Auftrags-History für den angegebenen Zeitraum, der durch zwei Parameter der Funktion festgelegt ist. Wird diese Funktion nicht aufgerufen, bevor man zur Abschluss- und Auftrags-History geht, erhalten wir keinerlei Information, da die History-Listen leer sind. Nach dem Aufruf von HistorySelect(), werden die Bedingungen untersucht, so wie es kurz zuvor geschrieben wurde.

Sobald eine neue Order hinzukommt, wählen wir sie zunächst aus und prüfen sie auf Fehler:

OrderGetTicket(OrdersTotal()-1);// Select the last order for work
_GetLastError=GetLastError();
Print("Error #",_GetLastError);ResetLastError();

Nachdem die Order gewählt wurde, erhalten wir mit Hilfe der GetLastError() Funktion einen Fehler-Code. Mit Hilfe der Print() Funktion drucken wir den Code ins Logbuch und setzen den Fehlercode mit Hilfe der ResetLastError() Funktion auf Null, damit wir beim nächsten GetLastError() Aufruf bei anderen Situationen nicht noch einmal den selben Fehlercode erhalten.

Wenn die Order erfolgreich ausgewählt wurde, muss nach der Fehlerüberprüfung ihr Status geprüft werden:

if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
{
   Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
   LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
}

Besitzt die Order den Status gestartet, ist also auf Exaktheit geprüft, wird sie wohl in naher Zukunft ausgeführt werden. Also geben wir einfach eine Alert(), die besagt, dass diese Order bearbeitet wird und speichern ihr Ticket für den nächsten OnTrade() Aufruf. Anstelle einer Alert(), können Sie natürlich auch jede andere Art von Meldung verwenden.

Im obigen Code ?????

OrderGetTicket(OrdersTotal()-1)

liefert das letzte Auftragsticket aus der gesamten Auftragsliste.

OrdersTotal()-1 gibt an, dass wir die jüngste Order bekommen müssen. Da die OrdersTotal() Funktion die Gesamtzahl der Orders liefert (z.B. wenn 1 Order in der Liste ist, gibt OrdersTotal() 1 zurück), und die Auftrags-Indexnummer von 0 ab gezählt wird, müssen wir die Gesamtzahl der Order um 1 vermindern, um die Indexnummer der letzten Order zu erhalten (wenn OrdersTotal() 1 liefert, ist die Indexnummer dieses Auftrags 0). Die OrderGetTicket() Funktion ihrerseits, liefert das Order-Ticket, dessen Nummer an sie übertragen wird.

Dies ist die erste Bedingung, die üblicherweise beim ersten OnTrade() Aufruf ausgelöst wird. Die nun folgende, zweite Bedingung wird beim zweiten OnTrade() Aufruf erfüllt, dann wenn die Order ausgeführt wird, in die History eingetragen ist und sich die Position öffnen sollte.

Fehlt die Order in der Liste, dann steht sie bereits in der History und muss definitiv dort vorhanden sein! Daher fragen wir mit Hilfe der HistoryOrderGetInteger() Funktion die Order-History ab, um den Orde-Status zu sehen. Wir können die History-Daten einer bestimmten Order nur einsehen, wenn wir ihr Ticket haben. Und dazu wurde in der ersten Bedingung das Ticket der ankommenden Order in der LastOrderTicket Variable gespeichert.

Wir erhalten also den Order-Statuts, wenn wir das Order-Ticket als den ersten Parameter für HistoryOrderGetInteger() angeben, und die Art der gewünschten Eigenschaft als zweiten. Nachdem wir den Order-Status abgefragt haben, erhalten wir den Fehlercode und schreiben diesen ins Logbuch. Dies ist wichtig, falls Ihr Order, mit dem wir arbeiten müssen, noch nicht in der History steht, und wir auf sie zugreifen (die Erfahrung zeigt, dass dies möglich ist und oft vorkommt. Zu Beginn dieses Beitrags habe ich das bereits ausführlich geschildert.)

Sollte ein Fehler auftreten, hält die Bearbeitung an, da es keine zu bearbeitenden Daten gibt und keine der folgenden Bedingungen erfüllt ist. War der HistoryOrderGetInteger() Aufruf jedoch erfolgreich und der Order-Status lautet "Order ist komplett ausgeführt",

// If order is fully executed
if (state == ORDER_STATE_FILLED)

erhält man eine andere Benachrichtigung:

// Then analyze the last deal
// --
  Alert(LastOrderTicket, "Order executed, going to deal");

Jetzt können wir also zur Bearbeitung des Abschlusses gehen, der durch diese Order in Gang gesetzt wurde. Zunächst müssen wir die Richtung des Abschlusses herausfinden (DEAL_ENTRY Eigenschaft). Mit Richtung ist nicht Kaufen oder Verkaufen gemeint, sondern in den Markt, aus dem Markt, Umkehren oder Angabe des Statusberichts. Mit Hilfe der DEAL_ENTRY Eigenschaft finden wir heraus, ob die Order auf Position eröffnen, Position abschließen oder auf Umkehren eingestellt ist. 

Zur Analyse des Abschlusses und seiner Ergebnisse, fragen wir Hilfe der folgenden Konstruktion ebenfalls die History ab:

switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
{
  ...
}

Das funktioniert genauso wie bei den Orders:

HistoryDealsTotal() liefert die Gesamtzahl der Abschlüsse. Um die Zahl des jüngsten Abschlusses zu erhalten, ziehen wir vom HistoryDealsTotal()-Wert 1 ab. Die sich daraus ergebende Zahl des Abschlusses wird an die HistoryDealGetTicket() Funktion übertragen, die ihrerseits das Ticket des gewählten Abschlusses an die HistoryDealGetInteger() Funktion übergibt. Und HistoryDealGetInteger() per festgelegtem Ticket und Art der Eigenschaft liefert die Richtung des Abschlusses.

Betrachten wir uns nun die Richtung in den Markt genauer. Die anderen Richtungen werden nur kurz angesprochen, das sie fast genauso bearbeitet werden:

Der Wert des Ausdrucks, den uns HistoryDealGetInteger() geliefert hat, wird mit den Werten der Case-Blocks verglichen, bis eine Übereinstimmung gefunden wird. Angenommen, wir gehen in den Markt, eröffnen also die Verkaufs-Order, wird der erste Block ausgeführt:

// Entering the market
case DEAL_ENTRY_IN:

Am Anfang des Blocks werden Sie über die Erzeugung des Abschlusses informiert. Zugleich stellt diese Nachricht auch sicher, dass alles in Ordnung war und der Abschluss bearbeitet wird.

Nach der Benachrichtigung kommt ein weiterer Switch-Block, der die Art des Abschlusses analysiert:

   switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
   {
      case 0:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
      break;
      
      case 1:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         
      break;
      
      default:
         Alert("Unprocessed code of type: ",
               HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
      break;
   }

Informationen zum Abschluss stehen in der History - so wie vorhin auch, nur ohne festgelegte Eigenschaft. Sie müssen jetzt den DEAL_TYPE angeben, um zu wissen, ob ein Kauf- oder Verkauf-Abschluss gemacht wird. Ich gehe hier nur auf Kauf und Verkauf ein. Es gibt aber noch vier weitere. Doch diese vier weiteren Abschlusstypen sind weniger häufig, sodass für sie anstelle von vier Case-Blocks nur ein Standard-Block verwendet wird. Es erscheint ein Alert() mit dem Typen-Code.

Wie Sie vielleicht bereits im Code festgestellt haben, wird nicht nur die Eröffnung von Kauf- und Verkaufs-Positionen verarbeitet, sondern auch ihre Zuwächse. Um festzustellen, wann die Position einen Zuwachs erfahren hat und wann sie eröffnet wurde, müssen Sie das Volumen des ausgeführten Abschlusses und die Position, die das Ergebnis dieses Abschlusses wurden, miteinander vergleichen. Ist das Volumen der Position gleich dem Volumen des ausgeführten Abschlusses, ist diese Position eröffnet worden. Sind Volumen der Position und des Abschlusses verschieden, hat diese Position einen Zuwachs erfahren. Dies gilt sowohl für Kauf- (im Case '0' Block) als auch Verkauf-Positionen (im Case '1' Block). Der letzte Block ist eine Standardeinstellung, die alle Situationen jenseits von Kauf und Verkauf bearbeitet. Die gesamte Bearbeitung besteht aus einer Meldung über den Typ-Code, der von der HistoryDealGetInteger() Funktion geliefert wurde.

Zum Schluss betrachten wir uns den letzten Schritt bei der Arbeit mit Positionen: die Bearbeitung von Veränderungen in den Stop Loss und Take Profit Werten. Um herauszufinden, welche Positions-Parameter geändert wurden, müssen wir den aktuellen und vorherigen Parameter-Status einer Position überprüfen. Die aktuellen Werte der Positions-Parameter erhält man immer mit Hilfe der Service-Funktionen, doch sollten die zurückliegenden Werte gespeichert werden.

Dazu schreiben wir eine spezielle Funktion, die die Positions-Parameter im Struktur-Array ablegt:

void GetPosition(_position &Array[])
  {
   int _GetLastError=0,_PositionsTotal=PositionsTotal();

   int temp_value=(int)MathMax(_PositionsTotal,1);
   ArrayResize(Array, temp_value);

   _ExpertPositionsTotal=0;
   for(int z=_PositionsTotal-1; z>=0; z--)
     {
      if(!PositionSelect(PositionGetSymbol(z)))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect() - Error #",_GetLastError);
         continue;
        }
      else
        {
            // If the position is found, then put its info to the array
            Array[z].type         = PositionGetInteger(POSITION_TYPE);
            Array[z].time         = PositionGetInteger(POSITION_TIME);
            Array[z].magic        = PositionGetInteger(POSITION_MAGIC);
            Array[z].volume       = PositionGetDouble(POSITION_VOLUME);
            Array[z].priceopen    = PositionGetDouble(POSITION_PRICE_OPEN);
            Array[z].sl           = PositionGetDouble(POSITION_SL);
            Array[z].tp           = PositionGetDouble(POSITION_TP);
            Array[z].pricecurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
            Array[z].comission    = PositionGetDouble(POSITION_COMMISSION);
            Array[z].swap         = PositionGetDouble(POSITION_SWAP);
            Array[z].profit       = PositionGetDouble(POSITION_PROFIT);
            Array[z].symbol       = PositionGetString(POSITION_SYMBOL);
            Array[z].comment      = PositionGetString(POSITION_COMMENT);
        _ExpertPositionsTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertPositionsTotal,1);
   ArrayResize(Array,temp_value);
  }

Um diese Funktion verwenden zu können, müssen wir den folgenden Code in den Block der globalen Variablen-Deklarierung einfügen:

/*
 *
 * Structure that stores information about positions
 *
 */
struct _position
{

long     type,          // Position type
         magic;         // Magic number for position
datetime time;          // Time of position opening

double   volume,        // Position volume
         priceopen,     // Position price
         sl,            // Stop Loss level for opened position
         tp,            // Take Profit level for opened position
         pricecurrent,  // Symbol current price
         comission,     // Commission
         swap,          // Accumulated swap
         profit;        // Current profit

string   symbol,        // Symbol, by which the position has been opened
         comment;       // Comment to position
};

int _ExpertPositionsTotal = 0;

_position PositionList[],     // Array that stores info about position
          PrevPositionList[];

Früher war der Prototyp der GetPosition() Funktion in den www.mql4.com Beiträgen zu finden, doch habe ich ihn jetzt dort nicht mehr gefunden und kann die Quelle daher nicht angeben. Die Arbeit dieser Funktion erkläre ich deswegen nicht im Einzelnen. Der Punkt ist, dass, sobald ein Parameter per Verweis ein Array des Typs _position überträgt (eine Struktur mit Feldern, die den Positionen-Feldern entsprechen), alle Informationen über die aktuell offenen Positionen und Werte ihrer Parameter an diesen übertragen werden.

Um die Veränderungen in den Positions-Parametern bequem nachverfolgen zu können, erzeugen wir zwei Arrays des Typs _position: PositionList[] (aktueller Status der Positionen) und PrevPositionList[] (voriger Status der Positionen).

Um mit der Arbeit an Positionen beginnen zu können, müssen wir den nächsten Aufruf in OnInit() und am Ende von OnTrade() hinzufügen:

GetPosition(PrevPositionList);

Und ebenfalls auch zu Anfang von Ontrade():

GetPosition(PositionList);

In den uns nun zur Verfügung stehenden PositionList[] und PrevPositionList[] Arrays befindet sich Information über Positionen im aktuellen bzw. vorhigen OnTrade() Aufruf.

Sehen wir uns nun den tatsächlichen Code zum Nachverfolgen von Änderungen in SL und TP an:

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{
   string _alerts = "";
   bool modify = false;
     
   for (int i=0;i<_ExpertPositionsTotal;i++)
   {
      if (PrevPositionList[i].sl != PositionList[i].sl)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Stop Loss changed from "+ PrevPositionList[i].sl +" to "+ PositionList[i].sl +"\n";
         modify = true;
      }
      if (PrevPositionList[i].tp != PositionList[i].tp)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Take Profit changed from "+ PrevPositionList[i].tp +" to "+ PositionList[i].tp +"\n";
         modify = true;
      }
      
   }
   if (modify == true)
   {
      Alert(_alerts);
      modify = false;
   }
}

Dieser Code ist gar nicht so umfangreich, das liegt allein an der immensen Vorarbeit. Ein Blick auf die Einzelheiten:

Alles fängt mit der Bedingung an:

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))

Wir erkennen, dass weder Order noch Positionen platziert oder gelöscht wurden. Wird die Bedingung erfüllt, haben sich höchstwahrscheinlich die Parameter einiger Positionen oder Orders verändert

Zu Beginn der Funktion sind zwei Variablen deklariert:

  • _alerts - speichert alle Meldungen über Veränderungen.
  • modify - zeigt Ihnen Meldungen über tatsächlich stattgefundene Veränderungen an.

Als nächsten Schritt in der Schleife prüfen wir bei jeder Position die Übereinstimmung der Werte Take Profits und Stop Losses beim vorigen und aktuellen OnTrade() Aufruf. Jede Information über alle Unstimmigkeiten wird in der _alerts Variable gespeichert und später dann in der Alert() Funktion angezeigt. Die Bearbeitung von Veränderungen offener Order erfolgt übrigens ganz genauso.

Das ist erst einmal alles in Bezug auf Positionen. Sehen wir uns nun die Platzierung von offenen Orders an.

4. Mit Orders arbeiten

Beginnen wir mit dem Ereignis "Platzierung offener Orders".

Sobald eine neue offene Order erscheint, wird das Handels-Ereignis nur einmal erzeugt. Das genügt jedoch für die Bearbeitung! Setzen Sie den Code, der für offene Orders funktioniert, in den Textkörper des Operators,

if (OrdersPrev < OrdersTotal())

und Sie erhalten folgendes:

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {
      switch(OrderGetInteger(ORDER_TYPE))
      {
         case 2:
            Alert("Pending order Buy Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 3:
            Alert("Pending order Sell Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 4:
            Alert("Pending order Buy Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 5:
            Alert("Pending order Sell Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 6:
            Alert("Pending order Buy Stop Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
                 
         case 7:
            Alert("Pending order Sell Stop Limit  #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;         
      }
   }
}

Der Code, der für offene Order funktioniert, beginnt also folgendermaßen:

   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {

Als Erstes wir der Order-Status geprüft. die Order muss den Status ORDER_STATE_PLACED haben, sollte also akzeptiert sein. Ist diese Bedingung erfüllt, druckt als nächster Schritt der Switch Operator eine Meldung je nach Ordertyp aus.

Als nächstes arbeiten wir mit den Ereignissen, die eintreten, wenn Order modifiziert werden. Die Order-Modifizierung ähnelt sehr der von Positionen. Und ganz ähnlich wird auch die Struktur erzeugt, die die Order-Eigenschaften speichert: 

/*
 *
 * Structure that stores information about orders
 *
 */
struct _orders
{

datetime time_setup,       // Time of order placement
         time_expiration,  // Time of order expiration
         time_done;        // Time of order execution or cancellation
         
long     type,             // Order type
         state,            // Order state
         type_filling,     // Type of execution by remainder
         type_time,        // Order lifetime
         ticket;           // Order ticket
         
long     magic,            // Id of Expert Advisor, that placed an order
                           // (intended to ensure that each Expert 
                           // must place it's own unique number)
                           
         position_id;      // Position id, that is placed on order,
                           // when it is executed. Each executed order invokes a
                           // deal, that opens new or changes existing 
                           // position. Id of that position is placed on 
                           // executed order in this moment.
                           
double volume_initial,     // Initial volume on order placement
       volume_current,     // Unfilled volume
       price_open,         // Price, specified in the order
       sl,                 // Stop Loss level
       tp,                 // Take Profit level
       price_current,      // Current price by order symbol
       price_stoplimit;    // Price of placing Limit order when StopLimit order is triggered
       
string symbol,             // Symbol, by which the order has been placed
       comment;            // Comment
                           
};

int _ExpertOrdersTotal = 0;

_orders OrderList[],       // Arrays that store info about orders
        PrevOrderList[];

Jedes Feld der Struktur entspricht einer Eigenschaft der Order. Nach Deklarierung der Struktur, werden die Variable des Int-Typs und zwei Arrays des _orders-Typs deklariert. Die _ExpertOrdersTotal Variable speichert die Gesamtzahl der Orders; die OrderList[] und PrevOrderList[] Arrays speichern Order-Informationen im aktuellen bzw. vorigen OnTrade() Aufruf.

Die Funktion selbst sieht so aus:

void GetOrders(_orders &OrdersList[])
  {
   
   int _GetLastError=0,_OrdersTotal=OrdersTotal();

   int temp_value=(int)MathMax(_OrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

   _ExpertOrdersTotal=0;
   for(int z=_OrdersTotal-1; z>=0; z--)
     {
      if(!OrderGetTicket(z))
        {
         _GetLastError=GetLastError();
         Print("GetOrders() - Error #",_GetLastError);
         continue;
        }
      else
        {
        OrdersList[z].ticket          = OrderGetTicket(z);
        OrdersList[z].time_setup      = OrderGetInteger(ORDER_TIME_SETUP);
        OrdersList[z].time_expiration = OrderGetInteger(ORDER_TIME_EXPIRATION);
        OrdersList[z].time_done       = OrderGetInteger(ORDER_TIME_DONE);
        OrdersList[z].type            = OrderGetInteger(ORDER_TYPE);
        
        OrdersList[z].state           = OrderGetInteger(ORDER_STATE);
        OrdersList[z].type_filling    = OrderGetInteger(ORDER_TYPE_FILLING);
        OrdersList[z].type_time       = OrderGetInteger(ORDER_TYPE_TIME);
        OrdersList[z].magic           = OrderGetInteger(ORDER_MAGIC);
        OrdersList[z].position_id     = OrderGetInteger(ORDER_POSITION_ID);
        
        OrdersList[z].volume_initial  = OrderGetDouble(ORDER_VOLUME_INITIAL);
        OrdersList[z].volume_current  = OrderGetDouble(ORDER_VOLUME_CURRENT);
        OrdersList[z].price_open      = OrderGetDouble(ORDER_PRICE_OPEN);
        OrdersList[z].sl              = OrderGetDouble(ORDER_SL);
        OrdersList[z].tp              = OrderGetDouble(ORDER_TP);
        OrdersList[z].price_current   = OrderGetDouble(ORDER_PRICE_CURRENT);
        OrdersList[z].price_stoplimit = OrderGetDouble(ORDER_PRICE_STOPLIMIT);
        
        OrdersList[z].symbol          = OrderGetString(ORDER_SYMBOL);
        OrdersList[z].comment         = OrderGetString(ORDER_COMMENT);
        
        _ExpertOrdersTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertOrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

  }

Ähnlich wie die GetPosition() Funktion, liest sie Informationen über die Eigenschaften jeder platzierten Order und stellt sie in ein Array, das ihr als Eingabe-Parameter übergeben wurde. Der Funktionscode muss am Ende Ihres Expert und seiner Aufrufe stehen und zwar so:

GetOrders(PrevOrderList);

In OnInit() und am Ende von OnTrade() platziert.

GetOrders(OrderList);

Zu Beginn von OnTrade() platziert.

Betrachten wir uns noch kurz den Code, der die Order-Modifizierung bearbeitet. Er ist eine Schleife und vervollständigt den Code der Position-Modifizierung:

   for (int i = 0;i<_ExpertOrdersTotal;i++)
   {
      if (PrevOrderList[i].sl != OrderList[i].sl)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Stop Loss from "+ PrevOrderList[i].sl +" to "+ OrderList[i].sl +"\n";
         modify = true;
      }
      if (PrevOrderList[i].tp != OrderList[i].tp)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Take Profit from "+ PrevOrderList[i].tp +" to "+ OrderList[i].tp +"\n";
         modify = true;
      }
   }

Die Schleife bearbeitet alle Orders und vergleicht die Werte von Stop Losses und Take Profits der aktuellen und vorigen OnTrade() Aufrufe. Werden Unterschiede entdeckt, werden diese in der _alerts Variable gespeichert. Ist die Schleife abgeschlossen, werden die Unterschiede von der Alert() Funktion angezeigt.

Der Code wird in den Textkörper des Operators eingefügt,

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{

und zwar direkt nach der Schleife, die mit den Positionen arbeitet.

Damit ist die Bearbeitung von Handels-Ereignissen allerdings noch nicht abgeschlossen. Dieser Beitrag erläutert nur die Hauptprinzipien der Arbeit mit einem Handels Ereignis. Insgesamt sind die, von dieser Methode angebotenen Möglichkeiten, ziemlich umfangreich und würden den Umfang dieses Beitrags sprengen.

Fazit

Die Fähigkeit, mit Handels-Ereignissen (als Teil der MQL5-Sprache) arbeiten zu können, ist ein potenziell leistungsfähiges Tool, mit dem man nicht nur relativ schnell Algorithmen zur Order-Verifizierung implementieren und Handelsberichte erstellen kann, sondern zugleich auch die Kosten der Systemressourcen senken und das Volumen des Quellcodes verringern kann. Zweifellos ein immenser Vorteil für jeden Entwickler.

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

Beigefügte Dateien |
tradecontrol_en.mq5 (20.25 KB)
Benutzerdefinierte Indikatoren in MQL5 für Anfänger Benutzerdefinierte Indikatoren in MQL5 für Anfänger

Als Anfänger erscheint einem jedes neue Thema immer kompliziert und schwer. Bekannte Themen sind uns klar und stellen kein Problem mehr dar. Dabei vergessen wir jedoch oft, dass jeder immer mal wieder komplett bei Null anfangen muss, das gilt selbst für unsere Muttersprache. Und nicht anders ist mit der MQL5 Programmiersprache, die eine große Auswahl bietet, wie man seine ganz persönlichen Handelsstrategien entwickeln kann - man kann sie ausgehend von grundlegenden Konzepten und einfachsten Beispielen erlernen. Dieser Beitrag erklärt die Interaktion eines technischen Indikators mit dem MetaTrader 5 Client-Terminal am Beispiel des einfachen, benutzerdefinierten SMA-Indikators (einfacher gleitender Mittelwert).

Verwendung von Objektzeigern in MQL5 Verwendung von Objektzeigern in MQL5

In MQL5 werden alle Objekte standardmäßig per Verweis übertragen, doch gibt es eine Möglichkeit, Objektzeiger zu verwenden. Dazu muss jedoch eine Prüfung des Zeigers durchgeführt werden, da das Objekt u.U. nicht initialisiert ist. In diesem Fall wird das MQL5-Programm mit schwerwiegendem Fehler beendet und entladen. Die automatisch erzeugten Objekte verursachen diesen Fehler nicht, sind in diesem Sinn also recht sicher. In diesem Beitrag versuchen wir den Unterschied zwischen Objektverweis und Objektzeiger zu erklären und werfen einen Blick darauf, wie man sichere Codes schreibt, die diese Zeiger verwenden.

Wie man in MQL5 Indikatoren aufruft Wie man in MQL5 Indikatoren aufruft

Mit der neuen, zur Verfügung stehenden Version der MQL Programmiersprache hat sich nicht nur der Ansatz im Umgang mit Indikatoren verändert, sondern es haben sich auch neue Möglichkeiten ihrer Erzeugung ergeben. Zudem hat man jetzt noch mehr Flexibilität bei der Arbeit mit den Indikator-Buffern - man kann jetzt die gewünschte Richtung der Indizierung angeben und genau so viele Indikatorwerte bekommen, wie man möchte. In diesem Beitrag werden die einfachen Methoden zum Aufruf von Indikatoren und Abruf von Daten aus dem Indikator-Buffer erklärt.

Unterschiedliche Zeichnungsstile in MQL5 Unterschiedliche Zeichnungsstile in MQL5

In MQL4 gibt es 6 - in MQL5 18 Zeichnungsstile. Aus diesem Grund ist ein Beitrag zur Präsentation der Zeichnungsstile von MQL5 durchaus angebracht. Im Folgenden werden daher die Zeichnungsstile in MQL5 im Einzelnen betrachtet. Darüber hinaus erzeugen wir einen Indikator zur Demonstration, wie man diese Zeichnungsstile nutzt und die graphische Darstellung (Plot) verfeinert.