Die Entwicklung eines oszillierenden ZigZag-Indikator Beispiel für die Durchführung der Anforderungsspezifikationen

19 Juli 2018, 16:20
Dmitry Fedoseev
0
182

Inhalt

Einführung

Der Artikel "Wie man eine Anforderungsspezifikation bei der Bestellung eines Indikators erstellt" enthält ein Beispiel für Anforderungsspezifikationen zur Entwicklung eines ZigZag-Indikators auf Basis verschiedener Oszillatoren. In diesem Artikel werde ich Ihnen zeigen, wie Sie diese Aufgabe Schritt für Schritt ausführen können.

Der oszillierende ZigZag-Indikator

Bevor wir fortfahren, lesen Sie bitte die Spezifikation, indem Sie auf den obigen Link klicken.  

Allgemeine Specifikationsanalyse

Die Hauptanforderungen für den zu entwickelnden Indikator sind augenscheinlich.

  1. Die Entwicklung erfolgt in Schritten.
  2. Achten wir darauf, die maximal mögliche Indikatorleistung zu implementieren.
  3. Der Indikator hat eine grafische Oberfläche.

Der ZigZag-Algorithmus. Der ZigZag-Algorithmus unterscheidet sich vom Standard-Algorithmus.

  1. Der ZigZag ändert seine Richtung nicht, wenn ein lokales Extremum gebildet wird, sondern wenn der Oszillatorwert über das überkaufte/überverkaufte Niveau hinausgeht. 
  2. Der ZigZag basiert auf dem Preisdiagramm. Dementsprechend wird ein neues Extremum durch die Preisdaten bestimmt.

Daher wird es notwendig sein, auf bestimmte Merkmale zu achten, die sich daraus ergeben. 

  1. Die Hoch/Tiefs des Indikators können nicht mit dem Hoch/Tief-Kursen übereinstimmen. Beim Wechsel der Richtung des ZigZag ist daher zu prüfen, ob der neue Preis Hoch/Tief etwas früher als der Eintritt des Oszillators in den überkauften/überverkauften Bereich erschienen ist (Abb. 1).



    Abb. 1. WPR-Ausbruch in den überkauften Bereich passiert durch die Balken markiert mit dem Pfeil 1,
    das neue ZigZag-Segment sollte jedoch bis zu dem durch den Pfeil 2 markierten Balken gezogen werden.

  2. Da die Richtungsänderung des ZigZag durch den Oszillator definiert wird, ändert sich ihr Wert mit der Form des Balkens. So kann ZigZag die Richtung ändern, aber während der Balken geformt wird, kann die Richtungsänderung abgebrochen werden. In solchen Fällen ist es notwendig, die korrekte Funktion der Anzeige zu gewährleisten.
  3. Da das neue Extremum durch ein Preisdiagramm (durch Hoch/Tief-Preise) definiert ist, kann die Bildung des neuen Hoch/Tief nicht aufgehoben werden, während der Balken gebildet wird. ZigZag kann jedoch auf einem Balken mit einem neuen Extremum umkehren. In diesem Fall wird das neue Hoch/Tief verworfen (Abb. 2).


     
    Abb. 2. 1 — Die obere Spitze des ZigZag ist zu dem Preis Hoch der entstehende Bar gebildet.
    2 — Der ZigZag dreht, sodass das zuvor erkannte Hoch verworfen wird.

    Natürlich ist der Fall mehrdeutig, da MetaTrader 5 den Zeichnungsstil Color ZigZag hat. Er ermöglicht das vertikale Zeichnen des ZigZag-Segments, ohne seine Oberseite nach links zu verschieben (für ein vorgegebenes Maximum). Eine solche Zeichnung gibt uns jedoch nicht die Möglichkeit, zwei ZigZag-Segmente (das vertikale und das benachbarte schräge) unabhängig voneinander zu zeichnen. Darüber hinaus ist diese Art des Zeichnens von ZigZag nicht sehr verbreitet, und die Spezifikation verlangt nicht explizit, diese Methode anzuwenden. Daher ist standardmäßig die gebräuchlichste Option ausgewählt.

Anzeige. ZigZag-Anzeige hat seine eigenen Eigenschaften.

  1. Neben der Anzeige des Indikators selbst sollte das Preischart auch Balken markieren, bei denen der Indikator in den überkauften (gelbe Punkte am oberen Ende des Balkens) und überverkauften (grüne Punkte am unteren Ende des Balkens) Bereich reicht. 
  2. Das Muster ergibt sich aus der gemeinsamen Lage der oberen und unteren Spitzen des ZigZag. Das ZigZag-Segment, das ein Muster bildet, sollte eine andere Farbe haben. Dies erweist sich als das größte Problem. Zuerst müssen wir nicht nur den Balken markieren, an dem das Muster erkannt wird, sondern auch die Farben mehrerer ZigZag-Segmente in der Historie ändern. Zweitens kann die ZigZag-Umkehrung und das neue Extremum aufgehoben werden, wenn der Balken gebildet wird. Deshalb müssen wir vor der Berechnung des Balkens das ZigZag-Segment löschen, auf dem das Muster markiert werden könnte (es sollte die neutrale Farbe zurückgegeben werden). Drittens können sich die Muster (auch entgegengesetzte) überlappen. Daher sollten wir die Farbe des zuvor erkannten Musters beim Löschen und Zeichnen eines ZigZag-Segments nicht stören (Abb. 3).



    Abb. 3. Überlappende Muster

    Betrachten wir das in Abb. 3 dargestellte ZigZag-Segment. Die Segmente 1-4 bilden den Aufwärtstrend. Das heißt, das Segment 1 sollte blau eingefärbt sein. Aber es wurde bereits Teil des Abwärtsmusters, deshalb ist es rot. Wenn das Segment 6 erscheint, bildet sich ein weiteres Aufwärtsmuster (Segmente 3-6). Da ZigZag-Segmente vor jeder Balkenberechnung ihre Ausgangsfarbe erhalten sollen, brauchen wir in diesem Fall nur die Segmente 5 und 6 zu löschen, da die Segmente 3 und 4 bereits auf ein anderes Muster verweisen.

    Die alternative Lösung besteht darin, die Farben aller Segmente in jedem neuen Muster zu ändern. Dies macht den Indikator jedoch weniger aussagekräftig bezüglich der Vergangenheit. Deshalb haben wir die erste Option gewählt, obwohl sie viel komplizierter ist.

Grafische Schnittstelle. Die Anforderungen an die grafische Oberfläche sind relativ einfach. Die Menge der Kontrollelement ist konstant, sie sollte je nach ausgewähltem Oszillator nicht verändert werden müssen. Es werden nur zwei numerische Parameter verwendet (überkaufte/überverkaufte Niveaus), deren Einstellung für alle Oszillatoren identisch ist.

Nachdem wir alle Merkmale der Aufgabe verstanden haben, können wir mit der Entwicklung des Indikators beginnen. Am Ende jeder Entwicklungsphase setzen wir den entsprechenden Dateinamen aus dem Anhang. Wenn Sie Schwierigkeiten haben, die Reihenfolge des Hinzufügens des Codes während des weiteren Lesens zu verstehen, empfehlen wir Ihnen, die Datei der entsprechenden Stufe im Editor zu öffnen und zu überprüfen.

Schritt 1 — Entwickeln des ZigZag

Erstellen wir den neuen Nutzerindikator OscZigZagStep1 im MetaEditor. Fügen wir eine externe Variable hinzu, um die Stelle im Code für Variablen zu markieren. Im Auswahlfenster der Ereignisbehandlung wählen wir die erste Option - OnCalculate(....,open,high,low,close), andere werden nicht benötigt. Legen wir im Fenster mit den Anzeigeparametern zwei Puffer an. Benennen wir den ersten Puffer "HighLow", Typ - Color Arrow und zwei Farben: Gold und LimeGreen. Die zweite heißt "ZigZag", Typ - Color Section und drei Farben: Grau, Kornblumenblau und Rot (Abb. 4).


Abb. 4. Auswahl der Darstellungsparameter im Fenster des Indikatorenentwicklungsassistenten

Da sich farbigen Punkte auf die Balken beziehen, ist es logischer, sie zuerst zu zeichnen (näher an Balken) und dann ZigZag zu zeichnen. Daher befinden sich die Puffer in dieser Reihenfolge.

Nach dem Klicken auf Fertig stellen erscheint die Indikatorendatei im Editor. Zuerst sollten wir die Werte der indicator_color1-Eigenschaften anpassen, indem wir überflüssige Farbmuster löschen. Die Zeile mit der Eigenschaft indicator_color1 sollte folgendes Aussehen haben:

#property indicator_color1  clrGold,clrLimeGreen

Die Eigenschaft indicator_color2 sollte auf gleicher Weise angepasst werden.

Suchen wir jetzt die Zeile mit der automatischen erstellen externen Variablen:

input int      Input1;

Wirt löschen sie und deklarieren die Variablen als Parameter des Indikators WPR:

input int         WPRperiod   =  14;
input double      WPRmax      =  -20;
input double      WPRmin      =  -80;

Danach deklariere wir die Variable für das Handle

int h;

Ganz zu Beginn der Funktion OnInit() laden wir den Indikator:

h=iWPR(Symbol(),Period(),WPRperiod);
if(h==INVALID_HANDLE){
   Print("Can't load indicator");
   return(INIT_FAILED);
}  

In der Funktion OnDeinit() geben wir das Handle frei:

void OnDeinit(const int reason){
   if(h!=INVALID_HANDLE){
      IndicatorRelease(h);
   }
}  

Bei der Verwendung des Indikatorenentwicklungsassistenten haben wir die angezeigten Puffer erstellt, aber wir benötigen auch Hilfspuffer. So benötigen wir zB. den Puffer für die Oszillatorwerte im Moment. Erhöhen wir den Wert mit indicator_buffers um eins (5 statt 4):

#property indicator_buffers 5

Dort, wo Arrays für Puffer bereits deklariert wurden, fügen wir ein weiteres Array hinzu - für den Puffer mit Werten des Oszillators:

double         wpr[];

In der Funktion OnInit() geben wir an, dass dieses Array als Indikatorpuffer für Zwischenberechnungen verwendet wird. Der Code wird am Ende von OnInit() hinzugefügt:

SetIndexBuffer(4,wpr,INDICATOR_CALCULATIONS); 

Gehen wir zur Funktion OnCalculate() und schreiben den Standardcode für die Berechnung des Bar-Count-Bereichs und kopieren die Werte des Oszillators WPR in den Puffer:

int start;

if(prev_calculated==0){
   start=0;
}
else{
   start=prev_calculated-1;
}

if(CopyBuffer(h,0,0,rates_total-start,wpr)==-1){
   return(0);
}

Jetzt können wir die Schleife für den Standardindikator schreiben zur Darstellung der Punkte, wo der Oszillator die Bereiche Überkauft/Überverkauft erreicht:

for(int i=start;i<rates_total;i++){
   HighLowBuffer[i]=EMPTY_VALUE;
   if(wpr[i]>WPRmax){
      HighLowBuffer[i]=high[i];
      HighLowColors[i]=0;
   }
   else if(wpr[i]<WPRmin){
      HighLowBuffer[i]=low[i];
      HighLowColors[i]=1;      
   }      
}

Zu diesem Zeitpunkt kann der Indikator an das Diagramm angehängt werden. Wir fügen auch einen Standard-WPR bei und überprüfen die Richtigkeit des Codes (Abb. 5).


Abb. 5. Darstellung der Bereiche Überkauft/Überverkauft auf dem Preischart

Setzen wir die Erstellung des ZigZag fort. Wir brauchen mehrere Hilfspuffer: einen für die aktuelle Richtung des Indikators und zwei für die Balkenindizes, die auf den letzten Eckpunkt und die ZigZag-Depression zeigen:

double         dir[]; // Richtung
double         lhb[]; // Index der letzten Hochs
double         llb[]; // Index des letzten Tiefs

Da wir drei Puffer hinzufügen, müssen wir die Eigenschaft erhöhen, die die Anzahl der Indikatorpuffer bestimmt:

#property indicator_buffers 8

In der Funktion OnInit() wenden wir die Funktion SetIndexBuffer() auf die neu deklarierten Arrays an:

SetIndexBuffer(5,dir,INDICATOR_CALCULATIONS);  
SetIndexBuffer(6,lhb,INDICATOR_CALCULATIONS);   
SetIndexBuffer(7,llb,INDICATOR_CALCULATIONS);    

Dieser Code befindet sich direkt nach dem letzten Aufruf der Funktion SetIndexBuffer(), die bereits in der Funktion OnInit() verfügbar ist.

Jetzt ein sehr wichtiger Punkt. Damit der Puffer für die Farben ordnungsgemäß funktioniert, müssen wir für ihn den leeren Wert für 0 definieren. Andernfalls werden anstelle des ZigZags diese seltsamen Linien im Chart angezeigt:


Abb. 6. Wenn wir keinen leeren Wert für einen Farbbereichspuffer angeben, funktioniert die ZigZag-Anzeige nicht gut

Um einen leeren Wert am Ende der Funktion OnInit () vor der Zeile mit return anzugeben, fügen wir die folgende Zeile hinzu:

PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); 

Bitte beachten Sie, dass der erste Parameter gleich 1 ist. In diesem Fall ist dies der Index in der Gruppe der angezeigten Puffer, d.h. 1 entspricht dem Puffer ZigZagBuffer[].

Die Funktion OnInit() ist bereit. Nun konzentrieren wir uns auf die Funktion OnCalculate() und setzen das Schreiben des Codes in der Standardschleife des Indikators fort.

Zu Beginn der Schleife verschieben wir die Daten entlang der Hilfspuffer nach dem ersten String mit dem Löschen des Puffers HighLowBuffer:

lhb[i]=lhb[i-1];      
llb[i]=llb[i-1];
dir[i]=dir[i-1];

In unserem Code, in dem die Ausgabe des Oszillators in den überkauften/überverkauften Zonen definiert wurde, fügen wir die Richtungseinstellung dem Pufferverzeichnis[] hinzu:

if(wpr[i]>WPRmax){
   HighLowBuffer[i]=high[i];
   HighLowColors[i]=0;
   dir[i]=1;
}
else if(wpr[i]<WPRmin){
   HighLowBuffer[i]=low[i];
   HighLowColors[i]=1;      
   dir[i]=-1;
} 

Nun, das Spannendste in der ersten Phase ist die Konstruktion von ZigZag. ZigZag-Richtung ist definiert. Der Puffer dir[] enthält den Wert 1, wenn er nach oben und -1, wenn er nach unten zeigt. Außerdem müssen wir die Balken definieren, auf denen sich die Richtung ändert. Die ZigZag-Basis besteht aus dem folgenden Code, der in 4 Zweige unterteilt ist:

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // Umkehr der Aufwärtsrichtung
   }
   else{
      // Fortsetzung der Aufwärtsbewegung
   }      
}
else if(dir[i]==-1){
   if(dir[i-1]==1){ 
      // Umkehr der Abwärtsrichtung
   }
   else{
      // Fortsetzung der Abwärtsbewegung
   }      
}

Betrachten wir die Richtungsänderung des ZigZag nach oben und die anschließende Aufwärtsbewegung. Die beiden anderen Zweige sind symmetrisch.

Änderung der Aufwärtsrichtung

1. Wir suchen den maximalen Preis auf dem Balkenbereich vom letzten Tief bis zum berechneten Balken (der Balken des Tiefs ist nicht im Bereich enthalten):

if(dir[i]==1){
   if(dir[i-1]==-1){ 
      // Umkehr der Aufwärtsrichtung
      // Suche des Maximums
      int hb=i;
      for(int j=i;j>llb[i];j--){
         if(high[j]>high[hb]){
            hb=j;
         }
      }
      //...
   }
   else{
      // Fortsetzung der Aufwärtsbewegung
   }      
}

2. Auf den gefundenen Balken setzen wir den ZigZag-Punkt, im Puffer lhb[] geben wir den Index dieses Balkens an - und durch den Puffer ZigZagColor setzen wir die neutrale Farbe: 

ZigZagBuffer[hb]=high[hb];
lhb[i]=hb;            
ZigZagColors[hb]=0;

3. Wenn wir diesen Balken neu berechnen, kann sich herausstellen, dass sich der Wert des Oszillators geändert hat und kein Punkt mehr vorhanden ist. Wir müssen ihn also löschen. Dies geschieht normalerweise durch Löschen des Puffers zu Beginn des Anzeigezyklus:

ZigZagBuffer[i]=0;

Aber in diesem Fall wird der aufsteigende ZigZag-Scheitelpunkt von dem durch eine unbekannte Anzahl von Balken gezählten Balken getrennt (Fig. 1). Daher ist es notwendig, den Index des Balkens, auf dem sich der neue Eckpunkt befindet, und den Zeitpunkt des Balkenzählens beizubehalten:

NewDotTime=time[i];
NewDotBar=hb;

Die Variablen NewDotTime und NewDotBar werden auf der globalen Ebene des Indikators deklariert.

4. Zu Beginn der Indikatorschleife überprüfen wir den Wert der Variablen NewDotTime mit dem Wert des Balken. Wenn es eine Übereinstimmung gibt, entfernen wir den neuen ZigZag-Punkt:

if(NewDotTime==time[i]){
   ZigZagBuffer[NewDotBar]=0;  
}

Aufwärtsbewegung

Betrachten wir einen Codeabschnitt, der die Fortsetzung der Aufwärtsbewegung definiert. Wenn der Preis eines hohen regulären Balkens den zuvor festgelegten ZigZag-Wert überschreitet, löschen wir den alten Punkt und fügen einen neuen hinzu:

if(high[i]>ZigZagBuffer[(int)lhb[i]]){ 
   // Entfernen des alten Punktes
   ZigZagBuffer[(int)lhb[i]]=0;
   // Setzen des neuen
   ZigZagBuffer[i]=high[i];
   ZigZagColors[i]=0;
   lhb[i]=i;
}

Wir bringen den Indikator in seinen ursprünglichen Zustand zurück (holen den entfernten Punkt), bevor wir den Balken zu Beginn der Indikatorschleife neu berechnen:

ZigZagBuffer[(int)lhb[i]]=high[(int)lhb[i]];
ZigZagBuffer[(int)llb[i]]=low[(int)llb[i]];  

Achten wir darauf, die Puffer lhb[] und llb[] mit Null zu initialisieren, um Array-Fehler zu Beginn der Anzeigeoperation zu vermeiden. Außerdem müssen wir die Variablen NewDotTime und NewDotBar auf Null setzen. Dies geschieht bei der Berechnung des Zählbereichs:

if(prev_calculated==0){
   start=1;
   lhb[0]=0;
   llb[0]=0;   
   NewDotTime=0; 
}
else{
   start=prev_calculated-1;
}

Damit ist die erste Phase der Indikatorenentwicklung abgeschlossen. In der Anlage unten heißt der Indikator zu diesem Zeitpunkt OscZigZagStep1.mq5.

Schritt 2 — Mustererkennung und Farbgebung

Um das Muster zu erkennen, müssen wir 5 ZigZag-Extrema vergleichen. Es wird nicht empfohlen, diese oberen Spitzen jedes Mal in einer Schleife zu suchen, da dies den Indikator verlangsamt (eine schnelle Indikatorberechnung ist ja eine Hauptanforderung). Es ist viel besser, neu auftretende Extreme in einem separaten Array zu speichern, um einen direkten und schnellen Zugriff darauf zu ermöglichen.   

Daten über ZigZag-Extrema sollen im Struktur-Array gespeichert werden. Die Struktur sollte Felder für Balkenindex, Wert, Richtung und ein weiteres Feld vom Typ bool enthalten. Es wird 'true' gespeichert, wenn ein Extremum der letzte im Muster ist (um die ZigZag-Farbgebung auf das zuvor erkannte Muster zu beschränken). Beschreiben wir die Struktur und deklarieren das Array:

struct SZZDot{
   int bar;
   double val;
   int dir;
   bool pat;
};

SZZDot ZZDot[];

Dann fügen wir den Aufruf der Funktion AddZZDot() an dem Ende jedes der vier Teile hinzu. Sie fügt dem Array ZZDot[] die neuen ZigZag-Scheitelpunkte hinzu:

if(dir[i]==1){ 
   if(dir[i-1]==-1){          
      //...
      AddZZDot(1,high[hb],hb,i);
   }
   else{ 
      if(high[i]>ZigZagBuffer[(int)lhb[i]]){
         //...
         AddZZDot(1,high[i],i,i);
      }
   }      
}
else if(dir[i]==-1){
   if(dir[i-1]==1){
      //...
      AddZZDot(-1,low[lb],lb,i);
   }
   else{
      if(low[i]<ZigZagBuffer[(int)llb[i]]){
         //...
         AddZZDot(-1,low[i],i,i);
      }
   }      
}

Wir übergeben die vier Parameter (Richtung, Wert, Index des Balkens mit einem Extremum und berechneten Balkenindex) der Funktion AddZdot(). Wir werden die Funktion selbst etwas später betrachten. Der Indikatorpuffer cnt[] dient uns für die Anzahl der erkannten Extrema (belegte Elemente im AADot[] Array). Deklarieren wir das Array cnt[]:

double         cnt[];

In der Funktion Oninit() rufen wir dazu die Funktion SetIndexBuffer() auf:

SetIndexBuffer(8,cnt,INDICATOR_CALCULATIONS);  

Wir ändern den Wert der Eigenschaft die die Anzahl der Puffer festlegt:  

#property indicator_buffers 9

Zu Beginn der Indikatorschleife verschieben wir den letzten Wert im Puffer:

cnt[i]=cnt[i-1];

Wir haben bereits erwähnt, dass die bei einer Balkenberechnung erkannte ZigZag-Umkehrung bei der nächsten Berechnung desselben Balkens verschwinden kann. Daher sollte ein im Array gespeichertes Extremum gelöscht werden. Diese Löschung erfolgt jedoch nicht durch Reduzierung des Arrays, sondern durch Verringerung des Zählers, der die Anzahl der belegten Array-Elemente zählt (cnt[] Puffer). Dadurch wird die Geschwindigkeit der Anzeige deutlich erhöht.

Werfen wir einen Blick auf den AddZdot() Befehl:

void AddZZDot(int d,double v,int b,int i){
   
   int c=(int)cnt[i];

   if(c==0){ 
      // während des Starten des Indikators oder einer kompletten Neuberechnung
      ArrayResize(ZZDot,1024);
      ZZDot[c].dir=d;
      ZZDot[c].val=v;
      ZZDot[c].bar=b;
      ZZDot[c].pat=false;
      cnt[i]=1;
   }
   else{
      if(ZZDot[c-1].dir==d){
         // aktualisieren des Extremums der gleichen Richtung
         ZZDot[c-1].val=v;
         ZZDot[c-1].bar=b;         
      }
      else{
         // Ergänzen des neuen Extremums
         // Erhöhen des Arrays um 1024 Elemente, wenn notwendig
         if(c>=ArraySize(ZZDot)){ 
            ArrayResize(ZZDot,c+1024);
         }
         // Ergänzen des neuen Extremums
         ZZDot[c].dir=d;
         ZZDot[c].val=v;
         ZZDot[c].bar=b;
         ZZDot[c].pat=false;
         cnt[i]=c+1;
      }
   }
}

Beim Start des Indikators oder dessen vollständiger Neuberechnung wird die Array-Größe auf 1024 gesetzt. Sein Anfangselement hat die Extremparameter, während der Extremwertzähler um 1 erhöht wird. Bei nachfolgenden Funktionsaufrufen wird die Richtung des letzten Extremums im Array überprüft. Entspricht er den Parametern, mit denen die Funktion aufgerufen wird, so werden die Daten des letzten Extremums aktualisiert. Bei entgegengesetzter Richtung wird ein neues Extremum hinzugefügt. 

Bei der Analyse der Aufgabe habe ich bereits erklärt, dass bei einer ZigZag-Umkehrung das letzte Extremum einer entgegengesetzten Richtung auf einen früheren Balken verschoben werden kann (Abb. 2). Setzen wir daher vor der Ausführung des ZigZag-Hauptcodes den im Voraus bekannten Extremwert auf das zuletzt belegte Element des ZZDot-Arrays. Dies geschieht am Ende der Indikatorschleife:

if(cnt[i]>0){
   int ub=(int)cnt[i]-1;
   if(ZZDot[ub].dir==1){
      ZZDot[ub].bar=(int)lhb[i];
      ZZDot[ub].val=high[(int)lhb[i]];
   }
   else{
      ZZDot[ub].bar=(int)llb[i];
      ZZDot[ub].val=low[(int)llb[i]];         
   }
}

Wenn nun ein neuer Scheitelpunkt auf dem zu scannenden Balken erkannt wird, wird sein Wert im ZZDot-Array aktualisiert. Im Falle einer Umkehr, bleibt das zuvor erkannte Extremum.

Vor der ersten Berechnung des Indikators und bei voller Zuweisung muss das Anfangselement des Arrays cnt [] initialisiert werden:

if(prev_calculated==0){
   //...
   cnt[0]=0;
}
else{
   start=prev_calculated-1;
}

Jetzt haben wir alle Daten der Extrema der ZigZags und können sie nun leicht abfragen. Konzentrieren wir uns auf die Muster und die Farbgebung. Das geht sobald wir zumindest 5 Extrema haben:

if(cnt[i]>=5)

Berechnen des Index des letzten Extremums im Array:

int li=(int)cnt[i]-1;

Gehen wir davon aus, dass das Muster für dieses Extremum nicht erkannt wurde. Das benötigen wir, um die Farbe auf neutral zurückzusetzen:

ZZDot[li].pat=false;

Rückgabe der ersten Farbe von diesem Teil des ZigZag:

for(int j=0;j<4;j++){
   if(ZZDot[li-j].pat){
      break;
   }
   ZigZagColors[ZZDot[li-j].bar]=0;
}

Hinweis: Sobald ein Extremum mit dem Muster erkannt wurde, ist die Schleife zu Ende.

Prüfen der Bedingungen des Musters:

if(ZZDot[li].dir==1){ // aufwärts
   if(
      ZZDot[li].val>ZZDot[li-2].val && 
      ZZDot[li-2].val>ZZDot[li-4].val && 
      ZZDot[li-1].val>ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 
      // Farbgebung 
   }
}
else{ // abwärts
   if( 
      ZZDot[li].val<ZZDot[li-2].val && 
      ZZDot[li-2].val<ZZDot[li-4].val && 
      ZZDot[li-1].val<ZZDot[li-3].val
   ){
      ZZDot[li].pat=true; 		
      // Farbgebung                 
   }            
}

Jetzt fehlt nur noch die Farbgebung. Er ist ähnlich der des Rücksetzens. Für eine Aufwärtsbewegung:   

for(int j=0;j<4;j++){
   if(j!=0 && ZZDot[li-j].pat){
      break;
   }
   ZigZagColors[ZZDot[li-j].bar]=1;
} 

Ein kleiner Unterschied zum Code der Zurücksetzung ist, dass die Schleife nicht durchlaufen wird, wenn j=0.

Der zweite Schritt der Entwicklung des Indikators ist fertig. Der Indikator schaut wie folgt aus: 


Abb. 7. Der Indikator am Ende von Schritt 2

Unten in der Anlage ist der Indikator des Schritts 2 der mit Namen OscZigZagStep2.mq5. 

Schritt 3 — Hinzufügen des Oszillators

Beschreiben wir die Enumeration:

enum EIType{
   WPR,
   CCI,
   Chaikin, 
   RSI,
   Stochastic
};

Deklarieren der externen Variablen zur Auswahl des Oszillators:

input EIType               Type        =  WPR;

Ergänzen wir die restlichen Parameter des Oszillators:

// CCI
input int                  CCIperiod   =  14;
input ENUM_APPLIED_PRICE   CCIprice    =  PRICE_TYPICAL;
input double               CCImax      =  100;
input double               CCImin      =  -100;
// Chaikin
input int                  CHfperiod   =  3;
input int                  CHsperiod   =  10;
input ENUM_MA_METHOD       CHmethod    =  MODE_EMA;
input ENUM_APPLIED_VOLUME  CHvolume    =  VOLUME_TICK;
input double               CHmax       =  1000;
input double               CHmin       =  -1000;
// RSI
input int                  RSIperiod   =  14;
input ENUM_APPLIED_PRICE   RSIprice    =  PRICE_CLOSE;
input double               RSImax      =  70;
input double               RSImin      =  30;
// Stochastic
input int                  STperiodK   =  5;  
input int                  STperiodD   =  3;
input int                  STperiodS   =  3;
input ENUM_MA_METHOD       STmethod    =  MODE_EMA;
input ENUM_STO_PRICE       STprice     =  STO_LOWHIGH;
input double               STmax       =  80;
input double               STmin       =  20; 

Deklarieren der Variablen für die Niveaus:

double max,min;

Auswählen eines Oszillators zu Beginn der Funktion OnStart():

switch(Type){
   case WPR:
      max=WPRmax;
      min=WPRmin;  
      h=iWPR(Symbol(),Period(),WPRperiod);      
   break;
   case CCI:
      max=CCImax;
      min=CCImin;  
      h=iCCI(Symbol(),Period(),CCIperiod,CCIprice);  
   break;      
   case Chaikin:
      max=CHmax;
      min=CHmin;  
      h=iChaikin(Symbol(),Period(),CHfperiod,CHsperiod,CHmethod,CHvolume);  
   break;          
   case RSI:
      max=RSImax;
      min=RSImin;  
      h=iRSI(Symbol(),Period(),RSIperiod,RSIprice);  
   break;   
   case Stochastic:
      max=STmax;
      min=STmin;  
      h=iStochastic(Symbol(),Period(),STperiodK,STperiodD,STperiodS,STmethod,STprice);  
   break; 
}

if(h==INVALID_HANDLE){
   Print("Can't load indicator");
   return(INIT_FAILED);
}

Ersetzen wir die Variablen WPRmax und WPmin durch die Variablen max und min in der Funktion OnCalculate(). 

Damit ist der dritte Schritt der Entwicklung des Indikators abgeschlossen. Nun können wir einen Oszillator im Eigenschattenfenster des Indikators auswählen. In der folgenden Anlage heißt der Indikator zu diesem Zeitpunkt OscZigZagStep3.mq5.

Schritt 4 — Entwickeln des grafischen Interfaces

Die Bibliothek IncGUI wird für die Entwicklung der grafischen Oberfläche verwendet. Diese Bibliothek ist in der Artikelserie "Benutzerdefinierte grafische Bedienelemente" entwickelt worden, sie besteht aus drei Teilen (Teil 1, Teil 2 und Teil 3). Die neueste überarbeitete Version der Bibliothek (IncGUI_v4.mqh) ist dem Artikel "Universaloszillator mit GUI" beigefügt. Sie ist auch hier beigefügt. Bevor wir mit der Entwicklung der grafischen Oberfläche beginnen, kopieren wir die Datei IncGUI_v4.mqh in das Verzeichnis MQL5/Includes des Terminal-Ordners.

Betrachten wir die Entwicklung der grafischen Oberfläche in Schritten.

Einbinden der Bibliothek. Erstellen wir eine Kopie des Indikators OscZigZagStep3 mit dem Namen OscZigZagStep3 und fügen sie der Bibliothek hinzu:

#include <IncGUI_v4.mqh>

The form class. Die Datei IncGUI_v4.mqh enthält die Klasse CFormTemplate, eine Art Vorlage für die Formularentwicklung. Fügen wir sie sofort nach dem Einbinden der Bibliothek in die Indikatordatei ein. Wir ändern den Namen von CFormTemplate in CForm.

Die Eigenschaften des Elementes. Jetzt setzen wir die Haupteigenschaften mit der Methode MainProperties():

m_Name         =  "Form";
m_Width        =  200;
m_Height       =  150;
m_Type         =  2;
m_Caption      =  "ZigZag on Oscillator";
m_Movable      =  true;
m_Resizable    =  true;
m_CloseButton  =  true;
  • Die Variable m_Name ist der Name (Präfix aller grafischen Objekte dieser Anzeige).
  • Die Variablen m_Width und m_Height sind die Breite und Höhe der Anzeige in Pixel.
  • Die Variable m_Type ist der Typ der Anzeige. Wenn der Wert 2 ist, wird im unteren Teil des Formulars die Schaltfläche Schließen angezeigt.
  • Die Variable m_Caption ist ein Kopf der Anzeige.
  • Die Variable m_Movable bestimmt die Beweglichkeit. Die Schaltfläche zum Verschieben befindet sich in der linken oberen Ecke der Anzeige.
  • Die Variable m_Resizable — die Anzeige kann aufgeklappt werden. Die entsprechende Schaltfläche befindet sich in der oberen rechten Ecke.
  • Die Variable m_CloseButton — zum Schließen der Anzeige. Die entsprechende Schaltfläche befindet sich in der oberen rechten Ecke.

Steuerungen. Erstellen von Elemente der Anzeige. Die Anzeige hat zwei Rahmen. Der eine Rahmen hat eine Gruppe von Auswahlknöpfen, während ein anderer zwei Eingabefelder enthält. Tragen wir den folgenden Code im Abschnitt 'public' der Klasse form ein:

CFrame m_frm1; // Rahmen 1
CFrame m_frm2; // Rahmen 2 
CRadioGroup m_rg; // Gruppe der Optionstasten      
CInputBox m_txt_max; // Textfeld für das obere Niveau     
CInputBox m_txt_min; // Textfeld für das untere Niveau

Initialisierung des Elementes. Initialisierung der Anzeige mit der Methode OnInitEvent()

Initialisieren des ersten Rahmens mit width/height von 85/97 Pixel, die Kopfzeile "Osc Type" und einer Breite von 44 Pixel:

m_frm1.Init("frame1",85,97,"Osc Type",44);

Ein Gruppe von Optionstasten werden auch in diesem Rahmen platziert.

Der zweite Rahmen mit denselben Dimensionen, der Kopfzeile "Levels" und einer Breite von 32 Pixel:

m_frm2.Init("frame2",85,97,"Levels",32);

Die Felder für die Eingabe der Level sind auch in diesem Rahmen.

Initialisieren der Gruppe von Optionstasten:

m_rg.Init();

Add radio buttons to the group:

m_rg.AddButton(" WPR",0,0);
m_rg.AddButton(" CCI",0,16);
m_rg.AddButton(" Chaikin",0,32);
m_rg.AddButton(" RSI",0,48);            
m_rg.AddButton(" Stochastik",0,64); 

Initialisieren der Textfelder für die Eingabe der unteren und oberen Level:

m_txt_max.Init("max",45,-1," Max");
m_txt_min.Init("min",45,-1," Min");

Beide Felder sind 45 Pixel breit und erlauben das Einfügen von Text (der dritte Parameter ist -1), eines heißt "Max", das andere "Min".

Darstellung der Anzeige. In der Methode OnShowEvent() wird die Methode Show() aller Elemente aufgerufen um jeweils deren Koordinaten festzulegen:

m_frm1.Show(aLeft+10,aTop+10);
m_frm2.Show(aLeft+105,aTop+10);
m_rg.Show(aLeft+17,aTop+20);
m_txt_max.Show(aLeft+115,aTop+30);
m_txt_min.Show(aLeft+115,aTop+50);     

Ausblenden der Elemente. Mit der Methode OnHideEvent() werden die Elemente ausgeblendet:

m_frm1.Hide();
m_frm2.Hide();            
m_rg.Hide();
m_txt_max.Hide();
m_txt_min.Hide(); 

Form header. Bei der Auswahl eines anderen Oszillators wäre es gut, dessen Namen im Kopf anzuzeigen, dafür fügen wir im Abschnitt 'public' die Methode hinzu, um den Text im Kopf zu ändern:

void SetCaption(string str){
   m_Caption="ZigZag on "+str;
   ObjectSetString(0,m_Name+"_Caption",OBJPROP_TEXT,m_Caption);
}

Erstellen des Objektes der Anzeige. Erstellen des Klassenobjektes CForm:

CForm form;

Ereignisse der Anzeige. Damit die Anzeige und die Elemente auf Benutzeraktionen reagieren können, sollte die Methode Event() von der OnChartEvent() Funktion des Indikators aufgerufen werden. Je nach Art des Ereignisses gibt die Methode unterschiedliche Werte zurück. Schließen der Anzeige entspricht 1. Der Indikator sollte dann vom Chart entfernt werden:

if(form.Event(id,lparam,dparam,sparam)==1){
   ChartIndicatorDelete(0,0,MQLInfoString(MQL_PROGRAM_NAME)); 
   ChartRedraw();
}

Ereignissteuerung. Die Gruppe der Optionstasten sollte bei einer Indikatorauswahl geändert werden, und die überkauften/überverkauften Werte ändern sich entsprechend den Ereignissen der Änderung der Werte in den Eingabefeldern. In beiden Fällen wird der Indikator vollständig neu berechnet. 

Für den Teil des Codes, in dem der Indikator in der Funktion OnInit() ausgewählt wird, verwenden wir eine separate Funktion:

bool LoadIndicator(int aType){
   switch(aType){
      case WPR:
         max=WPRmax;
         min=WPRmin;  
         h=iWPR(Symbol(),Period(),WPRperiod);      
      break;
      case CCI:
         max=CCImax;
         min=CCImin;  
         h=iCCI(Symbol(),Period(),CCIperiod,CCIprice);  
      break;      
      case Chaikin:
         max=CHmax;
         min=CHmin;  
         h=iChaikin(Symbol(),Period(),CHfperiod,CHsperiod,CHmethod,CHvolume);  
      break;          
      case RSI:
         max=RSImax;
         min=RSImin;  
         h=iRSI(Symbol(),Period(),RSIperiod,RSIprice);  
      break;   
      case Stochastic:
         max=STmax;
         min=STmin;  
         h=iStochastic(Symbol(),Period(),STperiodK,STperiodD,STperiodS,STmethod,STprice);  
      break; 
   }
   
   if(h==INVALID_HANDLE){
      Print("Can't load indicator");
      return(false);
   }   
   
   return(true);
   
}   

Es wird von der Funktion OnInit() des Indikators (ganz am Anfang) und von der Optionstaste Event aufgerufen. Unmittelbar nach dem Auswählen des Indikators in der Funktion OnInit() initialisieren wir die Anzeige, legen den Werte für die Elemente fest und zeigen alles an:

if(!LoadIndicator(Type)){
   return(INIT_FAILED);
}

form.Init(1);
form.m_rg.SetValue(Type);
form.m_txt_max.SetValue(max);   
form.m_txt_min.SetValue(min);  
form.SetCaption(EnumToString(Type));
form.Show(5,20);

In der Funktion OnChartEvent() verarbeiten wir die Ereignisse der Steuerelemente. Das Ereignis der Optionstaste zum Ändern des Indikators:

if(form.m_rg.Event(id,lparam,dparam,sparam)==1){
   
   if(h!=INVALID_HANDLE){
      IndicatorRelease(h);
      h=INVALID_HANDLE;
   }      
   
   if(!LoadIndicator(form.m_rg.Value())){
      Alert("Can't load indicator");
   }
   
   form.m_txt_max.SetValue(max);   
   form.m_txt_min.SetValue(min);    

   EventSetMillisecondTimer(100);
}

Geben wir zuerst das Handel des Indikators durch den IndicatorRelease() frei, wählen dann den neuen Indikator, setzen die neue Werte der Eingabefelder und starten den Timer. Die Verwendung des Timers ist notwendig, da bei der Neuberechnung des Indikators Fehler bei der Datenaktualisierung auftreten können. In solchen Fällen muss die Neuberechnung wiederholt werden, bis sie erfolgreich ist.

Ändern der Level:

if(form.m_txt_max.Event(id,lparam,dparam,sparam)==1 ||
   form.m_txt_min.Event(id,lparam,dparam,sparam)==1
){
   max=form.m_txt_max.ValueDouble();
   min=form.m_txt_min.ValueDouble();      
   EventSetMillisecondTimer(100);
}

Die neuen Werte werden zugewiesen und der Timer wird durch Eingabefelder der Ereignisse gestartet, sowie die Variablen 'min' und 'max'.  

Der Indikator wird in der Funktion OnTimer() neu berechnet. Im Erfolgsfall schaltet sich der Timer aus und die Anzeige setzt ihre Arbeit wie gewohnt fort — bei jedem Tick. Alle für die Neuberechnung des Indikators notwendigen Aktionen wurden im oben genannten Artikel "Der universell Oszillator mit dem graphischen Interface" ausführlich beschrieben. Deshalb werden wir hier nur grundlegende Unterschiede berücksichtigen. Der Universaloszillator wurde in einer Klassenmethode berechnet, die keine Preisdaten benötigt. Hier müssen wir die Funktion OnCalculate() aufrufen und Arrays mit Preisen übergeben. Deklarieren wir die Arrays:

datetime time[];
double open[];
double high[];
double low[];
double close[];
long tick_volume[];
long volume[];
int spread[];

Abfrage der Anzahl der Bars

int bars=Bars(Symbol(),Period());
      
if(bars<=0){
   return;
}

Wir brauchen nicht alle Preisdaten zur Berechnung des ZigZag. Es weden nur drei Arrays benötigt: 'time', 'high' und 'low'. Diese drei Arrays kopieren wir:

if(CopyTime(Symbol(),Period(),0,bars,time)==-1){
   return;
}

if(CopyHigh(Symbol(),Period(),0,bars,high)==-1){
   return;
}      

if(CopyLow(Symbol(),Period(),0,bars,low)==-1){
   return;
} 

Beim Testen des Indikators haben wir ein Problem: Die Anzahl der kopierten Daten ist manchmal geringer als die Anzahl der Balken, die durch die Funktion Bars() erhalten werden. Die Größe der Indikatorpuffer entspricht dem Wert der Funktion Bars(). Für eine korrekte Darstellung des Indikators ist es daher notwendig, die Arrays mit den kopierten Daten zu vergrößern, während die Daten an ihr Ende verschoben werden:

if(ArraySize(time)<bars){
   int sz=ArraySize(time);
   ArrayResize(time,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      time[j]=time[i];
   }   
}

if(ArraySize(high)<bars){
   int sz=ArraySize(high);
   ArrayResize(high,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      high[j]=high[i];
   }
}      

if(ArraySize(low)<bars){
   int sz=ArraySize(low);
   ArrayResize(low,bars);
   for(int i=sz-1,j=bars-1;i>=0;i--,j--){
      low[j]=low[i];
   }
} 

Jetzt müssen wir nur die Funktion OnCalculate() aufrufen:

int rv=OnCalculate(
            bars,
            0,
            time,
            open,
            high,
            low,
            close,
            tick_volume,
            volume,
            Spreizung (Spread)
);

Deaktivieren des Timers, wenn die Funktion OnCalculte() ohne Fehler durchlaufen wurde: 

if(rv!=0){
   ChartRedraw();     
   EventKillTimer();
   form.SetCaption(EnumToString((EIType)form.m_rg.Value()));
}

Der vollständige Code der Funktion OnTimer() sowie der vollständige Indikator sind in der angehängten Datei OscZigZagStep4.mq5 zu sehen.

Beim Start des Indikators auf dem Chart sollte die Anzeige mit den Elementen in der oberen linken Ecke angezeigt werden (Abb. 8).


Abb. 8. Grafisches Interface nach Schritt 4

Schlussfolgerung

Ich habe die Entwicklung des Indikators genau nach dem vorgeschlagenen Anforderungsspezifikationen demonstriert. Die Genauigkeit der Ausführung definiert jedoch nicht die gesamte Arbeit. In unserem Fall wurde die Aufgabe von einer technisch versierten Person vorbereitet, die sich mit den Möglichkeiten und Merkmalen des Terminals bestens auskennt. Einige Anforderungen müssen jedoch noch geklärt werden und sollten mit dem Kunden besprochen werden. Dies bezieht sich insbesondere auf die Farbgebung des ZigZag.

Wenn das Muster erkannt wird, werden mehrere frühere ZigZag-Segmente neu eingefärbt, was unsere Analyse des Indikators in der Historie stören kann. Die Tatsache, dass dem Muster neue Details hinzugefügt werden können, erschwert die visuelle Analyse zusätzlich. In der Praxis wird das Muster benötigt, um eine Handelsentscheidung zu treffen. Es kann realisiert werden, wenn das Muster erscheint oder später, aber nicht früher. Anstatt ZigZag zu färben, könnte man daher vorschlagen, Pfeile auf dem Balken zu zeichnen, auf dem das Muster erkannt wird. Eine andere Lösung ist das Zeichnen von horizontalen Linien auf einer Anzahl von Balken, während das Muster existiert, von dem Moment an, an dem es erkannt wird, bis in die Zukunft.  

Außerdem wurde bei der Entwicklung des Indikators seine unerwartete und unauffällige Eigenschaft offenbart, an die man schon bei der ersten Lesung der Aufgabe und erst recht bei der Zusammenstellung nicht denken kann. Ich meine einige Fälle, in denen es notwendig ist, das letzte ZigZag Maximum/Minimum zu löschen, wenn er sich umgekehrt. Wie im Artikel erwähnt, könnten wie den Puffer Color ZigZag verwenden, aber in diesem Fall würde es Schwierigkeiten beim Einfärben geben, da Color ZigZag zwei Datenpuffer und nur einen Puffer für Farbe hat. Wenn beide Datenpuffer Werte auf einem Balken haben (eine vertikale Linie verläuft entlang des Balkens), wird die im Farbpuffer angegebene Farbe gleichzeitig zwei ZigZag-Segmenten zugeordnet. Es ist möglich, ZigZag anstelle des farbigen Puffer ZigZag zu verwenden und ZigZag-Segmente mit grafischen Objekten neu zu bemalen oder einfach Pfeile oder Punkte zu setzen. Generell erfordert jede Aufgabe eine sehr sorgfältige Analyse und Vorbesprechung.

Anlagen

Die Dateien liegen bereits an den korrekten Verzeichnissen. Sie sollten in den gleichen Verzeichnissen des Terminals kopiert werden. In MQL5/Indicators sind die Dateien die den einzelnen Entwicklungsschritten entsprechen: OscZigZagStep1.mq5, OscZigZagStep2.mq5, ОscZigZagStep3.mq5 and OscZigZagStep4.mq5.

In MQL5/Includes finden wir die Datei IncGUI_v4.mqh mit der Entwicklung des grafischen Interfaces des Indikators OscZigZagStep4.


Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/4502

Beigefügte Dateien |
MQL5.zip (92.85 KB)
Wie man den Berechnungsblock eines Indikators in den Code eines Expert Advisors überträgt Wie man den Berechnungsblock eines Indikators in den Code eines Expert Advisors überträgt

Für die Übertragung des Codes eines Indikators in einen Expert Advisor kann es unterschiedliche Gründe geben. Aber wie kann man Vor- und Nachteile eines solchen Ansatzes bewerten? In diesem Artikel wird eine Technologie für die Übertragung des Codes eines Indikators in einen Expert Advisor vorgestellt. Es wurden mehrere Experimente hinsichtlich der Arbeitsgeschwindigkeit des Expert Advisors durchgeführt.

Die Behandlung der Ergebnisse der Optimierung mit einem grafischen Interface Die Behandlung der Ergebnisse der Optimierung mit einem grafischen Interface

Dies ist eine Fortsetzung der Idee der Verarbeitung und Analyse von Optimierungsergebnissen. Diesmal geht es darum, die 100 besten Optimierungsergebnisse auszuwählen und in einer GUI-Tabelle darzustellen. Der Benutzer kann eine Zeile in der Optimierungsergebnistabelle auswählen und erhält ein Saldo mehrerer Symbole und eine Drawdown-Grafik auf einer eigenen Seite.

Ein visueller Strategieentwickler Erstellen eines Handelsroboters ohne zu programmieren Ein visueller Strategieentwickler Erstellen eines Handelsroboters ohne zu programmieren

Dieser Artikel stellt einen visuellen Strategieentwickler vor. Es wird gezeigt, wie jeder Nutzer einen Handelsroboter oder ein Hilfsprogramm ohne zu programmieren, erstellen kann. Der erstellte Expert Advisor ist voll funktionsfähig und kann im Strategie-Tester getestet, in der Cloud optimiert oder auf einem realen Konto ausgeführt werden.

Social Trading. Kann eine gutes Signal weiter verbessert werden? Social Trading. Kann eine gutes Signal weiter verbessert werden?

Die meisten Abonnenten wählen ein Handelssignal nach der Schönheit der Bilanzkurve und nach der Anzahl der Abonnenten. Deshalb achten viele Anbieter heute eher auf schöne Statistiken als auf echte Signalqualität, spielen oft mit Losgrößen und reduzieren die Saldenkurve künstlich nur für ein ideales Erscheinungsbild. Dieses Papier befasst sich mit den Zuverlässigkeitskriterien und den Methoden, mit denen ein Anbieter seine Signalqualität verbessern kann. Eine beispielhafte Analyse einer bestimmten Signalhistorie wird vorgestellt, ebenso wie Methoden, die einem Anbieter helfen würden, diese profitabler und risikoärmer zu gestalten.