Thomas DeMarks Beitrag zur technischen Analyse

Ivan Morozov | 2 September, 2016


Einführung

Es wurde argumentiert, dass die technische Analyse sowohl Wissenschaft wie auch Kunst ist. Der Grund für diesen Dualismus sind unterschiedliche Betrachtungsweisen der Händler und Analysten. Zum Beispiel kann ein und dieselbe Trendlinie ganz unterschiedlich gezeichnet werden. Solche Unsicherheiten sind aber im Rohstoffhandel, wo Genauigkeit verlangt wird, verpönt. Jeder Händler, der jemals versucht hat, eine Trendlinie zu erzeugen, und entdecken musste, es gibt mehrere Wege das zu tun, ist diesem Problem begegnet. Diese Uneineindeutigkeit hilft nicht bei dem Erstellen eines akkuraten Handelssystems, das seine Marktanalyse auf Trendlinien aufbauen soll. Und es gibt andere Probleme durch diese Vielfalt: Unstimmigkeiten bei der Suche nach lokalen Extrema, Divergenzen und Konvergenzen könnten nicht sauber erkannt werden, wenn sie auf solchen Trendlinien beruhen.

Aber nicht alle haben diese Beliebigkeit in der technischen Analyse akzeptiert. Zum Beispiel konnte Thomas DeMark trotzdem einen analytischen Ansatz finden und bot eine Lösung dieses Problems. In seinem Buch "The New Science of Technical Analysis" beschreibt er seine Methoden einer genaueren Analyse der aktuellen Preisentwicklung. In diesem Artikel berichte ich über zwei seiner Erkenntnisse — TD-Punkte und TD-Linien. Natürlich handelt Thomas DeMarks Buch nicht nur davon: er schreibt auch über Periodizität im Markt, das Elliott-Wellen-Prinzip und vieles mehr.

Dieser Artikel zeigt und erklärt den Prozess der Erstellung von drei Indikatoren und zwei Expert Advisors auf den Ideen von Thomas DeMark. Ich glaube, dieser Artikel wird viele Händler ansprechen, insbesondere Forex-Neulinge.


1. TD-Punkte

Die erste Erfindung von Thomas DeMark vereinfacht die Suche nach Preisextrema für das Zeichnen von Trendlinien. Er entschied sich für den Tageschart, um die bestimmte Kerze zu finden, deren Hoch höher war als das der Kerzen des Tage davor und danach (ich verwende dieses Wort, um mich auf die Kerzen zu beziehen, die einem TD-Punkt darstellen). Ist diese Bedingung erfüllt, kann im Chart auf das Hoch der so bestimmten Kerze ein TD-Punkt gesetzt werden. Dementsprechend ist das Tief eines bestimmten Tages dann ein TD-Punkt, wenn es tiefer liegt als die Tiefs des Vortages und des Tages danach.

"Bullish" TD-Punkt"Bearish" TD-Punkt

Fig. 1. "Bullish" und "Bearish" TD-Punkte

Die erste Abbildung oben zeigt einen Level-1 TD-Punkt (er ist rot markiert). Wie man sieht, ist das Hoch der bestimmten Kerze größer als das der Vorherigen und der Folgenden. Deren Maxima sind durch graue Linien kenntlich gemacht. Das zweite Bild zeigt den gleichen Fall für eine "Bearisch" TD-Punkt. Die Regeln sind in gleicher Weise erfüllt, das Tief der bestimmten Kerze ist kleiner als das die beiden benachbarten Kerzen.

Es wurden nur Level-1 TD-Punkte betrachtet. Das heißt, der Preis der bestimmten Kerze wurde nur mit einer Kerze vor und nachher verglichen. Wenn ein Level-2 TD-Punkt bestimmt werden soll, muss das Hoch der bestimmten Kerze mit zwei Kerzen vor- und nachher verglichen werden. Das Gleiche gilt für die Tiefstpreise.

Level-2 TD-Punkt

Fig. 2. Beispiel eines Level-2 TD-Punkt

Das Bild oben zeigt einen Level-2 TD-Punkt. Das Hoch der definierten Kerze ist deutlich höher als die Hochs der beiden Kerzen vor- und nachher. 

Level-40 TD-Punkt

Fig. 3. Level-40 TD-Punkt.

Es können mehr als zwei Level von TD-Punkte existieren, je nach der Anzahl der maximalen und minimalen Werte, die mit der bestimmten Kerze verglichen werden sollen. Es ist logisch, dass zum Beispiel ein Level-3 TD-Punkt auch einer für niedrigere Level, Level-1 und Leve-2, ist. In seinem Buch beschreibt Thomas DeMark Punkte bis zum Level-3.

Hier sei anzumerken, dass Indikatoren, die nach ähnlichem Prinzip arbeiten, bereits seit längerem existieren. Tatsächlich sind Bill Williams' Fraktale nicht anders als Level-2 TD-Punkte. Ich möchte erinnern, sie sind definiert durch zwei Kerzen mit niedrigerem Tief jeweils vor den bestimmten Kerzen und zwei nach ihnen, das ganz genau der Definition der Level-2 TD-Punkte entspricht.


2. TD-Linien

TD-Punkte selbst sind nur Extrema. Wir brauchen zwei Punkte (2 Maxima oder 2 Minima), um eine Linie zu zeichnen. Thomas DeMark verwendet nun nur die beiden letzten  Punkte als die entscheidendsten.

Level-1 TD-LinieLevel-2 TD-Linie

Fig. 4. Level-1 TD-Linien/ Level-2 TD-Linien.

Das Bild links zeigt zwei Level-2 TD-Linien (blaue Linien — Minima, grüne Linien — Maxima). Die Linie eines Levels bezieht sich auf den Level der Punkte dieser Linie. Das Bild rechts zeigt die TD-Linien des Level-3.

Thomas DeMark hat auch 3 Preisprojektoren entwickelt, die direkt auf den TD-Linien aufbauen, die ich in aller Kürze im Teil "Additional information" erwähnen werde.


3. Erstellen von Indikatoren

3.1. iTDDots

Es wäre ziemlich aufwendig, bei jeder neuen Kerze neue Punkte und Linien manuell einzutragen. Ich bin überzeugt, wenn etwas ohne Qualitätseinbußen automatisiert werden kann, sollte man es tun. Das Erstellen eines Indikators, der TD-Punkte ermittelt, ist unten beschrieben. Wir arbeiten hier mit der MQL4 Sprache.

Skizzieren wir zunächst unser Vorgehen. Die aktuelle Kerze wird vom Indikator nicht berücksichtigt, da ihr Maximum und Minimum sich ändern könnte und das zu falsch gesetzten Punkten führt. Wir betrachten daher nur vorherige Kerzen.

Ein Plan der Arbeitsweise des Indikators:

  • Erstelle alle TD-Punkte der historischen Kerzen beim Start auf dem Chart. Der Level wird durch den Nutzer festgelegt.
  • Überprüfe jede neue Kerze auf die Möglichkeit eines neuen Punktes und mach weiter, solange einer erscheint.

Die Hauptaufgabe des Indikators ist ein Extremum gegenüber den Extrema der n benachbarten Kerzen zu finden. Darum schlage ich eine Funktion vor, die das Maximum und Minimum der Kerzen innerhalb des benötigten Intervalls bestimmt. Um die Zahl der zu berücksichtigten Kerzen zu bestimmen, multipliziere den Punkt-Level mit 2 und addiere 1 zum Ergebnis.

Vom Indikator verwendete Kerzen

Fig. 5. Vom Indikator verwendete Kerzen

 Das Bild oben verdeutlicht, wie der Indikator die Kerzen für die Berechnung nummeriert. Es zeigt deutlich, wie die Anzahl der Kerzen berechnet werden.

Jetzt werde ich zeigen, wie der Code entwickelt wurde.

Der Indikator benötigt zwei Puffer für die Darstellung der TD-Punkte, da ja ein und dieselbe Kerze sowohl ein Maximum wie ein Minimum haben könnte. Also, so startet das Programm:

#property indicator_chart_window        //Das Chartfenster des Indikators
#property indicator_buffers 2           //2 Puffer werden verwendet
#property indicator_plots   2           //2 Puffer werden dargestellt
#property indicator_color1 clrGreen     //Standardfarbe des ersten Puffers
#property indicator_type1   DRAW_ARROW  //Darstellungstyp des ersten Puffers
#property indicator_width1 2            //Standarddicke der Linie des ersten Puffers
#property indicator_color2 clrBlue      //Standardfarbe des Zweiten Puffers
#property indicator_type2   DRAW_ARROW  //Standardtyp des zweiten Puffers
#property indicator_width2 2            //Standarddicke der Linie des zweiten Puffers

Der Nutzer bestimmt den Level der TD-Punkte des Indikators auf dem Chart:

input int Level = 1;

Die folgenden globalen Variablen werden für die Berechnung benötigt:

bool new_candle = true;         
double  pdH,                    //Für das Maximum einer bestimmten Kerze
        pdL,                    //Für das Minimum einer bestimmten Kerze
        pricesH[],              //Array der Maxima
        pricesL[];              //Array der Minima
bool    DOTH,                   //Punkt anzeigen (Maxima)
        DOTL;                   //Punkt anzeigen (Minima)
double  UpDot[],                //Puffer für die oberen TD-Punkte
        DownDot[];              //Puffer für die unteren TD-Punkte

Unten jetzt die Funktion Init():

int OnInit()
  {
   ChartRedraw(0);                              //Erforderlich, um Probleme beim Umschalten auf einen anderen Zeitrahmen zu verhindern
   SetIndexBuffer(0, UpDot);
   SetIndexBuffer(1, DownDot);
   SetIndexEmptyValue(0,0.0);
   SetIndexEmptyValue(1,0.0);
   SetIndexArrow(0,159);                        //Die Symbolnummer der Wingdingsschrift
   SetIndexArrow(1,159);
   SetIndexLabel(0, "TD " + Level + " High");   //Für die Anzeige im Chartfenster
   SetIndexLabel(1, "TD " + Level + " Low");
   return(INIT_SUCCEEDED);
  }

Um die Preise der benötigten Kerzen zu erhalten, erstellte ich diese Funktion unten:

void GetVariables(int start_candle, int level)
  {
   /*Diese Funktion sucht sich die Informationen der Kerzen um die TD-Punkte zu finden. Alle Variablen hier wurden bereits als global deklariert*/
   pdH = iHigh(NULL, 0, start_candle + 1 + level);      //Hoch der bestimmten Kerze
   pdL = iLow(NULL, 0, start_candle + 1 + level);       //Tief der bestimmten Kerze
   
   ArrayResize(pricesH, level * 2 + 1);                 //Setze Arraygröße
   ArrayResize(pricesL, level * 2 + 1);                 //
   
   for (int i = level * 2; i >= 0; i--){                //Sammeln aller Preise  (Max. und Min.) der benötigten Kerzen im Array
      pricesH[i] = iHigh(NULL, 0, start_candle + i + 1);
      pricesL[i] = iLow(NULL, 0, start_candle + i + 1);
  }

Und, zuletzt, das Interessanteste — der Code der Funktion start():

int start()
  {
   int i = Bars - IndicatorCounted();                   //Nötig, um zu verhindern, immer alles zu berechnen, wenn eine neue Kerze erscheint
   for (; i >= 0; i--)
     {                                                  //Alle Aktivitäten, um die TD-Punkte zu finden ab hier statt
      DOTH = true;
      DOTL = true;
      GetVariables(i, Level);                           //Hole die aktuellen Preise
      
      for(int ii = 0; ii < ArraySize(pricesH); ii++)
        {                                              //Prüfe, ob sich ein TD-Punkt in diesem Intervall befindet
         if (pdH < pricesH[ii]) DOTH = false;
         if (pdL > pricesL[ii]) DOTL = false;
        }   
         
      if(DOTH) UpDot[i + Level + 1] = pdH;              //Wen ja, ist die Erstellung wie folgt 
      if(DOTL) DownDot[i + Level + 1] = pdL;
      
      if(UpDot[i + Level + 1] ==  UpDot[i + Level + 2]) UpDot[i + Level + 2] = 0;       //Das behandelt den Fall, dass zwei Kerzen
      if(DownDot[i + Level + 1] ==  DownDot[i + Level + 2]) DownDot[i + Level + 2] = 0; //nacheinander dasselbe Extremum haben und setzte de TD-Punkt
     }                                                                                  //auf die zweite Kerze des Paares
   return(0);
  }

So, damit haben wir den Indikator, der die TD-Punkte erstellt. Zwei Charts mit dem TDDots Indikator sind unten dargestellt. Der Erste hat Level = 1, und der Zweite Level = 10. Das heißt alle TD-Punkte des ersten Chart sind umgeben von mindestens eine Kerze mit kleinerem Maxima oder höherem Minima und 10 solcher Kerzen im zweiten Chart. Beide Charts sollen die Arbeitsweise des Indikators zeigen.

Alle Level-1 TD-PunkteAlle Level-10 TD-Punkte

Fig. 6. Arbeitsweise des Indikators, gezeigt: Level-1 TD-Punkte und Level-10 TD-Punkte.


3.2. iTDLines

Wie ich weiter oben bemerkte, verwendet Thomas DeMarke nur die beiden letzten Punkte für die TD-Linien. Ich werde das in meinem Indikator automatisieren. Das Ziel des Indikators ist es, zwei gerade Linien durch die Punkte zu zeichnen. Die Frage ist, wie machen wir das. Natürlich kann man lineare Funktionen von Type y = kx + b verwenden. Es müssen die Koeffizienten k und b bestimmt werden, damit die Linie genau durch die bestimmten Punkten geht.

Die Koordinaten der beiden Punkte müssen dafür bekannt sein. Durch die Verwendung der Formel unten können wir k und b der linearen Funktion berechnen. Das x ist die Nummer der Kerze von rechts des Charts und y der Preis.

k = (y2 - y1) / (x2 - x1),
b = (x2 * y1 - x1 * y2) / (x2 - x1),
mit x1 - Kerzennummer des ersten Punktes,
     x2 - Kerzennummer des zweiten Punktes,
     y1 - Preis des ersten Punktes,
     y2 - Preis des zweiten Punktes.

Kennen wir k und b, bleibt noch die einfache lineare Gleichung für jede Kerze zu lösen, um die Punkte auf der TD-Linie zu erhalten. 

Des Indikators Code dafür ist nachfolgend:

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2

#property indicator_color1 clrGreen
#property indicator_color2 clrBlue

input int Level = 1;

double LU[], LD[];

//Diese Variablen werden verwendet, um den Aufwand der Berechnung zu reduzieren.
datetime LastCount;

Eine Variable mit dem Wert für den Level der TD-Punkte. Sie wird genauso wie in TDDots durch den Nutzer bestimmt. Dann folgt die Deklaration der zwei Arrays mit den Werten aller Punkte der Linien. Die Variable LastCount soll garantieren, dass die Berechnungen nur bei einer neuen Kerze durchgeführt werden.

Jetzt wenden wir uns der Funktion zu, die die Werte für k und b für jede Linie berechnet. Wir beschreiben sie Stück für Stück:

void GetBK(double &Ub, double &Uk, double &Db, double &Dk, int &EndUN, int &EndDN)
  {
   double TDU[];
   double TDD[];
   int TDU_n[];
   int TDD_n[];
   ArrayResize(TDU, 2, 2);
   ArrayResize(TDD, 2, 2);
   ArrayResize(TDU_n, 2, 2);
   ArrayResize(TDD_n, 2, 2);

Diese Funktion retourniert sechs Werte. Während die Aufgabe der erste vier Variablen klar ist, ist die Bedeutung der letzten beiden komplexer. Sie helfen beim Zeichnen der Linien. Jede kennzeichnet die Länge der Linie in Kernzenanzahl, beginnend bei der aktuellen.

//übernehme die Werte der Preise der Punkte und die Nummer der Kerze beginnend bei der aktuellen
   int Ui = 0;
   int Di = 0;
   for(int i = 0;; i++)
     {
      double current_bar_U = iCustom(NULL, 0, "TDDots", Level, 0, i);
      double current_bar_D = iCustom(NULL, 0, "TDDots", Level, 1, i);
      
      if(current_bar_U > 0 && Ui < 2)
        {
         TDU[Ui] = current_bar_U;   //Preis
         TDU_n[Ui] = i;             //Nummer
         Ui++;
        }
      if(current_bar_D > 0 && Di < 2)
        {
         TDD[Di] = current_bar_D;
         TDD_n[Di] = i;
         Di++;
        }
      if(Ui == 2 && Di == 2) break;
     }

Dieser Teil des Codes übernimmt die Werte der letzten beiden TD-Punkte. Die Preise werden zur Weiterberechnung in Arrays gespeichert.

   Ub = ( (TDU_n[0] * TDU[1]) - (TDU[0] * TDU_n[1]) ) / ( TDU_n[0] - TDU_n[1] );
   Uk = (TDU[0] - TDU[1]) / (TDU_n[0] - TDU_n[1]);
   
   Db = ( (TDD_n[0] * TDD[1]) - (TDD_n[1] * TDD[0]) ) / ( TDD_n[0] - TDD_n[1] );
   Dk = (TDD[0] - TDD[1]) / (TDD_n[0] - TDD_n[1]);   
   
   EndUN = TDU_n[1];
   EndDN = TDD_n[1];
  }

Das die Kerzen gemäß den Zeitreihen nummeriert sind (von rechts nach links), müssen entgegengesetzte Werte der Punkte verwendet werden. In anderen Worten, in den oben genannten Formeln müssen wir x2 durch x1 ersetzen und x1 mit x2 usw. Das schaut dann so aus:

b = (x1 * y2 - x2 * y1) / (x1 - x2),
k = (y1 - y2) / (x1 - x2),
mit x1 - Kerzennummer des ersten Punktes,
     x2 - Kerzennummer des zweiten Punktes,
     y1 - Preis des ersten Punktes,
     y2 - Preis des zweiten Punktes.

Jetzt folgt die Funktion OnInit():

int OnInit()
  {
   SetIndexBuffer(0, LU);
   SetIndexLabel(0, "TDLU");
   SetIndexBuffer(1, LD);
   SetIndexLabel(1, "TDLD");
      
   SetIndexEmptyValue(0, 0);
   SetIndexEmptyValue(1, 0);
   
   LastCount = iTime(NULL, 0, 1);
   
   return(INIT_SUCCEEDED);
  }

Die Puffer werden in dieser Funktion benannt und initialisiert. Damit auch eine allererste Berechnung der Linien beim ersten Tick durchgeführt wird, wird der Variable LastCount die Zeit der vorherigen Kerze zugewiesen. Eigentlich könnte jeder Zeitpunkt vor dem aktuellen dieser Variablen zugewiesen werden.

Jetzt können wir die Funktion start() schreiben:

int start()
  {
   //Neue Kerze oder Erststart
   if(iTime(NULL, 0, 0) != LastCount)
     {
      double Ub, Uk, Db, Dk;
      int eUp, eDp;
      
      GetBK(Ub, Uk, Db, Dk, eUp, eDp);
      
      //Entferne die alten Werte
      for(int i = 0; i < IndicatorCounted(); i++)
        {
         LU[i] = 0;      
         LD[i] = 0;
        }
         
      //Berechne die neuen Werte
      for(i = 0; i <= eUp; i++)
        {
         LU[i] = Uk * i + Ub;
        }
         
      for(i = 0; i <= eDp; i++)
        {
         LD[i] = Dk * i + Db;
        }
         
      LastCount = iTime(NULL, 0, 0);
     }
      
   return 0;
  }

Es ist notwendig, die alten Werte zu löschen und durch aktuelle zu ersetzen. Dafür wurde eine extra Schleife zu Beginn der Funktion eingeführt. Nun, nach der obigen Gleichung, werden zwei Linien erstellt und der Variablen LastCount das aktuelle Datum zugewiesen, um unnötige Berechnungen zu vermeiden.

Als Ergebnis arbeitet der Indikator wie folgt:

Beispiel der Arbeitsweise des Indikators

Fig. 7. Beispiel der Arbeitsweise des Indikators: TD-Linien auf Basis von Level-5 TD-Punkte.

Es ist leicht zu verstehen, Fig. 7 zeigt den Indikator mit Level-5.


3.3 Horizontale Linien des Indikators

Sicherlich gibt es eine Reihe von Möglichkeiten, die Preisniveaus über horizontale Linien zu charakterisieren. Ich biete dafür eine einfache Möglichkeit. iTDDots, der ja schon existiert, werden wir dafür verwenden (siehe p. 3.1).

Der Punkt ist ganz einfach:

  1. Der Indikator erhält n Werte von TD-Punkte des vom Nutzer festgelegten Levels
  2. Der Durchschnittspreis der Punkte wird berechnet
  3. Es wird Horizontale auf Basis dieser Werte gezeichnet

Es gibt jedoch Fälle, in denen die TD-Punkte weit vom letzten Punkt sind, so dass die Horizontale zu weit vom aktuellen Preis entfernt ist. Zur Lösung dieses Problems wurde eine vom Nutzer bestimmbare Variable eingeführt. Sie garantiert, dass die Durchschnittswerte der letzten n Extrema sind nicht mehr als eine bestimmte Anzahl von Points voneinander entfernt sind.

Schauen wir uns den Code des Indikators an.

Wir brauchen 2 Puffer und 3 Variablen, die der Nutzer festlegt:

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
#property indicator_color1 clrGreen
#property indicator_type1   DRAW_LINE
#property indicator_width1 2
#property indicator_color2 clrBlue
#property indicator_type2   DRAW_LINE
#property indicator_width2 2

input int TDLevel = 1;       //Level der Punkte
input int NumberOfDots = 3;  //Anzahl der Punkte
input double Delta = 0.001;  //Maximale Differenz zwischen zwei Punkten

double TDLU[], TDLD[];

Zwei Horizontale werden erstellt durch den Indikator. Da jetzt möglicherweise Werte für eine Langzeitanalyse länger bereit stehen müssen (anders als bei den Trendlinien im TDline Indikator), habe ich mich entschieden, dafür graphische Objekte, wie horizontale Linien, zu verwenden. Damit wird es auch viel leichter mit diesen Indikator in die Zukunft zu projizieren, wenn die Werte der Horizontalen in den Indikatorpuffern gespeichert werden. Ich entschied, diese Werte unter der aktuellen Kerze mit dem Index 0 zu speichern, damit sie über eine Abfrage von iCustom durch ein anderes Programm leicht zu erreichen sind.

int OnInit()
  {
   SetIndexBuffer(0,TDLU);
   SetIndexBuffer(1,TDLD);
   SetIndexEmptyValue(0,0.0);
   SetIndexEmptyValue(1,0.0);
   SetIndexLabel(0, "U HL");
   SetIndexLabel(1, "D HL");
   ObjectCreate(0, "U horizontal level", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
   ObjectCreate(0, "D" horizontal level, OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
   return(INIT_SUCCEEDED);
  }

Unten ist die Funktion, die den Wert der Horizontalen berechnet.

double GetLevelPrice(int ud,int n,double delta,int level)
  {
   /* ud - Linientyp des Indikators. 0 - U, anderer Wert - D.
   n - Anzahl der Punkte.
   delta - maximale Differenz zwischen Ihnen
   level - Level der Punkte.*/

   //Vorbereitung der Arrays der Preise der Punkte
   double TDU[];
   double TDD[];
   ArrayResize(TDU,n,n);
   ArrayResize(TDD,n,n);
   ArrayInitialize(TDU,0);
   ArrayInitialize(TDD,0);
 
   //Die Schleife läuft zweimal, weil nur zwei Puffer existieren
   for(int Buffer=0; Buffer<2; Buffer++)
     {
      int N=0;
      int Fails=0;
      bool r=false;
      for(int i=0; r==false; i++)
        {
         double d=iCustom(NULL,0,"TDDots",level,Buffer,i);
         if(d>0)
           {
            if(N>0)
              {
               if(Buffer==0) double cp=TDU[N-1];
               else cp=TDD[N-1];
               if(MathAbs(d-cp)<=delta)
                 {
                  if(Buffer == 0)
                     TDU[N] = d;
                  else TDD[N]=d;
                  N++;
                 }
               //Ist der Abstand zu groß, wird 1 zum Fehler addiert
               else
                 {
                  Fails++;
                 }
              }
            else
              {
               if(Buffer == 0)
                  TDU[N] = d;
               else TDD[N]=d;

               N++;
              }
           }
         //Falls es zu viele Fehler gibt, wird die Schleife verlassen
         if(Fails>2 || N>n) r=true;
        }
     }
   
   //Berechnen des Durchschnitts
   double ATDU = 0;
   double ATDD = 0;
   N=0;
   for(i=0; i<ArraySize(TDU); i++)
     {
      ATDU=ATDU+TDU[i];
      if(TDU[i]==0)
        {
         i=ArraySize(TDU);
        }
      else
        {
         N++;
        }
     }
   ATDU=ATDU/N;
   N=0;
   for(i=0; i<ArraySize(TDD); i++)
     {
      ATDD=ATDD+TDD[i];
      if(TDD[i]==0)
        {
         i=ArraySize(TDD);
        }
      else
        {
         N++;
        }
     }
   ATDD=ATDD/N;

   //Der Funktion Rückgabewert
   if(ud == 0) return ATDU;
   else return ATDD;
  }

 Jetzt bleibt nur noch, in der Funktion, die bei jedem Tick gestartet wird, den Wert abzufragen und den existierenden Objekten zuzuweisen:

void start()
  { 
   //Ich lösche die Werte der Indikatorpuffer der vorherigen Kerzen
   TDLD[1] = 0;
   TDLU[1] = 0;

   TDLD[0] = GetLevelPrice(1, TDLevel, Delta, NumberOfDots);
   TDLU[0] = GetLevelPrice(0, TDLevel, Delta, NumberOfDots);
   
   //Falls Objekte verschwunden sein sollten
   if(ObjectFind("U horizontal level") < 0)
     {
      ObjectCreate(0, "U horizontal level", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
     }
   if(ObjectFind("D horizontal level") < 0)
     {
      ObjectCreate(0, "D" horizontal level, OBJ_HLINE, 0, iTime(NULL, 0, 0), 0);
     }
   
   ObjectSetDouble(0, "U horizontal level", OBJPROP_PRICE, TDLU[0]);
   ObjectSetDouble(0, "D horizontal level", OBJPROP_PRICE, TDLD[0]);
  }

Zur leichteren Verwendung löscht der Indikator nach seinem Entfernen vom Chart seine Objekte:

void OnDeinit(const int reason)
  {
   if(!ObjectDelete("U horizontal level")) Print(GetLastError());
   if(!ObjectDelete("D horizontal level")) Print(GetLastError());
  }

Damit bietet der Indikator einen einfachen Weg, horizontale Linien automatisch nach den Vorgaben des Nutzers zu erstellen.

Beispiel der Arbeitsweise des Indikators

Fig. 8. Beispiel der Arbeitsweise des Indikators 



4. Der Expert Advisor für den Handel mittels des Horizontalen Indikators

Jeder Indikator sollte helfen, Geld zu verdienen, vorzugsweise automatisch, wenn sich die Gelegenheit ergibt. Klar ist, dass der hier vorgestellte Expert Advisor nicht zu jeder Zeit in jeder Marktsituation Gewinne erzielen kann, aber das mit ihm verfolgte Ziel ist ein anderes. Er wurde geschrieben, um die Möglichkeiten des Einsatzes dieses Indikators zu zeigen und die Struktur eines Expert Advisors im Allgemeinen.

Es wird jetzt noch genauer. Unter Verwendung horizontaler Linien des Indikators schreiben wir einen Expert Advisor, der auf Basis der unten erwähnten Signale handelt.

Kaufbedingungen:

  • Der Preis durchbricht die obere Horizontale
  • Der Preis übersteigt um n Points die obere Horizontale

Verkaufbedingungen - gespiegelt:

  • Der Preis durchbricht die untere Horizontale
  • Der Preis unterschreitet um n Points die untere Horizontale

Lassen wir uns das im Bild anzeigen:

Kauf-SignalVerkaufssignal

Fig. 9. Kauf- und Verkaufbedingungen

Der Expert Advisor eröffnet nur eine Position und begleiten sie mit einem Trailing-Stopp, d.h. es wird ein Stopp-Loss durch die vom Nutzer festgelegte Anzahl von Points nachgezogen.

Die Positionsschließung erfolgt ausschließlich durch den Stopp-Loss.

Schauen wir uns den MQL4-Code des Expert Advisors an.
Beschreiben wir zuerst die Variablen, die der Nutzer bei seinem Start festlegt.

input int MagicNumber = 88341;      //Der Expert Advisor gibt seinen Positionen diese "Magic Number"
input int GL_TDLevel = 1;           //Level der TD-Punkte für den Horizontal-Indikator
input int GL_NumberOfDots = 3;      //Anzahl der Punkte für den Horizontal-Indikator
input double S_ExtraPoints = 0.0001;//Anzahl der zusätzlichen Points des Preises im Vergleich zu den Horizontalen
input double GL_Delta = 0.001;      //Differenz der Werte der TD-Punkte, die vom Horizontal-Indikator berücksichtigt werden
input int StopLoss = 50;            //Stopp-Loss Abstand
input double Lot = 0.01;            //Lot-Größe

Die nächste Funktion prüft, ob die TD-Linien durchbrochen wurden. Damit nicht bei jedem Durchbruch der Linie ein neues Signal durch den Indikator "iglevels" entsteht, prüfen wir, ob bei einen Ausbruch nach oben das Hoch der vorherigen Kerze unter der oberen Horizontale liegt oder für einen Ausbruch nach unten das Tief der vorherigen Kerze über der unteren Horizontale liegt. Dadurch erreichen wir, dass ein Signal nur einmal je Kerze erscheint. Der Code dieser Funktion ist wie folgt:

int GetSignal(string symbol,int TF,int TDLevel,int NumberOfDots,int Delta,double ExtraPoints)
  {
//Der Preiswert des Levels der oberen TD-Punkte
   double UL=iCustom(symbol,TF,"iglevels",GL_TDLevel,GL_NumberOfDots,GL_Delta,0,0)+ExtraPoints;
//...und der unteren TD-Punkte
   double DL=iCustom(symbol,TF,"iglevels",GL_TDLevel,GL_NumberOfDots,GL_Delta,1,0)-ExtraPoints;

   if(Bid<DL && iLow(symbol,TF,1)>DL)
     {
      return 1;
     }
   else
     {
      if(Ask>UL && iHigh(symbol,TF,1)<UL)
        {
         return 0;
        }
      else
        {
         return -1;
        }
     }
  }

Die folgenden Variablen werden für das Funktionieren des Expert Advisors global deklariert:

int Signal = -1;          //Aktuelles Signal
datetime LastOrder;       //Zeitpunkt der letzten Order. Das verhindert das Eröffnen mehrerer Positionen während ein und derselben Kerze.

In der Funktion Init() muss diesem Wert irgend ein alter Zeitwert zugewiesen werden, damit neue Positionen eröffnet werden können.

int OnInit()
  {
   LastOrder = iTime(NULL, 0, 1);
   return(INIT_SUCCEEDED);
  }

Im Folgenden die Funktion OnTick, in der alles Wichtige passiert:

void OnTick()
  {
   bool order_is_open=false;
//Suche offene Positionen
   for(int i=0; i<OrdersTotal(); i++)
     {
      if(!OrderSelect(i,SELECT_BY_POS)) Print(GetLastError());

      if(OrderMagicNumber()==MagicNumber)
        {
         order_is_open=true;
         break;
        }
     }

//Frage das aktuelle Signal ab
   Signal=GetSignal(Symbol(),0,GL_TDLevel,GL_NumberOfDots,GL_Delta,S_ExtraPoints);

//Berechne den Stopp-Loss
   double tsl=NormalizeDouble(StopLoss*MathPow(10,-Digits),Digits);


   if(order_is_open==true)
     {
      //Kalkuliere den Stopp-Loss Preis
      double p=NormalizeDouble(Ask-tsl,Digits);
      if(OrderType()==1) p=NormalizeDouble(Ask+tsl,Digits);

      if(OrderType()==0 && OrderStopLoss()<p)
        {
         if(!OrderModify(OrderTicket(),OrderOpenPrice(),p,0,0)) Print(GetLastError());
        }
      if(OrderType()==1 && OrderStopLoss()>p)
        {
         if(!OrderModify(OrderTicket(),OrderOpenPrice(),p,0,0)) Print(GetLastError());
        }
     }
//Wenn es keine offenen Positionen gibt
   if(order_is_open==false)
     {
      //und noch keine neue Position während dieser Kerze eröffnet wurde
      if(iTime(NULL,0,0)!=LastOrder)
        {
         //Kauf
         if(Signal==0)
           {
            if(!OrderSend(NULL,0,Lot,Ask,5,Ask-tsl,0,NULL,MagicNumber)) Print(GetLastError());
            LastOrder=iTime(NULL,0,0);
           }
         //Verkauf
         if(Signal==1)
           {
            if(!OrderSend(NULL,1,Lot,Bid,5,Ask+tsl,0,NULL,MagicNumber)) Print(GetLastError());
            LastOrder=iTime(NULL,0,0);
           }
        }
     }
  }

Das prüft, ob eine offenen Position existiert, und, falls es eine gibt, prüft der Expert Advisor ob der Stopp-Loss nachgezogen werden kann. Gibt es keine offene Position, handelt er entsprechend des erhaltenen Signals. Wichtig: Wollen Sie zwei Real-Zahlen in Ihrem Programm vergleichen, verwenden Sie vorher NormalizeDouble, sonst könnten Sie nicht nachvollziehbare Ergebnisse aus dem Vergleich erhalten.

Der Expert Advisor arbeitet wie folgt:

Beispiel der Arbeitsweise des Expert Advisors

Fig.10. Beispiel der Arbeitsweise des Expert Advisors im Strategie Tester

Fig. 10 zeigt, dass er sowohl Gewinne wie Verluste erzielt. Die Gewinne übersteigen die Verluste, weil durch die Einstellungen Verluste begrenzt sind, Positionen im Gewinn aber durch einen Stopp-Loss kontrolliert werden. Der EA kann daher positive wie negative Ergebnisse zeigen. Wir dürfen jedoch nicht vergessen, dass er nicht für den realen Handel sondern nur als Demonstration für die Möglichkeiten der Anwendung der TD-Punkte und der Indikatoren, die auf ihnen beruhen, erstellt wurde. Unser Expert Advisor leistet genau dies.


Testergebnis

Fig.11. Testergebnis

Dieser Expert Advisor kann weiter verbessert werden, durch das Hinzufügen einer teilweise Schließung der Position bei Erreichen eines bestimmten Gewinns, um zu vermeiden, dass eine Trendwende die Position in den Verlust zieht. In diesem Fall würde eine Position ohne Gewinn oder Verlust geschlossen werden als Form einer Kapitalsicherung. Es sollte auch erwähnt werden, dass der Stopp-Loss gemäß der Preisbewegungen nachgezogen werden muss.


5. Expert Advisor handelt die TD-Linien

Dieser Expert Advisor ist ein Beispiel der Verwendung der TD-Linien durch ein Handelssystem. Allgemein sind TD-Linien Trendlinien Es bedeutet, dass wenn der Preis eine TD-Linie kreuzt und sich weiter in diese Richtung bewegt, könnte sich der Trend geändert haben.

Der Expert Advisor handelt nur zwei Signale:

Verkaufssignal      Kauf-Signal

Fig.12. Die Handelssignale von etdlines

Eine Kaufposition wird eröffnet, wenn eine "Bullish" TD-Linie nach oben durchbrochen, und der Preis sich weiter um mehrere Points (nutzerbestimmt) in die gleiche Richtung bewegt hat. Außerdem sollte eine TD-Linie nach unten laufen, d.h. der kleinere Wert der Linie sollte einer späteren Kerze entsprechen.

Die Verkaufposition wird eröffnet, wenn eine "Bearish" TD-Linie nach unten durchbrochen wurde und die Preise weiter ein paar Points gefallen sind. Diese TD-Linie soll nach oben laufen.

Die Position wird begleitet von einem fixen Trailing Stopp. Die Positionsschließung erfolgt ausschließlich durch den Stopp-Loss.

Im Folgenden der Code der nutzerbestimmten Variablen:

input int Level=1;                 //Level der TD-Linien
input double Lot=0.01;             //Lotgröße
input int AddPips=3;               //Zusätzlich Anzahl von Punkten
input int Magic=88342;             //"Magic Number" der Positionen
input int Stop=50;                 //Stopp-Loss Abstand
input int Bars_To_Open_New_Order=2;//Wartezeit in Kerzen zwischen zwei neuen Positionen

datetime LastBar;
datetime Trade_is_allowed;

Dieser Expert Advisor verwendet eine Wartezeit zwischen dem Schließen einer alten Position und dem Öffnen einer neuen. Dies verhindert das zu häufige Öffnen neuer Positionen.

Der Expert Advisor initialisiert LastBar und Trade_is_allowed in der OnInit-Funktion:

int OnInit()
  {
   LastBar=iTime(NULL,0,1);
   Trade_is_allowed=iTime(NULL,0,0);

   return(INIT_SUCCEEDED);
  }

Die nächste Funktion liefert das Handelssignal:

int GetSignal()
  {
//Bei einer neuen Kerze
   if(LastBar!=iTime(NULL,0,0))
     {
      double DU = iCustom(NULL, 0, "itdlines", Level, 0, 0);
      double DD = iCustom(NULL, 0, "itdlines", Level, 1, 0);
      double DU1 = iCustom(NULL, 0, "itdlines", Level, 0, 1);
      double DD1 = iCustom(NULL, 0, "itdlines", Level, 1, 1);
     }

   double add_pips=NormalizeDouble(AddPips*MathPow(10,-Digits),Digits);

//U-Linien-Ausbruch --> Kaufen
   if(Ask>DU+add_pips && iLow(NULL,0,0)<Ask && DU<DU1)
     {
      return 0;
     }
   else
     {
      //D-Linien-Ausbruch --> Verkaufen
      if(Bid<DD-add_pips && iHigh(NULL,0,0)>Bid && DD>DD1)
        {
         return 1;
        }
      //Kein Ausbruch --> kein Signal
      else
        {
         return -1;
        }
     }

   return -1;
  }

Die Variable LastBar wird zur Bestimmung der neuen Kerze verwendet. Das ermöglicht die Berechnung der beiden letzten Punkte bei einer neuen Kerze und verhindert sie bei jedem neuen Tick. Die Funktion liefert: 0 — Kauf, 1 — Verkauf, -1 — kein Signal.

Zum Schluss die Funktion OnTick, wo alles passiert:

void OnTick()
  {
   int signal=GetSignal();
   bool order_is_open=false;

//Suche offene Positionen
   for(int i=0; i<OrdersTotal(); i++)
     {
      if(!OrderSelect(i,SELECT_BY_POS)) Print(GetLastError());

      //Prüfe die "Magic Number"
      if(OrderMagicNumber()==Magic)
        {
         order_is_open=true;
         i=OrdersTotal();
        }
     }

//Berechne den Stopp-Loss
   double stop=Stop*MathPow(10,-Digits);
//Prüfe auf offene Positionen
   if(order_is_open==true)
     {
      //Prüfe den Nachzug des Stopp-Loss

      //Kalkuliere den Stopp-Loss Preis
      double order_stop=NormalizeDouble(OrderStopLoss(),Digits);

      //Kaufposition
      if(OrderType()==0)
        {
         if(order_stop<NormalizeDouble(Ask-stop,Digits))
           {
            if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Ask-stop,Digits),0,0))Print(GetLastError());
           }
        }
      //Verkaufposition
      if(OrderType()==1)
        {
         if(order_stop>NormalizeDouble(Bid+stop,Digits))
           {
            if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Bid+stop,Digits),0,0))Print(GetLastError());
           }
        }
      Trade_is_allowed=iTime(NULL,0,0)+ChartPeriod(0)*60*Bars_To_Open_New_Order;
     }
//Es gibt noch keine offene Position
   else
     {
      if(signal>=0 && iTime(NULL,0,0)>Trade_is_allowed)
        {
         if(signal==0)
           {
            if(!OrderSend(NULL,signal,Lot,Ask,5,Ask-stop,0,NULL,Magic)) Print(GetLastError());
           }
         if(signal==1)
           {
            if(!OrderSend(NULL,signal,Lot,Bid,5,Bid+stop,0,NULL,Magic)) Print(GetLastError());
           }

        }
     }
  }

Zuerst wird das Signal abgefragt, und offenen Positionen werden in einer Schleife nach der nutzerbestimmten "Magic Number" gesucht (um andere System nicht zu beeinträchtigen). Wenn dann Positionen gefunden wurden, wird geprüft, ob der Stopp-Loss nachgezogen werden kann. Gibt es aber keine offenen Positionen, kann eine neue Position eröffnet werden, wenn der Zeitpunkt der aktuellen Kerze größer ist als der Wert von Trade_is_allowed (die ihren Wert bei jedem neuen Tick und einer offenen Position ändert).

Dieser Expert Advisor handelt entsprechend:

Arbeitsweise im Strategie Tester

Fig.13. Beispiel der Arbeitsweise des Expert Advisors etdlines

Es wird deutlich, der Expert Advisor ist erfolgreich bei großen Preisbewegungen, aber scharfen Kursbewegungen führen zu einer Reihe falscher Signale und zu Verlustpositionen. Die Testergebnisse des Expert Advisors im Bild unten:

Testergebnis

Fig.14. Testergebnisse von etdlines


Schlussfolgerung

Mein Ziel war es, Thomas DeMarks TD-Punkte und TD-Linien zu zeigen und sie in MQL4 zu verwirklichen. Dieser Artikel beschreibt das Erstellen von 3 Indikatoren und 2 Experten. Wie Sie sehen können, wurde die Idee von Demark logisch in Handelssysteme eingebettet und ermöglicht weitere Möglichkeiten, sie einzusetzen.  


Die Verwendung der unten beigefügten Indikatoren und Expert Advisor erfordert die Installierung von itddots.mq4, da es von allen verwendet wird. Das Gleiche gilt für die Verwendung von itdlines.mq4 durch etdlines.mq4 und iglevels.mq4 durch eglevels.mq4. Das ist sehr wichtig, da ohne die notwendigen Indikatoren die abhängigen Programme nicht funktionieren.