English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Channels zeichnen - Innen- und Außenansicht

Channels zeichnen - Innen- und Außenansicht

MetaTrader 5Indikatoren | 14 März 2016, 13:48
799 0
Dmitriy Skub
Dmitriy Skub

Einleitung

Ich glaube ohne Übertreibung sagen zu können, dass Channels das beliebteste Tool zur Analyse von Märkten und für Handelsentscheidungen nach dem gleitenden Durchschnitt sind. In diesem ersten Beitrag der Reihe zu Channels, sollen die mathematischen Grundlagen und die theoretische Implementierung eines Indikators erläutert werden, der auf dem Bildschirm des Client-Terminals einen Channel zeichnet, der durch drei Extrema festgelegt ist.

Auf den ersten Blick scheint das Zeichnen eines Channels keine besonders schwere Aufgabe zu sein, da es ja auf der Gleichung einer geraden Linie beruht, was man ja bereits aus der Grundschule kennt. Seine praktische Implementierung in den Client-Terminal wirft jedoch viele Fragen auf, die nicht so ganz direkt beantwortet werden können.

Wie organisiere ich am besten das Einrichten von Extremwerten und das Nachverfolgen ihrer Veränderungen? Was mache ich, wenn der Mittelteil eines Channels auf den fehlenden Balken liegt und wie zeichne ich ihn dann? Was passiert, wenn das linke Extremum eines Channels an einem Freitag und das rechte an einem Monat liegt und das Wochenende ohne Balken dazwischen? Wie erhält man die aktuellen Werte einer Channelgrenze?

Diese und noch weitere Fragen wird dieser erste Beitrag aus der Beitragsreihe zu Channels zu beantworten versuchen. Zudem finden Sie hier auch die Implementierung des Zeichnens von Channels nach drei festgelegten Extremwerten, mit Hilfe der Standardklassen und des Objekt-orientierten Ansatzes. Den Channel-Zeichner implementieren wir in Form eines Indikators.

Extrema einrichten

Die Position eines Channels auf einem Chart wird von mind. drei Extremwerten bestimmt. Wenn wir ein Extremum definieren müssen, dann so: ein Extremum ist der maximale oder minimale Wert einer Funktion in einem gegebenen Bereich. Der Punkt, an dem ein Extremum erreicht ist, heißt Extremwertpunkt. Und analog heißt der Extremwertpunkt bei einem Minimum Minimalpunkt, und bei einem Maximum natürlich Maximalpunkt.

Die mathematische Analyse definiert einen anderen Begriff: den lokalen Extremwert (also das minimale bzw. maximale Extrem). Beim Maximalpunkt (Minimalpunkt) ist der Wert der Funktion größer (kleiner) als der Wert aller angrenzender Punkte. Diese Definition stammt aus Wikipedia (aus dem Russischen übersetzt).

Damit wir Channels zeichnen können, brauchen wir lokale Extrema. Stellen wir das mal grafisch dar, ohne die mathematischen Formeln zu bemühen. Abb1. unten zeigt drei lokale Extrema, die durch rote Kurslevels gekennzeichnet sind. Die rechteckigen Punkte zeigen zwei Maxima und ein Minimum:

Abb. 1 Beispiele lokaler Extrema

Abb. 1 Beispiele lokaler Extrema

Beachten Sie bitte: nicht alle existierenden Extrema sind auf dem Chart markiert, nur die bedeutendsten. Für ein Kerzen- oder Balkenchart empfiehlt es sich zur Definition von Extremwerten den Begriff "Fraktal" zu verwenden - wenn mehrere angrenzende Balken links oder rechts eindeutig ab- oder aufsteigen (vgl. Abb1).

Da wir keine Veranlassung haben, einen automatischen Channelzeichner machen zu müssen, wird die Position der Extrema so eingerichtet, wie es Abb. 1 zeigt - durch die Position auf den Zeit- und Kursachsen. Und dazu eignen sich Kurskennzeichnungen am besten - die speziellen graphischen Objekte des MetaTrader 5 Client-Terminals. Eine Kurskennzeichnung besitzt die Zeit- und Kurseigenschaften, womit ein Extremwertpunkt auf einem Chart definitiv identifiziert werden kann.

Das Objekt zur Aufbewahrung von Extremwerten ist die TExtremum Klasse

Als erstes entwickeln wir eine Behälterklasse zur Aufbewahrung der Extrema und eine Klasse zur Bearbeitung einer Gruppe an Extrema. Da wir ja die im Terminal eingeschlossenen Standardklassen so oft wie möglich einsetzen wollen, wird die TExtremum Klasse von der Standardklasse CObject geerbt. Unten sehen Sie die Beschreibung unserer Klasse:

class TExtremum : public CObject
{
private:
  datetime  extr_datetime;              // data/time in an extremum point
  double    extr_price;                 // price in an extremum point
        
protected:
  virtual int  Compare(const CObject* _node, int _mode = 0) const;

public:
  void      TExtremum();               // constructor
  void      ~TExtremum();              // destructor
  void      SetExtremum(datetime _time, double _price);  // change date/time and price in an extremum point
  void      SetDateTime(datetime _time);                 // change date/time in an extremum point
  void      SetPrice(double _price);  // change price in an extremum point

public:
  datetime  GetDateTime() const;      // get date/time in an extremum point
  double    GetPrice() const;         // get price in an extremum point

public:
  virtual bool  SaveExtremum(string _dt_name, string _p_name);  // save extremum
  virtual bool  LoadExtremum(string _dt_name, string _p_name);  // load extremum
  virtual bool  DeleteExtremum(string _dt_name, string _p_name);// delete extremum
};

Die meisten Methoden sind trivial und müssen bei ihrer Implementierung nicht extra erläutert werden. Doch die TExtremum::Compare Methode verdient etwas Aufmerksamkeit. Sie wird in der CObject Klasse deklariert und zur Sortierung innerhalb einer Liste verwendet. Wir haben sie folgendermaßen implementiert:

//---------------------------------------------------------------------
//  Comparing two extremums by time:
//---------------------------------------------------------------------
int TExtremum::Compare(const CObject* _node, int _mode = 0) const
{
  datetime  temp = ((TExtremum* )_node).GetDateTime();
  datetime  curr = GetDateTime();
  if(curr > temp)
  {
    return(_mode > 0 ? 1 : -1);
  }
  else if(curr < temp)
  {
    return(_mode > 0 ? -1 : 1);
  }

  return(0);
}

Der Parameter _mode dient zur Einrichtung der Sortierrichtung. Ist er > 0, wird direkt sortiert (aufsteigend); ist er < 0, wird umgekehrt (absteigend) sortiert.

Zusätzlich dazu gibt es noch zwei Methoden zum Speichern/Laden eines Extremwerts. Unser Extremum speichern wir in globalen Variablen. Das sind die Methoden:

//---------------------------------------------------------------------
//  Save extremum (date/time):
//---------------------------------------------------------------------
bool TExtremum::SaveExtremum(string _dt_name, string _p_name)
{
  datetime  dt_result = GlobalVariableSet(_dt_name, (double)extr_datetime);
  datetime  p_result = GlobalVariableSet(_p_name, (double) extr_price);
  if(dt_result != 0 && p_result != 0)
  {
    return(true);
  }

  return(false);
}

//---------------------------------------------------------------------
//  Load extremum (date/time):
//---------------------------------------------------------------------
bool TExtremum::LoadExtremum(string _dt_name, string _p_name)
{
  double  dt_temp, p_temp;
  bool    result = GlobalVariableGet(_dt_name, dt_temp);
  result &= GlobalVariableGet(_p_name, p_temp);
  if(result != false)
  {
    extr_datetime = (datetime)dt_temp;
    extr_price = p_temp;
    return(true);
  }

  return(false);
}

Die zwei Methoden zum Lesen/Schreiben in globalen Variablen TExtremum::LoadExtremum und TExtremum::SaveExtremum liefern bei erfolgreicher Ausführung 'true'.

Bearbeitung der Liste der Extrema - die Class TExtremumList Klasse

Da wir sowohl eine Ablage und Sortierung der Extrema nach Zeit brauchen, sollten wir dieTExtremumList Klasse von der Standardklasse CList erben. Mit dieser Vererbung erhalten wir einen universellen 'Bearbeiter' von Extremwerten ohne Beschränkung ihrer Anzahl und Art, sodass wir die Anzahl der Channels, die gezeichnet werden, noch erweitern können. Wir können z.B. das Zeichnen des Channels bei nicht-linearer Regression nach verschiedenen Extremwerten hinzufügen.

Die Beschreibung dieser Klasse steht unten:

class TExtremumList : public CList
{
private:
  string              channel_prefix;     // channel name (prefix)
  ENUM_TIMEFRAMES      chart_timeframe;    // current timeframe
  string              chart_symbol;       // work symbols of the chart

protected:
  string    MakeDTimeName(int _nmb);     // get name for saving/reading data/time of an extremum
  string    MakePriceName(int _nmb);     // get name for saving/reading price of an extremum

public:
  void      TExtremumList();             // конструктор
  void     ~TExtremumList();             // деструктор
  void     SetChannelParams(string _pref, string _symbol = NULL, ENUM_TIMEFRAMES _curr_tf = PERIOD_CURRENT);
  void     AddExtremum(datetime _time, double  _price);
  void     DeleteAllExtremum();
  void     SaveExtremumList();
  void     LoadExtremumList();
  int      FindExtremum(datetime _dt);  // search extremum by specified time

public:
  datetime GetDateTime(int _index);
  double   GetPrice(int _index);
};

Die Hauptmethode der Klasse lautet TExtremumList::AddExtremum und dient der Ergänzung der Liste durch ein neues Extremum. Die Sortierung der Extrema in der Liste nach Zeit des Extremwertpunkts geschieht nach dem Hinzufügen. Hier sehen Sie den Code dieser Methode:

void TExtremumList::AddExtremum(datetime _time, double  _price)
{
//  Create extremum:
  TExtremum*    extr = new TExtremum();
  extr.SetExtremum(_time, _price);

//  Add it in the list:
  Add(extr);

//  Sort:
  Sort(1);
}

Wir verwenden hier die folgenden Methoden der Basisklasse: CList::Add - um der Liste ein neues Element hinzuzufügen, und CList::Sort - um Elemente in der Liste zu sortieren. CList::Sort verwendet die Methode TExtremum::Compare.

Sehen wir uns die Methode zur Suche eines Extremwerts mit einer gegebenen Zeit in der Liste an:TExtremumList::FindExtremum. Unten steht der Code der Methode:

int TExtremumList::FindExtremum(datetime _dt)
{
  int           k = 0;
  TExtremum*    extr = (TExtremum*)(GetFirstNode());
  while(extr != NULL)
  {
    if(extr.GetDateTime() == _dt)
    {
      return(k);
    }
    extr = (TExtremum*)(GetNextNode());
  }
  return(-1);                     // extremum not found
}

Wir verwenden hier die folgenden Methoden der Basisklasse CList::GetFirstNode - um das erste Element in der Liste zu erhalten (ist die Liste leer, sehen wir einen Null-Zeiger), und CList::GetNextNode - um das nächste Element in der Liste zu erhalten (gibt es kein nächstes Element und die Liste ist zu Ende, wird ein Null-Zeiger geliefert).

Hinweis:

Es gibt in den internen Daten der Klassenliste CList einen Zeiger auf ein aktuelles Element. Er wird verändert, sobald Methoden zum Verschieben in der Liste aufgerufen werden (CList::GetFirstNode, CList::GetNextNode, CList::GetPrevNode, usw.). Ist noch keine dieser Methoden zuvor aufgerufen worden, dann zeigt der Zeiger auf ein aktuelles Element auf das erste Element.

Sollte aber ein Extremum mit der gegebene Zeit erfolgreich gefunden worden sein, dann indiziert die Methode TExtremumList::FindExtremum das gefundene Element. Gibt es so ein Element nicht, liefert sie -1.

Die Methoden TExtremum::MakeDTimeName and TExtremum::MakePriceName sind Hilfsmethoden. Mit ihnen erhält man die Namen der globalen Variablen, die beim Speichern und Lesen der Extrema verwendet werden. Diese Methoden werden folgendermaßen implementiert:

string TExtremumList::MakeDTimeName(int _nmb)
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_DTime_Extr", _nmb);
  return(name);
}

string TExtremumList::MakePriceName( int _nmb )
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_Price_DTime_Extr", _nmb);
  return(name);
}

Ein Beispiel der erhaltenen Namen: "MainChannel_EURUSD_5_DTime_Extr1". So ein Name entspricht einem temporären Extremwertpunkt des MainChannel Channels (konventioneller Name), dem Symbol EURUSD, dem Zeitraum 5M und der Extremwertzahl 1. Die Zahl wird einem Extremum nach der ansteigenden Sortierung via seiner Zeit zugewiesen und beginnt mit 1. Das ist quasi der Index, der in einer aufsteigend sortierten Liste auf 1 gewechselt ist.

Die folgende Abbildung zeigt ein Beispiel des Wert der drei Extrema, die im Terminal gespeichert wurden:

Abb. 2 Die in den globalen Variablen gespeicherten Extrema

Abb. 2 Die in den globalen Variablen gespeicherten Extrema

Die oben beschriebenen Klassen finden sich in der diesem Beitrag angehängten Datei ExtremumClasses.mqh.

Indikator zum manuellen Einrichten der Extrema - ExtremumHandSet

Gut: jetzt haben wir ja alles zur Entwicklung des ersten Indikators, mit dessen Hilfe wir die Position der Extrema manuell einrichten können. Der Indikator-Code ist diesem Beitrag ebenfalls angehängt und steht in der Datei ExtremumHandSet.MQ5. Sehen wir uns das Zeichnen mal im einzelnen an.

Als erstes veranschaulichen wir das, was wir sehen wollen, visuell auf dem Bildschirm:

Abb. 3 Indikator zum Einrichten von Extrema

Abb. 3 Indikator zum Einrichten von Extrema

Mit Hilfe der linken Kurskennzeichnungen richten wir die Position der Extrema auf der Zeit- und Kursachse des Charts ein. Der Indikator muss die Position dieser Kennzeichnungen auf dem Chart festlegen, temporäre Extrempunkte auf dem Bildschirm anzeigen und sie in den globalen Variablen des Client-Terminals im oben beschrieben Format speichern. Darüber hinaus muss er die Bewegung der Kurskennzeichnungen auf dem Chart nachverfolgen und die geladenen temporären Extrempunkte korrigieren.

Das Nachverfolgen der Bewegungen der Kurskennzeichnungen auf dem Chart wird einmal pro Sekunde ausgeführt. Dadurch ist das System unabhängig vom Auftauchen von Quoten und Werktagen/Feiertagen und Wochenende.

Als erstes verknüpfen wir dazu die erforderlichen Libraries:

//---------------------------------------------------------------------
//  Included libraries:
//---------------------------------------------------------------------
#include  <TextDisplay.mqh>
#include  <ExtremumClasses.mqh>

Die erste Library enthält die Klassen zur Organisation des Anzeige von Textinformation auf dem Bildschirm (vgl. Beitrag "Seine eingene Marktbeobachtung mit Hilfe der Standard Library Klassen erzeugen"). Mit ihrer Hilfe zeigen wir die Werte der temporären Extrempunkte an.

Dann fügen wir die Eingabeparameter des Indikator hinzu (ich beschreibe hier nur die wichtigsten):

input string  PrefixString = "MainChannel";
//---------------------------------------------------------------------
input color   ExtremumPointColor = Yellow;
//---------------------------------------------------------------------
input bool    ShowInfo = true;

Der erste Parameter PrefixString setzt ein Präfix, das zur Namensgebung der globalen Variablen verwendet wird, wenn ein Extremum geschrieben/gelesen wird. Des weiteren ermöglicht es den Einsatz mehrerer Indikatoren dieser Art auf dem einem einzigen Chart. Das einzige was man dabei machen muss, ist jeweils andere Präfixe einzurichten.

Der Parameter ExtremumPointColor setzt eine Farbe für noch übrige Kurskennzeichnungen, die die Position der Extrema festlegen. Die Kurskennzeichnungen müssen eine festgelegte Farbe haben. Der entsprechende Abgleich wird im Indikator gemacht. Kennzeichnungen mit unterschiedlichen Farben werden ignoriert

Der Parameter ShowInfo kontrolliert die Anzeige der Textinformation über die spezifizierten Extrempunkte auf dem Bildschirm.

Als nächstes erzeugen wir die Objekte zur Anzeige der Information and Bearbeitung der Extrema:

TableDisplay    TitlesDisplay;    // displaying information on the screen
//---------------------------------------------------------------------
TExtremumList*  PrevExtr_List;    // list of previous extremums
TExtremumList*  CurrExtr_List;    // list of current extremums
TExtremumList*  NewExtr_List;     // list of new extremums

Diese Objekte werden folgendermaßen initialisiert:

PrevExtr_List = new TExtremumList();
PrevExtr_List.SetChannelParams(PrefixString, Symbol(), Period());
PrevExtr_List.LoadExtremumList();

CurrExtr_List = PrevExtr_List;

NewExtr_List = new TExtremumList();
NewExtr_List.SetChannelParams(PrefixString, Symbol(), Period());

In der Liste PrevExtr_List laden wir aus den globalen Variablen Extrema mit Hilfe der Methode TExtremumList::LoadExtremumList. Diese Liste bewahrt die Extrema zum Vergleich mit neuen auf, die von einem Chart gelesen werden, wenn die Kurskennzeichnungen auf den Bildschirm gezogen werden.

Die Liste CurrExtr_List wird als aktuelle Liste verwendet, sie bewahrt die aktuellen Extrema auf. Da wir zu Anfang ja nur die Extrema haben, die von den globalen Variablen gelesen wurden, werden sie wie aktuelle Extreme behandelt.

In der Liste NewExtr_List schreiben wir die neuen Extrema, die sich auf dem Chart zeigen.

Sehen wir uns nun die Funktionen an, die im Indikator eingesetzt werden. Die erste Funktion FindExtremumPoints wird zum Lesen und Prüfen der Parameter der Kurskennzeichnungen benutzt, die die Position der Extrema festlegen:

bool FindExtremumPoints(long _chart_id)
{
  string  name;

//  1. Search for the total number of objects with specified parameters and write them to the list:
  int total_objects = ObjectsTotal(_chart_id, -1, OBJ_ARROW_LEFT_PRICE);
  if(total_objects <= 0)
  {
    return(false);
  }

  NewExtr_List.Clear();
  for(int i = 0; i < total_objects; i++)
  {
    name = ObjectName(_chart_id, i, -1, OBJ_ARROW_LEFT_PRICE);

    if( IsGraphicObjectGood(_chart_id, name, OBJ_ARROW_LEFT_PRICE, ExtremumPointColor) == true)
    {
      NewExtr_List.AddExtremum(ObjectGetInteger( _chart_id, name, OBJPROP_TIME),
                               ObjectGetDouble(_chart_id, name, OBJPROP_PRICE));
    }
  }

//  2. If three extremums are found, we can try to draw a channel:
  if(NewExtr_List.Total() == 3)
  {

//  Save the list of new extremums:
    NewExtr_List.SaveExtremumList();
    return(true);
  }

  NewExtr_List.Clear();
  return(false);
}

Zunächst wird die Liste NewExtr_List durch Aufruf der Methode TExtremumList::Clear bereinigt und dann werden ihr alle gefundenen Extrempunkte mit spezifizierten Parameter hinzugefügt. Beträgt die Zahl der gefundenen Punkte 3, wird die Liste in den globalen Variablen gespeichert und die Funktion liefert 'true'.

Die andere Funktion CheakExtremumMoving verfolgt die Bewegung der Extrempunkte auf dem Chart. Wenn mind. ein Punkt entlang der Zeitachse auf dem Chart verschoben wird, liefert die Funktion 'true'.

Ihr Code steht hier:

//---------------------------------------------------------------------
//  Check whether extremums have been moved on the screen:
//---------------------------------------------------------------------
bool CheakExtremumMoving()
{
  if(FindExtremumLines(0) == true)
  {
    int  count = NewExtr_List.Total();
    int  index;
    for(int i = 0; i < count; i++)
    {
      index = CurrExtr_List.FindExtremum(NewExtr_List.GetDateTime(i));

//  If a new extremum is found:
      if(index == -1)
      {
        PrevExtr_List = CurrExtr_List;
        CurrExtr_List = NewExtr_List;
        return(true);
      }
    }
    CurrExtr_List = PrevExtr_List;
  }

  return(false);
}

Wir haben nun die Art und Weise erläutert, wie man Extrempunkte manuell setzen kann. Wir haben den einsatzbereiten Indikator, der die Kontrolle dieses Vorgangs und das Schreiben der Punkte in die globale Variablen erlaubt. Der komplette Code dieses Indikators steht in der ebenfalls angehängten Datei ExtremumHandSet.mq5. Nun können wir uns endlich it der Hauptsache beschäftigen - einen Channel zu zeichnen.

Einen Channel zeichnen - Ausflug in die Theorie

Ein linearer Channel besteht aus zwei parallelen Linien, die direkt durch Extrempunkte gehen. Zusätzlich dazu muss eine Linie durch zwei Punkte gehen und die andere durch den Punkt, der sich links parallel neben der ersten Linie befindet. Ein einfaches Schaubild zeigt was ich meine:

Einen Channel mit Hilfe dreier Extrempunkte zeichnen

Abb. 4 Einen Channel mit Hilfe dreier Extrempunkte zeichnen

Wie wir es ja aus der Geometrie kennen, kann nur eine gerade Linie zwei Punkte verbinden. In Abb. 4 ist diese Linie in rot gezeichnet. Sie geht durch zwei Punkte mit den folgenden Koordinaten - (T1, P1) und (T2, P2); die Punkte sind mit den Buchstaben A und B gekennzeichnet, sodass die Gleichung dieser Linie lautet :

(1)   P(t) = P1 + (t - T1)*(P2 - P1) / (T2 - T1); P(t) = der zum Zeitpunkt 't' berechnete Kurs.

Wir sollten durch Punkt C (das dritte Extremum) noch eine gerade Linie zeichnen, die parallel zur ersten verläuft. In Abb. 4 ist diese Linie in grün gezeichnet. Da sich die T1 und T2 Punkte für beide Linien gleichen, sollten wir die Werte von P1' und P2' finden (vgl. Abb. 4).

Bevor wir fortfahren, gestatten Sie mir eine wichtige Bemerkung: Das Terminal-Chart zeigt keine "Zeitlöcher" an. So sollten die Tage, an denen keine Quoten am Terminal ankommen, als Unterbrechungen der Kurs angezeigt werden. Und es ist schlecht, dass dies eben nicht der Fall ist, denn, warum sollte man sich ein leeres Chart ansehen? Wenn wir jedoch in der obigen Gleichung mit absoluter Zeit arbeiten, erhalten wir einen komplett falschen Channel.

Doch glücklicherweise ist die Situation nicht hoffnungslos. Wenn wir die absolute Zeit in die relative Anzahl eines Balkens verändern, dann können wir zum Zeichnen eines Channels diese Koordinaten verwenden, da die Aufzählung von Balken keine Unterbrechung haben kann (ist quasi ein Index in einem Kurs-Array).

Wenn wir noch weitergehen und annehmen, dass der Punkt A in Abb. 4 immer auf der Null-Koordinate (Null-Balken) der Zeitachse liegt, wird unsere Gleichung sogar noch leichter. Also: T1=0, T3=B3,Т2=В2. В3 und В2 sind hier die Zahlen eines Balkens in Relation zum Punkt T1 (dem Nullpunkt). Es ist klar, dass diese Annahme nicht zu einer Neigung der Linie führt. Wir erhalten dann die folgende Gleichung einer geraden Linie, die durch die Punkte A und B geht:

(2)   P(n) = P1 + n * (P2-P1) / B2, wobei P(n) = der für einen Balken mit der 'n' Zahl berechneten Kurs ist.

Jetzt kennen wir also die Werte P1, P2, P3 und B2, B3 und müssen nur noch die für P1' und P2' finden. Wenn wir zwei Gleichungen vereinen und sie lösen, erhalten wir die folgenden Formeln, mit deren Hilfe wir die unbekannten Werte finden:

(3)   P1' = P3 - B3 * (P2 - P1) / B2

(4)   P2' = P2 - P1 + P1'

Wenn wir den Wert P1' gefunden haben und ihn in die (4) Formel einsetzen bekommen wir den Wert P2' - und haben somit die komplette theoretische Grundlage zum Zeichnen eines Channels. Und die implementieren wir jetzt.

Channelgrenzen zeichnen - die TChannelBorderObject Klasse

Diese Klasse ist aus der Standardklasse CChartObjectTrend abgeleitet. Sie dient zum Speichern aller Parameter, die mit den Grenzen eines Channels verknüpft sind sowie zum Zeichnen/Löschen der Grenzlinien und zur Kontrolle der grafischen Parameter dieser Linie.

Die Beschreibung dieser Klasse steht unten:

class TChannelBorderObject : public CChartObjectTrend
{
//  General properties of a border:
private:
  bool             is_created;       // whether the graphical object is created on the screen
  long             chart_id;         // identifier of the chart window
  int              window;           // identifier of the subwindow

//  Parameters of a border line:
private:
  string           border_name;      // name of the border line
  color            border_color;     // color of the border line
  int              border_width;     // thickness of the border line
  ENUM_LINE_STYLE   border_style;     // style of the border line

//  Coordinates of a border:
private:
  datetime         point_left;       // time of the left point (T1)
  datetime         point_right;      // time of the right point (T2)
  double           price_left;       // price of the left point (P1)
  double           price_right;      // price of the right point (P2)

public:
  void     TChannelBorderObject();  // constructor
  void    ~TChannelBorderObject();  // destructor
  bool     IsCreated();             // check whether the line is created

//  Creating/deleting a line:
public:
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right, color _color, int _width, ENUM_LINE_STYLE _style);
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right);
  bool     CreateBorder(datetime _t_left, datetime _t_right, double _p_left, double _p_right);
  bool     RemoveBorder();          // delete line from the chart

//  Setting parameters of the line:
public:
  void     SetCommonParams(long _chart_id, int _window, string _name);
  bool     SetBorderParams(color _color, int _width, ENUM_LINE_STYLE _style);
  bool     SetBorderColor(color _color);
  bool     SetBorderWidth(int _width);
  bool     SetBorderStyle(ENUM_LINE_STYLE _style);

//  Getting values on the line:
  double   GetPrice(datetime _dt); // get price value in the specified position of the border line
};

Diese Klasse muss nicht extra kommentiert werden.

Konzentrieren wir uns nur auf die Methode, wie man an einem spezifizierten Punkt den Grenzkurs bekommt:

//---------------------------------------------------------------------
//  Get price value in the specified position of the border line:
//---------------------------------------------------------------------
double TChannelBorderObject::GetPrice(datetime _dt)
{
//  If the graphical object is created:
  if(is_created == true)
  {
    return(ObjectGetValueByTime( chart_id, border_name, _dt));
  }
  return(0.0);
}

Hierzu wird die Funktion des Terminals ObjectGetValueByTime verwendet - sie liefert den Kurswert für eine spezifizierte Zeit. Es ist weit bequemer hierzu die Möglichkeiten des Terminal zu verwenden, anstatt den Wert mittels einer mathematischen Formel zu berechnen.


Einen Channel zeichnen - die TSlideChannelObject Klasse

Diese Klasse ist aus der Standardklasse CList abgeleitet. Sie erfüllt folgende Zwecke:

  • Speichern der Objekte der Klasse TChannelBorderObject und Ausführen verschiedener Handlungen mit ihnen;
  • Berechnung der Punkte zum Zeichnen notwendiger Linien, die einen Channel ausmachen;
  • Speicherung und Modifikation der Parameter eines Channels;
  • Abrufen berechneter Werte, die einen gezeichneten Channel beschreiben (seine Höhe, Kurswert an den Grenzen, usw).

Der Code, der diese Klasse beschreibt ist zu umfangreich, um hier komplett gezeigt werden zu können. Alle diejenigen, die ihn sehen wollen, verweise ich auf die Datei SlideChannelClasses.mqh im Anhang an diesen Beitrag. Analysieren wir nun einige seiner Hauptbereiche.

Zunächst geht es um das Erhalten der B2 und B3 Werte an den T2 bzw. T3 (vgl. Abb. 4) Dazu wird der folgende Code verwendet:

//  Get relative shifts in bars relatively to the extremum points:
  total_bars = Bars(symbol, time_frame);     // total number of bars in history
  if(total_bars == 0)
  {
    return(false);                           // channel cannot be drawn
  }
  double  B2 = Bars(symbol, time_frame, point_left, point_right);
  double  B3 = Bars(symbol, time_frame, point_left, point_middle);

Um nicht nicht vorhandene Balken aufzurufen, verwenden wir die Funktion Balken im Terminal, die die Anzahl der Balken in der History für festgelegte Symbole und einen Zeitraum liefert. Ist diese Information noch nicht gebildet, gibt uns die Funktion einen Null-Wert, der zur Prüfung verwendet werden kann.

Liefert die Funktion einen Wert ungleich Null, dann können wir die В2 und В3 Werte holen, und zwar mit Hilfe derselben Funktion Balken, doch ihrem Aufruf in der anderen Form. Wir setzen Zeitgrenzen und erhalten so die Anzahl der Balken innerhalb dieses Bereichs. Da unsere linke Grenze die gleiche ist, erhalten wir die Verschiebung in Balken für die Punkte Т2 und Т3. Die Verschiebung für den Punkt T1 = 0.

Jetzt können wir alle Punkte der Channellinien berechnen. Maximal können dies neun sein, da unser Channel (zusätzlich zu seinen Ober- und Untergrenzen) die Mittellinie und die Linien der Prozentbereiche um die Grenzen und die Mittellinie herum anzeigen wird.

Sehen wir uns den Hauptteil dieser Berechnung mal genauer an. Die gesamte Berechnung erfolgt in der MethodeTSlideChannelObject::CalcChannel.

//  Coefficient of the line inclination:
  koeff_A = (price_right - price_left) / B2;

//  Price value on the AB line in the point T3:
  double  P3_AB = price_left + B3 * koeff_A;

// Determine the channel type - 2MAX_1MIN или 1MAX_2MIN:
  if(P3_AB > price_middle)              // 2MAX_1MIN
  {
    channel_type = CHANNEL_2MAX_1MIN;

    left_prices[BORDER_UP_INDEX] = price_left;
    right_prices[BORDER_UP_INDEX] = price_right;
        
    left_prices[BORDER_DN_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_DN_INDEX] = left_prices[BORDER_DN_INDEX] + (price_right - price_left);
  }
  else if(P3_AB < price_middle)         // 1MAX_2MIN
  {
    channel_type = CHANNEL_1MAX_2MIN;

    left_prices[BORDER_DN_INDEX] = price_left;
    right_prices[BORDER_DN_INDEX] = price_right;
        
    left_prices[BORDER_UP_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_UP_INDEX] = left_prices[BORDER_UP_INDEX] + (price_right - price_left);
  }
  else
  {
    return( false );                      // channel cannot be drawn (all extremums are on the same line)
  }

left_prices and right_prices sind hierbei die Arrays, die die Preiskoordinaten der neun Linien des Kanals beinhalten. Die Zeitkoordinaten aller Linien des Channels sind bereits bekannt.

Zuerst müssen wir den Koeffizienten der Linienneigung festlegen (vgl.. Formel (2)) koeff_A. Dann müssen wird den Kurswert der AB-Linie in Punkt T3 berechnen (s. Abb. 4). Damit wird ermittelt, welche Art von Channel fürs Zeichnen festgelegt wird - ein Channel, bestimmt durch zwei Maxima und einem Minimum oder durch zwei Minima und einem Maximum. Wir prüfen nach, welcher Punkt auf der Kursachse höher ist - Punkt C oder der Punkt, der die (P3', T3) Koordinaten besitzt. Je nach ihrer Position, legen wir fest, ob der Channel von der ersten oder der zweiten Art ist.

Sobald die Koordinaten zweier Hauptlinien des Channels (obere und untere) bestimmt sind, ist die Berechnung der Koordinaten der anderen sieben Linien kein großes Problem mehr. Wir berechnen beispielsweise die Koordinaten der Mittellinie mittels der Koordinaten der oberen und unteren Channelgrenzen und zwar folgendermaßen:

  left_prices[BORDER_MD_INDEX] = (left_prices[BORDER_DN_INDEX] + left_prices[BORDER_UP_INDEX ]) / 2.0;
  right_prices[BORDER_MD_INDEX] = (right_prices[BORDER_DN_INDEX] + right_prices[BORDER_UP_INDEX]) / 2.0;

Sie nehmen einfach den Durchschnittswert der oberen und unteren Grenzen des Channels.

Indikator zum Zeichnen eines Channels nach festgelegten Extrema- SlideChannel

Gut, die Klasse zum Zeichnen eines Channels haben wir ja bereits. Schreiben wir also jetzt einen Indikator, der die Parameter der Extrema aus den globalen Variablen liest und einen Channel auf das Chart zeichnet. Und so sieht er aus:

Abb. 5 Beispiel eines mit Hilfe von Extrema gezeichneten Channels

Abb. 5 Beispiel eines mit Hilfe von Extrema gezeichneten Channels

Die Information über den gezeichneten Channel wird hier ebenfalls angezeigt - seine Breite, der Abstand (in Punkten) vom aktuellen Kurs zu den Channelgrenzen und seine Mittellinie.

Verknüpfen wir die erforderlichen Libraries:

#include  <TextDisplay.mqh>
#include  <SlideChannelClasses.mqh>

Die erste Library enthält die Klassen zur Organisation des Anzeige von Textinformation auf dem Bildschirm (vgl. Beitrag "Seine eigene Marktbeobachtung mit Hilfe der Standard Library Klassen erzeugen"). Mit ihrer Hilfe zeigen wir die Werte der temporären Extrempunkte an.

Dann fügen wir die Eingabeparameter des Indikator hinzu (ich beschreibe hier nur die wichtigsten):

input string          PrefixString = "MainChannel";
//---------------------------------------------------------------------
input ENUM_TIMEFRAMES  ExtremumTimeFrame = PERIOD_CURRENT;
//---------------------------------------------------------------------
input bool            ShowInfo = true;

Der erste Parameter PrefixString, genau derselbe wie im ExtremumHandSet Indikator, setzt ein Präfix, das zur Namensgebung der globalen Variablen verwendet wird, wenn Extrema gelesen werden. Des weiteren ermöglicht er den Einsatz mehrerer Indikatoren dieser Art auf dem einem einzigen Chart. Das einzige was man dabei machen muss, ist jeweils andere Präfixe einzurichten.

Der Parameter ExtremumTimeFrame setzt einen Zeitrahmen, der zum Lesen der Extrempunkte aus den globalen Variablen verwendet wird. Das ist ein sehr nützlicher Parameter, da mit ihm synchrone Channels zu unterschiedlichen Zeitrahmen gezeichnet werden können. Wenn man z.B. das Extrem bei H1 ansetzt, kann man denselben Channel auf dem Zeitrahmen M5 zeichnen. Dazu muss man nur den Indikator zum Zeichnen von Channels dem M5-Chart hinzufügen und schon werden alle Veränderungen synchron angezeigt.

Der Parameter ShowInfo kontrolliert die Anzeige der Textinformation über die Parameter des Channels auf dem Bildschirm..

Nun müssen die Objekte zur Anzeige der Information und zum Zeichnen des Channels erzeugt werden:

TableDisplay         ChannalDisplay;  // displaying of general information about a channel on the screen
TableDisplay         BordersDisplay;  // displaying information about the borders of a channel on the screen
//---------------------------------------------------------------------
TSlideChannelObject  Channel;         // drawing of a channel

Das Objekt zum Zeichnen eines Channels wird folgendermaßen initialisiert

  Channel.CreateChannel(PrefixString, 0, 0, Symbol(), period_current, curr_left_point, curr_middle_point, 
                        curr_right_point, curr_left_price, curr_middle_price, curr_right_price);
  Channel.SetBorderWidth(BorderWidth );
  Channel.SetmiddleWidth(middleLineWidth);
  Channel.SetUpBorderColor(UpBorderColor);
  Channel.SetDnBorderColor(DnBorderColor);
  Channel.SetmiddleColor(middleLineColor );
  Channel.ShowBorderZone(ShowBorderPercentageLines);
  Channel.BorderZonePercentage( PercentageZoneSize);
  Channel.Showmiddle(ShowmiddleLine);
  Channel.ShowmiddleZone( ShowmiddlePercentageLines);
  Channel.middleZonePercentage(PercentagemiddleZoneSize);

Wie erzeugen hier zunächst einen Channel durch Aufrufen der Methode TSlideChannelObject::CreateChannel, und richten dann die erforderlichen Parameter für die Channellinie ein In welcher Abfolge Sie dies einrichten spielt keine Rolle - Sie können auch umgekehrt vorgehen - zuerst die Parameter einrichten und dann den Channel erzeugen.

Der Parameter period_current ist der Zeitraum, der verwendet wird, wenn Extrema aus den globalen Variablen gelesen werden. Er kann sich vom Zeitraum des aktuellen Charts durchaus unterscheiden.

Sehen wir uns die Hauptfunktionen an, die in diesem Indikator eingesetzt werden. Die erste Funktion GetExtremums wird zum Lesen der Position von Extrema und dem Aktualisieren des Channels, gemäß der erhaltenen Werte, verwendet

void GetExtremums()
{
  double  temp;
  string  name;

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr2");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_middle_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol( ), "_", period_current, "_Price_Extr2");
  if( GlobalVariableGet(name, temp) != false )
  {
    curr_middle_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_price = temp;
  }

//  Update the position of channel:
  Channel.SetExtremums(curr_left_point, curr_middle_point, curr_right_point, 
                       curr_left_price, curr_middle_price, curr_right_price);
}

Für die Aktualisierung des Channels auf dem Bildschirm, verwenden wir die Methode TSlideChannelObject::SetExtremums. sie berechnet die Koordinaten der Channellinien neu und zeichnet den Channel erneut auf den Bildschirm.

Das Video unten zeigt Ihnen ein Beispiel des Zecihnens eines Channels zu unterschiedlichen Zeitrahmen:

Die Reihenfolge, in der Sie die Indikatoren starten, ist egal, doch ist es logisch, zuerst den ExtremumHandSet Indikator zu starten, danach die drei linken Kurskennzeichnungen in Gelb (die Farbe der Kennzeichnungen wird in den Indikator-Parametern eingestellt und Gelb ist noch die Standardeinstellung) und zuletzt den SlideChannel Indikator, der dann den Channel nach spezifizierten Extrema zeichnet.

Damit ein Channel synchron mit den Extrema des ersten Charts gezeichnet wird, sollten Sie den Zeitrahmen im Parameter ExtremumTimeFrame des SlideChannel Indikators auf den gleichen Zeitrahmen des Charts einstellen, auf dem die Extrema eingerichtet sind.

Das ist das Ergebnis der Trennung der Funktion zur Einrichtung der Extrempunkte des Channels von der Funktion, die ihn dann auf dem Terminal-Bildschirm zeichnet.


Fazit

In diesem Beitrag haben wir uns den kompletten Weg betrachtet - vom Einrichten der Position eines Channels auf dem Bildschirm bis zu seiner grafischen Darstellung. Und das war doch alles nicht so kompliziert, oder? Vor allem angesichts der Verwendung der Standardklassen und des Objekt-orientierten Programmierens.

Doch eine Frage bleibt noch: Wie sollen wir Channels für eine Arbeit auf dem Markt einsetzen? Zunächst einmal sind sie zur technischen Analyse des aktuellen Status des Finanzinstruments notwendig. Und zweitens sind sie nach dieser Analyse für sinnvolle Entscheidungen unentbehrlich. Sie helfen Ihnen hier ganz enorm.

Es kann auch ein halbautomatischer Expert Advisor entwickelt werden, der die Grenzen eines Channels im Hinblick auf das Öffnen oder schließen einer Position analysiert. Das kann sowohl beim Durchbrechen einer Grenze oder beim Abprallen von ihr funktionieren. Und genau das ist das Thema des nächsten Beitrags - Die Arbeitsweisen mit einem Channel - Abprall oder Durchbruch.

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

Beigefügte Dateien |
extremumclasses.mqh (12.58 KB)
extremumhandset.mq5 (11.89 KB)
slidechannel.mq5 (13.7 KB)
textdisplay.mqh (15.21 KB)
Erzeugung von Multi-Expert Advisors auf Grundlage von Handelsmodellen Erzeugung von Multi-Expert Advisors auf Grundlage von Handelsmodellen
Der Einsatz des Objekt-orientierten Ansatzes in MQL5 erleichtert die Erzeugung von Multi-Currency/Multi-System und Multi-Timeframe Expert Advisors ganz erheblich. Stellen Sie sich nur vor, Ihr einziger EA handelt mit mehreren Dutzend Handelsstrategien auf allen verfügbaren Instrumenten und allen möglichen Zeitrahmen ! Und zusätzlich kann er im Tester problemlos getestet werden und hat zudem für alle, in dieser Zusammenstellung eingeschlossenen Strategien ein oder mehrere Arbeitssysteme zur Geldverwaltung.
Parallele Berechnungen in MetaTrader 5 Parallele Berechnungen in MetaTrader 5
Seit Anbeginn der Menschheit ist Zeit von unschätzbarem Wert und wir tun alles, um sie nicht unnötig zu vergeuden. In diesem Sinne beschreibt Ihnen dieser Beitrag, wie Sie die Arbeit Ihres Expert Advisors beschleunigen können, wenn Ihr Computer über einen Mutli-Core Prozessor verfügt. Zudem verlangt die Implementierung der vorgeschlagenen Methode keine Kenntnisse anderer Programmiersprachen außer MQL5.
Die Indikatoren der Micro-, Mittel- und Haupttrends Die Indikatoren der Micro-, Mittel- und Haupttrends
Ziel dieses Beitrags ist die Untersuchung der Möglichkeiten von Handelsautomatisierung und ihrer Analyse in Form von Indikatoren und des Expert Advisors, auf Basis einiger Vorschläge aus James Hyerczyks Buch "Pattern, Price & Time: Using Gann Theory in Trading Systems". Ohne jeden Anspruch auf Vollständigkeit untersuchen wir hier nur ein Modell - den ersten Teil der Gann-Theorie.
Design und Implementierung neuer grafischer Benutzerschnittstellen-Widgets auf Grundlage der CChartObject Klasse Design und Implementierung neuer grafischer Benutzerschnittstellen-Widgets auf Grundlage der CChartObject Klasse
Nachdem ich den vorigen Beitrag über eine halb-automatischen Expert Advisor mit grafischer Benutzerschnittstelle verfasst hatte, hat sich herausgestellt, dass es durchaus wünschenswert ist, diese Schnittstelle noch mit einigen neuen Funktionalitäten für komplexere Indikatoren und Expert Advisors aufzupeppen. Nachdem ich mich mit den MQL5 Standard Library-Klassen vertraut gemacht hatte, habe ich neue Widgets implementiert. In diesem Beitrag geht es also um das Design und die Implementierung neuer MQL5 grafischer Benutzerschnittstellen-Widgets, die in Indikatoren und Expert Advisors verwendet werden können, und zwar: CChartObjectSpinner, CChartObjectProgressBar und CChartObjectEditTable.