English Русский 中文 Español 日本語 Português
Wolfe Wellen

Wolfe Wellen

MetaTrader 5Beispiele | 26 Mai 2017, 17:24
2 506 0
Dmitry Fedoseev
Dmitry Fedoseev

Inhalt

Einleitung

Die Wolfe Wellen stellen ein Muster der grafischen Analyse dar, die von Bill Wolfe erkannt und ausführlich beschrieben wurde. Das Muster ähnelt einem Dreieck oder einem Keil (Bill Wolfe selbst hat es als einen Aufwärtskeil bezeichnet), weist aber einige Nuancen auf. Die von Bill Wolfe vorgeschlagene Methode hilft nicht nur ein Muster zu erkennen und somit den Moment und die Richtung des Einstiegs zu bestimmen, sondern auch das Ziel vorherzusagen, das der Preis erreichen muss sowie die Zeit des Erreichens.

In diesem Artikel werden die Regeln der Erkennung der Wolfe Wellen ausführlich betrachtet. Basierend auf dem ZigZag Indikator aus dem Artikel "Universeller ZigZag" erstellen wir einen Indikator für die automatische Erkennung und Anzeige der Wellen auf dem Chart. Zusätzlich wird ein einfacher Expert Advisor erstellt, der nach Signalen des erhaltenen Indikator handelt. Dies ermöglicht es, die Arbeit des Indikators zu überprüfen und die ersten Eindrücke von der von Bill Wolfe vorgeschlagenen Methode der grafischen Analyse zu bekommen. 

Regeln der Erkennung der Wolfe Wellen

Betrachten wir die Wolfe Wellen am Beispiel des Kaufs (Abb. 1). Der Preis bildet zwei fallende Tiefs (blaue Linie, Punkte 1 und 3) und zwei fallende Hochs (Punkte 2 und 4). Nach der Umkehr und der Bildung eines Tiefs im Punkt 4 fällt der Preis weiter, wenn der Preis die Linie 1—3 bildet, wird gekauft (Punkt 5). 


Abb. 1. Wolfe-Wollen für den Kauf. Die blaue Linie — Preis, die rote Linie — Linie für die Bestimmung des Ziels. Einstieg im Punkt 5, Ziel — Punkt 7

Der Punkt 6, der Schnittpunkt der Linien 1-3 und 2-4, bestimmt die Zeit des Erreichens des Ziels. Der Wert des Ziels (Punkt 7) wird als Schnittpunkt der Linie 1-4 und der vertikalen Linie definiert, gezogen durch den Punkt 6. Es gibt keinen konkreten Algorithmus für die Berechnung des Stoploss Levels außer einer allgemeinen Empfehlung, Stoploss auf eigenem Ermessen zu verwenden. Hier enden die Regeln der Erkennung der Wolfe Wellen, die im Buch von Bill Wolfe erläutert wurden. 

Bei der Entwicklung des Indikators für diesen Artikel wurden einige weitere Regeln hinzugefügt.

  1.  Der Punkt 3 muss deutlich unterhalb des Punktes 1 liegen, dies wird durch die Überprüfung der Bedingung erreicht:

    v3<v1-d1

    wobei:

    • v3 — Level des Punktes 3;
    • v1 — Level des Punktes 1;
    • d1 — vertikaler Abstand zwischen den Punkten 1 und 2 (Strecke 1''-2''), multipliziert mit dem Koeffizienten K1 (K1 — ein Parameter im Fenster der Eigenschaften, standardmäßig gleich 0.1).

  2. Die Linie 1-4, die das Ziel bestimmt, muss nach oben gerichtet werden, d.h. der Punkt 4 muss oberhalb des Punktes 1 liegen. Dafür wird die Bedingung überprüft:

    v4>v1+d1;

    wobei v4 — Level des Punktes 4. 

  3. Der Punkt 4 muss deutlich unterhalb des Punktes 2 liegen, dies wird durch die Überprüfung der Bedingung erreicht:

    v4<v2-d2;

    wobei v2 — Level des Punktes 2, d2 — vertikaler Abstand zwischen den Punkten 2 und 3 (Strecke 2''-3''), multipliziert mit dem Koeffizienten K2 (Koeffizient K2 — ein Parameter im Fenster der Eigenschaften, standardmäßig gleich 0.1).

  4. Die Linien 2-4 und 1-3, die die Zeit des Erreichens des Ziels bestimmen, müssen sich rechts schneiden, dafür muss dir Höhe 2-2' deutlich höher als die Höhe 4-4' sein. Die Erfüllung dieser Bedingung wird durch die folgende Überprüfung realisiert:

    h2-h4>K3*h2;

    wobei h2 — Länge der Strecke 2-2', h4 — Länge der Strecke 4-4', K3 — Koeffizient (der Koeffizient K3 ist ein Parameter im Fenster der Eigenschaften, standardmäßig gleich 0.1).

Die dargelegten Regeln beanspruchen nicht, absolut richtig und perfekt zu sein. Weiter beschäftigt sich der Artikel mit dem Prozess der Erstellung des Indikators. Der Indikator wird so erstellt, dass Sie nach dem Lesen des Artikels den Code entsprechend Ihren Vorstellungen und Ideen selbst korrigieren können. 

Auswahl des ZigZags für Erweiterung

Vor dem Beginn der Arbeit laden wir den Anhang zum Artikel "Universeller ZigZag" herunter, welcher verschiedene Varianten des ZigZags beinhaltet. Wählen wir eine Variante aus. Die Varianten iUniZigZagPrice und iUniZigZagPriceSW schließen wir gleich aus, denn diese sind für die Berechnung des ZigZags von einem anderen Indikator auf dem Chart vorhergesehen, deswegen sind sie nur für visuelle Darstellung und Analyse hilfreich. Von Interesse sind andere Indikatoren, die man für die Erstellung von Expert Advisors verwenden kann. Wir schließen auch die Varianten iCloseZigZag und iHighLowZigZag aus, denn diese sind nur einleitende Beispiele für die Erstellung eines ZigZags. Es bleiben zwei Varianten übrig: iUniZigZag und iUniZigZagSW. iUniZigZagSW, der in einem Unterfenster läuft, wird bevorzugt, weil er mehr Möglichkeiten hat. Im Anhang ist aber auch der Indikator iUniZigZagSWEvents vorhanden, der ein Beispiel für die Verwendung der Funktion iCustom() für den Aufruf des Indikators iUniZigZagSW darstellt. Bleiben wir bei dieser Variante, denn sie erlaubt es uns, alle Möglichkeiten des Indikators iUniZigZagSW zu nutzen und den Code der Erkennung der Wolfe Wellen vom Code des ZigZags zu trennen.

Der iUniZigZagSWEvents Indikator wird im Preischart angezeigt, für das Zeichnen verwendet er vier Puffer: zwei mit Pfeilen und zwei mit Punkten. Gerade das, was man für die Erkennung der Wolfe Wellen braucht. Mit Pfeilen werden die Stellen der Erkennung der Muster markiert, und mit Punkten — die Ziel-Ebenen. Der Indikator zeichnet die Wellen und alle Konstruktionen für die Bestimmung des Ziels mithilfe grafischer Objekte, darunter der Trendlinie. Wenn man sie als eine Strecke zeichnet, ohne den Strahl zu verlängern, passt sie sehr gut für die Anzeige verschiedener Konstruktionen.  

Da die Wolfe Wellen nicht nur für die Erkennung des Moments und der Richtung des Einstiegs, sondern auch für die Vorhersage des Ziels verwendet werden, treten bei der Anwendung des iUniZigZagSW Indikators einige Schwierigkeiten auf. Der Indikator hat den Parameter SrcSelect für die Auswahl der Quelle der zu analysierenden Daten, nach welchen der ZigZag gezeichnet wird. Man kann eine der vier Varianten auswählen:

  • Src_HighLow — nach high und low Preisen;
  • Src_Close — nach close Preisen;
  • Src_RSI — nach dem RSI Indikator;
  • Src_MA — nach dem MA Indikator.

Auf Basis des im Artikel erstellten Indikator wird ein Expert Advisor entwickelt. Wenn man den ZigZag nach Preis zeichnet, kann man für das Setzen des Takeprofit Levels das vorhergesagte Ziel verwenden. Das Ziel wird problemlos im Chart angezeigt. Wenn man den Zigzag nach RSI (SrcSelect=Src_RSI) zeichnet, wird das Ziel nicht für den Preis, sondern für den RSI Indikator sein. D.h. wenn der RSI Indikator den Zielwert erreicht, muss man Positionen zum Marktpreis schließen, und das wichtigste ist dabei, dass der Zielpreis und die Konstruktionen auf dem Chart nicht angezeigt werden können.

Bei der Verwendung des ZigZags nach Preis (Src_HighLow oder Src_Close) werden auf dem Chart der Zielpreis und die Konstruktionen angezeigt, in übrigen Fällen wird nur der Pfeil angezeigt, der das erkannte Muster und ihre Richtung kennzeichnet. Der Zielwert wird im entsprechenden Indikator-Puffer enthalten sein (um Positionen zum Marktpreis zu schließen, oder für andere Zwecke), er wird aber unsichtbar sein.

Höchstwahrscheinlich kann die Idee des Schließens zum Marktpreis in der Praxis nicht umgesetzt werden. Es liegt daran, dass die meisten Indikatoren ihre Werte in einem begrenzten Bereich ändern, und das Ergebnis der Konstruktion diesen Bereich überschreiten kann. Nichtsdestotrotz wird der Puffer den Zielwert beinhalten.

Sammeln von Daten über ZigZag-Spitzen

Fangen wir mit der Erstellung des Indikators an. Öffnen wir die Datei iUniZigZagSWEvents im Editor und speichern ihn unter dem Namen iWolfeWaves, die Arbeit wird in dieser Datei weitergeführt.

Es wäre sehr praktisch, direkten Zugang zu allen Hochs des Zigzags zu haben, so dass man sie nicht jedes Mal in der Historie suchen soll. Erstellen wir ein Array für das Abspeichern der Hochs. Nun wird bei der Änderung der Richtung des ZigZags dem Array ein neues Element hinzugefügt. Wenn der Indikator einfach die letzte Strecke verlängern wird (Extremum aktualisieren), wird das letzte Element des Arrays aktualisiert.

Für jede Spitze werden wir ihren Wert, Richtung und den Index des Balkens, auf welchem sie liegt, abspeichern (Nummerierung der Indexe von links nach rechts). Dafür verwenden wir eine Struktur aus drei Feldern:

struct SPeackTrough{#
   double   Val; // Wert
   int      Dir; // Richtung
   int      Bar; // Index des Balkens
};

Erstellen wir ein Array für diese Strukturen:

SPeackTrough PeackTrough[];

Hätten wir nur den ZigZag nach high und low (SrcSelect gleich Src_HighLow) verwendet, wäre es bei der Änderung der Richtung des Zigzags ausreichend gewesen, das Arrays zu vergrößern, Werte zu setzen und das letzte Element je nach Verlängerung der letzten Strecke des Indikators zu aktualisieren. Mit dem Zigzag nach close-Preis (SrcSelect gleich Src_Close) oder nach Daten eines anderen Indikators sieht das ganze komplizierter aus. Während sich der Balken bildet, auf welchem der Zigzag seine Richtung geändert hat, kann der ZigZag in den ursprünglichen Zustand zurückgesetzt werden (Zustand vor dem Eröffnen des sich bildenden Balkens). Dies bedeutet, bei jeder neuen Berechnung eines und desselben Balkens muss das Array für die Spitzen in den ursprünglichen Zustand, den er auf dem vorherigen Balken hatte, zurückgesetzt werden. Wenn man häufig die Größe des Arrays ändert, kann dies die allgemeine Performance des Indikators reduzieren. Deswegen führen wir eine zusätzliche Variable, in welcher die verwendete Größe des Arrays gespeichert wird, und das Array selbst wird blockweise vergrößert wenn nötig. Vor einer erneuten Berechnung eines Balkens werden wir den Ausgangswert dieser Variablen zurückgeben.

Für das Abspeichern der Größe des Arrays verwenden wir zwei Variablen, in einer von denen die Größe des Arrays auf dem vorherigen Balken und in der anderen die Größe des Arrays auf dem berechneten Balken gespeichert werden:

int PreCount; // die Größe des PeackTrough Arrays auf dem vorherigen Balken
int CurCount; // die Größe des PeackTrough Arrays auf dem berechneten Balken

Nachdem ein Balken gebildet wurde, und seine Berechnung abgeschlossen ist, oder der nächste Balken in der Historie berechnet wurde, muss der Wert aus der Variablen CurCount in die Variable PreCount übertragen werden. Vor jeder Berechnung eines neuen sich bildenden Balkens, übertragen wir den Wert aus der Variablen PreCount in die Variable CurCount. In allen Berechnungen wird nur die CurCount Variable verwendet, die PreCount Variable ist eine Hilfsvariable. Wir können erfahren, dass die Bildung eines Balkens abgeschlossen ist, nur wenn sich der nächste Balken öffnet (oder bei der Berechnung des nächsten Balkens in der Historie). Die Erscheinung eines neuen Balkens wird nach Zeit bestimmt. Wenn sich die Zeit des Balkens geändert hat, heißt das dass ein neuer Balken erschien ist (oder dass die Berechnung des nächsten Balkens in der Historie angefangen hat). Für die Ermittlung des neuen Balkens wird eine Hilfsvariable benötigt:

datetime LastTime;

Die Variablen PreCount, LastCount und LastTime sind globale Variablen des Indikators. Sie können auch als statische in der Funktion OnCalculate() des Indikators deklariert werden. 

Nun gehen wir auf die Funktion OnCalculate() ein. Nach dem Wert der Variablen prev_calculated wird festgelegt, ob die erste Berechnung des Indikators oder die Berechnung nur neuer Balken durchgeführt wird. Null bedeutet eine vollständige Berechnung, dabei müssen die Variablen PreCount, CurCount und LastTime initialisiert werden. Der nächste Code, der den Bereich der Balken für die Berechnung festlegt und die Hilfsvariablen initialisiert, ist in der Funktion OnCalculte() ganz oben:

int start; // Variable für den Index des ersten berechneten Balken

if(prev_calculated==0){ // Berechnung aller Balken 
   start=1;
   CurCount=0;   
   PreCount=0;
   LastTime=0;
}
else{ // Berechnung des neuen Balkens
   start=prev_calculated-1;
}

Beschäftigen wir uns nun mit der Standardschleife des Indikators. Lassen wir am Anfang der Schleife die Übertragung der Werte in den Variablen PreCount und CurCount stehen:

for(int i=start;i<rates_total;i++){

   if(time[i]>LastTime){ // die erste Berechnung des neuen (nächsten) Balkens
      LastTime=time[i];
      PreCount=CurCount;
      PreDir=CurDir;
   }
   else{ // erneute Berechnung des Balkens
      CurCount=PreCount;
      CurDir=PreDir;
   }

In allen Berechnungen wird nur die Variable CurCount verwendet, die Variable PreCount dient ausschließlich zur Aufrechterhaltung eines aktuellen Wertes in der Variablen CurCount. Beim Eröffnen eines neuen Balkens beinhaltet die Variable CurCount den Wert, der infolge der Berechnung des vorherigen Balkens erhalten wurde. Deswegen übertragen wir ihren Wert in die Variable PreCount. Infolge der Berechnung eines neuen Balkens in der Variablen CurCount kann sich der Wert ändern, aber ob dieser endgültig ist, kann man nur beim Eröffnen des nächsten Balkens erfahren. Deswegen wird bei einer erneuten Berechnung eines und desselben Balkens der Variablen CurCount der Wert der Variablen PreCount übergeben.

Weiter muss der folgende Code des Indikators iUniZigZagSWEvents in der Hauptschleife stehen:

UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;

UpDotBuffer[i]=EMPTY_VALUE;
DnDotBuffer[i]=EMPTY_VALUE;      

// Richtung

double dir[2];
if(CopyBuffer(handle,3,rates_total-i-1,2,dir)<=0){
   return(0);
}
if(dir[0]==1 && dir[1]==-1){
   DnArrowBuffer[i]=high[i];
   c++;

}
else if(dir[0]==-1 && dir[1]==1){
   UpArrowBuffer[i]=low[i];
   c++;
}

// neues Maximum

double lhb[2];
if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){
   return(0);
}
if(lhb[0]!=lhb[1]){
   UpDotBuffer[i]=high[i];
}

// neues Minimum

double llb[2];
if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
   return(0);
}
if(llb[0]!=llb[1]){
   DnDotBuffer[i]=low[i];
}  

Den Teil des Codes, der Pfeile zeichnet, brauchen wir nicht mehr, löschen wir ihn.

Der Indikator wird alle Änderungen des ZigZags verfolgen, nicht nur die Änderung der Richtung, sondern auch jede Verlängerung der letzten Strecke, weil die letzte Strecke für die Bestimmung des Punktes 5 benötigt wird (s. Abb. 1). Dafür wird ein Teil des Codes verwendet, das vom Zeichnen neuer Extrema im Abschnitt oben übrig geblieben ist.

Für die Verfolgung der Richtung des ZigZags und die Bestimmung der Momente des Wechsels werden weitere Variablen benötigt, die den Variablen CurCount und PreCount ähnlich sind: das sind die Variablen PreDir und CurDir:

int PreDir; // die Richtung des ZigZags auf dem vorherigen Balken
int CurDir; // die Richtung des Zigzags auf dem Balken, der berechnet wird

Sie können auch sowohl global als auch statisch in der Funktion OnCalculate() sein. Am Anfang der Berechnung des Indikators müssen sie auch initialisiert werden, und ihre Werte müssen bei jeder Berechnung des Balkens übertragen werden, alles wie mit den Variablen PreCount und CurCount. Unten ist der komplette Code der Funktion OnCalculate() für diese Phase der Erstellung des Indikators angeführt:

int OnCalculate(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[])
  {

   int start; // die Variable für den ersten Balken der Berechnung
   
   if(prev_calculated==0){ // vollständige Berechnung
      start=1; 
      CurCount=0;
      PreCount=0;
      CurDir=0;
      PreDir=0;      
      LastTime=0;
   }
   else{ // Berechnung nur der neuen  Balken
      start=prev_calculated-1;
   }

   // Hauptschleife des Indikators
   for(int i=start;i<rates_total;i++){
   
      if(time[i]>LastTime){ // neuer Balken
         LastTime=time[i];
         PreCount=CurCount;
         PreDir=CurDir;
      }
      else{ // erneute Berechnung des Balkens
         CurCount=PreCount;
         CurDir=PreDir;
      }

      // Löschen der Puffer, die Pfeile und Punkte zeichnen
      
      UpArrowBuffer[i]=EMPTY_VALUE;
      DnArrowBuffer[i]=EMPTY_VALUE;
      
      UpDotBuffer[i]=EMPTY_VALUE;
      DnDotBuffer[i]=EMPTY_VALUE;    
      
      // Hilfsvariablen
      
      double hval[1];
      double lval[1];
      
      double zz[1];
      
      // neues Maximum
      
      double lhb[2];
      // erhalten wir zwei Puffer-Elemente mit den Indexen der Balken der neuen Maxima
      if(CopyBuffer(handle,4,rates_total-i-1,2,lhb)<=0){ 
         return(0);
      }
      if(lhb[0]!=lhb[1]){ // es gibt ein neues Maximum
         // den Wert des Preismaximums erhalten (oder der Daten, nach welchen der ZigZag berechnet wird)
         if(CopyBuffer(handle,0,rates_total-i-1,1,hval)<=0){
            return(0);
         }      
         if(CurDir==1){ // die letzte bekannte Richtung nach oben 
            // aktualisieren wir die Daten über den letzten Punkt des ZigZags
            RefreshLast(i,hval[0]);
         }
         else{ // der ZigZag hat die Richtung geändert
               // fügen wir einen neuen Wert hinzu
            AddNew(i,hval[0],1);
         }
         // Überprüfung der Bedingungen für die Erkennung der Wolfe Wellen nach unten  
      }
      
      // neues Minimum
      
      double llb[2];
      // erhalten wir zwei Elemente des Puffers mit den Indices der Balken der neuen Minima
      if(CopyBuffer(handle,5,rates_total-i-1,2,llb)<=0){
         return(0);
      }
      if(llb[0]!=llb[1]){ // es gibt ein neues Minimum
         // den Wert des Preisminimums erhalten (oder der Daten, nach welchen der ZigZag berechnet wird)
         if(CopyBuffer(handle,1,rates_total-i-1,1,lval)<=0){
            return(0);
         }         
         if(CurDir==-1){ //die letzte bekannte Richtung nach unten
            // aktualisieren wir die Daten über den letzten Punkt des ZigZags
            RefreshLast(i,lval[0]);
         }
         else{ // der ZigZag hat die Richtung geändert
            // fügen wir einen neuen Wert hinzu
            AddNew(i,lval[0],-1);
         }
         // Überprüfung der Bedingungen für die Erkennung der Wolfe Wellen nach oben 
      }      
   }   
   return(rates_total);
}

In diesem Code sehen wir die Funktionen AddNew() und RefreshLast(). Beiden Funktionen wird der Index des Balkens, auf welchem sich der ZigZag geändert hat, sowie der Wert des neuen Maximums oder Minimums übergeben, der Funktion AddNew() wird darüber hinaus die Richtung des ZigZags übergeben.

Die Funktion des Hinzufügens des neuen Punktes AddNew():

void AddNew(int i,double v,int d){
   if(CurCount>=ArraySize(PeackTrough)){ // keine freien Elemente im Array
      ArrayResize(PeackTrough,ArraySize(PeackTrough)+1024); // Vergrößern des Arrays
   }
   PeackTrough[CurCount].Dir=d; // Setzen der Richtung
   PeackTrough[CurCount].Val=v; // Setzen des Wertes
   PeackTrough[CurCount].Bar=i; // Setzen des Balkens
   CurCount++; // Vergrößern der Variablen mit der Anzahl der aktiven Elemente des Arrays   
   CurDir=d; // Abspeichern der letzten Richtung des Zigzags
} 

Die Funktion der Aktualisierung des letzten Punktes RefreshLast():

void RefreshLast(int i,double v){
   PeackTrough[CurCount-1].Bar=i; // Setzen eines neuen Balkens
   PeackTrough[CurCount-1].Val=v; // Setzen eines neuen Wertes
} 

Bei diesem Schritt kann man den Indikator speichern, in der Zukunft werden wir ihn als Grundlage bei der Entwicklung von Indikatoren für die Erkennung verschiedener Muster des ZigZags verwenden. Im Anhang hat der Indikator den Namen "iWolfeWaves_Step_1".

Ein wenig Geometrie

Bei der Erkennung der Wolfe Wellen und beim Zeichnen der Konstruktionen, die das Ziel definieren, werden Grundkenntnisse der Geometrie benötigt. Betrachten wir diese Aufgaben einzeln und schreiben wir Funktionen für ihre Lösung.

Aufgabe 1. Die gerade Linie ist durch das Punktepaar x-y definiert, wobei х — Index des Balkens, у — Wert (Preis oder Indikatorwert). Die x-Koordinate des dritten Punktes ist vorgegeben, der Wert der Linie in diesem Punkt muss ermittelt werden (Abb. 2).


Abb. 2. Vorgegeben: X1, Y1, X2, Y2, X3. Y3 muss ermittelt werden.

Lösung der Aufgabe 1. Ermitteln wir den Inkrement der Linie auf der Y-Achse je Einheit des Inkrements auf der X-Achse:

D=(Y2-Y1)/(X2-X1)

Wobei D — Inkrement, Y1 — Wert des Preises oder des Indikators im Punkt 1, Y2 — Wert des Preises oder des Indikators im Punkt 2, X1 — Index des Balkens im Punkt 1, X2 — Index des Balkens im Punkt 2.  

Ermitteln wir Y3:

Y3=Y1+(X3-X1)*D

Wobei X3 — Index des Balkens im Punkt3, Y3 — gesuchter Wert der Linie im Punkt 3.

Wir erhalten die folgende Funktion:

double y3(double x1,double y1,double x2,double y2,double x3){
   return(y1+(x3-x1)*(y2-y1)/(x2-x1));
}

Der Funktion werden die folgenden Parameter übergeben:

  • x1 — Index des Balkens im Punkt 1;
  • y1 — Wert im Punkt 1;
  • x2 — Index des Balkens im Punkt 2;
  • y2 — Wert im Punkt 2.

Aufgabe 2. Zwei Linien werden durch zwei Punktepaare х-у definiert. Man muss die x-Koordinate ihres Schnittpunktes ermitteln (Abb. 3). Hier kann sich die Frage stellen, warum gerade die x-Koordinate? Das ist irrelevant, nach dem Erhalten der x-Koordinate wird die y-Koordinate des Punktes 3 berechnet (unter Verwendung der Gleichung einer der Geraden). Deswegen kann man zuerst die y-Koordinate des Punktes 3, und dann nach der Gleichung einer der Geraden ihre x-Koordinate berechnen.  


Abb. 3. Zwei Linien sind vorgegeben, man muss ihren Schnittpunkt ermitteln


Zunächst einmal verwenden wir die Koordinaten der zwei Punkte und erhalten die Gleichungen der Linien als y=a+b*x.

Führen wir vorläufige Berechnungen durch. Die Steigung der Linie (in Einheiten auf der y-Achse je Einheit auf der x-Achse):

D1=(Y12-Y11)/(X12-X11)

Wobei D1 — gesuchter Steigungswert der ersten Linie (Änderung des Wertes der Linie um einen Balken), X11 — Index des Balkens im Punkt 1 der ersten Linie, X12 — Index des Balkens im Punkt 2 der ersten Linie, Y11 — Wert der ersten Linie im Punkt 1, Y12 — Wert der ersten Linie im Punkt 2.     

Die Steigung der Linie 2:

D2=(Y22-Y21)/(X22-X21)

Wobei D2 — gesuchter Steigungswert der zweiten Linie (Änderung des Wertes der Linie um einen Balken), X21 — Index des Balkens im Punkt 1 der zweiten Linie, X22 — Index des Balkens im Punkt 2 der zweiten Linie, Y21 — Wert der zweiten Linie im Punkt 1, Y22 — Wert der zweiten Linie im Punkt 2.

Nun erhalten wir die Gleichung der Linien. Gleichung der Linie 1:

Y3=Y11+D1*(X3-X11)

Wobei Y3 — Wert der Linie im Schnittpunkt (Punkt 3), X3 — Index des Balkens im Punkt 3.

Gleichung der Linie 2:

Y3=Y21+D2*(X3-X21)

Im Schnittpunkt sind die Werte der Linien gleich, deswegen setzen wir die Gleichung der Linie 1 und die Gleichung der Linie 2 gleich:

Y11+D1*(X3-X11)=Y21+D2*(X3-X21);

Verwenden wir die erhaltene Gleichung, um X3 auszudrücken. Als Ergebnis erhalten wir die Funktion TwoLinesCrossX() für die Ermittlung der x-Koordinate der Schneidung zweier Linie:

double TwoLinesCrossX(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(y22-y21)/(x22-x21);
   double k1=(y12-y11)/(x12-x11);
   return((y11-y21-k1*x11+k2*x21)/(k2-k1));
}

Der Funktion werden die folgenden Parameter übergeben:

  • x11 — Index des Balkens im Punkt 1 der ersten Linie;
  • y11 — Wert im Punkt 1 der ersten Linie;
  • x12 — Index des Balkens im Punkt 2 der ersten Linie;
  • y12 — Wert im Punkt 2 der ersten Linie;
  • x21 — Index des Balkens im Punkt 1 der zweiten Linie;
  • y21 — Wert im Punkt 1 der zweiten Linie;
  • x22 — Index des Balkens im Punkt 2 der zweiten Linie;
  • y22 — Wert im Punkt 2 der zweiten Linie.

Nachdem die x-Koordinate des Schnittpunktes der zwei Linien ermittelt wurde, kann man die y-Koordinate anhand der Koordinaten der zwei Punkte einer der Linien und die Funktion y3(), die bei Lösung der Aufgabe 1 erhalten wurde, berechnen.

Wenn man zuerst die y-Koordinate erhalten muss, müssen die Gleichungen der Linien so umgewandelt werden, dass die x-Koordinate durch die y-Koordinate ausgedrückt ist. Zuerst für eine Linie:

X3=X11+(Y3-Y11)/D1

Dann für die zweite Linie:

X3=X21+(Y3-Y21)/D2

Setzen wir die Gleichungen gleich:

X11+(Y3-Y11)/D1=X21+(Y3-Y21)/D2

Drücken wir Y3 aus. Als Ergebnis erhalten wir die Funktion TwoLinesCrossY():

double TwoLinesCrossY(double x11,double y11,double x12,double y12,double x21,double y21,double x22,double y22){
   double k2=(x22-x21)/(y22-y21);
   double k1=(x12-x11)/(y12-y11);
   return((x11-x21-k1*y11+k2*y21)/(k2-k1));
}

Die Parameter dieser Funktion sind gleich den Parametern der Funktion TwoLinesCrossX(). 

Erkennung der Wellen

Nun verfügen wir über einen einfachen Zugang zu allen Spitzen des ZigZags sowie über geometrische Hilfsfunktionen, fangen wir mit der Erkennung der Wolfe Wellen an. Man muss den Moment erwischen, wenn die letzte Strecke des ZigZags die Linie 1-3 durchkreuzt (s. Abb. 1), also den Punkt 5. Wir werden die Bedingungen für die Erkennung der Wolfe Wellen bei der Erscheinung jedes neuen Extremums des ZigZags überprüfen (sowohl bei der Änderung der Richtung, als auch bei der Verlängerung der letzten Strecke). Oben im Code der Funktion OnCalculate() sind die Stellen, an welchen die Bedingungen überprüft werden müssen, ausführlich kommentiert. Aus diesen Funktionen werden die Funktionen CheckDn() und CheckUp() aufgerufen. Gehen wir auf die Funktion CheckUp() ausführlich ein:

void CheckUp(int rates_total,const double & low[],const datetime & time[],int i){

   if(CurCount<5 || CurDir!=-1){ 
      // wenn es wenig Spitzen gibt, oder der ZigZag nicht nach unten gerichtet ist, führen wir keine Überprüfung durch
      return;
   }   
   
   // bereiten wir kurze Variablen mit den Daten über die Spitzen vor 

   // Variablen mit den Werten der Spitzen
   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   // Variablen mit den Balken der Spitzen
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
                  
   if(CurLastBuySig!=i4){ // wenn in dieser Konfiguration des ZigZags noch keine Wellen erkannt wurden
      double d1=K1*(v2-v1); // minimaler Wert der Verschiebung der Spitze 3 hinsichtlich der Spitze 1
      if(v3<v1-d1){ // die Spitze 3 liegt deutlich unterhalb der Spitze 1
         if(v4>v1+d1){ // Steigung der Linie 1-4 nach oben 
            double d2=K2*(v2-v3); // minimaler Wert der Verschiebung der Spitze 4 hinsichtlich der Spitze 2
            if(v4<v2-d2){ // die Spitze 4 liegt deutlich unterhalb der Spitze 2
               double v5l=y3(i1,v1,i3,v3,i); // Wert des Punktes 5
               if(v5<v5l){ // die letzte Strecke des ZigZags hat die Linie 1-3 durchkreuzt
                  double v4x=y3(i1,v1,i3,v3,i4); // Wert im Punkt 4'
                  double v2x=y3(i1,v1,i3,v3,i2); // Wert im Punkt 2'
                  double h4=v4-v4x; // Höhe 4-4'
                  double h2=v2-v2x; // Höhe 2-2'
                  if(h2-h4>K3*h2){ // Linie 1-3 und 2-4 nähern sich rechts an
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // Balken, auf welchem sich die Linien 1-3 und 2-4 schneiden
                     double tv=y3(i1,v1,i4,v4,tb); // Wert im Schnittpunkt der Linien 1-3 und 2-4
                     UpArrowBuffer[i]=low[i]; // Anzeige des Pfeils nach oben
                     UpDotBuffer[i]=tv; // Anzeige des Punktes an der Ebene des Ziels
                     CurLastBuySig=i4; // speichern, dass das Muster des ZigZags in dieser Konfiguration erkannt wurde
                     if(_DrawWaves){ // Zeichnen des Musters und der Konstruktion
                        DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

Für die Erkennung der Wellen werden mindestens 5 Hochs des ZigZags benötigt. Eine zusätzliche Bedingung: für die Erkennung der Wellen, die auf eine Wende nach oben hinweist, muss der ZigZag nach unten gerichtet sein:

if(CurCount<5 || CurDir!=-1){ 
   // wenn es wenig Spitzen gibt, oder der ZigZag nicht nach unten gerichtet ist, führen wir keine Überprüfung durch
   return;
}   

Die Daten über die Spitzen können direkt aus dem Array PeackTrough abgerufen werden, das ist aber unbequem. Verwenden wird Hilfsvariablen mit kurzen Namen:

// Variablen mit den Werten der Spitzen
double v1=PeackTrough[CurCount-5].Val;
double v2=PeackTrough[CurCount-4].Val;
double v3=PeackTrough[CurCount-3].Val;
double v4=PeackTrough[CurCount-2].Val;
double v5=PeackTrough[CurCount-1].Val;
   
// Variablen mit den Balken der Spitzen
int i1=PeackTrough[CurCount-5].Bar;
int i2=PeackTrough[CurCount-4].Bar;               
int i3=PeackTrough[CurCount-3].Bar;
int i4=PeackTrough[CurCount-2].Bar;
int i5=PeackTrough[CurCount-1].Bar;

Wenn eine Welle erkannt und der Pfeil gesetzt wurde, braucht man diese Konfiguration des ZigZags nicht mehr zu verwenden. Die Identifizierung der Konfiguration des ZigZags erfolgt durch die Überprüfung des Index der Spitze 4 (der letzten sich gebildeten Spitze):

if(CurLastBuySig!=i4){ // wenn in dieser Konfiguration des ZigZags noch keine Wellen erkannt wurden

Für das Abspeichern des Index der Konfiguration wird ein Paar von Variablen verwendet, die ähnlich den Variablen CurCount und PreCount sind.

Nun sind wir unmittelbar an die Erkennung der Wellen herangekommen. Es wird der Wert der minimalen Verschiebung des Punktes 3 hinsichtlich des Punktes 1 und des Punktes 2 hinsichtlich des Punktes 1 berechnet:

double d1=K1*(v2-v1); // minimaler Wert der Verschiebung der Spitze 3 hinsichtlich der Spitze 1

Weiter werden die Verschiebungen der Punkte überprüft:

if(v3<v1-d1){ // die Spitze 3 liegt deutlich unterhalb der Spitze 1
   if(v4>v1+d1){ // Steigung der Linie 1-4 nach oben 

Berechnen wir den minimalen Wert der Verschiebung des Punktes 4 hinsichtlich des Punktes 2:

double d2=K2*(v2-v3); // minimaler Wert der Verschiebung der Spitze 4 hinsichtlich der Spitze 2

Überprüfen wir die Punkten 2 und 4:

if(v4<v2-d2){ // die Spitze 4 liegt deutlich unterhalb der Spitze 2

Berechnen wir den Wert des Punktes, der auf der Linie 1-3 liegt und dem berechneten Balken entspricht:

double v5l=y3(i1,v1,i3,v3,i); // Wert des Punktes 5

Überprüfen wir, ob die Linie 1-3 berührt wurde:

if(v5<v5l){ // die letzte Strecke des ZigZags hat die Linie 1-3 durchkreuzt

Berechnen wir die Werte in den Punkten 4' und 2':

double v4x=y3(i1,v1,i3,v3,i4); // Wert im Punkt 4'
double v2x=y3(i1,v1,i3,v3,i2); // Wert im Punkt 2'

Berechnen wir die Höhe 4-4' und 2-2':

double h4=v4-v4x; // Höhe 4-4'
double h2=v2-v2x; // Höhe 2-2'

Verwenden wir diese Höhen, um die Annäherung der Linien 1-3 und 2-4 rechts zu überprüfen:

if(h2-h4>K3*h2){ // Linie 1-3 und 2-4 nähern sich rechts an

Wenn diese Bedingung erfüllt ist, heißt das, dass eine Welle erkannt wurde. 

Definieren wir das Ziel. Zunächst einmal den Balken des Erreichens des Ziels:

double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4); // Balken, auf welchem sich die Linien 1-3 und 2-4 schneiden

Beachten Sie, dass für eine genaue Berechnung die Variable double verwendet wird.     

Wert des Ziels:

double tv=y3(i1,v1,i4,v4,tb); // Wert im Schnittpunkt der Linien 1-3 und 2-4

 Zeigen wir die Zeichen an und speichern die ID der Konfiguration des ZigZags:

UpDotBuffer[i]=tv; // Anzeige des Punktes an der Ebene des Ziels
CurLastBuySig=i4; // speichern, dass das Muster des ZigZags in dieser Konfiguration erkannt wurde

Zeichnen wird die Wellen und die Konstruktion, die das Ziel festlegt:

if(_DrawWaves){ // Zeichnen des Musters und der Konstruktion
   DrawObjects(BuyColor,BuyTargetColor,v1,v2,v3,v4,v5l,i1,i2,i3,i4,i5,time,i,tb,tv,rates_total);
}

Mit dem Zeichnen der Wellen und Konstruktionen, also mit der Funktion DrawObjects() beschäftigt sich ein Kapitel des Artikels. 

Die Wellen nach unten (für den Verkauf) werden mithilfe der Funktion CheckDn ermittelt, die identisch mit der Funktion CheckUp ist, abgesehen von einigen mit der Richtung verbundenen Unterschieden. Unten führen wir den Code der Funktion an und betrachten, wodurch sie sich von der Funktion CheckUp() unterscheidet:

void CheckDn(int rates_total,const double & high[],const datetime & time[],int i){

   // wenig Spitzen oder nicht nach oben gerichtet 
   if(CurCount<5 || CurDir!=1){ 
      return;
   }

   double v1=PeackTrough[CurCount-5].Val;
   double v2=PeackTrough[CurCount-4].Val;
   double v3=PeackTrough[CurCount-3].Val;
   double v4=PeackTrough[CurCount-2].Val;
   double v5=PeackTrough[CurCount-1].Val;
   
   int i1=PeackTrough[CurCount-5].Bar;
   int i2=PeackTrough[CurCount-4].Bar;               
   int i3=PeackTrough[CurCount-3].Bar;
   int i4=PeackTrough[CurCount-2].Bar;
   int i5=PeackTrough[CurCount-1].Bar;
   
   if(CurLastSellSig!=i4){               
      double d1=K1*(v1-v2); // die Spitze v1 liegt oberhalb der Spitze v2
      if(v3>v1+d1){ // die Spitze v3 liegt oberhalb der Spitze v1
         if(v4<v1-d1){ // die Spitze v4 liegt unterhalb der Spitze v1                     
            double d2=K2*(v3-v2); // die Spitze v3 liegt oberhalb der Spitze v2                     
            if(v4>v2+d2){ // die Spitze v4 liegt oberhalb der Spitze v2  
               double v5l=y3(i1,v1,i3,v3,i);
               if(v5>v5l){ // der ZigZag durchbricht die Linie 1-3 nach oben
                  double v4x=y3(i1,v1,i3,v3,i4);
                  double v2x=y3(i1,v1,i3,v3,i2);
                  double h4=v4x-v4; // Punkt 4' oberhalb des Punktes 4
                  double h2=v2x-v2; // Punkt 4' oberhalb des Punktes 4
                  if(h2-h4>K3*h2){   
                     double tb=TwoLinesCrossX(i1,v1,i3,v3,i2,v2,i4,v4);
                     double tv=y3(i1,v1,i4,v4,tb);                              
                     DnArrowBuffer[i]=high[i];
                     DnDotBuffer[i]=tv;
                     CurLastSellSig=i4;   
                     if(_DrawWaves){
                        // Zeichnen mit anderen Farben
                        DrawObjects(SellColor,
                                    SellTargetColor,
                                    v1,
                                    v2,
                                    v3,
                                    v4,
                                    v5l,
                                    i1,
                                    i2,
                                    i3,
                                    i4,
                                    i5,
                                    time,
                                    i,
                                    tb,
                                    tv,
                                    rates_total);
                     }
                  }
               }
            }
         }
      }
   }
}

Der erste Unterschied bei der ersten Überprüfung:

// wenig Spitzen oder nicht nach oben gerichtet 
if(CurCount<5 || CurDir!=1){ 
   return;
}

Wenn es wenig Spitzen gibt, oder der ZigZag nach unten gerichtet ist, beenden wir die Funktion.

Für die Richtung nach unten müssen die Hochs und Tiefs Stellen tauschen: die Punkten 1, 3, 5, 6 liegen oben, und die Punkten 2, 4, 7 — unten, deswegen tauschen auch einige Variablen Stellen in den Formeln. Ermittlung des minimalen Abstands zwischen den Spitzen 1, 3 und 1, 4:

double d1=K1*(v1-v2); // die Spitze v1 liegt oberhalb der Spitze v2

Überprüfung der Spitzen 1, 3:

if(v3>v1+d1){ // die Spitze v3 liegt oberhalb der Spitze v1

Überprüfung der Spitzen 1, 4:

if(v4<v1-d1){ // die Spitze v4 liegt unterhalb der Spitze v1 

Berechnung des minimalen Abstands zwischen den Spitzen 2, 3 und dessen Überprüfung:

double d2=K2*(v3-v2); // die Spitze v3 liegt oberhalb der Spitze v2                     
if(v4>v2+d2){ // die Spitze v4 liegt oberhalb der Spitze v2  

Überprüfung, ob sich der Punkt 5 gebildet hat (der ZigZag durchbricht die Linie 1-3 nach oben):

if(v5>v5l){ // der ZigZag durchbricht die Linie 1-3 nach oben

Berechnung der Höhen 2-2' und 4-4' für die Überprüfung, ob sich die Linien 1-3 und 2-4 rechts annähern:

double h4=v4x-v4; // Punkt 4' oberhalb des Punktes 4
double h2=v2x-v2; // Punkt 4' oberhalb des Punktes 4

Die Wellen und Konstruktionen werden mit einer anderen Farbe gezeichnet:

// Zeichnen mit anderen Farben
DrawObjects(SellColor,
            SellTargetColor,
            v1,
            v2,
            v3,
            v4,
            v5l,
            i1,
            i2,
            i3,
            i4,
            i5,
            time,
            i,
            tb,
            tv,
            rates_total);

Zeichnen der Wellen und des Ziels

Alle Wellen und Konstruktionen werden mithilfe eines einzigen Algorithmus gezeichnet, deswegen wird nur die Funktion DrawObjects() verwendet. Die Elemente, die nach oben und nach unten gerichtet sind, werden mit verschiedenen Farben gezeichnet. Dafür wird der Funktion der Parameter der Farbe BuyColor oder SellColor übergeben. Die Wellen und Konstruktionen werden auch mit verschiedenen Farben gezeichnet, deswegen werden der Funktion noch die Parameter BuyTargetColor oder SellTargetColor übergeben. Diese Variablen stellen externe Parameter des Indikators dar, mit welchen man passende Farben einstellen kann. Außer Farbe werden einige weitere externe Parameter benötigt. Unten sind alle zusätzlichen Parameter der Funktion für das Zeichnen der Wellen und Konstruktionen angeführt:

input bool   DrawWaves       =  true;             // Zeichnen der Wellen und Konstruktionen aktivieren
input color  BuyColor        =  clrAqua;          // Farbe der Kaufwellen
input color  SellColor       =  clrRed;           // Farbe der Verkaufswellen
input int    WavesWidth      =  2;                // Linienstärke
input bool   DrawTarget      =  true;             // zusätzliches Aktivieren/Deaktivieren der Konstruktionen
input int    TargetWidth     =  1;                // Stärke der Konstruktionen
input color  BuyTargetColor  =  clrRoyalBlue;     // Farbe der Konstruktionen für den Kauf
input color  SellTargetColor =  clrPaleVioletRed; // Farbe der Konstruktionen für den Verkauf

Nach der Farbe werden der Funktion die Variablen mit den Werten und Indexen der Balken der Spitzen übergeben. Eine Ausnahme bildet der Wert der Spitze 5, welcher nicht der Wert am Ende der Strecke des ZigZags, sondern der auf der Linie 1-3 berechnete Wert übergeben wird. Die Koordinaten aller Punkte des ZigZags sind in Balken angegeben, und für grafische Objekte wird Zeit benötigt, deswegen wird der Funktion der Pointer auf das Array time übergeben. Weiter wird der Index des berechneten Balkens übergeben — i, Balken des Ziels — tb, Wert des Ziels — tv und die Gesamtanzahl der Balken auf dem Chart — rates_total. 

Wie wir bereits am Anfang des Artikel erwähnt haben, müssen die Wellen und Konstruktionen nur dann gezeichnet werden, wenn der ZigZag nach high/low (SrcSelect gleich Src_HighLow) oder close (SrcSelect gleich Src_Close) ausgewählt wurden. Das heißt, je nach dem Wert der Variablen SrcSelect, muss das Zeichnen zwingend in der Funktion OnInit() deaktiviert werden (Varaible DrawWaves).  Deklarieren wir dafür eine zusätzliche Variable, die statt der Variablen DrawWaves verwendet wird:

bool _DrawWaves;

Weiter weisen wir ihr den Wert der Variablen DrawWaves in der Funktion OnInit() oder false für das Deaktivieren zu. Zusätzlich setzen wir eine unsichtbare Farbe für das Zeichnen des Ziels in den Puffer:

if(SrcSelect==Src_HighLow || SrcSelect==Src_Close){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=false;
   PlotIndexSetInteger(2,PLOT_LINE_COLOR,clrNONE);
   PlotIndexSetInteger(3,PLOT_LINE_COLOR,clrNONE);      
}   

Gehen wir auf die Funktion DrawObjects() ein. Zunächst einmal führen wir den ganzen Codes der Funktion an und betrachten wir ihn ausführlich:

void DrawObjects( color col,
                  color tcol,
                  double v1,
                  double v2,
                  double v3,
                  double v4,
                  double v5,
                  int i1,
                  int i2,
                  int i3,
                  int i4,
                  int i5,
                  const datetime & time[],
                  int i,
                  double target_bar,
                  double target_value,
                  int rates_total){

   // Präfix der Namen der grafischen Objekte 
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

   // Zeichnen der Wellen                   
   fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
   fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
   fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
   fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

   // Zeichnen der Konstruktionen
   if(DrawTarget){   
    
      datetime TargetTime;
      
      // Erhalten des ganzen Wertes des Indexes des Balkens des Ziels 
      int tbc=(int)MathCeil(target_bar);
      
      if(tbc<rates_total){ // Ziel innerhalb der existierenden Balken des Charts
         TargetTime=time[tbc];
      }
      else{ // Ziel in der Zukunft
         TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
      }
      
      // Berechnung der Werte der Konstuktionslinien auf dem Balken des Ziels
      double tv13=y3(i1,v1,i3,v3,tbc);   
      double tv24=y3(i2,v2,i4,v4,tbc);  
      double tv14=y3(i1,v1,i4,v4,tbc); 

      // Konstruktionen

      fObjTrend(prefix+"13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);   
      fObjTrend(prefix+"24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);  
      fObjTrend(prefix+"14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);
      
      // die horizontale Linie des Ziels
      fObjTrend(prefix+"67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);   
      // die vertikale Linie des Ziels 
      fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);      
   }
}

Gezeichnet wird mithilfe mehrerer Trendlinien gezeichnet, für welche zuerst ein gemeinsamer Namenspräfix erzeugt wird:

// Präfix der Namen der grafischen Objekte 
string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time[i])+"_";

Dann werden die Wellen gezeichnet, die Koordinaten aller Spitzen werden der Funktion übergeben:

// Zeichnen der Wellen                   
fObjTrend(prefix+"12",time[i1],v1,time[i2],v2,col,WavesWidth);
fObjTrend(prefix+"23",time[i2],v2,time[i3],v3,col,WavesWidth);   
fObjTrend(prefix+"34",time[i3],v3,time[i4],v4,col,WavesWidth);
fObjTrend(prefix+"45",time[i4],v4,time[i5],v5,col,WavesWidth);

Zeichnen wir die Konstruktion, die das Ziel definiert. Überprüfen wir, ob das Zeichnen der Konstruktionen aktiviert ist:

// Zeichnen der Konstruktionen
if(DrawTarget){  

Wenn aktiviert, die Konstruktionen zeichnen. Bei der Anzeige des Indikators auf der Historie erscheint der Ziel-Balken höchstwahrscheinlich auf einem der vorhandenen Balken der Historie, wenn die Wellen aber auf vor kurzem erschienenen Balken erkannt werden, kann das Ziel in der Zukunft liegen — rechts vom letzten Balken. Das heißt, es werden zwei Varianten der Berechnung der Zeit des Ziel-Balkens benötigt. Deklarieren wir dafür die folgende Variable:

datetime TargetTime;

Der Wert der Variablen target_bar stellt eine Bruchzahl dar, runden wir ihn auf die nächste ganze Zahl auf:

// Erhalten des ganzen Wertes des Indexes des Balkens des Ziels 
int tbc=(int)MathCeil(target_bar);

Weiter werden wir die erhaltene Variable tbc verwenden. Hier könnte man die Funktion MathFloor() verwenden — die nächste kleinste ganze Zahl erhalten. Dies hätte keine Auswirkung auf das Ergebnis, denn die Konstruktionen spielen nur eine informative Rolle. Unter Verwendung von MathCeil() schneiden sich die Enden der Linien 1-3 und 2-4 unbedingt neben dem Balken des Ziels und die Konstruktionen werden natürlicher aussehen.

Bestimmen wir die Zeit des Erreichens des Ziels. Wenn sich das Ziel auf einem der existierenden Balken liegt, reicht es, den Index des Balkens des Ziels zu berechnen und seine Zeit aus dem Array time zu erhalten. Wenn das Ziel rechts vom letzten Balken liegt, muss man ermitteln, mit welchem Abstand (in Balken) das Ziel vom letzten Balken liegt, und die Zeit berechnen:

if(tbc<rates_total){ // Ziel innerhalb der existierenden Balken des Charts
   TargetTime=time[tbc];
}
else{ // Ziel in der Zukunft
   TargetTime=time[rates_total-1]+(tbc-rates_total+1)*PeriodSeconds();
}

Berechnen wir die Werte aller Linien (1-3, 2-4 и 1-4) auf dem Balken des Ziels:

// Berechnung der Werte der Konstuktionslinien auf dem Balken des Ziels
double tv13=y3(i1,v1,i3,v3,tbc);   
double tv24=y3(i2,v2,i4,v4,tbc);  
double tv14=y3(i1,v1,i4,v4,tbc); 

Obwohl der Funktion der früher berechnete Wert des Ziels übergeben wird (Variable target_value), wird er für die Konstruktionen und für die Linie 2-4 erneut berechnet. Das ist damit verbunden, dass statt des genauen Wertes aus der Variablen target_bar der Wert aus der Variablen tbc verwendet wird, der ein bisschen größer als target_bar ist. Dank diesen Berechnungen auf der genauen Koordinate target_bar schneiden sich die Linien direkt an der Ebene target_value.

Zeichnen wir die Linien nach den berechneten Werten:

fObjTrend(prefix+"13",time[i1],v1,TargetTime,tv13,tcol,TargetWidth);   
fObjTrend(prefix+"24",time[i2],v2,TargetTime,tv24,tcol,TargetWidth);  
fObjTrend(prefix+"14",time[i1],v1,TargetTime,tv14,tcol,TargetWidth);

Die Linien werden mithilfe der Hilfsfunktion fObjTrend() gezeichnet:

void fObjTrend(   string  aObjName,
                  datetime aTime_1,
                  double   aPrice_1,
                  datetime aTime_2,
                  double   aPrice_2,
                  color    aColor      =  clrRed,  
                  color    aWidth      =  1,                
                  bool     aRay_1      =  false,
                  bool     aRay_2      =  false,
                  string   aText       =  "",
                  int      aWindow     =  0,                  
                  color    aStyle      =  0,
                  int      aChartID    =  0,
                  bool     aBack       =  false,
                  bool     aSelectable =  false,
                  bool     aSelected   =  false,
                  long     aTimeFrames =  OBJ_ALL_PERIODS
               ){
   ObjectCreate(aChartID,aObjName,OBJ_TREND,aWindow,aTime_1,aPrice_1,aTime_2,aPrice_2);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_BACK,aBack);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_COLOR,aColor);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTABLE,aSelectable);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_SELECTED,aSelected);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_TIMEFRAMES,aTimeFrames);
   ObjectSetString(aChartID,aObjName,OBJPROP_TEXT,aText);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_WIDTH,aWidth);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_STYLE,aStyle);
   ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_LEFT,aRay_1);   
   ObjectSetInteger(aChartID,aObjName,OBJPROP_RAY_RIGHT,aRay_2);
   ObjectMove(aChartID,aObjName,0,aTime_1,aPrice_1);
   ObjectMove(aChartID,aObjName,1,aTime_2,aPrice_2);   
}

Die Funktion ist universell und kann für eine schnelle Erstellung der Trendlinie mit der Einrichtung aller ihrer Parameter verwendet werden. Der Verwendungszweck aller Parameter ist in der Tabelle 1 angeführt. Die Parameter, die besonders häufig verändert werden, stehen ganz am Anfang (5 obligatorische Parameter), die anderen sind optional und können der Funktion nicht übergeben werden. Dank solcher Variabilität ist die Verwendung der Funktion sehr bequem.

Tabelle 1. Parameter der Funktion fObjTrend()

Parameter Verwendungszweck
string aObjName Objektname
datetime aTime_1 Zeit des ersten Ankerpunktes
double aPrice_1 Preis des ersten Ankerpunktes
datetime aTime_2 Zeit des zweiten Ankerpunktes
double aPrice_2 Preis des zweiten Ankerpunktes
color aColor Farbe
color aWidth Stärke
bool aRay_1 die Linie von der Seite des ersten Ankerpunktes verlängern
bool aRay_2 die Linie von der Seite des zweiten Ankerpunktes verlängern
string aText Text des Tipps
int aWindow Unterfenster
color aStyle Stil der Linie
int aChartID Chart-ID
bool aBack im Hintergrund zeichnen
bool aSelectable Objekt kann markiert werden
bool aSelected Objekt markiert
long aTimeFrames auf welchen Zeitrahmen die Linie anzeigen

Es bleibt, zwei zusätzliche Linien zu zeichnen: eine vertikale auf dem Balken des Ziels und eine horizontale an der Ebene des Ziels:

fObjTrend(prefix+"67",TargetTime,tv24,TargetTime,tv14,tcol,TargetWidth);   
fObjTrend(prefix+"7h",time[i],target_value,TargetTime,target_value,tcol,TargetWidth);  

Als Ergebnis erhalten wir die Abbildung der Wellen und Konstruktionen:


Abb. 4. Wolfe Wellen und Konstruktionen für die Ermittlung des Ziels beim Kauf

Löschen grafischer Objekte

Bei der Verwendung des ZigZags nach Сlose (SrcSelect=Src_Close) oder nach einem anderen Indikator, kann das Muster während der Bildung des Balkens auftreten und verschwinden. Dafür werden am Anfang der Schleife des Indikators alle Puffern mit Pfeilen und Punkten gelöscht: 

UpArrowBuffer[i]=EMPTY_VALUE;
DnArrowBuffer[i]=EMPTY_VALUE;
      
UpDotBuffer[i]=EMPTY_VALUE;
DnDotBuffer[i]=EMPTY_VALUE;  

Darüber hinaus müssen grafische Objekte am Anfang der Schleife gelöscht werden. Wenn das Zeichnen der Wellen und Konstruktionen aktiviert ist, wird am Anfang der Schleife des Indikators die Funktion DeleteObjects() aufgerufen:

if(_DrawWaves){
   DeleteObjects(time[i]);
}  

Code der Funktion DeleteObjects():

void DeleteObjects(datetime time){
   string prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+IntegerToString(time)+"_";
   ObjectDelete(0,prefix+"12");
   ObjectDelete(0,prefix+"23");
   ObjectDelete(0,prefix+"34");
   ObjectDelete(0,prefix+"45");
   ObjectDelete(0,prefix+"13");
   ObjectDelete(0,prefix+"24"); 
   ObjectDelete(0,prefix+"14");    
   ObjectDelete(0,prefix+"67"); 
   ObjectDelete(0,prefix+"7h");    
}

Der Funktion wird die Zeit des berechneten Balkens übergeben, in dieser Funktion werden auch alle grafischen Objekte mit den Namen, die dem berechneten Balken entsprechen, gelöscht.

Beim Löschen des Indikators vom Chart muss man alle von ihm erstellten grafischen Objekte löschen. Aus der Funktion DeInit(), die automatisch beim Beenden des Indikators ausgeführt wird, wird die Funktion ObjectsDeleteAll() aufgerufen. Als zweiter Parameter wird der Funktion der Name des Indikators übergeben, der auch als Präfix für alle durch den Indikator erstellten grafischen Objekten dient. So werden nur die grafischen Objekte gelöscht, die zu einem konkreten Indikator gehören:

void OnDeinit(const int reason){
   ObjectsDeleteAll(0,MQLInfoString(MQL_PROGRAM_NAME));
   ChartRedraw(0);
}  

Alert Funktion

Fügen wir dem Indikator eine Alert Funktion beim Erscheinen eines Pfeiles hinzu, wie beim Indikator des universellen Trends aus dem Artikel "Universeller Trend mit dem grafischen Interface". 

Die Alert Funktion kann das Auftreten des Pfeils auf einem sich bildenden Balken (passt bei der Verwendung des ZigZags nach high-low) oder auf einem vollständigen Balken (passt bei der Verwendung des ZigZags nach close und nach anderen Indikatoren) verfolgen. Für die Auswahl des Typs des Alerts erstellen wir eine Aufzählung:

enum EAlerts{
   Alerts_off=0,  // Alert deaktiviert
   Alerts_Bar0=1, // Alert nach einem sich bildenden Balken
   Alerts_Bar1=2  // Alert nach einem vollständigen Balken
};

Fügen wir dem Fenster der Eigenschaften die folgende Variable hinzu:

input EAlerts              Alerts         =  Alerts_off;

Der Code der Alert Funktion befindet sich in der separaten Funktion CheckAlerts(). Der Funktion wird die Anzahl der Balken auf dem Chart und das Zeit-Array übergeben:

void CheckAlerts(int rates_total,const datetime & time[]){
   if(Alerts!=Alerts_off){ // Alerts aktiviert
      static datetime tm0=0; // Variable für die Zeit des Balkens des letzten Buy-Alerts
      static datetime tm1=0; // Variable für die Zeit des Balkens des letzten Buy-Alerts
      if(tm0==0){ // die erste Ausführung der Funktion
         // Initialisierung der Variablen
         tm0=time[rates_total-1];
         tm1=time[rates_total-1];
      }
      string mes=""; // Variable für die Meldung

      // es gibt einen Pfeil nach oben, und auf dem letzten Balken gab es noch keinen Alert
      if(UpArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm0!=time[rates_total-1]
      ){
         tm0=time[rates_total-1]; // die Zeit des letzten Alerts speichern
         mes=mes+" buy"; // Meldung erzeugen
      }

      // es gibt einen Pfeil nach unten, und auf dem letzten Balken gab es noch keinen Alert
      if(DnArrowBuffer[rates_total-Alerts]!=EMPTY_VALUE && 
         tm1!=time[rates_total-1]
      ){
         tm1=time[rates_total-1]; // die Zeit des letzten Alerts speichern
         mes=mes+" sell"; // Meldung erzeugen
      } 
      if(mes!=""){ // Meldung
         // das Fensters mit der Meldung öffnen
         Alert(MQLInfoString(MQL_PROGRAM_NAME)+"("+Symbol()+","+IntegerToString(PeriodSeconds()/60)+"):"+mes);
      }        
   }   
}

Die Funktion CheckAlerts() wird am Ende der Funktion OnCalculate() nach der Hauptschleife aufgerufen. Am Ende der Funktion OnCalculate() wird auch die Funktion der Aktualisierung des Charts für die Beschleunigung des Zeichnens der Wellen und Konstruktionen aufgerufen:

if(_DrawWaves){
   ChartRedraw(0);
}
Damit ist die Erstellung des Indikators abgeschlossen. Er heißt iWolfeWaves und ist im Anhang zu finden.

Expert Advisor

Der Indikator ist relativ kompliziert geworden. Versuchen wir zu prüfen, dass er nicht nur auf der statischen Historie richtig läuft, und schätzen wir die Effizienz der vorgeschlagenen Methode der grafischen Analyse ein. Erstellen wir dafür einen einfachen Expert Advisor.

Um die Effizient zu überprüfen, muss der Expert Advisor Positionen nach allen Signalen des Indikators eröffnen. Deswegen wird der Expert Advisor für Konten, die Hedging erlauben, erstellt und wird die Anzahl der offenen Positionen nicht beschränken.

Erstellen wir einen neuen Expert Advisor mit dem Namen eWolfeWaves im Editor. Kopieren wir alle externen Parameter aus dem Indikator und fügen wir diese in die Datei des Expert Advisors ein. Unten fügen wir zusätzliche Parameter hinzu, die Stoploss und Takeprofit definieren:

input double               StopLoss_K     =  1;      // Koeffizient des Stoploss
input bool                 FixedSLTP      =  false;  // festgelegte Stoploss und Takeprofit
input int                  StopLoss       =  50;     // Wert des festgelegten Stoploss
input int                  TakeProfit     =  50;     // Wert des festgelegten Takeprofit

Diese Parameter erlauben es, eine der zwei Varianten für das Setzen von Stoploss und Takeprofit auszuwählen.

Wenn FixedSLTP=false, wird die Variable StopLoss_K verwendet. In diesem Fall wird Takeprofit nach dem Indikator gesetzt — an der Ebene des Punktes des Ziels, und Stoploss wird proportional dem Wert von Takeprofit unter Verwendung des Koeffizienten StopLoss_K berechnet. Diese Variante für die Festlegung von Stoploss und Takeprofit passt nur für einen ZigZag nach Preis: nach high-low oder nach close (SrcSelect gleich Src_HighLow oder Src_Close).

Wenn FixedSLTP=true, werden die Variablen StopLoss und TakeProfit verwendet. Diese Variante bietet die Möglichkeit, den ZigZag nach Indikatoren anzuwenden, kann aber auch mit ZigZags nach Preis verwendet werden.   

Überprüfen wir den Kontotyp in der Funktion OnInit(), wenn das Konto den Hedging-Modus nicht unterstützt, wird der Start des Experten beendet:

if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
   Print("Its not hedging account");
   return(INIT_FAILED);
}

Wenn der Experte nicht im visuellen Modus getestet wird, deaktivieren wir das Zeichnen der Wellen und Konstruktionen:

bool _DrawWaves;

if(MQLInfoInteger(MQL_VISUAL_MODE)){
   _DrawWaves=DrawWaves;
}
else{
   _DrawWaves=false;
}

Die Variable _DrawWaves wird statt der Variablen DrawWaves beim Aufruf des Indikators iWolfeWaves durch die Funktion iCustom() verwendet. Rufen wir den Indikator auf und überprüfen wir, ob er erfolgreich geladen wurde:

h=iCustom(  Symbol(),
            Period(),
            "iWolfeWaves",
            Alerts,
            SrcSelect,
            DirSelect,
            RSIPeriod,
            RSIPrice,
            MAPeriod,
            MAShift,
            MAMethod,
            MAPrice,
            CCIPeriod,
            CCIPrice,
            ZZPeriod,
            K1,
            K2,
            K3,
            _DrawWaves,
            BuyColor,
            SellColor,
            WavesWidth,
            DrawTarget,
            TargetWidth,
            BuyTargetColor,
            SellTargetColor);
            
if(h==INVALID_HANDLE){
   Print("Cant load indicator");
   return(INIT_FAILED);
}

Wenn der Indikator nicht geladen werden konnte, wird der Expert Advisor beendet.

Bei der Verwendung des Indikators nach high-low verschwindet der Pfeil nicht, d.h. der Expert Advisor kann auf einem sich bildenden Balken arbeiten. In anderen Fällen muss der Expert Advisor den Pfeil des Indikators auf dem ersten vollständigen Balken überprüfen. Dafür deklarieren wir die globale Variable Shift:

int Shift;

Weisen wir ihr einen Wert entsprechend dem Typ des ZigZags zu: 

if(SrcSelect==Src_HighLow){
   Shift=0;
}
else{
   Shift=1;
}

Unten ist der ganze Code der Funktion OnInit() angeführt:

int OnInit(){

   // Überprüfung des Kontotyps
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING){
      Print("Its not hedging account");
      return(INIT_FAILED);
   }

   // Zeichnen der Wellen und Konstruktionen deaktivieren

   bool _DrawWaves;
   
   if(MQLInfoInteger(MQL_VISUAL_MODE)){
      _DrawWaves=DrawWaves;
   }
   else{
      _DrawWaves=false;
   }

   // Laden des Indikators
   h=iCustom(  Symbol(),
               Period(),
               "iWolfeWaves",
               Alerts,
               SrcSelect,
               DirSelect,
               RSIPeriod,
               RSIPrice,
               MAPeriod,
               MAShift,
               MAMethod,
               MAPrice,
               CCIPeriod,
               CCIPrice,
               ZZPeriod,
               K1,
               K2,
               K3,
               _DrawWaves,
               BuyColor,
               SellColor,
               WavesWidth,
               DrawTarget,
               TargetWidth,
               BuyTargetColor,
               SellTargetColor);
       
   // überprüfen, ob der Indikator erfolgreich geladen wurde         
   if(h==INVALID_HANDLE){
      Print("Cant load indicator");
      return(INIT_FAILED);
   }
   
   // Ermittlung des Balkens auf welchem der Expert Advisor die Pfeilen des Indikators "schaut"
   if(SrcSelect==Src_HighLow){
      Shift=0;
   }
   else{
      Shift=1;
   }

   return(INIT_SUCCEEDED);
}

Gehen wir auf die Funktion OnTick() ein. Manchmal braucht man, dass der Expert Advisor sowohl nach Ticks als auch nach Balken arbeitet. Fügen wir Variablen für die Zeit des sich bildenden Balkens und des letzten verarbeiteten Balkens (die Variablen sind in der Funktion OnTick() deklariert) hinzu:

datetime tm[1];     // die Zeit des sich bildenden Balkens
static datetime lt; // die Zeit des letzten verarbeiteten Balkens

Erhalten wir die Zeit des letzten (sich bildenden) Balkens:

Sif(CopyTime(Symbol(),Period(),0,1,tm)<=0)return;

Überprüfen wir die Zeit des Balkens:

if(Shift==0 || tm[0]!=lt){

Wenn Shift==0, arbeitet der Expert Advisor auf jedem Tick. Anders nur wenn der Wert der Variablen lt nicht gleicht der Zeit des sich bildenden Balkens ist (Mal pro Balken). 

Deklarieren wir die Hilfsvariablen und erhalten wir die Werte des Indikators:

double tp,sl; // Variablen für die Berechnung von Stoploss und Takeprofit

double buf_buy[1];         // für den Kaufpfeil 
double buf_sell[1];        // für den Verkaufspfeil 

double buf_buy_target[1];  // für das Kauf-Ziel
double buf_sell_target[1]; // für das Verkaufs-Ziel 

if(CopyBuffer(h,0,Shift,1,buf_buy)<=0)return;
if(CopyBuffer(h,1,Shift,1,buf_sell)<=0)return;
if(CopyBuffer(h,2,Shift,1,buf_buy_target)<=0)return;
if(CopyBuffer(h,3,Shift,1,buf_sell_target)<=0)return;

Wenn es Handelssignale gibt, werden Stoploss und Takeprofit berechnet sowie Positionen eröffnet:

// es gibt einen Pfeil und auf diesem Balken wurde keine Kaufposition eröffnet
if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
   // Stoploss und Takeprofit
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_buy_target[0],_Digits);
      double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
   }
   // Eröffnen
   if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
   // die Zeit des letzten Eröffnens speichern
   SLastBuyTime=tm[0];
}

Wenn festgelegte Stoploss und Takeprofit (FixedStopLoss=true) verwendet werden, wird für die Berechnung von Takeprofit zum Eröffnungspreis der Position (Ask-Preis beim Kauf) der Wert der Variablen TakeProfit, multipliziert mit _Point, addiert. Für die Berechnung von Stoploss wird vom Eröffnungspreis der Wert der Stoploss-Variablen, multipliziert mit _Point, subtrahiert. Nach der Berechnungen werden die erhaltenen Werte mithilfe der Funktion NormalizeDouble() auf die Stellenanzahl normalisiert, die der Stellenanzahl der Quotes entspricht. Diesen Wert kann man aus der Variablen _Digits erhalten.

Wenn Stoploss und Takeprofit nicht festgelegt wurden, bestimmen wir zuerst den Wert von Takeprofit und berechnen proportional den Wert von Stoploss. Wenn keine Position eröffnet werden konnte, wird die Funktion OnTick() beendet, auf dem nächsten Tick wird es erneut versucht, eine Position zu eröffnen. Solange das Signal des Indikators besteht, d.h. innerhalb eines Balkens, wird es versucht, eine Position zu eröffnen. Wenn erfolgreich, wird der Variablen LastBuyTime die Zeit des aktuellen Balkens zugewiesen, damit auf diesem Balken keine Position mehr eröffnet wird (wenn Shift=0). Die Variable LastBuyTime ist eine globale Variable des Expert Advisors.

Analog zum Kauf, aber mit einigen Änderungen, erfolgt der Verkauf:

// es gibt einen Pfeil und auf diesem Balken wurde keine Verkaufsposition eröffnet
if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
   // Stoploss und Takeprofit
   if(FixedSLTP){
      tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
      sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
   }
   else{
      tp=NormalizeDouble(buf_sell_target[0],_Digits);
      double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
   }
   // Eröffnen
   if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
   // die Zeit des letzten Eröffnens speichern
   LastSellTime=tm[0];        
}  

Beim Verkauf wird statt der Variablen LastBuyTime die Variable LastSelTime verwendet, und die Stoploss und Takeprofit Levels werden vom Bid-Preis berechnet.

Am Ende der Variablen lt wird die Zeit des sich bildenden Balkens gesetzt, damit der Expert Advisor nichts mehr auf diesem Balken tut (wenn er balkenweise arbeitet, d.h. Shift=1). Unten ist der ganze Code der Funktion OnTick() angeführt:

void OnTick(){
   
   datetime tm[1]; // Zeit des sich bildenden Balkens
   static datetime lt; // die Zeit des letzten verarbeiteten Balkens
   
   // Zeit kopieren
   Sif(CopyTime(Symbol(),Period(),0,1,tm)<=0)return;
   
   if(Shift==0 || tm[0]!=lt){ // Überprüfung für das Arbeiten ein Mal pro Balken

      double tp,sl; // Variablen für die Berechnung von Stoploss und Takeprofit

      double buf_buy[1];         // für den Kaufpfeil 
      double buf_sell[1];        // für den Verkaufspfeil 
      
      double buf_buy_target[1];  // für das Kauf-Ziel
      double buf_sell_target[1]; // für das Verkaufs-Ziel     
      
      if(CopyBuffer(h,0,Shift,1,buf_buy)<=0)return;
      if(CopyBuffer(h,1,Shift,1,buf_sell)<=0)return;
      if(CopyBuffer(h,2,Shift,1,buf_buy_target)<=0)return;
      if(CopyBuffer(h,3,Shift,1,buf_sell_target)<=0)return;

      // es gibt einen Pfeil, und auf diesem Balken wurde keine Kaufposition eröffnet
      if(buf_buy[0]!=EMPTY_VALUE && LastBuyTime!=tm[0]){
         // Stoploss und Takeprofit
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_buy_target[0],_Digits);
            double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(ask-StopLoss_K*(tp-ask),_Digits);         
         }
         // Eröffnen
         if(!trade.Buy(0.1,Symbol(),0,sl,tp))return;
         // die Zeit des letzten Eröffnens speichern
         SLastBuyTime=tm[0];
      }
      
      // es gibt einen Pfeil und auf diesem Balken wurde keine Verkaufsposition eröffnet
      if(buf_sell[0]!=EMPTY_VALUE && LastSellTime!=tm[0]){
         // Stoploss und Takeprofit
         if(FixedSLTP){
            tp=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*TakeProfit;
            sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)+_Point*StopLoss;            
         }
         else{
            tp=NormalizeDouble(buf_sell_target[0],_Digits);
            double bid=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
            sl=NormalizeDouble(bid+StopLoss_K*(bid-tp),_Digits);         
         }
         // Eröffnen
         if(!trade.Sell(0.1,Symbol(),0,sl,tp))return; 
         // die Zeit des letzten Eröffnens speichern
         LastSellTime=tm[0];        
      }      
      
      lt=tm[0];
   }
}

Im Anhang ist der Expert Advisor in der Datei eWolfeWaves zu finden. 

Testen wir den erhaltenen Expert Advisor. Wenn man nach dem Testen den Indikator auf den Chart zieht, kann man sehen, dass der Einstieg bei jedem Pfeil unabhängig von offenen Positionen erfolgt (Abb. 5).


Abb. 5. Der Expert Advisor steigt bei jedem Pfeil des Indikators ein

Natürlich interessiert uns in erster Linie, wie effektiv der erstellte Indikator im Handel ist. Die Testergebnisse des Expert Advisors mit den Standardparametern für die ganze Historie auf EURUSD H1 sind auf der Abbildung 6 dargestellt.


Abb. 6. Testergebnisse des Expert Advisors für die ganze Historie auf EURUSD H1

Ganz am Anfang der Testperiode ist ein deutlicher Einbruch zu sehen, der wahrscheinlich mit einer schlechten Qualität der Historie verbunden ist. Aber auch wenn das nicht stimmt, beginnt direkt danach ungefähr seit 1991 eine Periode des kontinuierlichen Wachstums. Insgesamt sind die Testergebnisse positiv, und dies ohne Optimierung und zusätzliche Überprüfungen.

Noch einige Tipps aus dem Buch von Bill Wolfe

Neben den Regeln der Erkennung der Wellen, gibt Bill Wolfe noch einige Ratschläge: psychologische Anmerkungen und technische Anmerkungen, wie er sie selbst nennt. Eine der wichtigsten technischen Anmerkungen ist der Tipp, das Tickvolumen zu überwachen - in Umkehrpunkten kann das Tickvolumen sinken, was die Umkehr bestätigen kann. Der zweite Ratschlag ist, Trendlinien zu überwachen. Die von Bill Wolfe entdeckten Wellen treten häufig nach einem Trendwechsel, mit anderen Worten, nach einem Ausbruch der Trendlinie, auf. Das heißt, die Wellen, die nach dem Trendwechsel entstehen, sind zuverlässiger. Der dritte Tipp - die Linie 1-4 zu verfolgen, besonders den Punkt 4, und falls unvorhersehbare Umstände auftreten, aussteigen: im Fall einer Gegenwelle, einer Erhöhung des Volumens oder bei einem schnellen Erzielen des Profits.

Fazit

Die positiven Testergebnisse des Expert Advisors (auch mit Standardeinstellungen) zeigen, dass die im Artikel vorgestellte Methode der grafischen Analyse effektiv und interessant für die Weiterforschung ist.

Wahrscheinlich will jemand den Indikator vervollkommnen. Momentan haben externe Parameter drei Variablen-Koeffizienten: K1, K2, K3. Der Koeffizient K1 wird für die Überprüfung der Stelle des Punktes 3 hinsichtlich des Punktes 1 und des Punktes 4 hinsichtlich des Punktes 1 verwendet. Wahrscheinlich ist es besser, für diese Überprüfungen separate Koeffizienten zu verwenden. Von der anderen Seite erschwert die Erhöhung der Anzahl der Parameter die Optimierung des Systems und erhöht das Risiko, dass die Parameter angepasst statt optimiert werden. Vielleicht wäre es besser, die Koeffizienten K1 und K2 zu vereinen. Dies macht die Einrichtung des Indikators einfacher und verständlicher. Oder vielleicht sollte man nur einen Koeffizienten haben. Der Code des Indikators ist relativ deutlich in Funktionen aufgeteilt, was jegliche Erweiterungen erleichtert und es jedem erlaubt, mit seinen Modifikationen zu experimentieren.  

Neben der Erkennung von Wolfe Wellen, kann der Indikator als Vorlage für die Erstellung anderer Indikatoren für die Erkennung anderer Muster des ZigZags verwendet werden. Es reicht, den Code der Funktionen CheckUp und CheckDn zu ändern. Wichtig ist, dass der Zugang zu den Werten der Zigzag Spitzen ermöglicht wurde.

Besonders möchte ich den Trick mit den Variablen CurCount, PreCount und LastTime hervorheben. Diese Methode ist nicht ausschließlich für die Lösung der spezifischen Aufgabe beim Schreiben des Indikators für diesen Artikel vorgesehen. Bei der Entwicklung von Indikatoren werden häufig zusätzliche Puffer für die infolge Zwischenberechnungen erhaltenen Werte benötigt. Auf jedem Balken wird der Wert aus dem vorherigen Element des Puffers ins aktuelle Element des Puffers übertragen und nur selten geändert. In den Berechnungen wird der Wert eines Elements verwendet, und dafür wird ein ganzer Puffer gebraucht. Die Anwendung der Methode mit zwei Variablen ermöglicht es, das Volumen des Arbeitsspeichers des Indikators zu reduzieren.

Anhang

Im Anhang sind die Dateien der in diesem Artikel erstellten Indikatoren und des Expert Advisors zu finden. Alle Dateien befinden sich in den Ordnern so, wie es im Terminal sein muss. Im Anhang finden Sie die folgenden Dateien:

  • Indicators/iWolfeWaves_Step_1.mq5
  • Indicators/iWolfeWaves.mq5
  • Experts/eWolfeWaves.mq5 

Damit das alles funktioniert, werden zusätzliche Dateien aus dem Artikel "Universeller ZigZag" benötigt:

  • Indicators/iUniZigZagSW.mq5
  • Include/CSorceData.mqh
  • Include/CZZDirection.mqh>
  • Include/CZZDraw.mqh

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

Beigefügte Dateien |
MQL5.zip (8.67 KB)
Wie lang ist der Trend? Wie lang ist der Trend?
Der Artikel beleuchtet mehrere Methoden, einen Trend zu identifizieren, mit dem Ziel die Dauer des Trend relativ zu der des Seitwärtsmarktes (flat market) zu bestimmen. Theoretisch sollte dass Verhältnis von Trends zu Seitwärtsbewegungen zwischen 30% bis 70% liegen. Das werden wir überprüfen.
DeMarks Sequential (TD SEQUENTIAL) unter Verwendung künstlicher Intelligenz DeMarks Sequential (TD SEQUENTIAL) unter Verwendung künstlicher Intelligenz
In diesem Artikel werde ich erzählen, wie man durch das Kreuzen einer sehr bekannten Strategie mit einem neuronalen Netz erfolgreich handeln kann. Es wird um die Strategie Thomas Demarks "Sequential" unter Verwendung künstlicher Intelligenz gehen. Wir werden NUR nach dem ersten Teil der Strategie arbeiten, dabei verwenden wir die Signale der "Setzung" und "Kreuzung".
Die Rezepte MQL5 - Die Erstellung des Ringpuffers für eine schnelle Berechnung der Indikatoren im gleitenden Fenster Die Rezepte MQL5 - Die Erstellung des Ringpuffers für eine schnelle Berechnung der Indikatoren im gleitenden Fenster
Der Ringpuffer — er ist die einfachste und zugleich wirksamste Organisationsform für die Berechnungen von Daten in einem gleitenden Fenster. Im Artikel wird beschrieben, wie dieser Algorithmus funktioniert, und es wird gezeigt, wie mit seiner Hilfe Berechnungen im gleitenden Fenster einfacher und schneller durchgeführt werden können.
Die Rezepte MQL5 - die Handelssignale der Pivots Die Rezepte MQL5 - die Handelssignale der Pivots
Im Artikel wurde der Prozess der Entwicklung und der Realisierung des Klasse-Signalgebers auf der Grundlage der Pivots dargestellt — der Wendeebenen. Auf der Grundlage dieser Klasse wird die Strategie unter Verwendung der Standardbibliothek gebaut. Es werden die Möglichkeiten der Entwicklung der Pivots-Strategie durch das Hinzufügen der Filter betrachtet.