MQL5-Kochbuch: Umgang mit typischen Chartereignissen

Denis Kirichenko | 27 Juni, 2016

Einleitung

In diesem Artikel würde ich sehr gerne die auf meiner praktische Erfahrungen basierenden Möglichkeiten beschreiben, die die Benutzung von OnChartEvent() samt typischer, seitens verschiedener MQL5-Entwickler vordefinierter Standardereignisse bietet. Beispiele zur Verwendung dieses Steuerungsprogramms finden sich bereits in verschiedenen MQL5-Artikeln sowie der Code-Datenbank.

All diesen Dokumenten gegenüberstehend habe ich allerdings nun die Absicht, dieses Instrument im Hinblick auf ereignisorientiertes Programmieren (EOP) zu analysieren. Ich glaube, dass sich dieses Steuerungsprogramm erfolgreich für vollständig automatisierte sowie halbautomatische Handelssysteme verwenden lässt.


1. Das „ChartEvent“-Ereignis

Sehen wir uns zunächst an, um was für einen Typ von Ereignis es sich handelt.

Entsprechend unserer Daten und Dokumente, tritt das Ereignis ChartEreignis immer dann auf, wenn man mit einem Chart interagiert - vor allem dann, wenn:

  • eine Taste auf der Tastatur gedrückt wird, solange ein Chartfenster angewählt ist.
  • ein grafisches Objekt erstellt wird.
  • ein grafisches Objekt gelöscht wird.
  • auf ein grafisches Objekt geklickt wird.
  • ein grafisches Objekt mit der Maus bewegt wird.
  • man damit aufhört, den Text eines Textfelds eines grafischen LabelEdit-Objekts zu editieren.

Dieses Ereignis erlaubt es uns also, mit einem Chart auf verschiedene Weise zu interagieren. Noch mehr als das - solch eine Interaktion könnte durchaus das Resultat manueller Trading-Vorgänge also auch algorithmischer Operationen (automatisiertes Trading) sein.

MQL5-Entwickler klassifizieren das Ereignis ChartEreignis entsprechend der Typen, die wir in der Aufzählung ENUM_CHART_EVENT vorfinden.

Hierbei sei angemerkt, das diese Liste eine ganze Reihe benutzerfreundlicher Ereignisse enthält, die als eine versteckte Notreserve für Programmierer fungieren. Die Anzahl der IDs für benutzerdefinierte Ereignisse von Entwicklern beträgt inzwischen 65535.

Um mit angepassten Ereignissen zu arbeiten, existiert eine spezielle Generatorfunktion - EventChartCustom() - die Programmierer unterstützen soll. Allerdings deckt dieser Artikel nicht die Gefilde benutzerspezifscher Ereignisse ab.


2. ChartEvent-Steuerungsprogramm & Generator

Sämtliche Prozesse des Ereignisses ChartEvent werden durch eine spezielle Funktion verarbeitet: OnChartEvent(). Dies geschieht in Übereinstimmung mit dem Konzept der MQL5-Sprache, bei der beispielsweise das Ereignis Trade durch die Funktion OnTrade() und das Ereignis Init durch die Funktion OnInit() gehandhabt wird.

Die Funktion OnChartEvent() weist folgende Signatur auf:

void OnChartEvent(const int id,         // event identifier  
                  const long& lparam,   // parameter of the event of type long
                  const double& dparam, // parameter of the event of type double
                  const string& sparam  // parameter of the event of type string
                  )

Sämtliche Eingabeparameter sind konstant und sie senden nützliche Informationen aus, sobald das Steuerungsprogramm aufgerufen wird.

Daher kann der Wert des Parameters id darauf hinweisen, welches spezifische Ereignis das Steuerungsprogramm aufgerufen hat. Andere können wiederum Werte der Typen long, double oder string aufweisen. Auf diese Weise können zusätzliche Informationen eines Ereignisses erhalten werden.

Wir werden uns später ein Beispiel ansehen, bei dem uns die Werte eines spezifischen Parameters als Ausgangspunkt einer Analyse dienen sollen.

Der modifizierte Teil des Ereignisses ChartEvent, der von einem Programmierer zu implementieren ist, ist eng mit der Funktion EventChartCustom() verbunden: Diese Funktion generiert dieses Ereignis. Die Signatur der Funktion sieht wie folgt aus:

bool  EventChartCustom(long    chart_id,        // receiving chart identifier
                       ushort  custom_event_id, // event identifier
                       long    lparam,          // the long parameter
                       double  dparam,          // the double parameter
                       string  sparam           // the string parameter
                       )

Tatsächlich ist die Generatorfunktion in der Lage, ein Ereignis zu kreieren und es an jeden beliebigen Chart, inklusive dem aktuellen, ungeachtet seiner Eingabeparameter zu senden. Zu Letztgenanntem zählen die Typen: ushort, long, double und string.

Die Funktionen OnChartEvent() und EventChartCustom() bilden zusammen ein mächtiges Werkzeug, das ein sehr gutes Beispiel für die Vorteile des objektorientierten Programmierens darstellt.


3. Templates für eine standardisierte Ereignisverwaltung

Ich werde nun auf die unterschiedlichen Typen von Chartereignissen eingehen, indem ich jeweils ein kurzes Beispiel anführen werde. Jedes Ereignis wird dabei seine ganz eigene EventProcessor.mq5-Version als auch seinen eigenen Code erhalten, der die Verarbeitung eines Chartereignisses enthält. Es gibt insgesamt 10 MQL5-Ereignistypen.

Für die folgenden drei von ihnen müssen wir dabei einen Chart präparieren: Maus-, Generierung-eines-Grafikobjekts- und Entfernung-eines-Grafikobjekts-Ereignisse. Das geschieht mithilfe der Funktion ChartSetInteger(). Diese erlaubt es einem Chart, auf spezifische Ereignisse zu reagieren.

Ein typischer Block zur Verarbeitung von Chartereignissen könnte wie folgt aussehen:

void OnChartEvent(const int id, 
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   string comment="Last event: ";

//--- select event on chart
   switch(id)
     {
      //--- 1
      case CHARTEVENT_KEYDOWN:
        {
         comment+="1) keystroke";
         break;
        }
      //--- 2
      case CHARTEVENT_MOUSE_MOVE:
        {
         comment+="2) mouse";
         break;
        }
      //--- 3
      case CHARTEVENT_OBJECT_CREATE:
        {
         comment+="3) create graphical object";
         break;
        }
      //--- 4
      case CHARTEVENT_OBJECT_CHANGE:
        {
         comment+="4) change object properties via properties dialog";
         break;
        }
      //--- 5
      case CHARTEVENT_OBJECT_DELETE:
        {
         comment+="5) delete graphical object";
         break;
        }
      //--- 6
      case CHARTEVENT_CLICK:
        {
         comment+="6) mouse click on chart";
         break;
        }
      //--- 7
      case CHARTEVENT_OBJECT_CLICK:
        {
         comment+="7) mouse click on graphical object";
         break;
        }
      //--- 8
      case CHARTEVENT_OBJECT_DRAG:
        {
         comment+="8) move graphical object with mouse";
         break;
        }
      //--- 9
      case CHARTEVENT_OBJECT_ENDEDIT:
        {
         comment+="9) finish editing text";
         break;
        }
      //--- 10
      case CHARTEVENT_CHART_CHANGE:
        {
         comment+="10) modify chart";
         break;
        }
     }
//---
   Comment(comment);
  }

In jedem Fall habe ich einen String hinzugefügt, der das ausgewählte Ereignis beschreiben soll. Als Folge hiervon können wir in der Kommentarzeile das letzte Ereignis sehen, dass sich im Chart ereignete. Wenn Sie das Template ausführen und verschiedene Modifikationen am Chart vornehmen, werden Sie feststellen, dass die Kommentarzeile unterschiedliche Aufzeichnungen enthält.

Offensichtlich hat ein EA, der lediglich den Typ von Ereignissen bestimmt nur einen geringen Nutzen. Wir müssen also seine Möglichkeiten erweitern.


4. Beispiele für eine standardisierte Ereignisverwaltung


4.1 Tastaturereignisse

Kommen wir zu unserem ersten Fall und lassen wir unseren EA auf unsere Tastaturtasten reagieren. Er soll etwas kaufen, wenn wir „Pfeil nach oben“, und etwas verkaufen, wenn wir „Pfeil nach unten“ betätigen. In diesem Fall sieht das wie folgt aus:

//--- 1
      case CHARTEVENT_KEYDOWN:
        {
         //--- "up" arrow
         if(lparam==38)
            TryToBuy();

         //--- "down" arrow
         else if(lparam==40)
            TryToSell();

         comment+="1) keystroke";
         //---         
         break;
        }

Bitte sehen Sie sich den angehängten EA-Quellcode an, um sich mit den Details der Implementierung von TryToBuy() und TryToSell() vertraut zu machen. Handelsparameter (Lot-Größe, Stop Loss, Take Profit, usw.) werden dabei als Eingabevariablen spezifiziert (InpLot, InpStopLoss, InpTakeProfit, usw.). Ferner sollte erwähnt werden, dass der Parameter lparam den Code eines gedrückten Buttons verarbeitet.



Die aktualisierte Version unseres EAs wollen wir nun EventProcessor1.mq5 nennen.


4.2 Mausereignisse

Dieser Ereignistyp wird nur dann verarbeitet, falls für einen Chart die Eigenschaft CHART_EVENT_MOUSE_MOVE spezifiziert wurde. Zu diesem Zweck weist der Initialisierungsblock eines EAs folgende Strings auf:

//--- mouse move
bool is_mouse=false;
if(InpIsEventMouseMove)
   is_mouse=true;
ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,is_mouse);

Es gilt, darauf zu verweisen, dass die Verwendung einer Maus - wie könnte es anders sein - sehr oft Mausereignissen entstehen lassen wird. Daher könnte es sich anbieten, diesen Ereignistyp zu deaktivieren. Die beiden Parameter param und dparam des Steuerungsprogramms geben Meldungen betreffend die Koordinaten X und Y aus.

Wir werden nun ein erfundenes Beispiel diskutieren. Lassen Sie uns annehmen, es gäbe eine Nullbalkenverschiebung aus Richtung der rechten Grenze. Wenn Sie die Maus über den Teil des Bildschirms bewegen, der sich rechts von der Verschiebung befindet, erscheint ein Fenster, das Ihnen vorschlägt, zu kaufen oder entsprechend zu verkaufen.

Um dies zu bewerkstelligen, müssen wir zunächst die Verschiebung bestimmen. Wir führen daher eine Eingabevariable ein, um die prozentuale Größe einer Nullbalkenverschiebung aus Richtung der rechen Grenze zu bestimmen (InpChartShiftSize).

Abb.1 Fenster einer Handelsoperation

Abb.1 Fenster einer Handelsoperation

Wir werden nun die beiden Funktionen verwenden, die die Verschiebung ermöglichen und die deren Größe bestimmen: ChartShiftSet() und ChartShiftSizeSet(). Dann wollen wir bestimmen, ob die X-Koordinate des Cursors vorher auf der linken Seite der Grenze war und ob sie nach rechts gewandert ist. Falls ja, wird ein entsprechendes Kaufen/Verkaufen-Fenster erscheinen (Abb. 1).

Der Code, um die Zielvorgabe zu implementieren, ist folgender:

//--- 2
      case CHARTEVENT_MOUSE_MOVE:
        {
         comment+="2) mouse";
         //--- if a mouse event is processed
         if(InpIsEventMouseMove)
           {
            long static last_mouse_x;

            //--- enable shift
            if(ChartShiftSet(true))
               //--- set shift size 
               if(ChartShiftSizeSet(InpChartShiftSize))
                 {
                  //--- chart width
                  int chart_width=ChartWidthInPixels();

                  //--- calculate X coordinate of shift border
                  int chart_shift_x=(int)(chart_width-chart_width*InpChartShiftSize/100.);

                  //--- border crossing condition
                  if(lparam>chart_shift_x && last_mouse_x<chart_shift_x)
                    {
                     int res=MessageBox("Yes: buy / No: sell","Trade operation",MB_YESNO);
                     //--- buy
                     if(res==IDYES)
                        TryToBuy();
                     //--- sell
                     else if(res==IDNO)
                        TryToSell();
                    }

                  //--- store mouse X coordinate
                  last_mouse_x=lparam;
                 }
           }

         //---
         break;
        }

Das Kaufen und Verkaufen übernimmt die zuvor generierte Handelsfunktion. Die aktualisierte Version des EAs soll nun auf den Namen EventProcessor2.mq5 hören.


4.3 Generierung-eines-Grafikobjekts-Ereignis

Dieser Ereignistyp tritt dann auf, wenn ein Objekt auf einem Chart generiert wird. Ähnlich einem Mausereignis benötigt auch dieser Typ eine Erlaubnis, um mit der Eigenschaft CHART_EVENT_OBJECT_CREATE zu interagieren. Es muss im Initialisierungsblock nur ein einziges Mal spezifiziert werden, wenn wir auf das Erscheinen eines neuen grafischen Objektes reagieren wollen.

//--- object create
bool is_obj_create=false;
if(InpIsEventObjectCreate)
   is_obj_create=true;
ChartSetInteger(0,CHART_EVENT_OBJECT_CREATE,is_obj_create);

Dabei wird nur ein Parameter des Steuerungsprogramms Informationen erhalten: und zwar der Stringparameter sparam, der den Namen generierter, grafischer Objekte enthält. Wir können also Objekte durch bloße Kenntnis ihres Namens aufspüren und dann entscheiden, was mit ihnen passieren soll.

Und hier ein einfaches Beispiel: Wir werden eine horizontale Linie auf dem Chart zeichnen und unserem Roboter die Anweisung geben, diese beim maximalen Kurs aller Balken zu platzieren und daraufhin zwei zusätzliche Linien zu zeichnen. Die untere Linie wird beim minimalen Kurs und die dritte wird genau zwischen die erste und die zweite gezeichnet.

Und hier der Code zur Implementierung dieser Aufgabe:

//--- 3
      case CHARTEVENT_OBJECT_CREATE:
        {
         comment+="3) create graphical object";
         //--- if graphical object creation event is processed
         if(InpIsEventObjectCreate)
           {
            //--- capture creation of horizontal line
            int all_hor_lines=ObjectsTotal(0,0,OBJ_HLINE);

            //--- if this is the only line
            if(all_hor_lines==1)
              {
               string hor_line_name1=sparam;

               //--- calculate levels
               int visible_bars_num=ChartVisibleBars();

               //--- arrays for high and low prices
               double highs[],lows[];
               //---
               int copied=CopyHigh(_Symbol,_Period,0,visible_bars_num-1,highs);
               if(copied!=visible_bars_num-1)
                 {
                  Print("Failed to copy highs!");
                  return;
                 }
               copied=CopyLow(_Symbol,_Period,0,visible_bars_num-1,lows);
               if(copied!=visible_bars_num-1)
                 {
                  Print("Failed to copy lows!");
                  return;
                 }
               //--- high and low prices
               double ch_high_pr,ch_low_pr,ch_mid_pr;
               //---
               ch_high_pr=NormalizeDouble(highs[ArrayMaximum(highs)],_Digits);
               ch_low_pr=NormalizeDouble(lows[ArrayMinimum(lows)],_Digits);
               ch_mid_pr=NormalizeDouble((ch_high_pr+ch_low_pr)/2.,_Digits);

               //--- place created line on high
               if(ObjectFind(0,hor_line_name1)>-1)
                  if(!ObjectMove(0,hor_line_name1,0,0,ch_high_pr))
                    {
                     Print("Failed to move!");
                     return;
                    }
               //--- create line on low
               string hor_line_name2="Hor_line_min";
               //---
               if(!ObjectCreate(0,hor_line_name2,OBJ_HLINE,0,0,ch_low_pr))
                 {
                  Print("Failed to create the 2nd horizontal line!");
                  return;
                 }
               //--- create line between high and low 
               string hor_line_name3="Hor_line_mid";
               //---
               if(!ObjectCreate(0,hor_line_name3,OBJ_HLINE,0,0,ch_mid_pr))
                 {
                  Print("Failed to create the 3rd horizontal line!");
                  return;
                 }
              }
           }
         break;
        }

Unserer aktuellster EA trägt nun die Bezeichnung EventProcessor3.mq5.

Abb. 2 Resultate der Durchführung eines Generierung-eines-grafischen-Objekts-Ereignisses

Abb. 2 Resultate der Durchführung eines Generierung-eines-grafischen-Objekts-Ereignisses

Nachdem ich diese Prozedur abgeschlossen hatte, erhielt ich die folgende Darstellung (Abb. 2). Die integrierten Funktionen eines EAs erlauben es diesem also, auf die Generierung eines grafischen Objektes zu reagieren.


4.4 Veränderung-eines-Grafikobjekts-Ereignis

Dieser Ereignistyp ähnelt ein wenig dem vorherigen. Er wird ausgelöst, sobald eine Eigenschaft der Grafikobjekte durch einen Eigenschaftsdialog verändert wird. Dieses Tool erweist sich zum Beispiel immer dann als besonders nützlich, wenn es darum geht, die grafischen Eigenschaften von Objekten desselben Typs zu synchronisieren.

Stellen Sie sich bitte eine Anzahl derartiger Objekte auf einem Chart vor. Die meisten Trader haben jede Menge verschiedene Linien auf ihrem Chart. Diese Linien müssen für einige Zeit unsichtbar gemacht, ohne dabei gelöscht zu werden. Unsere Aufgabe wird es nun sein, eine Lösung für dieses Problem zu finden. Eine geänderte Linie kann dabei entfärbt werden. Gleiches trifft auch auf andere grafische Objekte zu. Der entsprechende Code könnte dann wie folgt aussehen:

//--- 4
      case CHARTEVENT_OBJECT_CHANGE:
        {
         comment+="4) change object properties via properties dialog";
         //---
         string curr_obj_name=sparam;
         //--- find the changed object
         if(ObjectFind(0,curr_obj_name)>-1)
           {
            //--- get object color
            color curr_obj_color=(color)ObjectGetInteger(0,curr_obj_name,OBJPROP_COLOR);
            //--- total number of objects on chart
            int all_other_objects=ObjectsTotal(0);
            //--- find other objects
            for(int obj_idx=0;obj_idx<all_other_objects;obj_idx++)
              {
               string other_obj_name=ObjectName(0,obj_idx);
               if(StringCompare(curr_obj_name,other_obj_name)!=0)
                  if(!ObjectSetInteger(0,other_obj_name,OBJPROP_COLOR,curr_obj_color))
                    {
                     Print("Failed to change the object color!");
                     return;
                    }
              }
            //--- redraw chart
            ChartRedraw();
           }
         //---
         break;

Nehmen wir an, dass ein Chart eine Reihe von Linien enthielte (Abb. 3).

Abb. 3 Mehrfarbige, dynamische Linien

Abb. 3 Mehrfarbige, dynamische Linien

Wenn wir versuchen, die Farbe einer dieses Linien mittels Eigenschaftsdialog zu verändern - oder präziser: sie zu entfärben (Abb. 4) - dann wird unser Chart keine sichtbaren Linien mehr enthalten. Allerdings werden wir dennoch sämtliche Grafikobjekte sehen können.

Abb. 4 Die Farbe einer Linie ändern

Abb. 4 Die Farbe einer Linie ändern

Die aktualisierte Version des EAs wird EventProcessor4.mq5 genannt.


4.5 Entfernung-eines-Grafikobjekts-Ereignis

Wie der Name dieses Ereignistyps bereits vermuten lässt, tritt er während des Löschens eines Chartobjekts auf. Es handelt sich dabei um das letzte Ereignis einer Gruppe, das einer direkten Autorisierung bedarf, damit die Ausführung fortgesetzt wird. Das geschieht mithilfe folgender Eigenschaft: CHART_EVENT_OBJECT_DELETE.

//--- object delete
   bool is_obj_delete=false;
   if(InpIsEventObjectDelete)
      is_obj_delete=true;
   ChartSetInteger(0,CHART_EVENT_OBJECT_DELETE,is_obj_delete);

Und hier ein anderes hypothetisches Beispiel. Auf dem Chart, der mit Ihrem EA verbunden ist, existiert ein Set an grafischen Objekten verschiedener Typen. Wir nehmen nun an, dass wir nur alle Objekte eines dieser Typen löschen wollen. Zum Beispiel sämtliche vertikale Linien (Abb. 5).

Abb. 5 Fünf vertikale und einige andere Linien

Abb. 5 Fünf vertikale und einige andere Linien

Wir müssen lediglich eine der vertikalen Linien entfernen, den Rest erledigt der EA für uns (Abb. 6).

Abb. 6 Verbleibende Linien

Abb. 6 Verbleibende Linien

Die folgenden Einträge werden im Verzeichnis „Experts“ erscheinen:

NS      0       10:31:17.937    EventProcessor5 (EURUSD.e,W1)   Vertical lines before removing: 4
MD      0       10:31:17.937    EventProcessor5 (EURUSD.e,W1)   Vertical lines removed from the chart: 4
QJ      0       10:31:18.078    EventProcessor5 (EURUSD.e,W1)   Vertical lines after removing: 0

An dieser Stelle gilt es noch einen wichtigen Aspekt zu erwähnen. Sobald ein Objekt erst einmal entfernt wurde, haben wir keinen Zugriff mehr auf dessen Eigenschaften. Falls wir also ein Interesse an dessen Daten haben sollten, müssen wir diese vor dessen Entfernung abrufen. Wenn wir also den Typ eines zu entfernenden Objekts herausfinden wollen, so müssen wir es vor dessen Entfernung speichern. Ich persönlich schlage MQL5-Entwicklern vor, per Terminal eine Historie eines Charts zu erstellen. Hierdurch können wir jederzeit auf entfernte Objekte verweisen.

Diese letzte Version des EA wollen wir EventProcessor5.mq5 nennen.


4.6 Mausklick-auf-Chart-Ereignis

Dieses Ereignis tritt ein, falls der Chart mit der linken Maustaste angeklickt wird. Ein Rechtsklick zeigt im Übrigen ein Kontextmenü an, wohingegen ein Klick mit der mittleren Taste, ein Fadenkreuz einblenden wird. Die beiden Parameter param und dparam des Steuerungsprogramms geben Meldungen betreffend die Koordinaten X und Y aus.

Die folgende, kleine Aufgabe soll uns als Beispiel dienen. Wir wollen, dass ein „Kaufen“-Pfeil an dem Punkt gezeichnet wird, an dem mit der Maus geklickt wird. Das Objekt „Pfeil“ besitzt übrigens nur einen Ankerpunkt. Daher bedarf es lediglich einer einzigen Transformation der beiden Koordinaten X und Y in die Werte Zeit und Preis des Ankers.

Und hier der Code für obiges Beispiel:

//--- 6
      case CHARTEVENT_CLICK:
        {
         comment+="6) mouse click on chart";
         //--- object counter 
         static uint sign_obj_cnt;
         string buy_sign_name="buy_sign_"+IntegerToString(sign_obj_cnt+1);
         //--- coordinates 
         int mouse_x=(int)lparam;
         int mouse_y=(int)dparam;
         //--- time and price
         datetime obj_time;
         double obj_price;
         int sub_window;
         //--- convert the X and Y coordinates to the time and price values
         if(ChartXYToTimePrice(0,mouse_x,mouse_y,sub_window,obj_time,obj_price))
           {
            //--- create object
            if(!ObjectCreate(0,buy_sign_name,OBJ_ARROW_BUY,0,obj_time,obj_price))
              {
               Print("Failed to create buy sign!");
               return;
              }
            //--- redraw chart
            ChartRedraw();
            //--- increase object counter
            sign_obj_cnt++;
           }
         //---
         break;
        }

Die aktuelle Version des EAs trägt den Namen EventProcessor6.mq5.


4.7 Mausklick-auf-Grafikobjekt-Ereignis

Dieser Typ von Chartereignissen unterscheidet sich von den vorherigen nur durch den Umstand, dass der Mausklick auf einem grafischen Objekt erfolgt. Der String-Parameter sparam wird den Namen desjenigen Objekts enthalten, auf das geklickt wurde. Im vorherigen Beispiel haben wir einen „Kaufen“-Pfeil erstellt. Nun wollen wir, dass sich dieser Pfeil durch einen Klick auf Objekte diesen Typs in einen „Verkaufen“-Pfeil verändert.

Der Code dieses Anwenderblocks könnte wie folgt aussehen:

//--- 7
      case CHARTEVENT_OBJECT_CLICK:
        {
         comment+="7) mouse click on graphical object";
         //---
         string sign_name=sparam;

         //--- delete buy arrow
         if(ObjectDelete(0,sign_name))
           {
            //--- redraw chart
            ChartRedraw();
            //---
            static uint sign_obj_cnt;
            string sell_sign_name="sell_sign_"+IntegerToString(sign_obj_cnt+1);

            //--- coordinates 
            int mouse_x=(int)lparam;
            int mouse_y=(int)dparam;
            //--- time and price
            datetime obj_time;
            double obj_price;
            int sub_window;
            //--- convert the X and Y coordinates to the time and price values
            if(ChartXYToTimePrice(0,mouse_x,mouse_y,sub_window,obj_time,obj_price))
              {
               //--- create object
               if(!ObjectCreate(0,sell_sign_name,OBJ_ARROW_SELL,0,obj_time,obj_price))
                 {
                  Print("Failed to create sell sign!");
                  return;
                 }
               //--- redraw chart
               ChartRedraw();
               //--- increase object counter
               sign_obj_cnt++;
              }
           }
         //---
         break;
        }

Da wir es hier mit einem Beispiel zu tun haben, habe ich mich dafür entschieden, die Verarbeitung eines Mausklicks unangetastet zu lassen. Nach dem Ausführen des EAs habe ich die linke Maustaste drei Mal betätigt und auf diese Weise drei „Kaufen“-Pfeile erzeugt (Abb. 7). Ich habe ihre Position gelb markiert, damit sie leichter zu finden sind.

Abb. 7 „Kaufen“-Pfeile

Abb. 7 „Kaufen“-Pfeile

Wenn wir nun auf einen beliebigen der „Kaufen“-Pfeile klicken, erhalten wir folgende grafische Darstellung (Abb.8).

Abb. 8 „Kaufen“ & „Verkaufen“-Pfeile

Abb. 8 „Kaufen“ & „Verkaufen“-Pfeile

„Verkaufen“-Pfeile erscheinen genauso wie geplant. Allerdings vermissen wir das Auftauchen von „Kaufen“-Pfeilen. Es gibt einen Grund dafür, dass ich eine Objektliste auf dem Chart angelegt habe, auf der ich die Namen der „Kaufen“-Pfeile gelb markiert habe.

Es ist unschwer zu erkennen, dass der EA den vierten, fünften und sechsten „Kaufen“-Pfeil angelegt hat. Warum ist das passiert? Ganz einfach - der erste Klick auf das Objekt hat zwei Ereignisse ausgelöst: das erste ist der eigentliche Klick auf das Objekt und das zweite ist ein Klick auf den Chart. Das letzte Ereignis generierte einen „Kaufen“-Pfeil. An diesem Punkt bedarf es eines Mechanismus, der die Ausführung des zweiten Ereignisses (ein Klick auf den Chart) unterbindet. Meiner Meinung handelt es sich bei der Zeitkontrolle (control on time) um einen derartigen Mechanismus.

Fügen wir nun die globable Variable gLastTime hinzu. Diese wird die Erstellung eines „Kaufen“-Pfeils adäquat unterbinden. Falls also ein simpler Klick bis zu 250 Millisekunden nach der Erstellung eines „Verkaufen“-Pfeils erfolgt, so wird dieser einfach ignoriert.

Bevor der Chart neu gezeichnet wird, muss der untere String zum Block der Verarbeitung von Klicks auf ein Objekt hinzugefügt werden:

//--- store the moment of creation
gLastTime=GetTickCount();

Dem Block zur Verarbeitung von Klicks auf den Chart sollte ferner eine Zeitverifizierung hinzugefügt werden.

uint lastTime=GetTickCount();
if((lastTime-gLastTime)>250)
  {
   //--- click handling
  }

Lassen Sie uns nun auf dem Chart drei Pfeile des „Kaufen“-Typs erstellen (Abb. 9).

Abb. 9 „Kaufen“-Pfeile

Abb. 9 „Kaufen“-Pfeile

Trotz ihrer geringen Größe wollen wir versuchen, auf sie zu klicken. Nach dem Anklicken haben sie sich alle in Pfeile des „Verkaufen“-Typs transformiert (Abb. 10).

Abb. 10 „Verkaufen“-Pfeile

Abb. 10 „Verkaufen“-Pfeile

Ähnlich der vorherigen werden wir auch diese neue Version auf den Namen EventProcessor7.mq5. taufen.


4.8 Grafikobjekt-mit-Maus-bewegen-Ereignis

Dieses Ereignis wird immer dann ausgelöst, falls ein Objekt innerhalb eines bestimmten Gebiets des Charts hin und her bewegt wird. Das Steuerungsprogramm erhält dabei den Namen dieses Objekts in Form des String-Parameters sparam.

Und hier ein weiteres Beispiel: Intraday-Trader handeln nicht selten innerhalb ganz bestimmter Zeitintervalle. Dabei stellen vertikale Linien die Grenzen eines Zeitintervalls dar. Die grafische Darstellung dürfte in etwa der Abb. 11 entsprechen. Ich habe mir die Freiheit genommen, das für uns relevante Intervall hervorzuheben.

Abb. 11 Grenzen eines Zeitintervalls

Abb. 11 Grenzen eines Zeitintervalls

Das Zeitintervall kann manuell modifiziert werden. Unser Semi-Roboter wird sich daraufhin dieser Veränderung anpassen.

Wir legen die globalen Variablen gTimeLimit1_name und gTimeLimit2_name an, die die Namen der beiden Vertikalen beschreiben sollen. Außerdem müssen wir diverse Variablen anlegen, die die Namen der Rechtecke beschreiben sollen - die genau die Zeit auf dem Chart abdecken, während der nicht getradet wird. Natürlich sind ferner noch globale Variablen für die Ankerpunkte zu erstellen. Da wir es mit zwei Rechtecken zu tun haben, können wir mit vier Punkten rechnen.

Der Code im Falle des Steuerprogramms CHARTEVENT_OBJECT_DRAG:

//--- 8
      case CHARTEVENT_OBJECT_DRAG:
        {
         comment+="8) move graphical object with mouse";
         string curr_obj_name=sparam;
         //--- if one of the vertical lines is moved
         if(!StringCompare(curr_obj_name,gTimeLimit1_name) || 
            !StringCompare(curr_obj_name,gTimeLimit2_name))
           {
            //--- the time coordinate of vertical lines
            datetime time_limit1=0;
            datetime time_limit2=0;
            //--- find the first vertical line
            if(ObjectFind(0,gTimeLimit1_name)>-1)
               time_limit1=(datetime)ObjectGetInteger(0,gTimeLimit1_name,OBJPROP_TIME);
            //--- find the second vertical line
            if(ObjectFind(0,gTimeLimit2_name)>-1)
               time_limit2=(datetime)ObjectGetInteger(0,gTimeLimit2_name,OBJPROP_TIME);

            //--- if vertical lines are found
            if(time_limit1>0 && time_limit2>0)
               if(time_limit1<time_limit2)
                 {
                  //--- update properties of rectangles
                  datetime start_time=time_limit1;
                  datetime finish_time=time_limit2;
                  //---
                  if(RefreshRecPoints(start_time,finish_time))
                    {
                     //---
                     if(!ObjectMove(0,gRectLimit1_name,0,gRec1_time1,gRec1_pr1))
                       {
                        Print("Failed to move the 1st point!");
                        return;
                       }
                     if(!ObjectMove(0,gRectLimit1_name,1,gRec1_time2,gRec1_pr2))
                       {
                        Print("Failed to move the 2nd point!");
                        return;
                       }
                     //---
                     if(!ObjectMove(0,gRectLimit2_name,0,gRec2_time1,gRec2_pr1))
                       {
                        Print("Failed to move the 1st point!");
                        return;
                       }
                     if(!ObjectMove(0,gRectLimit2_name,1,gRec2_time2,gRec2_pr2))
                       {
                        Print("Failed to move the 2nd point!");
                        return;
                       }
                    }
                 }
           }
         //---
         break;
        }

Der Code enthält eine angepasste RefreshRecPoints()-Funktion. Diese befasst sich mit dem Aktualisieren der Werte der Ankerpunkte der beiden Rechtecke. Der EA-Initialisierungsblock enthält Informationen darüber, wie man grafische Objekte erstellt. Die aktualisierte Version soll EventProcessor8.mq5 heißen.


4.9 Ende-des-Editierens-eines-Textes-Ereignis

Dieser Ereignistyp gleicht keinem der anderen. Er wird ausgelöst, sobald der Text im Dateneingabefeld editiert wird. Der Parameter sparam enthält den Namen des Objekts, an dem gerade gearbeitet wird.

Hier ein entsprechendes Beispiel: Im Dateneingabefeld geben wir die auszuführenden Handelsoperationen ein. Wir wollen uns dabei auf zwei Operationen beschränken - Kaufen und Verkaufen. Wenn wir jetzt im Dateneingabefeld das Wort „Kaufen“ eingeben, so wird der EA einen Posten erwerben. Geben wir hingegen „Verkaufen“ ein, wird ein Posten verkauft werden. Wir ändern daraufhin die Einstellung dieses Feldes auf schreibungsunabhängig (Groß- & Kleinschreibung ignorieren). Nun genügt ein simples „kaufen“ oder „verkaufen“, um obige Aktionen in Gang zu setzen. Der Text als auch das Eingabefeld werden rot (Verkaufen) beziehungsweise blau (Kaufen) gefärbt (Abb. 12).

Abb. 12 Mittels Textfeld kaufen

Abb. 12 Mittels Textfeld kaufen

Der Code für den Fall CHARTEVENT_OBJECT_ENDEDIT:

//--- 9
      case CHARTEVENT_OBJECT_ENDEDIT:
        {
         comment+="9) end of editing a text in the data entry field";
         //---
         string curr_obj_name=sparam;
         //--- if specified text field is being edited
         if(!StringCompare(curr_obj_name,gEdit_name))
           {
            //--- get object description
            string obj_text=NULL;
            if(ObjectGetString(0,curr_obj_name,OBJPROP_TEXT,0,obj_text))
              {
               //--- check value
               if(!StringCompare(obj_text,"Buy",false))
                 {
                  if(TryToBuy())
                     //--- set text color
                     ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrBlue);
                 }
               else if(!StringCompare(obj_text,"Sell",false))
                 {
                  if(TryToSell())
                     //--- set text color
                     ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrRed);
                 }
               else
                 {
                  //--- set text color
                  ObjectSetInteger(0,gEdit_name,OBJPROP_COLOR,clrGray);
                 }
               //--- redraw chart
               ChartRedraw();
              }
           }
         //---
         break;
        }

Der aktualisierte EA hört nun auf den Namen EventProcessor9.mq5. Den Block, um das Textfeld zu erstellen, finden Sie übrigens in der Quelldatei.


4.10 Chart-modifizieren-Ereignis

Das letzte Ereignis, dem wir uns im vorliegenden Artikel zuwenden, ist eng mit einer Änderung der Charteinstellungen verbunden. Es handelt sich um ein äußerst seltsames Ereignis, da wir nun mit dem Chart direkt und nicht mehr länger nur mit Objekten, die sich darauf befinden, interagieren. Laut den Entwicklern wird dieses Ereignis genau dann ausgelöst, wenn die Größe eines Charts geändert wird oder wenn neue Einstellungen vorgenommen werden.

Und hier ein weiteres Beispiel: Lassen Sie uns für einen Moment annehmen, dass es nicht möglich wäre, verschiedene Charteinstellungen zu ändern. In solch einem Fall würden alle derartigen Versuche ignoriert werden. Tatsächlich gibt der EA ganz einfach die vorherigen Werte erneut aus. Nehmen wir eine kurze Veränderung der Chartparameter vor:

  • Gitter darstellen;
  • Typ der Chartanzeige;
  • Hintergrundfarbe.

Der Code für diesen Fall:

//--- 10
      case CHARTEVENT_CHART_CHANGE:
        {
         //--- current height and width of the chart         
         int curr_ch_height=ChartHeightInPixelsGet();
         int curr_ch_width=ChartWidthInPixels();
         //--- if chart height and width have not changed
         if(gChartHeight==curr_ch_height && gChartWidth==curr_ch_width)
           {
            //--- fix the properties:
            //--- display grid
            if(!ChartShowGridSet(InpToShowGrid))
              {
               Print("Failed to show grid!");
               return;
              }
            //--- type of chart display
            if(!ChartModeSet(InpMode))
              {
               Print("Failed to set mode!");
               return;
              }
            //--- background color
            if(!ChartBackColorSet(InpBackColor))
              {
               Print("Failed to set background сolor!");
               return;
              }
           }
         //--- store window dimensions
         else
           {
            gChartHeight=curr_ch_height;
            gChartWidth=curr_ch_width;
           }
         //---
         comment+="10) modify chart";
         //---
         break;
        }

Die letzte Version soll folgenden Namen tragen: EventProcessor10.mq5.


Fazit

In diesem Artikel habe ich versucht, Ihnen die verschiedenen Typen von Chartereignissen, auf die Sie in MetaTrader 5 treffen werden, ein wenig näherzubringen. Ich hoffe, dass ich damit dem ein oder anderen Programmierer, der seine ersten Schritte im Bereich der Ereignisbehandlung tut, zumindest ein wenig unter die Arme greifen konnte.