English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Marktbeobachtung mithilfe vorgefertigter Klassen

Marktbeobachtung mithilfe vorgefertigter Klassen

MetaTrader 5Beispiele | 14 März 2016, 12:06
685 0
Dmitriy Skub
Dmitriy Skub

Einleitung

Unser Hauptziel ist die Entwicklung eines benutzerfreundlichen und zugleich erweiterbaren Werkzeugs zur Wiedergabe beliebiger Informationstexte auf dem Bildschirm des Ausgabegerätes. Dabei kann es sich beispielsweise um die aktuellen Einstellungen des jeweiligen automatischen Handelssystems (Expert-Systems) oder die Darstellung der von ihm für einen bestimmten Zeitraum gelieferten Ergebnisse in Form einer Tabelle handeln. Es können aber auch die Kurswerte oder eine Übersicht über die Werte der von dem jeweiligen Händler verwendeten Indikatoren oder ein eigenes Handelsprotokoll des eingesetzten Expert-Systems sein. All diese Informationen können, solange das Expert-System bzw. der Indikator, die diese Bibliotheken verwenden, laufen, dynamisch auf dem Bildschirm angezeigt werden.

Als Grundlage zur Erstellung der Bibliothek dient eine Auswahl der im Lieferumfang der MetaTrader 5-Anwendungsinstanz auf dem Ausgabegerät enthaltenen Klassen der Standardbibliothek. Wir sehen uns diese Auswahl zunächst einmal an und entscheiden danach, wie sie ergänzt und erweitert werden muss, um die gestellte Aufgabe zu erfüllen.

Die Standardklassen zur Erstellung grafischer Objekte

Die uns interessierende Auswahl von Klassen befindet sich in den Verzeichnissen Include und Include\ChartObjects.

Es handelt sich dabei um folgende Dateien:

  • Object.mqh - mit der Basisklasse CObject zur Erstellung aller übrigen mit grafischen Objekten verbundenen Klassen.
  • ChartObject.mqh - ebenfalls mit einer Basisklasse und zwar der aus CObject abgeleiteten Klasse CChartObject zur Erweiterung des Funktionsumfangs sowie zur Aufnahme der allgemeinen Daten und Methoden aller grafischen Objekte.
  • ChartObjectsTxtControls.mqh - mit einer Reihe von Klassen zur Ausgabe verschiedener mit Text versehener grafischer Objekte auf dem Bildschirm. Dabei handelt es sich um deren Basisklasse CChartObjectText nebst den aus ihr abgeleiteten Klassen: CChartObjectLabel, CChartObjectEdit und CChartObjectButton.

Untersuchen wir diese Klassen etwas eingehender.

Die universelle Basisklasse CObject

Da der Text zur Beschreibung der Klasse recht kurz ist, zeigen wir ihn hier in Gänze:

class CObject
{
protected:
   CObject          *m_prev;
   CObject          *m_next;

public:
                     CObject();

   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }

   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }

   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
};

//+------------------------------------------------------------------+
void CObject::CObject()
{
   m_prev=NULL;
   m_next=NULL;
}

Unübersehbar enthält diese Klasse ausschließlich Daten und Methoden zu allgemeinen Zwecken, die keinen unmittelbaren Bezug zur Bildschirmanzeige auf dem Ausgabegerät haben.

Stattdessen verfügt sie über eine wichtige Eigenschaft: sie kann zum Anlegen einfach oder doppelt verknüpfter Listen verwendet werden. Diese Eigenschaften erhält sie von den Datenfeldern CObject::m_prev und CObject::m_next der Art CObject* und den Methoden, um sie auszulesen/zu beschreiben. Das Feld CObject::m_prev verweist auf das vorhergehende Element der Liste, CObject::m_next entsprechend auf das nachfolgende. Ausführlichere Hinweise zum Anlegen von Listen folgen noch.

Darüber hinaus gibt es eine Methode zum Vergleichen zweier Objekte der Art CObject*, sie heißt CObject::Compare und kann zur Sortierung von Elementen innerhalb einer Liste verwendet werden. Zwei weitere nützliche Methoden helfen uns beim Speichern/Auslesen von Datenfeldern in Dateien, es sind die Methoden CObject::Save bzw. CObject::Load. Diese Methoden müssen an die abgeleiteten Klassen weitergegeben werden, um den gewünschten Funktionsumfang zu erhalten.

Und dann ist da noch eine Methode zur Ermittlung der Art des Objekts: CObject::Type. Sie wird von Nutzen sein, wenn wir es mit Listen zu tun haben, die Objekte unterschiedlicher Art beinhalten.

Somit weist die Basisklasse CObject (wie auch ihre Instanzen) folgende Merkmale auf:

  • sie ermittelt ihre Lage in Bezug auf ihre Nachbarelemente innerhalb der betreffenden Liste;
  • sie ermittelt ihre Art;
  • sie speichert die Datenfelder in einer Datei und stellt sie aus dieser wieder her;
  • sie vergleicht sich selbst mit der angegebenen Objektart.

Die Mehrzahl der oben vorgestellten Methoden ist virtuell und wird in der Basisklasse nicht wirklich umgesetzt. Die Basisklasse verfügt über keine echten Eigenschaften mit greifbarer Bedeutung. Wie in der OOP üblich muss jeder „Nachkomme“ den Funktionsumfang umsetzen, der im Vergleich zu seinen Vorfahren neu ist.

Die Basisklasse zum Anlegen grafischer Objekte: CChartObject

CChartObject ist ein Nachkomme der Klasse CObject.

Wie sich ihrer Bezeichnung entnehmen lässt, handelt es sich dabei um eine Klasse zur Beschreibung irgendeines abstrakten grafischen Objekts. Gleichwohl beinhaltet dieses abstrakte Objekt bereits reale greifbare Eigenschaften und Methoden für die Arbeit mit diesen Eigenschaften. Diese Eigenschaften sind allen in der MetaTrader 5-Anwendung für Ausgabegeräte vorhandenen grafischen Objekten gemein, weshalb es folgerichtig ist, sie in besagter Klasse unterzubringen.

Sehen wir sie uns etwas eingehender an. Zur Einbettung eines grafischen Objekts in ein vorgegebenes Fenster werden folgende Angaben benötigt:

protected:
  long       m_chart_id;    // identifier of the chart, which contains 
                               // specified graphic object (0 - current chart)
  int        m_window;      // number of chart sub-window (0 - main window)
  string     m_name;        // unique name of the object on the chart
  int        m_num_points;  // number of points for anchoring the object

Vor der Angabe oder dem Auslesen der Eigenschaften eines realen grafischen Objekts muss es mit seinem „Abbild“ im Programm verknüpft werden. Das erledigt die Methode CChartObject::Attach. In den Nachkommen dieser Klasse wird sie unmittelbar nach dem Anlegen des grafischen Objekts in dem Diagramm aufgerufen.

bool CChartObject::Attach(long chart_id,string name,int window,int points)
{
  if(ObjectFind(chart_id,name)<0)
  {
    return(false);
  }

  if(chart_id==0) chart_id=ChartID();

  m_chart_id  =chart_id;
  m_window    =window;
  m_name      =name;
  m_num_points=points;

  return(true);
}

Zunächst prüfen wir, ob das grafische Objekt tatsächlich vorhanden ist. Liegt es vor, werden seine Eigenschaften in den inneren Objektfeldern der Klasse CChartObject gespeichert. Danach können die Eigenschaften (Farbe, Lagedaten usw.) des grafischen Objekts ausgelesen und geändert werden.

Hier sind die Methoden zum Speichern (CChartObject::Save) und Auslesen (CChartObject::Load) der Eigenschaften des grafischen Objekts bereits umgesetzt. In der abgeleiteten Klasse müssen vor den eigenen zunächst die Speicher-/Auslesemethoden der „Elternklasse“ aufgerufen werden.

Die Klasse CChartObject (und ihre Instanzen) weist gegenüber der Basisklasse folgende neue Eigenschaften auf:

  • sie verknüpft ein reales grafisches Objekt mit seinem „Programmabbild“;
  • sie kann die allen grafischen Objekten gemeinen Eigenschaft auslesen und ändern;
  • sie kann ein grafisches Objekt aus dem Diagramm löschen;
  • sie kann ein grafisches Objekt im Diagramm verschieben (platzieren).


Die Klasse zur Erstellung grafischer Textobjekte: CChartObjectText

Kommen wir jetzt zu der Datei ChartObjectsTxtControls.mqh. Hier befinden sich die Beschreibungen der Klassen, die zur Ausgabe unterschiedlicher Text enthaltender grafischer Objekte in den Diagrammen vorgesehen sind. Wir sehen uns ihre wesentlichen Merkmale einmal genauer an.

Ihre Basisklasse heißt CChartObjectText. Sie beinhaltet die mit der Wiedergabe von Text verbundenen Eigenschaften und Methoden.

Es folgt der vollständige Wortlaut der Beschreibung der Klasse:

class CChartObjectText : public CChartObject
{
public:
   double            Angle() const;
   bool              Angle(double angle);
   string            Font() const;
   bool              Font(string font);
   int               FontSize() const;
   bool              FontSize(int size);
   ENUM_ANCHOR_POINT  Anchor() const;
   bool              Anchor(ENUM_ANCHOR_POINT anchor);

   bool              Create(long chart_id,string name,int window,datetime time,double price);

   virtual int       Type() const { return(OBJ_TEXT); }

   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
};

Im Gegensatz zu CChartObject beinhaltet sie die Methoden zum Auslesen und Ändern der Eigenschaften eben der grafischen Textobjekte - Ausrichtung, Schriftart, -größe und -format sowie die Art (die Stelle) der Einbettung und die Koordinaten des grafischen Objekts. Und es ist eine neue Methode aufgetaucht: CChartObjectText:: Create. Sie ermöglicht das Anlegen eines echten grafischen Objekts der Art OBJ_TEXT im Diagramm.

Ihre Umsetzung sieht aus wie folgt:

bool CChartObjectText::Create(long chart_id,string name,int window,datetime time,double price)
{
  bool result = ObjectCreate( chart_id, name, OBJ_TEXT, window, time, price );
  if(result)
  {
    result &= Attach(chart_id, name, window, 1 );
  }

  return(result);
}

Nach erfolgreicher Erstellung des grafischen Objekts (die Funktion ObjectCreate gibt den Wert „true“ aus) wird die bereits erörterte Methode CChartObject:: Attach aufgerufen.

Somit enthält CChartObjectText gegenüber ihrer „Elternklasse“ einige neue Eigenschaften:

  • sie kann die Eigenschaft grafischer Textobjekte auslesen und ändern;
  • sie kann in einem Diagramm echte grafische Objekte der Art OBJ_TEXT anlegen.

Die Klasse zum Erstellen grafischer Objekte der Art „Beschriftung“: CChartObjectLabel

In der Hierarchie der Standardklassen nimmt die Klasse CChartObjectLabel den nächsten Platz ein. Mit ihrer Hilfe kann in dem jeweiligen Diagramm ein grafisches Objekt vom Typ OBJ_LABEL (Beschriftung) angelegt werden.

Hier ist die Beschreibung der Klasse:

class CChartObjectLabel : public CChartObjectText
{
public:
   int               X_Distance() const;
   bool              X_Distance(int X);
   int               Y_Distance() const;
   bool              Y_Distance(int Y);
   int               X_Size() const;
   int               Y_Size() const;
   ENUM_BASE_CORNER  Corner() const;
   bool              Corner(ENUM_BASE_CORNER corner);

   bool              Time(datetime time) { return(false);  }
   bool              Price(double price) { return(false);  }

   bool              Create(long chart_id,string name,int window,int X,int Y);

   virtual int       Type() const        { return(OBJ_LABEL); }

   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
};

Hier ist auf den Unterschied zwischen grafischen Objekten der Arten OBJ_TEXT und OBJ_LABEL zu achten.

Objekte der ersten Art sind mit dem Kursdiagramm (mit den Koordinaten Kurs/Zeit) oder einem Diagramm in einem Unterfenster verknüpft. Die der zweiten Art dagegen mit den Bildschirmkoordinaten des entsprechenden Fensters oder Unterfensters (in Bildpunkten/Pixeln), in denen das Diagramm wiedergegeben wird.

Folglich werden Objekte vom Typ OBJ_TEXT bei der Verschiebung des Diagramms mit bewegt, während Objekte der Art OBJ_LABEL auf ihrer Bildschirmposition verharren, auch wenn das Diagramm verschoben wird. Es muss also je nach anstehender Aufgabe eine bestimmte Art der grafischen Textobjekte ausgewählt werden.

Gegenüber ihrer Elternklasse weist CChartObjectLabel die folgenden Unterscheidungsmerkmale:

  • die Platzierung des grafischen Objekts erfolgt anhand der Bildschirmkoordinaten des Fensters;
  • sie kann die Ausrichtung auslesen und ändern. Eigentlich handelt es sich dabei lediglich um die Angabe des Anfangs der Koordinaten in einer der vier Ecken des Diagrammfensters.
  • sie kann in einem Diagramm echte grafische Objekte der Art OBJ_LABEL anlegen.

Die Klasse zur Erstellung grafischer Objekte der Art „Eingabefeld“: CChartObjectEdit

Als Nächste in der Standardklassenhierarchie folgt CChartObjectEdit. Das ist die Klasse zur Erstellung grafischer Objekte der Art OBJ_EDIT (Eingabefeld).

Weil Objekte dieser Art genauso angebunden werden wie solche der Art OBJ_LABEL, nämlich an Bildschirmkoordinaten (in Bildpunkten), ist es naheliegend, sie aus der Klasse CChartObjectLabel abzuleiten, was in der Bibliothek der Standardklassen auch geschieht:

class CChartObjectEdit : public CChartObjectLabel
{
public:
   bool              X_Size(int X);
   bool              Y_Size(int Y);
   color             BackColor() const;
   bool              BackColor(color new_color);
   bool              ReadOnly() const;
   bool              ReadOnly(bool flag);

   bool              Angle(double angle) { return(false);    }

   bool              Create(long chart_id,string name,int window,int X,int Y,int sizeX,int sizeY);

   virtual int       Type() const        { return(OBJ_EDIT); }

   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
};

Die Unterschiede zwischen den Eingabefeldern vom Typ OBJ_EDITund den Beschriftungen bestehen in Folgendem:

  • ein Eingabefeld besitzt die Eigenschaften „Breite“ und „Höhe“ (angegeben in Bildpunkten), die die Größe seiner Wiedergabe in dem Diagramm begrenzen. Die Größe der Beschriftung dagegen stellt sich automatisch so ein, dass der ganze Text zu sehen ist.
  • es gibt Methoden zur Zulassung/Sperrung von Textänderungen - CChartObjectEdit:: ReadOnly (Nur Lesen);
  • es wurde eine Methode zur Änderung der Hintergrundfarbe des von dem grafischen Objekt belegten Platzes hinzugefügt;
  • die Anzeigeausrichtung des Objekts kann nicht geändert werden. Eingabefelder können nur in waagerechter Ausrichtung angezeigt werden;
  • in einem Diagramm kann ein echtes grafisches Objekt der Art OBJ_EDIT angelegt werden.

Die Klasse zur Erstellung grafischer Objekte der Art „Schaltfläche“: CChartObjectButton

Eine weitere Klasse in der Hierarchie der grafischen Textobjekte ist die Klasse CChartObjectButton. Das Objekt wird als Schaltfläche bezeichnet, es ist zum Anlegen von Steuerelementen in Form anklickbarer Flächen in einem Diagramm gedacht. Diese Klasse ist aus der Klasse CChartObjectEdit abgeleitet und hat deren Funktionsumfang übernommen („geerbt“):

class CChartObjectButton : public CChartObjectEdit
{
public:
  bool             State() const;
  bool             State(bool state);

  virtual int       Type() const { return(OBJ_BUTTON); }

  virtual bool      Save(int file_handle);
  virtual bool      Load(int file_handle);
};

Die Unterschiede zwischen einer Schaltfläche der Art OBJ_BUTTON und einem Eingabefeld sind:

  • die Gestaltung in Form einer drückbaren Taste ähnelt der in den Dialogfeldern von Windows verwendeten;
  • sie verfügt über neue Methoden zum Auslesen/Ändern des Zustands der Schaltfläche (gedrückt/freigegeben): CChartObjectButton::State;
  • in einem Diagramm kann ein echtes grafisches Objekt der Art OBJ_BUTTON angelegt werden.

Allgemeiner Aufbau der Standardklassen zur Wiedergabe grafischer Textobjekte

In veranschaulichter Form kann man sich den Aufbau (die Hierarchie) der Klassen der Standardbibliothek folgendermaßen vorstellen:

Allgemeiner Aufbau der Standardklassen

Abbildung 1. Allgemeiner Aufbau der Standardklassen

Die Klasse CObject ist ebenfalls eine Basisklasse für andere Standardklassen, wie etwa die weiter unten betrachtete Klasse zur Verwaltung der Liste CList sowie die Mehrzahl der übrigen.


Erweiterung des Funktionsumfangs der Standardklassen

Wir haben jetzt also kurz die Hierarchie der Standardklassen zur Erstellung grafischer Textobjekte in einem Diagramm erörtert. Jetzt erweitern wir diese Hierarchie um neue Klassen. Vor allem anderen müssen wir festlegen, welche Funktionen zur Umsetzung des jeweiligen Vorhabens erforderlich sind. Also legen wir die Vorgaben fest. Da wir es mit der Ausgabe schriftlicher Angaben zu tun haben, ist es folgerichtig diese Angaben in der folgenden gegliederten Form anzulegen:

KOPFZEILE TEXT

Dieser Aufbau der Wiedergabe von Text ist für die Mehrzahl der einfachen Fälle geeignet.

Zum Beispiel kann ein Indikator zur Ausgabe der Eckdaten eines Finanzinstrumentes (Kürzels) in einem Diagramm etwa wie folgt aussehen:

Beispiel für die gegliederte Ausgabe von Informationen in Textform

Abbildung 2. Beispiel für die gegliederte Ausgabe von Informationen in Textform

Hier kommen sechs Textfelder mit dem oben vorgestellten Aufbau zum Einsatz. Einige Bestandteile des Aufbaus können weggelassen werden. So fehlt beispielsweise in dem oberen Feld der Abbildung 2 die Kopfzeile. In allen übrigen Feldern gibt es sowohl als auch: Kopfzeile und Text. Dieser Indikator ist in der Datei PricelInfo.mq5 im Anhang zu diesem Beitrag zu finden.

Platzierung der grafischen Objekte

Der zweite Gesichtspunkt, den wir zu beachten haben, ist die Art der Platzierung grafischer Textobjekte in einem Diagramm. Die gewählte Methode zur Angabe der Koordinaten in Bildpunkten ermöglicht zwar die Platzierung eines Objekts an einer beliebigen Stelle des Diagrammfensters,

ist jedoch in der Praxis ungeeignet, wenn zahlreiche Textobjekte an verschiedenen Stellen des Diagramms platziert werden müssen, weil alle Bildschirmkoordinaten pixelgenau berechnet werden müssen. Außerdem müssen bei einer Änderung der Bildschirmauflösung alle Koordinaten neu berechnet werden, damit sich die relative Lage der Objekte auf dem Bildschirm nicht ändert.

Wenn wir das Diagrammfenster in Gedanken in gleich große Rechtecke (Felder) aufteilen und jedem dieser Rechtecke waage- und senkrechte Koordinaten zuordnen, erhalten wir ein universelles von der Bildschirmauflösung unabhängiges Platzierungssystem.

Der Anwender muss bei der Erstellung des grafischen Textobjekts als Parameter nur noch die maximale Anzahl der Felder entlang der waagerechten und der senkrechten Achse sowie die Koordinaten des Objekts in diesen Feldern angeben. Die in der entsprechenden Klasse angelegten Funktionen passen Koordinaten der grafischen Objekte bei einer Änderung der Bildschirmauflösung automatisch an. So müssen die Koordinaten ohne weiteres Nachjustieren nur einmal eingegeben werden.


Automatische Erzeugung unverwechselbarer Objektbezeichnungen

Die nächste Aufgabe, die wir lösen müssen, ist die automatische Erzeugung der Bezeichnungen für die grafischen Objekte. Die wesentliche Vorgabe für die Erzeugungsmethode besteht darin, eine im Rahmen des betreffenden Fensters unverwechselbare Bezeichnung zu liefern. Dadurch kann eine für die praktische Arbeit ausreichende Anzahl von Objekten in dem Diagramm verteilt werden, ohne dass wir uns ständig neue Bezeichnungen ausdenken müssen.

Wir schlagen folgendes Verfahren zur Erzeugung der Bezeichnungen vor (eine aus folgenden Feldern bestehende Zeile):

Datum Uhrzeit Anzahl der Millisekunden

Zum Bezug des Teils der Zeile, der das Datum und die Uhrzeit enthält, verwenden wir folgenden Aufruf:

TimeToString(TimeGMT(), TIME_DATE|TIME_MINUTES|TIME_SECONDS);

Um die Anzahl der Millisekunden zu beziehen, den folgenden:

DoubleToString(GetTickCount(), 0);

Aber selbst wenn wir die Zeit mit einer Genauigkeit von einer Millisekunde messen, bleibt es vollkommen möglich, dass wir auch bei einem zwei- oder mehrmaligen Aufruf der Funktion GetTickCount() denselben Wert erhalten. Das liegt an der begrenzten Ausgabefeinheit der integrierten Uhren des Betriebssystems und des Prozessors. Deshalb ist es erforderlich, weitere Maßnahmen zur Aufdeckung einer solchen Situation zu ergreifen.

Das vorgeschlagene Vorgehen wird in folgender Funktion umgesetzt:

string GetUniqName()
{
  static uint prev_count = 0;

  uint count = GetTickCount();
  while(1)
  {
    if(prev_count == UINT_MAX)
    {
      prev_count = 0;
    }
    if(count <= prev_count)
    {
      prev_count++;
      count = prev_count;
    }
    else
    {
      prev_count = count;
    }

//  Verify that there is no object already existing with such a name:
    string name = TimeToString(TimeGMT(), TIME_DATE|TIME_MINUTES|TIME_SECONDS)+" "+DoubleToString(count, 0);
    if(ObjectFind(0, name) < 0)
    {
      return(name);
    }
  }

  return(NULL);
}

Einzige Einschränkung dieser Methode: die Erzeugung von nicht mehr als 4.294.967.295 (UINT_MAX) einzigartiger Bezeichnungen je Sekunde. Das sollte offenkundig für die Praxis reichen. In jedem Fall wird das Vorhandensein eines grafischen Objektes mit derselben Bezeichnung so ein weiteres Mal geprüft.

Die Klasse zur Wiedergabe der Kopfzeile der Informationsübersicht: TTitleDisplay

Die Klasse zur Ausgabe der Kopfzeile auf dem Bildschirm des Ausgabegerätes wird unten vorgestellt:

class TTitleDisplay : public CChartObjectLabel
{
protected:
  long    chart_id;
  int     sub_window;
  long    chart_width;       // width of the chart in pixels
  long    chart_height;      // height of the chart graph in pixels
  long    chart_width_step;  // step of the coordinates grid in the horizontal direction
  long    chart_height_step; // step of the coordinates grid in the vertical direction
  int     columns_number;    // number of columns
  int     lines_number;      // number of lines
  int     curr_column;
  int     curr_row;

protected:
  void    SetParams(long chart_id, int window, int cols, int lines);// specify the object's parameters

protected:
  string  GetUniqName();    // get a unique name
  bool    Create(long chart_id, int window, int cols, int lines, int col, int row);
  void    RecalcAndRedraw();// recount the coordinates and re-draw

        
public:
  void    TTitleDisplay();  // constructor
  void    ~TTitleDisplay(); // destructor
};

Die grundlegende Methode zur Erstellung eines grafischen Textobjekts:

bool Create(long chart_id, int window, int _cols, int _lines, int _col, int _row);

Die Zuordnung der Eingangsparameter sieht aus wie folgt:

  • chart_id - der Bezeichner für das Fenster (0 = Hauptfenster);
  • window - die Nummer des Unterfensters (0 = Hauptfenster);
  • cols - die Höchstzahl senkrechter grafischer Textobjekte (die Anzahl der Spalten);
  • lines - die Höchstzahl waagerechter grafischer Textobjekte (die Anzahl der Zeilen);
  • col - die senkrechte Koordinate des grafischen Objekts (zwischen „0“ und „cols - 1“);
  • row - die waagerechte Koordinate des grafischen Objekts (zwischen „0“ und „cols - 1“);

Die Methode TTitleDisplay:: SetParams berechnet die mit seiner Platzierung auf dem Bildschirm verbundenen Parameter des Objekts.

So sieht die Umsetzung aus:

void TTitleDisplay::SetParams(long _chart_id, int _window, int _cols, int _lines)
{
  this.chart_id = _chart_id;
  this.sub_window = _window;
  this.columns_number = _cols;
  this.lines_number = _lines;

//  Specify the size of the window in pixels:
  this.chart_width = GetSystemMetrics(SM_CXFULLSCREEN);
  this.chart_height = GetSystemMetrics(SM_CYFULLSCREEN);

//  Calculate the step of the coordinates grid:
  this.chart_width_step = this.chart_width/_cols;
  this.chart_height_step = this.chart_height/_lines;
}

Hier kommt der Aufruf GetSystemMetrics der Funktion WinAPI zum Einsatz, um die aktuellen Bildschirmeinstellungen (die Auflösung des Arbeitsbereichs) abzurufen. Die Funktion wird aus dem Windows-Stammverzeichnis user32.dll importiert.

Die Klasse TFieldDisplay zum Anlegen von Informationstext ist ähnlich aufgebaut. Ausführlichere Angaben dazu bietet die Datei mit der Bibliothek TextDisplay.mqh im Anhang zu diesem Beitrag.

Die Standardklasse zum Bearbeiten von Objekten in einer Liste: CList

Wir sehen uns jetzt noch eine weitere Standardklasse an, die wir bei der Umsetzung unseres Vorhabens brauchen. Mithilfe dieser Klasse können die Objekte in den Listen gruppiert werden. Sie befindet sich in der Datei Include\Arrays\List.mqh.  In dieser Klasse liegen sowohl eine Beschreibung als auch eine Umsetzung der Standardklasse CList vor. CList ist eine Ableitung aus der Basisklasse CObject, über die in diesem Beitrag bereits geschrieben wurde. Sie enthält eine Auswahl an Methoden zur Bearbeitung der Objekte einer Liste. Dabei handelt es sich um das Hinzufügen zu einer Liste, das Entfernen aus einer Liste, den Zugriff auf ein beliebiges Element der Liste sowie die Bereinigung der Liste.

Wir sehen uns die wichtigsten Methoden etwas genauer an:

  • Hinzufügen:
   int Add(CObject *new_node);
   int Insert(CObject *new_node,int index);

Es gibt zwei Methoden, um einer Liste ein neues Element hinzuzufügen. Die erste, CList::Add, ermöglicht das Anfügen eines neuen Elements, new_node, an das Ende der bestehenden Liste. Die zweite ,CList::Insert, dagegen lässt das Platzieren des neuen Elements, new_node, an einer beliebigen, durch die Kennziffer index ausgewiesenen Stelle in der Liste zu.

  • Entfernen:
   bool  Delete(int index);

Die Methode CList::Delete ermöglicht das Löschen des Elements mit der angegebenen Kennziffer index aus der Liste. Dabei wird neben der Löschung des Elements aus der Liste auch der von dem Element des Typs CObject belegte Speicherplatz freigegeben. Mit anderen Worten, das Objekt wird „physisch“ gelöscht.

  • Zugriff auf Listenelemente:
   int       IndexOf(CObject* node);
   CObject*  GetNodeAtIndex(int index);
   CObject*  GetFirstNode();
   CObject*  GetPrevNode();
   CObject*  GetNextNode();
   CObject*  GetLastNode();

Die Methode CList:: IndexOf gibt die Kennziffer des gesuchten Listenelements aus. Die Kennziffernvergabe beginnt bei „0“, das erste Element erhält demnach die Kennziffer „0“. Die Methode, die die gegenläufige Operation ausführt, gibt die Kennziffer anhand des Elementzeigers aus. Ist das Element in der Liste nicht vorhanden, wird „-1“ ausgegeben.

Drei weitere Methoden sorgen für die Navigation in der Liste CList:: GetFirstNode gibt das erste Element der Liste aus, CList:: GetNextNode das nachfolgende Element und CList:: GetLastNode das letzte.

  • Liste löschen:
   void  Clear();

Die Methode CList::Clear ermöglicht die Entfernung aller Elemente aus der Liste bei gleichzeitiger Freigabe des gesamten von den Objekten belegten Speicherplatzes.

Das sind die wesentlichen Methoden der Klasse CList, alle weiteren werden in dem der Standardbibliothek gewidmeten Kapitel der MQL5-Hilfe behandelt.

Die Klasse zum Anlegen einer Tabelle zur Ausgabe von Text auf dem Bildschirm: TableDisplay

Jetzt haben wir alles, was wir zur Umsetzung unseres Vorhabens benötigen. Hier ist die elementare Klasse zur Anordnung grafischer Textobjekte in Form einer Tabelle beliebigen Umfangs:

class TableDisplay : public CList
{
protected:
  long  chart_id;
  int   sub_window;

public:
  void  SetParams(long _chart_id, int _window, ENUM_BASE_CORNER _corner = CORNER_LEFT_UPPER);
  int   AddTitleObject(int _cols, int _lines, int _col, int _row, 
                      string _title, color _color, string _fontname = "Arial", int _fontsize = 8);
  int   AddFieldObject(int _cols, int _lines, int _col, int _row, 
                          color _color, string _fontname = "Arial", int _fontsize = 8);
  bool  SetColor(int _index, color _color);
  bool  SetFont(int _index, string _fontname, int _fontsize);
  bool  SetText(int _index, string _text);

public:
  void  TableDisplay();
  void  ~TableDisplay();
};

Vor dem Einfügen der grafischen Objekte in die Tabelle müssen einige Parameter festgelegt werden: die Kennziffer des Fensters, die Nummer des Unterfensters und den Ausgangspunkt für die Koordinaten. Dazu wird die Methode TableDisplay:: SetParams aufgerufen. Anschließend kann der Tabelle eine beliebige Anzahl von Kopfzeilen und Textfeldern hinzugefügt werden.

Der vollständige Text der von uns erstellten Bibliothek findet sich in der Datei TextDisplay.mqh im Anhang zu diesem Beitrag.


3. Ein Beispiel für das Anlegen einer Marktbeobachtungstabelle

Wir betrachten das Beispiel des Anlegens einer Bildschirmtabelle zur Ausgabe der Werte einiger Kürzel in der Form eines Indikators.

Sie sollte in etwa so aussehen:

Beispiel für eine Bildschirmtabelle

Abbildung 3. Beispiel für eine Bildschirmtabelle

  • Schritt 1 - Einbindung der Bibliothek in den Quellcode des Indikators:
#include  <TextDisplay.mqh>
  • Schritt 2 - Anlegen der Datenfelder (Arrays) mit den Bezeichnungen und Koordinaten für die Kopfzeilen:
#define  NUMBER  8
//---------------------------------------------------------------------
string  names[NUMBER]   = {"EURUSD", "GBPUSD", "AUDUSD", "NZDUSD", "USDCHF", "USDCAD", "USDJPY", "EURJPY"};
int     coord_y[NUMBER] = {2,        3,        4,        5,        6,      7,        8,       9};
Die Koordinaten beginnen bei „0“.
  • Schritt 3 - Anlegen eines Tabellenobjekts der Art TableDisplay zur Aufnahme aller abzubildenden Textobjekte:
TableDisplay  Table1;
  • Schritt 4 - Einfügen der Kopfzeilen- und Textfelder zu den Objekten in die Tabelle:
int OnInit()
{
//  Creating a table
  Table1.SetParams(0, 0);

  for(int i=0; i<NUMBER; i++)
  {
    Table1.AddFieldObject(40, 40, 3, coord_y[i], Yellow);
  }

  for(int i=0; i<NUMBER; i++)
  {
    Table1.AddTitleObject(40, 40, 1, coord_y[i], names[i]+":", White);
  }

  ChartRedraw(0);
  EventSetTimer(1);

  return(0);
}

Idealerweise erfolgt dies in der Ereignisverarbeitungsroutine OnInit. Zunächst werden die Textfelder mithilfe der Methode TableDisplay:: AddFieldObject eingefügt. Danach kommen ihre Kopfzeilen mit der Methode TableDisplay:: AddTitleObject dazu.

Das Hinzufügen erfolgt in getrennten Arbeitsgängen aus zweierlei Anlass: Erstens, wenn die Anzahl der Kopfzeilen ganz allgemein nicht mit der Anzahl der Textfelder übereinstimmt, und zweitens, wenn der Zugriff auf die aktualisierten Textfelder einfacher zu organisieren ist, wenn sie nacheinander (in diesem Fall von null bis zu dem Wert NUMBER - 1) durchnummeriert sind.

  • Schritt 5 - Hinzufügen des Programmcodes für die Aktualisierung der dynamischen Informationen:

Die Aktualisierung der dynamischen Informationen erfolgt in diesem Fall bei einem entsprechenden Zeitgeberereignis. Die Verarbeitungsroutine OnTimer für dieses Ereignis muss die Kurswerte für die angegebenen Kürzel empfangen, um sie auf dem Bildschirm anzuzeigen.

Ihr Text folgt:

//---------------------------------------------------------------------
double    rates[NUMBER];
datetime  times[NUMBER];
MqlTick   tick;
//---------------------------------------------------------------------
// OnTimer event handler
//---------------------------------------------------------------------
void OnTimer()
{
  for(int i=0; i<NUMBER; i++)
  {
//  Obtain the price values:
    ResetLastError();
    if(SymbolInfoTick(names[i], tick) != true)
    {
      Table1.SetText(i,"Err "+DoubleToString(GetLastError(),0));
      Table1.SetColor(i,Yellow);
      continue;
    }

    if(tick.time>times[i])
    {
       Table1.SetText(i, DoubleToString(tick.bid, (int)(SymbolInfoInteger(names[i], SYMBOL_DIGITS))));

       if(tick.bid>rates[i])
       {
         Table1.SetColor(i, Lime);
       }
       else if(tick.bid<rates[i])
       {
         Table1.SetColor(i, Red);
       }
       else
       {
         Table1.SetColor(i, Yellow);
       }
       rates[i] = tick.bid;
       times[i] = tick.time;
    }
  }

  ChartRedraw(0);
}

Zu Zyklusbeginn werden die Kursänderungsdaten (Ticks) für das betreffende Kürzel ausgelesen und auf ihre Aktualität geprüft, hat sich der Zeitpunkt des Eintretens der Kursänderung gegenüber dem der vorhergehenden nicht geändert, so gelten die Daten als nicht aktuell. Anschließend wird der Kurswert im Hinblick auf die vorhergehende Kursänderung analysiert.

Liegt der aktuelle Wert über dem vorhergehenden, wird für das Textfeld die Hintergrundfarbe grün festgelegt. Liegt er darunter, ist der Hintergrund rot, bei Gleichheit gelb. Am Ende des Zyklus werden der aktuelle Wert und der Zeitpunkt der Kursänderung für die Analyse im nächsten Verarbeitungszyklus für das Zeitgeberereignis gespeichert.

Generell hängt der Programmcode zur Aktualisierung der dynamischen Informationen von der jeweiligen Aufgabe ab. De facto muss der Anwender lediglich diesen Teil des Codes erstellen. Angenommen wir haben uns entschieden, in der Abbildung 2 rechts vom Kurs die Kursdifferenz hinzuzufügen. Sehen wir uns an, wie das gemacht wird.

Alles, was wir tun müssen, ist, in der Tabelle weitere Textfelder anzulegen:

  for(int i=0; i<NUMBER; i++)
  {
    Table1.AddFieldObject(40, 40, 5, coord_y[i], Yellow);
  }

sowie in der Verarbeitungsroutine für Zeitgeberereignisse den Code zur Aktualisierung des Wertes der Kursdifferenz zu ergänzen:

  Table1.SetText(i+NUMBER, DoubleToString((tick.ask-tick.bid)/SymbolInfoDouble(names[i], SYMBOL_POINT), 0));

Als Ergebnis erhalten wir folgende Darstellung:

Kurse mit Differenzangabe

Abbildung 4. Kurse mit Differenzangabe

Beachten Sie bitte, dass die Kennziffern der Ausgabefelder für die Kursdifferenz ab dem Wert NUMBER (bei i = 0) beginnen.

  • Schritt 6 - Löschen der angelegten Objekte
void OnDeinit(const int _reason)
{
  EventKillTimer();

//  Removing the elements of display:
  Table1.Clear();
}

Hier wird der Zeitgeber zurückgesetzt und die von uns angelegte Tabelle bereinigt. Dabei werden auch alle Objekte aus dem Speicher gelöscht.

Der vollständige Text für diesen Indikator ist in der Datei PricelList.mq5 im Anhang zu diesem Beitrag zu finden. Der Anhang enthält die „optimierte“ Fassung des Indikators mit Anzeige der Kursdifferenz sowie einer Reihe externer Parameter zur Angabe der Farbe der Kopfzeilen und der Lage der Tabelle im Ausgabefenster.

Fazit

Die angehängte Datei MarketWatch.mq5 (in MarketWatch.mqh) befindet sich ein Indikator zur Ausgabe der wesentlichen Parameter von Handelsinstrumenten in Form einer zusammenfassenden Übersichtstabelle. Zu jedem Instrument (Kürzel) werden die Angaben ähnlich wie in Abbildung 2 abgebildet.

Außerdem wird die prozentuale Kursänderung für bestimmte zeitliche Intervalle angezeigt. Eine Auswahl an Kürzeln (nicht mehr als 16) und Zeitintervallen wird in Form von Zeilen mit durch Semikola getrennten Elementen.  Abbildung 5 zeigt die Ergebnisse der Arbeit dieses Indikators:

Der Marktbeobachtungsindikator

Abbildung 5. Der Marktbeobachtungsindikator

Wir haben eine der Möglichkeiten zur Wiedergabe von Informationstexten auf dem Bildschirm des MetaTrader 5-Ausgabegerätes erörtert.

Dank der Verwendung der im Lieferumfang der Anwendungsinstanz für das Ausgabegerät enthaltenen Klassen der Standardbibliothek war es möglich, den zur Abbildung der Informationstexte in Form einer zweidimensionalen Tabelle fehlenden Funktionsumfang recht einfach und schnell zu entwickeln. Das ist die Stärke des objektorientierten Ansatzes sowie der ihn verwirklichenden Programmiersprache MQL5.

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/179

Beigefügte Dateien |
marketwatch.mq5 (11.51 KB)
priceinfo.mq5 (8.76 KB)
pricelist.mq5 (7.29 KB)
textdisplay.mqh (15.54 KB)
marketwatch.mqh (9.48 KB)
Bildung von Kursreihenmittelwerten für Zwischenberechnungen ohne zusätzliche Puffer Bildung von Kursreihenmittelwerten für Zwischenberechnungen ohne zusätzliche Puffer
In diesem Beitrag geht es sowohl um traditionelle als auch um neuartige Mittelwertbildungsalgorithmen verpackt in einfachste Klassen mit jeweils nur einer Datenart. Sie sind zur universellen Verwendung bei nahezu jeder Indikatorentwicklung gedacht. Ich hoffe, dass die vorgeschlagenen Klassen sich als gute Alternative zu den „unhandlichen“ Aufrufen benutzerdefinierter und technischer Indikatoren erweisen werden.
Ein einfaches Beispiel zur Erstellung eins Indikators mittels Qualitativaussagenlogik (unscharfer oder fuzzy Logik) Ein einfaches Beispiel zur Erstellung eins Indikators mittels Qualitativaussagenlogik (unscharfer oder fuzzy Logik)
Dieser Artikel ist der praktischen Anwendung des Konzepts der Qualitativaussagenlogik zur Finanzmarktanalyse gewidmet. Wir legen das Beispiel eines Indikators dar, der Signale auf der Grundlage zweier auf dem Envelopes-Indikator fußender unscharfer Regeln erzeugt. Der entwickelte Indikator nutzt verschiedene Indikatorzwischenspeicher: 7 für die Berechnungen, 5 für die Diagrammausgabe und 2 für die Farben.
Anlegen eines Spektrumanalysators Anlegen eines Spektrumanalysators
Der hier vorliegende Beitrag möchte seine Leser mit einer möglichen Variante der Verwendung der grafischen Objekte der Programmiersprache MQL5 vertraut machen. Es wird ein Indikator analysiert, der mithilfe grafischer Objekte ein Feld zur Steuerung eines einfachen Spektrumanalysators anlegt. Der Beitrag richtet sich an Leser mit Grundkenntnissen in MQL5.
Der MQL5-Assistent: Erstellen von Expert-Systemen ohne Programmierung Der MQL5-Assistent: Erstellen von Expert-Systemen ohne Programmierung
Möchten Sie eine Handelsstrategie ausprobieren, ohne Zeit mit Programmieren zu vergeuden? In dem Assistenten („Wizard“) von MQL5 können Sie einfach die Art der Handelssignale auswählen, Module zur Pflege der Positionen und für die Kapitalverwaltung hinzufügen, und fertig ist der Lack! Erstellen Sie eigene Modulumsetzungen oder bestellen Sie sie mithilfe des Dienstes „Freie Mitarbeit“, und kombinieren Sie Ihre neuen Module mit den bereits vorhandenen.