Universeller ZigZag
Inhaltsverzeichnis
- Einleitung
- Besonderheiten des ZigZag Indikators
- Varianten des Zeichnens des ZigZags
- Einfacher ZigZag basierend auf High/Low
- Einfacher ZigZag basierend auf Close
- Beginn der Erstellung eines universellen ZigZags
- Klasse für Ausgangsdaten
- Klasse für Richtung
- Klasse für Zeichnen
- Zusammenführen der drei Klassen
- Variante für Preischart
- Variante für Price
- Aufruf aus einem Expert Advisors
- Fazit
- Anhang
Einleitung
Der ZigZag Indikator (Abb. 1) ist einer der beliebtesten Indikatoren unter MetaTrader 5 Nutzern. Bislang ist bereits eine Vielzahl von ZigZag-Varianten entwickelt worden. Aber einige von ihnen funktionieren viel zu langsam und sind dadurch für Expert Advisors ungeeignet. Die anderen geben ständig Fehler aus, was deren Verwendung sogar für eine visuelle Beobachtung erschwert. Auch mit den Indikatoren, die schnell und ohne Fehler arbeiten, können Schwierigkeiten bei deren Anwendung für die Entwicklung eines Expert Advisors oder eines anderen Indikators entstehen. Es liegt daran, dass es nicht so einfach ist, Daten aus dem ZigZag Indikator zu bekommen und zu interpretieren.
Abb. 1. Der ZigZag Indikator
Dieser Artikel beschäftigt sich mit der Frage, was für das Zeichnen eines ZigZag-Indikators benötigt wird. Wir betrachten verschiedene Möglichkeiten der Erstellung eines ZigZag, ziehen Schlussfolgerungen und erhalten einen einheitlichen Algorithmus. Basierend auf diesem Algorithmus wird ein universeller Indikator erstellt, der es erlaubt, verschiedene ZigZag-Typen im Eigenschaften-Fenster auszuwählen.
Bei der Erstellung des Indikators wird die objekt-orientierte Programmierung verwendet. Wir erstellen mehrere Basisklassen für verschiedene Phasen der Erstellung des ZigZags, für jede Basisklasse entwickeln wir mehrere Kindklassen. Die Unterteilung in Basis- und Kinderklassen wird durchgeführt, um die Erstellung neuer Varianten von ZigZag möglichst zu vereinfachen.
Neben dem Zeichnen des ZigZags beschäftigt sich der Artikel mit der Verwendung des resultierenden Indikators bei der Entwicklung anderer Indikatoren und Expert Advisors. Unsere Aufgabe besteht darin, sicherzustellen, dass das Erhalten der Daten von ZigZag und dessen Verwendung als Bestandteil eines anderen Algorithmus unkompliziert und nicht zeitaufwendig ist.
Besonderheiten des ZigZag Indikators
Der ZigZag Indikator (Abb. 1) stellt eine gebrochene Linie dar, die lokale Hochs und Tiefs verbindet. Anfänger mögen sofort denken: wäre es großartig, bei Tiefs zu verkaufen und bei Hochs zu kaufen! Natürlich ist dieser Gedanke sehr verlockend, aber leider sieht der ZigZag nur in der Historie so aus. In der Wirklichkeit ist die Situation ein bisschen anders. Es ist erst nach einigen Balken deutlich, ob sich ein neues Tief oder Hoch gebildet hat. Auf der Abb. 2 ist eine Situation zu sehen, wenn sich das letzte Segment des Indikators gebildet hat (sich nicht mehr ändert), der Preis sich umgekehrt hat und in die entgegengesetzte Richtung bewegt (nach oben).
Abb. 2. Der ZigZag ist nach unten gerichtet, der Preis hat sich nach oben umgekehrt
Aber nach einigen Balken fällt der Preis (Abb. 3) und das letzte Segment des ZigZags geht weiter nach unten.
Abb. 3. Der Preis hat die Bewegung nach unten fortgesetzt und das letzte Segment des ZigZags bildet sich weiter
Diesmal hat der Indikator sein Tief erreicht, aber das können wir erst einige Balken später überprüfen (Abb. 4).
Abb. 4. Nach 10 Balken hat der Zigzag ein neues Segment nach oben gezeichnet und die Bildung eines Tiefs wurde festgestellt
Diese Besonderheiten des ZigZags erklärt am besten die Abbildung 5. Mit farbigen Punkten sind die Balken markiert, auf welchen das Entstehen eines vorherigen Hochs oder Tiefs festgestellt wurde. Auf den Balken mit dem blauen Punkt begann der Indikator neue Segmente nach oben zu zeichnen, auf den Balken mit den roten Punkten — neue Segmente nach unten.
Abb. 5. Mit roten und blauen Punkten sind die Balken markiert, auf welchen eine Umkehr des ZigZags deutlich wurde
Trotz dieser Besonderheit bleibt der ZigZag Indikator beliebt und attraktiv. Er erleichtert die visuelle Analyse von Charts, hilft das Rauschen auszufiltern und bestimmt die Hauptrichtung der Preisbewegung. In der Praxis kann der Indikator für die Ermittlung von Unterstützungs/Widerstandslevels und Erkennung von Mustern verwendet werden. Darüber hinaus kann man Trendlinien sowie beliebige grafische Werkzeuge der technischen Analyse wie Fibonacci Levels, Fächer u.a basierend auf dem ZigZag Indikator zeichnen. Man kann nicht alles auflisten, für was alles Trader ZigZag anwenden.
Varianten des Zeichnens des ZigZags
Der ZigZag hat zwei Zustände: entweder ist er nach oben gerichtet oder nach unten. Wenn die Linie des Indikators nach oben gerichtet ist, überwachen wir den Preis, um zu prüfen, ob ein neues Hoch entsteht. Wenn die Linie nach unten gerichtet ist, warten wir auf das Entstehen eines neuen Tiefs. Darüber hinaus müssen wir prüfen, ob die Bedingungen für die Änderung der Richtung erfüllt sind. Das heißt, für das Zeichnen eines ZigZag müssen wir:
- Ausgangsdaten erhalten,
- Bedingungen für die Änderung der Richtung der Linie definieren und
- das Entstehen neuer Hochs und Tiefs überwachen.
Die Ausgangsdaten können eine einfache Reihe, z.B. Schlusskurs eines Balkens, oder zwei Reihen, z.B. Hoch- und Tiefkurse des Balkens, darstellen. Wenn eine Datenreihe verwendet wird, kann das nicht nur der Schlusskurs sein, sondern auch praktisch jeder Indikator, egal ob Oszillator oder Gleitender Durchschnitt. Beim Zeichnen eines ZigZags anhand von Daten eines Indikators kann man zwei Datenreihen verwenden: eine mit den Daten des Indikators, der nach High-Preisen gebildet wurde, und die andere - nach Low-Preisen.
Die Bedingungen für die Änderung der Richtung stellen den wichtigsten Punkt dar, der verschiedene Typen von ZigZag definiert. Diese Bedingungen können ganz unterschiedlich sein. Als solche Bedingung kann zum Beispiel das Entstehen eines Hochs/Tiefs über n-Balken auf dem aktuellen Balken dienen. Mit anderen Worten: wenn der Wert der Ausgangsreihe auf dem aktuellen Balken über die letzten n Balken minimal oder maximal ist, definiert das die Richtung des ZigZags. Nach diesem Prinzip funktioniert der klassische ZigZag Indikator. Eine andere Methode basiert auf dem Wert des Rücksetzers von dem festgestellten maximalen oder minimalen Wert. Der Rücksetzer kann in Punkten (wenn die Datenreihe ein Preis ist) oder in Einheiten (wenn es um einen Indikator geht) gemessen werden. Die verfügbaren Methoden beschränken sich nicht auf diese zwei, die Richtung kann basierend auf jedem Indikator bestimmt werden — Stohastic, ADX usw. Wenn Stochastic über 50 liegt, heißt das, dass der ZigZag nach oben gerichtet ist, wenn unter 50 — nach unten. Versuchen wir nun die Richtung nach ADX zu bestimmen: die ZigZag-Linie ist nach oben gerichtet, wenn die PDI-Linie oberhalb der MDI-Linie liegt, wenn umgekehrt ist die ZigZag-Linie nach unten gerichtet.
Wir können eine Vielzahl verschiedener ZigZags bekommen, indem wir verschiedene Varianten für den Punkt 1 und Punkt 2 miteinander kombinieren. Es hindert uns nichts daran, für den Punkt 1 die Daten von RSI zu nutzen und die Richtung basierend auf dem Stohastic Indikator zu bestimmen usw. Der Punkt 3 wird nur dafür benötigt, damit der Indikator als ein ZigZag aussieht, obwohl unterschiedliche Varianten der grafischen Darstellung möglich sind.
Da unser Ziel ist es, einen universellen Indikator zu erstellen, müssen wir den Algorithmus möglichst sorgfältig in Teile teilen: ein Teil wird für alle ZigZags identisch sein (bezeichnen wir ihn als gemeinsamen Teil), der andere hängt vom ZigZag-Typ ab (bezeichnen wir ihn als individuellen Teil). Im individuellen Teil werden die Indikatorpuffer mit Ausgangsdaten gefüllt: Preisdaten oder Indikatordaten, ein weiterer Puffer (der die Richtung der ZigZag-Linie bestimmt) wird mit den Werten 1 oder -1 gefüllt. Diese drei Puffer werden dem gemeinsamen Teil übergeben, der auf ihrer Basis den Indikator zeichnet.
Erstellen wir einen separaten Indikator, der nach High/Low Preisen des Balkens arbeitet und seine Richtung basierend auf dem High/Low des n-Balkens ändert.
Einfacher ZigZag basierend auf High/Low
Erstellen Sie einen neuen Indikator im MetaEditor (Hauptmenü — Datei — Erstellen oder Strg+N). Geben Sie den Namen iHighLowZigZag ein, erstellen Sie den externen Parameter period (Typ int, Wert 12), wählen Sie den Event-Handler OnCalculate(...,open,high,low,close) aus, erstellen Sie einen Puffer mit dem Namen ZigZag (Typ Section, Farbe Red) und noch drei Puffer mit den Namen Direction, LastHighBar und LastLowBar (Typ line, Farbe none).
Der Puffer ZigZag wird für die Anzeige des ZigZags verwendet, die anderen sind Hilfspuffer. Ändern Sie den Typ INDICATOR_DATA zu INDICATOR_CALCULATIONS für alle Hilfspuffer beim Aufruf der Funktion SetIndexBuffer() in der Funktion OnInit(). Im oberen Teil der Datei ändern Sie den Wert der Eigenschaft indicator_plots: setzen Sie 1. Danach wird der Indikator nur den Puffer ZigZag zeichnen und es wird keine überflüssigen Linien auf dem Chart geben, zusätzliche Puffer können dabei über die Funktion iCustom() aufgerufen werden.
Zuerst wird der Index des Balkens, mit welchem die Berechnung (die Variable start) beginnt, in der Funktion OnCalculate() berechnet, so dass die Berechnung aller Balken nur beim Start des Indikators durchgeführt wird und dass danach nur jede neue Bar berechnet wird. Darüber hinaus initialisieren wir die Elemente der Puffer:
if(prev_calculated==0)
{ // beim Start
// Initialisieren der Elemente der Puffer
DirectionBuffer[0]=0;
LastHighBarBuffer[0]=0;
LastLowBarBuffer[0]=0;
start=1; // Berechnung mit den nächsten Elementen nach den initialisierten Elementen beginnen
}
else
{ // während der Arbeit
start=prev_calculated-1;
}
}
Nun die Hauptschleife des Indikators:
{
Wie bereits oben erwähnt, muss der Code in zwei Teile geteilt werden: Bestimmung der Richtung des ZigZags und das Zeichnen. Halten wir uns an dieses Prinzip auch jetzt. Schreiben wir zuerst den Code für die Bestimmung der Richtung. Dafür verwenden wir die Funktionen ArrayMaximum() und ArrayMinimum(). Wenn ein Hoch oder Tief auf dem zu berechnenden Balken erkannt wird, wird dem Element des Direction-Puffers der Wert 1 oder -1 zugewiesen. Damit man Informationen über die aktuelle Richtung auf jedem Balken hat, nehmen wir den Wert des vorherigen Elements des Puffers Direction und weisen wir ihn dem aktuellen Element zu:
// den Wert der vorher ermittelten Richtung
DirectionBuffer[i]=DirectionBuffer[i-1];
// Berechnung des anfänglichen Balkens für die Funktionen
// ArrayMaximum() und ArrayMinimum()
int ps=i-period+1;
// Erkennen der Balken mit dem Hoch und Tief in einem
// Bereich von 'period' Balken
int hb=ArrayMaximum(high,ps,period);
int lb=ArrayMinimum(low,ps,period);
// wenn ein Hoch oder Tief erkannt wurde
if(hb==i && lb!=i)
{ // Hoch erkannt
DirectionBuffer[i]=1;
}
else if(lb==i && hb!=i)
{ // Tief erkannt
DirectionBuffer[i]=-1;
}
Bitte achten Sie auf den letzten Codeteil: hier wird ein Hoch oder ein Tief ermittelt und es wird überprüft, ob es auf dem aktuellen Balken ein Hoch und kein Tief gibt, oder umgekehrt: ob ein Tief, aber kein Hoch vorhanden ist. Manchmal gibt es sehr lange Balken, auf welchen beide Richtungen erkannt werden. In diesem Fall befindet sich die vorher ermittelte Richtung im Puffer Direction.
Es ist möglich, einen ZigZag im MetaTrader5 Terminal zu erstellen, der vertikale Linien zeichnet, so kann man zwei Richtungsänderungen auf einem Balken darstellen. Aber in diesem Artikel betrachten wir solche ZigZag-Indikatoren nicht.
Setzen wir die Beschreibung des Codes in der Hauptschleife fort: das nächste Fragment wird für das Zeichnen der ZigZag-Linie verantwortlich sein. Mit den anderen Puffern gehen wir genauso wie mit dem Puffer Direction vor:
LastLowBarBuffer[i]=LastLowBarBuffer[i-1];
In diesen Puffern befinden sich die Daten über Balkenindexe mit dem letzten Hoch und Tief des ZigZags. Die Indexe dieser Balken werden für das Zeichnen des Indikators benötigt, darüber hinaus erleichtern diese Puffer den Aufruf des ZigZags von einem Expert Advisors aus. Wir müssen nicht über die Balken in einer Schleife iterieren, um das letzte Hoch zu finden.
Stellen Sie sicher, dass der ZigZag Puffer gelöscht wurde:
Das muss getan werden, denn der Indikator wird nicht nur beim Start, sondern auch bei anderen Events komplett berechnet, z.B. beim Laden der Historie. Es kann alte Daten im Puffer geben, die die Darstellung der Indikatorlinie verzerren würden.
Nun kommen wir zum Zeichnen. Hier wird der Algorithmus in vier Zweige geteilt: Beginn einer neuen Bewegung nach oben, Beginn einer neuen Bewegung nach unten, Fortsetzung einer Bewegung nach oben, Fortsetzung einer Bewegung nach unten. Für die Überprüfung der Werte der Richtung für den zu berechnenden und den vorherigen Balken verwenden wir die Operatoren switch:
{
case 1:
switch((int)DirectionBuffer[i-1])
{
case 1:
// Fortsetzung einer Bewegung nach oben
...
break;
case -1:
// Beginn einer neuen Bewegung nach oben
...
break;
}
break;
case -1:
switch((int)DirectionBuffer[i-1])
{
case -1:
// Fortsetzung einer Bewegung nach unten
...
break;
case 1:
// Beginn einer neuen Bewegung nach unten
...
break;
}
break;
Es bleibt noch vier Codebblöcke zu schreiben. Gehen wir ausführlich auf zwei von ihnen ein: Beginn einer neuen Bewegung nach oben und Fortsetzung der Bewegung nach oben. Eine neue Bewegung nach oben beginnt, wenn sich der Wert im Puffer Direction von -1 auf 1 ändert. Dabei zeichnen wir einen neuen ZigZag-Punkt und speichern den Balkenindex, auf welchem die neue Richtung begonnen hat:
LastHighBarBuffer[i]=i;
Die Fortsetzung einer Bewegung ist ein wenig komplizierter. Es wird überprüft, ob der Wert auf dem aktuellen Balken größer als der vorher bekannte Hoch des ZigZags ist. Wenn der Wert größer ist, muss das Ende des letzten Segments verschoben werden, d. h. der vorher gezeichnete Punkt muss gelöscht werden und ein neuer Punkt muss gesetzt werden. Hier speichern wir auch die Informationen über den Balken, auf welchem der neue Punkt gezeichnet wurde:
if(high[i]>high[(int)LastHighBarBuffer[i]])
{ // neues Hoch
// Löschen des alten ZigZag-Punktes
ZigZagBuffer[(int)LastHighBarBuffer[i]]=EMPTY_VALUE;
// Setzen eines neuen Punktes
ZigZagBuffer[i]=high[i];
// Index des Balkens mit dem neuen Hoch
LastHighBarBuffer[i]=i;
}
Das war es. Vergessen Sie nicht, die schließende geschweifte Klammer am Ende der Schleife zu setzen. Es bleibt nur den Indikator im visuellen Modus im Strategietester zu testen. Der voll funktionsfähige Indikator
Einfacher ZigZag basierend auf Close
Nun passen wir den eben erstellten Indikator der Arbeit basierend auf dem Close Preis an. Wir müssen nicht alles von Anfang an schreiben: speichern wir den Indikator iHighLowZigZag unter dem Namen iCloseZigZag und ersetzten wir den Aufruf der Arrays High und Low durch den Aufruf des Arrays Close. Man könnte glauben, das war es, aber das Testen zeigt, dass der Indikator inkorrekt läuft (Abb. 6).
Abb. 6. Inkorrekte Arbeit des ZigZags basierend auf Close, konvertiert vom ZigZag basierend auf High/Low
Schauen wir uns genauer an, warum das passiert. Wenn der High-Preis des aktuellen Balkens ein Hoch innerhalb eines bestimmten Bereichs von Balken gebildet hat, bleibt dieses Hoch, unabhängig davon, wie sich der Schlusspreis des Balkens ändert, das Maximum. Wenn der Schlusspreis ein Hoch gebildet hat, kann er sich ändern, während sich der Balken weiter formt, und das Hoch hört auf zu existieren. Beim Ermitteln eines neuen Hochs/Tiefs der gleichen Richtung wird der alte Punkt gelöscht. Und hier werden wir mit einem Problem konfrontiert. Das neue Hoch entsteht nicht, der neue Punkt wurde gelöscht, aber der alte wurde ebenso gelöscht. D. h. wir müssen die Position des alten Punktes wiederherstellen. Die Information über die letzten Extrema befindet sich in den Puffern LastHighBarBuffer und LastLowBarBuffer. Aus diesen Puffern stellen wir zwei letzte Punkte wieder her. Fügen wir der Hauptschleife des Indikators zwei Codezeilen vor dem switch Operator hinzu:
ZigZagBuffer[(int)LastLowBarBuffer[i]]=close[(int)LastLowBarBuffer[i]];
Nach dieser Modifizierung beginnt der Indikator richtig zu funktionieren. Der resultierende Indikator iCloseZigZag ist im Anhang zu finden.
Beginn der Erstellung eines universellen ZigZags
Die Universalität des ZigZags wird erreicht, indem drei Aufgaben separat gelöst werden:
- Füllen von Puffern mit Ausgangsdaten. Es werden zwei Puffer verwendet. Sie werden benötigt, damit man sie mit High- und Low-Preisen füllen kann. Um einen ZigZag basierend auf Close oder einen anderen Indikator zu erhalten, müssen beide Puffer mit den gleichen Werten gefüllt werden.
- Der Puffer Direction wird basierend auf der Analyse der Ausgangsdaten gefüllt.
- Zeichnen des ZigZags.
Jede Aufgabe wird mithilfe einer separaten Basisklasse und zusätzlichen Kindklassen gelöst, was die Auswahl verschiedener Varianten und deren Kombinationen über das Eigenschaften-Fenster des Indikators ermöglicht.
Klasse für Ausgangsdaten
Erstellen Sie die include-Datei CSorceData.mqh und fügen Sie der Datei die Klasse CSorceData hinzu. Das wird die Basisklasse sein. Sie wird die virtuelle Methode Calculate beinhalten, die der Funktion OnCalculate() des Indikators ähnlich ist, allerdings einige Änderungen aufweist. Der Methode werden zwei zusätzliche Arrays übergeben: BufferHigh[] und BufferLow[]. Diese Puffer werden mit Daten gefüllt, basierend auf welchen der ZigZag später berechnet wird. Da nicht nur der Preis als Ausgangsdaten dienen kann, sondern auch die Werte eines beliebigen Indikators, heißt das, dass man das Laden des Indikators kontrollieren muss. Dafür fügen wir die virtuelle Methode CheckHandle() (vom Typ bool) hinzu:
{
private:
public:
virtual int Calculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[],
double &BufferHigh[],
double &BufferLow[])
{
return(0);
}
virtual bool CheckHandle()
{
return(true);
}
};
Nun erstellen wir einige Kindklassen. Eine für High/Low:
{
private:
public:
int Calculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[],
double &BufferHigh[],
double &BufferLow[])
{
int start=0;
if(prev_calculated!=0)
{
start=prev_calculated-1;
}
for(int i=start;i<rates_total;i++)
{
BufferHigh[i]=high[i];
BufferLow[i]=low[i];
}
return(rates_total);
}
};
Die zweite — für den Close-Preis. Sie unterscheidet sich von der ersten Kindklasse nur durch den Code in der Schleife:
{
BufferHigh[i]=close[i];
BufferLow[i]=close[i];
}
Diese Klasse heißt CClose:public CSorceData. Die Methode CheckHandle() wird noch nicht verwendet.
Erstellen wir auch ein paar Klassen, um Daten von Indikatoren zu erhalten. Wählen wir Indikatoren mit unterschiedlicher Anzahl von Parametern und unterschiedlicher Position aus (im Preischart oder in einem separaten Fenster) — RSI und Moving Average. Schreiben wir unsere Klassen für sie.
Erstellen wir eine Klasse für RSI, benennen wir sie CRSI:public CSorceData. Zu private fügen wir eine Variable für den Handle des Indiaktors hinzu:
int m_handle;
Fügen wir einen Konstruktor hinzu: dem Konstruktor werden die Parameter von RSI übergeben, hier wird der Indikator geladen:
{
m_handle=iRSI(Symbol(),Period(),period,price);
}
Die Methode CheckHandle():
{
return(m_handle!=INVALID_HANDLE);
}
In der Methode Calculate verwenden wir keine Schleifen, kopieren wir einfach die Puffer:
if(prev_calculated==0)
{
to_copy=rates_total;
}
else
{
to_copy=rates_total-prev_calculated;
to_copy++;
}
if(CopyBuffer(m_handle,0,0,to_copy,BufferHigh)<=0)
{
return(0);
}
if(CopyBuffer(m_handle,0,0,to_copy,BufferLow)<=0)
{
return(0);
}
return(rates_total);
Bitte beachten Sie, wenn das Kopieren (Aufruf der Funktion CopyBuffer()) fehlgeschlagen ist, gibt die Methode 0 zurück, bei einem erfolgreichen Kopieren — rates_total. Das wurde extra dafür getan, damit man später die Möglichkeit hat, den Indikator neu zu berechnen, wenn das Kopieren nicht erfolgreich war.
Auf die gleiche Weise erstellen wir eine Klasse mit dem Namen CMA:public CSorceData für den Gleitenden Durchschnitt. Unterschiede gibt es nur im Konstruktor:
{
m_handle=iMA(Symbol(),Period(),period,shift,method,price);
}
Die Methoden Calculate() sind in diesem Fall absolut gleich, aber für andere Indikatoren kann es einige Unterschiede geben, insbesondere mit den Nummern der Puffer. Die voll funktionsfähige Datei CSorceData.mqh ist im Anhang zu finden. Es ist jedoch anzumerken, dass die Datei nur vorläufig als "voll funktionsfähig" bezeichnet werden kann. Denn sie muss noch durch das Hinzufügen neuer Kindklassen für andere Indikatoren erweitert werden.
Klasse für Richtung
Die Klasse befindet sich in der Datei CZZDirection.mqh, die Basisklasse heißt CZZDirection. Die Klasse beinhaltet eine virtuelle Methode Calculate(). Der Methode werden die Parameter übergeben, die es erlauben, Balken für die Berechnung (die Variablen rates_total und prev_calculated), Puffer mit den Ausgangsdaten und einen Puffer für Richtung zu bestimmen. Früher haben wir bereits erwähnt, dass die Richtung des ZigZags nach einem Indikator bestimmt werden kann, deswegen fügen wir die Möglichkeit hinzu, Indikatoren zu verwenden. Fügen wir die virtuelle Methode CheckHandle() hinzu:
{
private:
public:
virtual int Calculate(const int rates_total,
const int prev_calculated,
double &BufferHigh[],
double &BufferLow[],
double &BufferDirection[])
{
return(0);
}
virtual bool CheckHandle()
{
return(true);
}
};
Nun schreiben wir eine Kindklasse für die Bestimmung der Richtung wie im Indikator iHighLowZigZa. Um die Richtung basierend auf dieser Methode zu bestimmen, wird der Paramter period benötigt, deswegen fügen wir zu private die Variable m_period und den Konstruktor mit dem Parameter der Periode hinzu:
{
private:
int m_period;
public:
void CNBars(int period)
{
m_period=period;
}
int Calculate(const int rates_total,
const int prev_calculated,
double &BufferHigh[],
double &BufferLow[],
double &BufferDirection[]
)
{
int start;
if(prev_calculated==0)
{
BufferDirection[0]=0;
start=1;
}
else
{
start=prev_calculated-1;
}
for(int i=start;i<rates_total;i++)
{
BufferDirection[i]=BufferDirection[i-1];
int ps=i-m_period+1;
int hb=ArrayMaximum(BufferHigh,ps,m_period);
int lb=ArrayMinimum(BufferLow,ps,m_period);
if(hb==i && lb!=i)
{ // Hoch erkannt
BufferDirection[i]=1;
}
else if(lb==i && hb!=i)
{ // Tief erkannt
BufferDirection[i]=-1;
}
}
return(rates_total);
}
Wir erstellen eine weitere Kindklasse für die Bestimmung der Richtung basierend auf dem CCI Indikator. Wenn CCI über Null liegt, ist der ZigZag nach oben gerichtet, wenn unter Null — nach unten:
{
private:
int m_handle;
public:
void CCCIDir(int period,ENUM_APPLIED_PRICE price)
{
m_handle=iCCI(Symbol(),Period(),period,price);
}
bool CheckHandle()
{
return(m_handle!=INVALID_HANDLE);
}
int Calculate(const int rates_total,
const int prev_calculated,
double &BufferHigh[],
double &BufferLow[],
double &BufferDirection[]
)
{
int start;
if(prev_calculated==0)
{
BufferDirection[0]=0;
start=1;
}
else
{
start=prev_calculated-1;
}
for(int i=start;i<rates_total;i++)
{
BufferDirection[i]=BufferDirection[i-1];
double buf[1];
if(CopyBuffer(m_handle,0,rates_total-i-1,1,buf)<=0)return(0);
if(buf[0]>0)
{
BufferDirection[i]=1;
}
else if(buf[0]<0)
{
BufferDirection[i]=-1;
}
}
return(rates_total);
}
};
Dem Konstuktor der Klasse werden die Parameter von CCI übergeben, dann wird der Indikator geladen. Es wird die Methode CheckHandle() verwendet, die nach der Erstellung eines Objekts aufzurufen ist. In der Hauptschleife wird CCI geprüft und der Puffer BufferDirection wird gefüllt.
Die Datei CZZDirection.mqh ist im Anhang zu finden.
Klasse für Zeichnen
Es gibt verschiedene Varianten, den ZigZag zu zeichnen. Man kann ihn mit einer Linie einzeichnen, er kann gefärbt werden, man kann Punkte bei Hochs setzen usw. In diesem Artikel betrachten wir nur eine Methode, aber erstellen auch eine Basisklasse und Kindklassen für den Fall einer weiteren Entwicklung. Die Klasse befindet sich in der Datei CZZDraw.mqh, der Name der Klasse ist CZZDraw. Die Klasse beinhaltet eine virtuelle Methode Calculate() mit den gleichen Parametern wie bei der Klasse Direction. Darüber hinaus, werden der Klasse drei Arrays für den ZigZag übergeben: BufferLastHighBar (für den Index des letzten Hochs), BufferLastLowBar (für den Index des letzten Tiefs) und BufferZigZag (der ZigZag selbst).
{
private:
public:
virtual int Calculate(const int rates_total,
const int prev_calculated,
double &BufferHigh[],
double &BufferLow[],
double &BufferDirection[],
double &BufferLastHighBar[],
double &BufferLastLowBar[],
double &BufferZigZag[]
)
{
return(0);
}
};
{
private:
public:
virtual int Calculate(const int rates_total,
const int prev_calculated,
double &BufferHigh[],
double &BufferLow[],
double &BufferDirection[],
double &BufferLastHighBar[],
double &BufferLastLowBar[],
double &BufferZigZag[]
)
{
int start;
if(prev_calculated==0)
{
BufferLastHighBar[0]=0;
BufferLastLowBar[0]=0;
start=1;
}
else
{
start=prev_calculated-1;
}
for(int i=start;i<rates_total;i++)
{
BufferLastHighBar[i]=BufferLastHighBar[i-1];
BufferLastLowBar[i]=BufferLastLowBar[i-1];
BufferZigZag[i]=EMPTY_VALUE;
BufferZigZag[(int)BufferLastHighBar[i]]=BufferHigh[(int)BufferLastHighBar[i]];
BufferZigZag[(int)BufferLastLowBar[i]]=BufferLow[(int)BufferLastLowBar[i]];
switch((int)BufferDirection[i])
{
case 1:
switch((int)BufferDirection[i-1])
{
case 1:
if(BufferHigh[i]>BufferHigh[(int)BufferLastHighBar[i]])
{
BufferZigZag[(int)BufferLastHighBar[i]]=EMPTY_VALUE;
BufferZigZag[i]=BufferHigh[i];
BufferLastHighBar[i]=i;
}
break;
case -1:
BufferZigZag[i]=BufferHigh[i];
BufferLastHighBar[i]=i;
break;
}
break;
case -1:
switch((int)BufferDirection[i-1])
{
case -1:
if(BufferLow[i]<BufferLow[(int)BufferLastLowBar[i]])
{
BufferZigZag[(int)BufferLastLowBar[i]]=EMPTY_VALUE;
BufferZigZag[i]=BufferLow[i];
BufferLastLowBar[i]=i;
}
break;
case 1:
BufferZigZag[i]=BufferLow[i];
BufferLastLowBar[i]=i;
break;
}
break;
}
}
return(rates_total);
}
};
Wir werden nicht ausführlich auf diese Klasse eingehen, sie wurde bereits in den Kapiteln "Einfacher ZigZag basierend auf High/Low" und "Einfacher ZigZag basierend auf Close" beschrieben. Die Datei CZZDraw.mqh ist im Anhang zu finden.
Zusammenführen der drei Klassen
Es bleibt nur einen Indikator unter Verwendung der drei oben erstellten Klassen zu schreiben. Die Klasse der Ausgangsdaten erlaubt es, Preisdaten und Daten des RSI Indikators zu verwenden, der in der Regel in einem Unterfenster läuft. Aber während die Preisdaten in einem Unterfenster angezeigt werden können, kann der RSI Indikator nicht im Preischart angezeigt werden. Das heißt wir werden einen Indikator für das Unterfenster erstellen.
Erstellen Sie einen neuen Indikator im MetaEditor (Hauptmenü — Datei — Erstellen oder Strg+N). Im Wizard geben Sie den Namen iUniZigZagSW ein, erstellen Sie den externen Parameter period (Typ int, Wert 12), wählen Sie den Event-Händler OnCalculate(...,open,high,low,close) und erstellen Sie die folgenden Puffer:
Name | Stil | Farbe |
---|---|---|
High | Line | Green |
Low | Line | Green |
ZigZag | Section | Red |
Direction | Line | none |
LastHighBar | Line | none |
LastLowBar | Line | none |
Nach der Erstellung des neuen Indikators beziehen wir drei Dateien mit den Klassen mit ein:
#include <CZZDirection.mqh>
#include <CZZDraw.mqh>
Der Indikator muss Parameter für die Auswahl des Typs von Ausgangsdaten und des Typs für die Bestimmung der Richtung haben. Dafür erstellen wir zwei Aufzählungen:
{
Src_HighLow=0,
Src_Close=1,
Src_RSI=2,
Src_MA=3
};
enum EDirection
{
Dir_NBars=0,
Dir_CCI=1
};
Erstellen wir zwei externe Parameter für diese Typen:
input EDirection DirSelect=Dir_NBars;
Für die Ausgangsdaten von RSI und MA werden entsprechende Parameter wie für den CII Indikator benötigt. Fügen wir diese hinzu:
input ENUM_APPLIED_PRICE RSIPrice = PRICE_CLOSE;
input int MAPeriod = 14;
input int MAShift = 0;
input ENUM_MA_METHOD MAMethod = MODE_SMA;
input ENUM_APPLIED_PRICE MAPrice = PRICE_CLOSE;
input int CCIPeriod = 14;
input ENUM_APPLIED_PRICE CCIPrice = PRICE_TYPICAL;
Des Weiteren brauchen wir einen Parameter für die Bestimmung der Richtung über n Balken:
Und jetzt etwas Interessanteres — drei Pointers in Übereinstimmung mit den Typen der Basisklassen (unterhalb der externen Parameter):
CZZDirection * dir;
CZZDraw * zz;
In der Funktion OnInit laden wir die entsprechenden Kindklassen in Übereinstimmung mit den Variablen SrcSelect und DirSelect. Zuerst SrcSelect:
{
case Src_HighLow:
src=new CHighLow();
break;
case Src_Close:
src=new CClose();
break;
case Src_RSI:
src=new CRSI(RSIPeriod,RSIPrice);
break;
case Src_MA:
src=new CMA(MAPeriod,MAShift,MAMethod,MAPrice);
break;
}
Nach dem die Klassen geladen worden sind, überprüfen wir den Handle:
{
Alert("Fehler beim Laden des Indikators");
return(INIT_FAILED);
}
Anschließend DirSelect:
{
case Dir_NBars:
dir=new CNBars(ZZPeriod);
break;
case Dir_CCI:
dir=new CCCIDir(CCIPeriod,CCIPrice);
break;
}
Prüfung des Handles:
{
Alert("Fehler beim Laden des Indikators 2");
return(INIT_FAILED);
}
Die dritte Klasse:
In der Funktion OnDeinit() löschen wir die Objekte:
{
if(CheckPointer(src)==POINTER_DYNAMIC)
{
delete(src);
}
if(CheckPointer(dir)==POINTER_DYNAMIC)
{
delete(dir);
}
if(CheckPointer(zz)==POINTER_DYNAMIC)
{
delete(zz);
}
}
Und schließlich der letzte Schliff — die Funktion OnCalculate(). Die Methoden Calculate() der Klassen CSorceData und CZZDirection können 0 zurückgeben, deswegen überprüfen wir das Ergebnis. Im Fall eines Fehlers (0 erhalten), geben wir auch 0 zurück, damit der Indikator beim nächsten Tick komplett neu berechnet wird:
rv=src.Calculate(rates_total,
prev_calculated,
time,
open,
high,
low,
close,
tick_volume,
volume,
spread,
HighBuffer,
LowBuffer);
if(rv==0)return(0);
rv=dir.Calculate(rates_total,
prev_calculated,
HighBuffer,
LowBuffer,
DirectionBuffer);
if(rv==0)return(0);
zz.Calculate(rates_total,
prev_calculated,
HighBuffer,
LowBuffer,
DirectionBuffer,
LastHighBarBuffer,
LastLowBarBuffer,
ZigZagBuffer);
return(rates_total);
Der Indikator iUniZigZagSW ist im Anhang zu finden.
Variante für Preischart
Der resultierende Indikator beinhaltet alle vorher erstellten Varianten, sowohl mit den Ausgangsdaten, die dem Preischart entsprechen, als auch für das Unterfester. Es wäre praktisch, den ZigZag auch im Preischart zu sehen. Dafür müssen wir die Datenquelle RSI opfern. Machen wir eine Kopie des Indikators mit dem Namen iUniZigZag, ändern wir die Eigenschaft indicator_separate_window zu indicator_chart_window, löschen wir die Variante Src_RSI aus der Aufzählung ESorce und die Variante mit RSI aus der Funktion OnInit(), und wir erhalten eine Variante für den Preischart. Der Indikator iUniZigZag ist im Anhang zu finden.
Variante für Price
Es ist möglich, Indikatoren für das MetaTrader Terminal zu erstellen, die nicht basierend auf strikt vorgegebenen Ausgangsdaten, sondern basierend auf einem beliebigen Indikator im Chart laufen. Beim Hinzufügen eines solchen Indikators zum Chart oder zum Unterfenster durch den Parameter "Anwenden auf" wählen Sie "Daten des vorherigen Indikators" oder "Daten des ersten Indikators" aus. Ändern wir den Indikator iUniZigZagSW so, dass man ihn auf einen anderen Indikator anwenden könnte. Speichern wir den Indikator unter dem Namen iUniZigZagPriceSW und löschen wir alles, was zur Klasse CSorceData gehört, ändern wir den Typ der Funktion OnCalculate, und am Anfang der Funktion schreiben wir eine Schleife für das Füllen der Puffer HighBuffer und LowBuffer mit den Werten des Arrays price:
const int prev_calculated,
const int begin,
const double &price[]
)
{
int start;
if(prev_calculated==0)
{
start=0;
}
else
{
start=prev_calculated-1;
}
for(int i=start;i<rates_total;i++)
{
HighBuffer[i]=price[i];
LowBuffer[i]=price[i];
}
int rv;
rv=dir.Calculate(rates_total,
prev_calculated,
HighBuffer,
LowBuffer,
DirectionBuffer);
if(rv==0)return(0);
zz.Calculate(rates_total,
prev_calculated,
HighBuffer,
LowBuffer,
DirectionBuffer,
LastHighBarBuffer,
LastLowBarBuffer,
ZigZagBuffer);
return(rates_total);
}
Auf die gleiche Weise kann man eine Variante erstellen, die basierend auf Price im Preischart funktioniert. Dafür reicht es, die Eigenschaft indicator_separate_window des Indikators iUniZigZagPriceSW durch indicator_chart_window zu ersetzen. Der Indikator iUniZigZagPriceSW sowie der Indikator iUniZigZagPrice, die Variante basierend auf Price für den Preischart, sind im Anhang zu finden.
Aufruf aus einem Expert Advisors
Gewöhnlich wird beim Aufruf des ZigZags von einem Expert Advisor aus nach dem letzten Hoch oder Tief in einer Schleife gesucht, es wird über Balken iteriert, die Werte im Puffer, welcher den ZigZag zeichnet, werden geprüft. Das alles zusammen funktioniert sehr langsam. Der in diesem Artikel entwickelte ZigZag hat zusätzliche Puffer, die es erlauben, die benötigten Daten schnell zu erhalten. Der Puffer DirectionBuffer beinhaltet die Daten über die Richtung des letzten ZigZag-Segments. Die Puffer LastHighBarBuffer und LastLowBarBuffer beinhalten die Indexe der Balken, auf welchen das letzte Hoch und das letzte Tief markiert sind. Wenn man den Indexbalken beim Zählen von einer Seite und die Anzahl von Balken kennt, kann man den Balkenindex beim Zählen von der anderen Seite berechnen (im Indikator wird von links nach rechts gezählt, in der Funktion CopyBuffer() - von rechts nach links). Wenn man den Balkenindex hat, kann man den ZigZag-Wert auf diesem Balken erhalten.
Um die Daten des Indikators zu erhalten, kann man den folgenden Code verwenden. Experimentieren wir mit dem Indikator iUniZigZagSW. Laden wir den Indikator in der Funktion OnInit():
DirSelect,
RSIPeriod,
RSIPrice,
MAPeriod,
MAShift,
MAMethod,
MAPrice,
CCIPeriod,
CCIPrice,
ZZPeriod);
// Richtung
double dir[1];
if(CopyBuffer(handle,3,0,1,dir)<=0)
{
Print("Fehler beim Erhalten von Daten des ZigZags");
return;
}
if(dir[0]==1)
{
cs=cs+"Richtung: nach oben";
}
if(dir[0]==-1)
S {
cs=cs+"Richtung: nach unten";
}
Comment(cs,"\n",GetTickCount());
Nun erhalten wir die Werte von ein paar letzten Hochs/Tiefs. Wenn die Linie des Inidkators nach oben gerichtet ist, erhalten wir den Balkenindex des letzten Hochs aus dem Puffer LastHighBarBuffer. Danach berechnen wir den Balkenindex, von links nach rechts gezählt. Unter Verwendung dieses Index erhalten wir den Wert des Puffers ZigZagBuffer. Man kann auch weiter gehen: für den gleichen Balken, auf welchem wir den ZigZag-Wert erhalten haben, können wir den den Wert aus dem Puffer LastLowBarBuffer erhalten. Das wird der Balkenindex des vorherigen Tiefs sein. Und so weiter. Indem wir LastHighBarBuffer und LastLowBarBuffer abwechselnd aufrufen, können wir die Daten über alle Hochs und alle Tiefs der Indikatorlinie sammeln. Unten ist ein Codebeispiel für das Erhalten von zwei letzten Punkten des ZigZag-Indikators, wenn er nach oben gerichtet ist, angeführt:
{
// Balkenindex des letzten Hochs, von Null nach links gezählt
if(CopyBuffer(handle,4,0,1,lhb)<=0)
{
Print("Fehler beim Erhalten von Daten des ZigZags 2");
return;
}
// Balkenindex
ind=bars-(int)lhb[0]-1;
// der ZigZag-Wert auf dem Balken ind
if(CopyBuffer(handle,2,ind,1,zz)<=0)
{
Print("Fehler beim Erhalten von Daten des ZigZags 3");
return;
}
//===
// Index des Tiefs, das diesem Hoch vorausgeht
if(CopyBuffer(handle,5,ind,1,llb)<=0)
{
Print("Fehler beim Erhalten von Daten des ZigZags 4");
return;
}
// Balkenindex, von Null nach rechts gezählt
ind=bars-(int)llb[0]-1;
// der ZigZag-Wert auf dem Balken ind
if(CopyBuffer(handle,2,ind,1,zz1)<=0)
{
Print("Fehler beim Laden von Daten des ZigZags 5");
return;
}
cs=cs+"\n"+(string)zz1[0]+" "+(string)zz[0];
}
else if(dir[0]==-1)
{
}
Das vollständige Beispiel ist im Expert Advisor unter dem Namen eUniZigZagSW im Anhang zu finden. Der Expert Advisor gibt eine Meldung über die Richtung des ZigZags als Kommentar aus und als zweite Zeile - zwei Zahlen mit den Werten von zwei letzten Punkten des ZigZags (Abb. 7). Die dritte Zeile ist eine Zahl, die die Funktion GetTickCount() zurückgibt, damit man sieht, dass der Expert Advisor läuft.
Abb. 7. Der Expert Advisor gibt einen Kommentar in der linken oberen Ecke aus
Natürlich kann man die Daten über die letzten zwei Punkte des Indikators von den Puffern LastHighBarBuffer und LastLowBarBuffer erhalten, indem man seine Werte dem Null-Balken oder dem ersten Balken entnimmt, aber der Sinn dieses Beispiels besteht darin, die Daten von einer beliebigen Anzahl von ZigZag-Punkten konsequent zu extrahieren.
Aufruf von einem anderen Indikator aus
Wenn ein Indikator basierend auf einem anderen Indikator erstellt werden muss, ist es im Fall des ZigZags einfacher, nicht den Indikator über iCustom() aufzurufen, sondern eine Kopie von ihm zu machen und diese zu modifizieren. In einigen Fällen kann solcher Ansatz angemessen sein (in Bezug auf die Schnelligkeit und Einfachheit der Modifizierung), in den anderen aber nicht (in Bezug auf eine erneute Anwendung und die Universalität des Codes). Auf die Indikatoren, die in diesem Artikel erstellt werden, kann man über die Funktion iCustom bei der Entwicklung anderer Indikatoren zugreifen.
An sich ist ein ZigZag basierend auf historischen Daten ist nicht mehr das, was er während der Bildung dieser Historie war, aber wir haben die Puffer LastHighBarBuffer und LastLowBarBuffer, in welchen die Daten über vorläufige Zustände des ZigZags gespeichert werden. Für die Veranschaulichung schreiben wir einen Indikator, der Pfeile bei der Änderung der Richtung der Indikatorlinie einzeichnet (Änderung des Wertes von DirectionBuffer) und der Punkte auf den Balken markiert, auf welchen neue Hochs/Tiefs des ZigZags festgestellt wurden (Änderung der Werte der Puffer LastHighBarBuffer und LastLowBarBuffer). Wir werden auf den Code dieses Indikators nicht ausführlicher eingehen, sie können ihn im Anhang unter dem Namen iUniZigZagSWEvents. Die Ansicht des Indikators ist auf der Abbildung 8 dargestellt.
Abb. 8. Der Indikator iUniZigZagSWEvents
Fazit
Da der Artikel schließlich ein Lehrstoff und keine fertigen Lösungen bietet, haben alle im Artikel erstellten Indikatoren einen minimalen Set von Ausgangsdaten und Typen für die Bestimmung der Richtung. Die Erstellung der Indikatoren wird allerdings sehr ausführlich betrachtet. Nachdem Sie den Artikel gelesen haben, sollten Sie in der Lage sein, die benötigten Kindklassen selbständig zu erstellen. Wenn man versucht, einen absolut universellen Indikator zu schreiben, wird man mit Schwierigkeiten nicht so sehr bei der Entwicklung, sondern vielmehr bei der Anwendung konfrontiert. Beim Hinzufügen verschiedener Indikatoren als Ausgangsdaten für die Bestimmung der Richtung müssen zum Eigeschaften-Fenster Parameter dieser Indikatoren hinzugefügt werden. Letztendlich wird die Anzahl dieser Parameter zu groß und es wird sehr unbequem sein, einen solchen Indikator zu nutzen. Es wird ergonomischer, separate Indikatoren zu erstellen, indem man die im Artikel erhaltenen universellen Klassen verwendet.
Anhang
- iHighLowZigZag.mq5 — einfacher ZigZag basierend auf High/Low.
- iCloseZigZag.mq5 — einfacher ZigZag basierend auf Close.
- CSorceData.mqh — Klasse für die Auswahl von Ausgangsdaten.
- CZZDirection.mqh — Klasse für die Bestimmung der Richtung des ZigZags.
- CZZDraw.mqh — Klasse für das Zeichnen des ZigZags.
- iUniZigZagSW.mq5 — universeller ZigZag für das Unterfenster.
- iUniZigZag.mq5 — universeller ZigZag für den Preischart.
- iUniZigZagPriceSW.mq5 — universeller ZigZag basierend auf Price für das Unterfester.
- iUniZigZagPrice.mq5 — universeller ZigZag basierend auf Price für den Preischart.
- eUniZigZagSW — Beispiel für den Aufruf des Indikators iUniZigZagSW mithilfe der Funktion iCustom() aus einem Expert Advisor.
- iUniZigZagSWEvents — Beispiel für die Erstellung eines anderen Indikators mit dem Aufruf des Indikators iUniZigZagSW mithilfe der Funktion iCustom().
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2774
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Leider ist die deutsche Übersetzung grauenhaft, teilweise gänzlich unverständlich.
"Im jeder Bar Informationen über die aktuelle ZickZack-Richtung zu haben, bevor eine bestimmte Richtung gefunden wird, nehmen wir den Wert aus dem vorherigen Pufferelement Direction und ordnen ihn auf das aktuelle Element zu"
Da braucht's schon sehr viel Fantasie dazu das zu verstehen, was da gemeint ist.
Leider ist die deutsche Übersetzung grauenhaft, teilweise gänzlich unverständlich.
"Im jeder Bar Informationen über die aktuelle ZickZack-Richtung zu haben, bevor eine bestimmte Richtung gefunden wird, nehmen wir den Wert aus dem vorherigen Pufferelement Direction und ordnen ihn auf das aktuelle Element zu"
Da braucht's schon sehr viel Fantasie dazu das zu verstehen, was da gemeint ist.
Man wundert sich das die so arm sind nicht mal EINEN Übersetzer im Team zu haben.
Teilweise sind sogar die Codes selber Übersetzt ....geht natürlich dann voll in die Hose.
Und das lassen sie auf ihre GELDGEBER zu .....man man man ....
Gruß