Muster von Ausbrüchen aus einem Kanal

Dmitriy Gizlyk | 12 Februar, 2018


Einführung

Der globale Markt ist ein uralter Kampf zwischen Verkäufern und Käufern. Verkäufer wollen mehr verdienen, indem sie zu einem höheren Preis verkaufen, während Käufer nicht bereit sind, ihr verdientes Geld zu geben und einen billigeren Preis zahlen wollen. Nach der Theorie der Ökonomie findet sich der wahre Preis am Punkt der Gleichheit von Angebot und Nachfrage. Das scheint wahr zu sein. Das Problem liegt jedoch in der Marktdynamik, denn Angebot und Nachfrage verändern sich ständig.

Der Kampf führt zu Kursschwankungen. Diese Schwankungen bilden Kanäle, die Händler analysieren, um Markttrends zu finden. Diese Bewegungen wiederum bilden Fluktuationen höherer Ordnung. Eines der ersten Anzeichen für eine Trendwende ist der Ausbruch aus einem entstandenen Preiskanal.

1. Theoretische Aspekte der Strategie

Preiskanäle und Trendlinien sind mit die wichtigsten grafischen Analyseformen. Preiskanäle zeigen den aktuellen Trend und die Schwankungsbreite der Preis innerhalb dieses Trends. Je nach aktuellem Trend können die Kanäle aufsteigend, absteigend oder seitwärts (flach) sein.

Das Terminal MetaTrader 5 unterstützt vier Arten von Kanälen.

  1. Äquidistanter Kanal
  2. Standardabweichungskanal
  3. Regressionskanal
  4. Andrews Gabel
Weitere Details zu den Prinzipien der Kanalkonstruktion und ihren Unterschieden finden Sie in der Terminal-Hilfe. In diesem Artikel werden wir die allgemeinen Aspekte einer Kanalerstellung betrachten.

Als Beispiel werden wir den Chart des EURUSD M30 und die Kursschwankungen analysieren.

Chart EURUSD M30

Indem wir den Kursverlauf Trends unterteilen, können wir drei Preiskanäle markieren. Äquidistante Kanäle sind in der untenstehenden Tabelle dargestellt. Absteigende Kanäle sind mit roten Linien markiert, ein aufsteigender Kanal ist blau markiert. Die Zeichnung eines absteigenden Kanals beginnt mit der oberen Kanalgrenze, die den Trend anhand der Höchststände der Preisschwankungen bestimmt. Die untere Grenze wird auf Basis der Tiefs parallel zur oberen ermittelt. Die untere Grenze kann bei maximaler oder mittlerer Abweichung gezeichnet werden. Die Konstruktion aufsteigender Kanäle ist umgekehrt: Zuerst wird die untere Grenze gezeichnet, dann die obere. Wenn wir einen Seitwärtskanal zeichnen, sollten wir auf den vorherigen Trend achten, denn flache Preisschwankungen wirken oft als Korrektur der vorherigen Bewegung, die sich nach einer Seitwärtsbewegung fortsetzen kann.

Chart des EURUSD M30 mit Preiskanälen.

Für einen Handel auf Basis der Kanäle werden in der Regel zwei Arten von Strategien verwendet: Der Handel innerhalb des Kanals (eine Trendstrategie) und der Kanalausbruch (eine Gegentrendstrategie). In diesem Artikel befassen wir uns mit der Strategie des Kanalausbruchs, die auf eine Trendwende hinweist.

Wenn sich der Trend ändert, verlässt der Preis den Kanal in die entgegengesetzte Richtung des aktuellen Trends. Ein Kanal gilt als unterbrochen, wenn eine Kerze außerhalb seiner Grenzen schließt.

Berücksichtigen Sie, dass der Preis nach dem Kanalausbruch sich zurück in den Kanal bewegt und sich erst dann in eine neue Trendrichtung bewegt. Diese Bewegung führt häufig dazu, dass vor der Kursbewegung die Stop-Loss' von Händlern ausgelöst werden. Um dies zu vermeiden, werden wir in den Markt erst eintreten, nachdem der Preis in den Kanal zurückgekehrt ist. 

2. Automatisieren der Suche nach Mustern

Um einen Algorithmus für das Finden von Mustern zu erstellen, werden wir die von Dmitri Fedoseev in seinem Artikel [1] vorgeschlagene Methode verwenden. Lassen Sie uns die Definition einer horizontalen Formation aus dem in diesem Artikel beschriebenen Indikator verwenden. Sein Code sollte der Klasse CChannel hinzugefügt werden.

Wir haben uns entschieden, eine Position zu eröffnen, nachdem der Preis wieder an die Kanalgrenzen zurückgekehrt ist und nicht sofort nach einem Ausbruch. In diesem Fall kann es zu einer Situation kommen, wenn wir darauf warten, dass ein Preis auf einen Kanal zurückkehrt, während der EA bereits nach einem neuen Kanal sucht. Um eine parallelen Existenz mit mehreren Kanälen zu ermöglichen, wird die erzeugte Klasse nur ein Muster finden und verarbeiten. Fassen wir alle Klassen in einem Array zusammen. Sobald das Muster verarbeitet und eine entsprechende Reihenfolge geöffnet wird, wird die Instanz der Klasse gelöscht. Daher müssen wir, indem wir den Zickzack-Indikator in einer Klasse initialisieren, den Indikator für jede Klasse aufrufen. Um dies zu vermeiden, werden wir den Indikator im Hauptprogramm initialisieren und nur das Handle des Indikators der Klasse übergeben.

Um doppelte Kanälen zu vermeiden, werden wir der Klasse bei der Initialisierung zusätzlich die bisherige Zeit des Kanalausbruchs übergeben. Dadurch wird sichergestellt, dass die nächste Klasseninstanz nach einem Kanal nach dem Ausbruch aus dem vorherigen Kanal sucht.

Diese Klasse ist unten dargestellt.

class CChannel : public CObject
  {
private:
   string            s_Symbol;      // Symbol
   ENUM_TIMEFRAMES   e_Timeframe;   // Zeitrahmen
   int               i_Handle;      // Handle des Indikators
   datetime          dt_LastCalc;   // Letzte berechnete Bar
   SPeackTrough      PeackTrough[]; // Array mit den Spitzen der ZigZags
   int               CurCount;      // Zähler der Spitzen
   int               PreDir;        // Vorherige Richtung der ZigZags
   int               CurDir;        // Aktuelle Richtung der ZigZags
   int               RequiredCount; // Minimale Anzahl an Spitzen für einen Kanal
   double            d_Diff;        
   bool              b_FoundChannel;
   bool              b_Breaked;
   datetime          dt_Breaked;
   double            d_BreakedPrice;                     

   void              RefreshLast(datetime time,double v);
   void              AddNew(datetime time,double v,int d);
   bool              CheckForm(double base);
   double            GetRessistPrice(SPeackTrough &start_peack, datetime time);
   double            GetSupportPrice(SPeackTrough &start_peack, datetime time);
   bool              DrawChannel(MqlRates &break_bar);
   bool              DrawChannel(void);
   bool              UnDrawChannel(void);

public:
                     CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe);
                    ~CChannel();
   bool              Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time,bool &breaked,datetime &breaked_time);
  };

In den Parametern der Funktion der Klasseninitialisierung wird Folgendes übergeben: Das Indikatorhandle, die Startzeit der Kanalsuche, der Name des Symbols und der Zeitrahmen. Im Funktionskörper werden die übergebenen Daten in den entsprechenden Variablen gespeichert und die Initialwerte anderen Variablen zugewiesen.

CChannel::CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe) : RequiredCount(4),
                                                                                             CurCount(0),
                                                                                             CurDir(0),
                                                                                             PreDir(0),
                                                                                             d_Diff(0.1),
                                                                                             b_Breaked(false),
                                                                                             dt_Breaked(0),
                                                                                             b_FoundChannel(false)
  {
   i_Handle=handle;
   dt_LastCalc=fmax(start_time-1,0);
   s_Symbol=symbol;
   e_Timeframe=timeframe;
  }

Die Funktion UnDrawChannel wird in der Funktion zur Deinitialisierung der Klassen aufgerufen. Sie entfernt zuvor hinzugefügte grafische Objekte aus dem Diagramm.

Die Hauptoperationen werden in der Funktion Calculate ausgeführt. Zu seinen Parametern gehören Verweise auf Variablen zum Schreiben von Informationen über Kanalausbrüche und einen durch das Muster geöffnete Position. Die Verwendung von Referenzen in Parametern erlaubt der Funktion, die Werte mehrerer Variablen zurückzugeben.

Die Kurse des Symbols, die mit der zuletzt gespeicherten Spitze beginnen, werden in das Array am Anfang der Funktion geladen. Wenn das Laden der benötigten Anführungszeichen fehlschlägt, gibt die Funktion false zurück.

bool CChannel::Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time, bool &breaked,datetime &breaked_time)
  {
   MqlRates rates[];
   CurCount=ArraySize(PeackTrough);
   if(CurCount>0)
     {
      dt_LastCalc=PeackTrough[CurCount-1].Bar;
      CurDir=PeackTrough[CurCount-1].Dir;
     }
   int total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc-PeriodSeconds(e_Timeframe),0),TimeCurrent(),rates);
   if(total<=0)
      return false;

  Danach initialisieren wir die zurückgegebenen Variablen.

   stop_loss=-1;
   breaked=b_Breaked;
   breaked_time=dt_Breaked;
   deal_time=0;

Dann wird die Schleife der Datenverarbeitung über jede Bar gestartet. Zuerst wird das Erscheinen einer neuen Spitze des ZigZags überprüft. Gibt es eine neue Spitze oder die wurde verschoben (repainted), werden die Daten mithilfe der Funktionen RefreshLast und AddNew im Array gespeichert.

   for(int i=0;i<total;i++)
     {
      if(rates[i].time>dt_LastCalc)
        {
         dt_LastCalc=rates[i].time;
         PreDir=CurDir;
        }
      else
         continue;

      // neues maximu       

      double lhb[2];
      if(CopyBuffer(i_Handle,4,total-i-1,2,lhb)<=0)
         return false;

      if(lhb[0]!=lhb[1])
        {
         if(CurDir==1)
            RefreshLast(rates[i].time,rates[i].high);
         else
            AddNew(rates[i].time,rates[i].high,1);
        }

      // neues Minimum

      double llb[2];
      if(CopyBuffer(i_Handle,5,total-i-1,2,llb)<=0)
         return false;

      if(llb[0]!=llb[1])
        {
         if(CurDir==-1)
            RefreshLast(rates[i].time,rates[i].low);
         else
            AddNew(rates[i].time,rates[i].low,-1);
        }

Im nächsten Schritt wird geprüft, ob die minimale Anzahl von Spitzen, die zur Identifizierung eines Kanals benötigt werden, gebildet wurde. Wenn ja, dann prüfen wir, ob die aktuelle Kursbewegung mit der Kanalbildung übereinstimmt. Diese Prüfung wird in der Funktion CheckForm durchgeführt.

Wenn sie übereinstimmen, wird der Variablen b_FoundChannel "true" zugewiesen. Andernfalls wird die älteste Spitze aus der Liste der Spitzen verworfen, die Initialwerte ​​werden Variablen zugewiesen und die Operation kehrt an den Anfang der Schleife zurück.

      double base=(CurCount>=2 ? MathAbs(PeackTrough[1].Val-PeackTrough[0].Val) : 0);
   
      if(CurCount>=RequiredCount && !b_FoundChannel)
        {
         if(CurDir!=PreDir)
           {
            if(CheckForm(base))
              {
               b_FoundChannel=true;
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
        }

Nachdem der Kanal gefunden wurde, wird ein Ausbruch gesucht. Wenn der Kanal unterbrochen ist, wird den Variablen b_Breaked und breaked "true" zugewiesen. Die Eröffnungszeit der Kerze, die 'ausgebrochen' ist, wird in den Variablen dt_Breaked und breaked_time gespeichert, und der Extremwert der Kerze wird in d_BreakedPrice gespeichert. Dann wird die Funktion DrawChannel aufgerufen, um den Kanal und den Punkt des Ausbruchs auf dem Chart zu zeichnen. Beachten Sie, dass die Funktion nach einem Ausbruch in entgegengesetzter Richtung zum aktuellen Trend sucht. Wenn sich der Trend verstärkt und der Preis den Kanal in der aktuellen Trendrichtung verlässt, initialisiert die Klasse die Erstellung einer neuen Klasseninstanz, um nach dem Kanal zu suchen (siehe die globale Funktion SearchNewChannel weiter unten).

Sobald der Ausbruch gefunden ist, suchen wir nach dem Muster für die Positionseröffnung. Ein Eröffnungssignal wird erzeugt, wenn der Preis den Kanal durchbricht und dann zu seiner Grenze zurückkehrt. Ein zusätzliches Eingangssignal ist das Schließen einer Kerze über dem Extremum der Kerze des Ausbruchs für eine Kaufposition oder darunter für eine Verkaufsposition. Dieses Muster wird für den Markteintritt verwendet, wenn der Preis den Kanal in einer starken Bewegung durchbricht und sich ohne Korrektur weiter bewegt.

Wenn ein Signal erzeugt wird, schreiben wir den gewünschten Auftragstyp in die Variable "Typ", berechnen den Stop-Loss und speichern ihn in der entsprechenden Variable. In der Variablen deal_time wird die Eröffnungszeit jener Kerze gespeichert, die das Signal ausgelöst hat.

      if(b_FoundChannel)
        {
         if(PeackTrough[0].Dir==1)
           {
            if(PeackTrough[0].Val>PeackTrough[2].Val)
              {
               if(!b_Breaked)
                 {
                  if((rates[i].close-GetRessistPrice(PeackTrough[0],rates[i].time))>=(d_Diff*base))
                    {
                     b_Breaked=breaked=true;
                     dt_Breaked=breaked_time=rates[i].time;
                     d_BreakedPrice=rates[i].high;
                     DrawChannel(rates[i]);
                     continue;
                    }
                  if(CurCount>4 && PeackTrough[CurCount-1].Dir==1 && (GetRessistPrice(PeackTrough[1],rates[i].time)-PeackTrough[CurCount-1].Val)>0)
                    {
                     int channels=ArraySize(ar_Channels);
                     if(ar_Channels[channels-1]==GetPointer(this))
                       {
                        SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe));
                       }
                    }
                 }
               else
                 {
                  if(rates[i].time<=dt_Breaked)
                     continue;
                  //---
                  double res_price=GetRessistPrice(PeackTrough[0],rates[i].time);
                  if(((rates[i].low-res_price)<=0 && (rates[i].close-res_price)>0 && (rates[i].close-res_price)<=(d_Diff*base)) || rates[i].close>d_BreakedPrice)
                    {
                     type=ORDER_TYPE_BUY;
                     stop_loss=res_price-base*(1+d_Diff);
                     deal_time=rates[i].time;
                     return true;
                    }
                 }
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
         else
           {
            if(PeackTrough[0].Val<PeackTrough[2].Val)
              {
               if(!b_Breaked)
                 {
                  if((GetSupportPrice(PeackTrough[0],rates[i].time)-rates[i].close)>=(d_Diff*base))
                    {
                     b_Breaked=breaked=true;
                     dt_Breaked=breaked_time=rates[i].time;
                     d_BreakedPrice=rates[i].low;
                     DrawChannel(rates[i]);
                     continue;
                    }
                  if(CurCount>4 && PeackTrough[CurCount-1].Dir==-1 && (PeackTrough[CurCount-1].Val-GetSupportPrice(PeackTrough[1],rates[i].time))>0)
                    {
                     int channels=ArraySize(ar_Channels);
                     if(ar_Channels[channels-1]==GetPointer(this))
                       {
                        SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe));
                       }
                    }
                 }
               else
                 {
                  if(rates[i].time<=dt_Breaked)
                     continue;
                  double sup_price=GetSupportPrice(PeackTrough[0],rates[i].time);
                  if(((sup_price-rates[i].high)<=0 && (sup_price-rates[i].close)>0 && (sup_price-rates[i].close)<=(d_Diff*base)) || rates[i].close<d_BreakedPrice)
                    {
                     type=ORDER_TYPE_SELL;
                     stop_loss=sup_price+base*(1+d_Diff);
                     deal_time=rates[i].time;
                     return true;
                    }
                 }
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
        }
     }
   return b_Breaked;
  }

Der vollständige Code der Klasse CChannel und ihrer Funktionen ist unten angefügt.

3. Erstellen eines Expert Advisor zum Testen der Strategie

Nachdem wir nun die Klasse Channel-Search erstellt haben, müssen wir unsere Strategie testen. Erstellen wir einen Expert Advisor, um die Strategie zu testen. Unsere Kanäle werden mit Hilfe des universelle Indikators ZigZag gesucht, der in dem entsprechenden Artikel [3] beschrieben wurde. Deshalb muss dieser Indikator heruntergeladen und neu kompiliert werden. Ich habe ihn der Einfachheit halber der Liste der Ressourcen hinzugefügt. Dieser Ansatz ermöglicht es, den Expert Advisor zwischen den Terminals zu transferieren, ohne dass der Indikator übertragen werden muss. Ich habe auch unsere CChannel-Klasse und eine Standard-Klasse für die Durchführung von Handelsgeschäften in den EA aufgenommen.
#resource "\\Indicators\\ZigZags\\iUniZigZagSW.ex5"
#include <\\Break_of_channel_DNG\\Channel.mqh>
#include <Trade\\Trade.mqh>

The Expert Advisor parameters will be identical to the parameters of the indicator.

input ESorce               SrcSelect      =  Src_HighLow;
input EDirection           DirSelect      =  Dir_NBars;
input int                  RSIPeriod      =  14;
input ENUM_APPLIED_PRICE   RSIPrice       =  PRICE_CLOSE;
input int                  MAPeriod       =  14;
input int                  MAShift        =  0;
input ENUM_MA_METHOD       MAMethod       =  MODE_SMA;
input ENUM_APPLIED_PRICE   MAPrice        =  PRICE_CLOSE;
input int                  CCIPeriod      =  14;
input ENUM_APPLIED_PRICE   CCIPrice       =  PRICE_TYPICAL;
input int                  ZZPeriod       =  50;

Der EA hat vier globale Variablen. Folgendes wird in diese Variablen gespeichert:

  • Das Handle des Indikators,
  • Der Array von Zeigern zu den Kanälen (Objekte der CChannel-Klasse),
  • Ein Zeiger auf die Klasse CTrade (wird zur Ausführung von Handelsoperationen benötigt),
  • Die Öffnungszeit des Balkens, auf dem der letzte Ausbruch stattgefunden hat.
int         zz_handle;
CChannel   *ar_Channels[];
CTrade     *Trade;
datetime    dt_last_break;

In der Funktion OnInit des EA rufen wir den Indikator auf und initialisieren erforderliche Klassen. Die Funktion sollte INIT_FAILED im Falle eines Fehlers zurückgeben.

int OnInit()
  {
//---
   zz_handle=iCustom(Symbol(),Period(),"::Indicators\\ZigZags\\iUniZigZagSW",SrcSelect,
                                             DirSelect,
                                             RSIPeriod,
                                             RSIPrice,
                                             MAPeriod,
                                             MAShift,
                                             MAMethod,
                                             MAPrice,
                                             CCIPeriod,
                                             CCIPrice,
                                             ZZPeriod);
                                             
   if(zz_handle==INVALID_HANDLE){
      Alert("Error load indicator");
      return(INIT_FAILED);
   }  
//---
   Trade=new CTrade();
   if(CheckPointer(Trade)==POINTER_INVALID)
      return INIT_FAILED;
//---
   dt_last_break=0;
//---
   return(INIT_SUCCEEDED);
  }

Um den Speicher freizugeben, löschen wir alle verwendeten Klasseninstanzen in der Funktion OnDeinit.

void OnDeinit(const int reason)
  {
//---
   int total=ArraySize(ar_Channels);
   for(int i=0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])!=POINTER_INVALID)
         delete ar_Channels[i];
     }
   ArrayFree(ar_Channels);
   if(CheckPointer(Trade)!=POINTER_INVALID)
      delete Trade;
  }

Die Hauptarbeit wird in der Funktion OnTick ausgeführt.

Wir haben entschieden, dass ein Kanal als beendet betrachtet werden sollte, wenn eine Kerze außerhalb seiner Grenzen schließt. Der Kanal wird basierend auf vollständig geformten ZigZag-Spitzen gezeichnet. Der EA muss also nicht bei jedem Tick Aktionen ausführen. Daher muss in dieser Funktion zuerst das Öffnen einer neuen Bar überprüft werden.

void OnTick()
  {
//---
   static datetime last_bar=0;
   if(last_bar>=SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE))
      return;
   last_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);

Beachten Sie, dass die Variable last_bar nur in diesem Codeblock verwendet wird, weshalb sie nicht global deklariert wird. Wie Sie wissen, wird die Initialisierung aller lokalen Variablen jedes Mal nach dem Start der entsprechenden Funktion durchgeführt. Deshalb gehen die in der Variable gespeicherten Daten beim nächsten OnTick-Start verloren. Um Datenverlust zu vermeiden, wird die Variable mit dem Modifikator "static" deklariert. Diese Variable behält dadurch ihre Werte auch bei nachfolgenden Funktionsstarts.

Der nächste Schritt ist die Bestimmung, wie viele Kanäle im Array gespeichert sind. Wenn es keine Kanäle gibt, starten Sie die Suche ab dem zuletzt gespeicherten Ausbruch.

   int total=ArraySize(ar_Channels);
   if(total==0)
      if(SearchNewChannel(dt_last_break))
         total++;

Danach arbeiten wir mittels einer Schleife mit jedem gespeicherten Kanal. Zuerst wird der Zeiger auf das Klassenobjekt überprüft. Wenn der Zeiger nicht korrekt ist, löschen wir ihn aus dem Array und gehen zum nächsten.

   for(int i=0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])==POINTER_INVALID)
        {
         DeleteChannel(i);
         i--;
         total--;
         continue;
        }

Dann wird die Funktion Calculate der Klasse aufgerufen. Ihre Parameter sind Verweise auf Variablen, über die die Funktion Informationen über die Ergebnisse der durchgeführten Operationen zurückgibt. Diese Variablen müssen vor dem Funktionsaufruf deklariert werden. Zusätzlich gibt die Funktion einen boolschen Wert zurück. So können wir die Funktion als logischen Ausdruck für die if-Anweisung aufrufen, und weitere Operationen werden nur dann ausgeführt, wenn die Funktion erfolgreich ist.

      ENUM_ORDER_TYPE type;
      double stop_loss=-1;
      bool breaked=false;
      datetime breaked_time=0;
      datetime deal_time=0;
      if(ar_Channels[i].Calculate(type,stop_loss,deal_time,breaked,breaked_time))
        {

Nach erfolgreicher Ausführung der Funktion speichern Sie erneut die Zeit der Kerze, an dem der letzte Kanalausbruch stattgefunden hat.

         dt_last_break=fmax(dt_last_break,breaked_time);

Wenn sich der zuletzt gespeicherte Kanal bereits beendete, initialisieren wir die Suche nach einem neuen Kanal, der sich nach dem letzten Ausbruch bildete.

         if(breaked && i==(total-1))
            if(SearchNewChannel(breaked_time))
              { 
               if(total>=5)
                  i--;
               else
                  total++;
              }

Beachten Sie, dass die Funktion SearchNewChannel die letzten fünf Kanäle speichert. Daher wächst der Wert der Variablen "total" nur dann, wenn weniger als 5 Kanäle im Array vorhanden sind. Andernfalls reduzieren Sie die Variable i, die den Index des zu bearbeitenden Kanals angibt.

Dann prüfen wir das Entstehen eines Signals, um eine Position zu eröffnen und senden bei Bedarf eine entsprechende Order. Der Expert Advisor ist nur für Testzwecke gedacht. Das ist der Grund, warum es nicht über einen Money-Management-Block verfügt, so dass alle Positionen mit einem minimalen Lot eröffnet werden. Nach dem Absenden des Auftrages sollte der bearbeitete Kanal gelöscht werden.

         if(deal_time>=0 && stop_loss>=0)
           {
            int bars=Bars(_Symbol,PERIOD_CURRENT,deal_time,TimeCurrent());
            double lot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
            switch(type)
              {
               case ORDER_TYPE_BUY:
                 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
                    Trade.PositionClose(_Symbol);
                 if(bars<=2)
                    Trade.Buy(lot,_Symbol,0,fmax(stop_loss,0));
                 break;
               case ORDER_TYPE_SELL:
                 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
                    Trade.PositionClose(_Symbol);
                 if(bars<=2)
                    Trade.Sell(lot,_Symbol,0,fmax(stop_loss,0));
                 break;
              }
            DeleteChannel(i);
            i--;
            total--;
           }
        }
     }
  }


Bitte beachten Sie zwei wichtige Punkte in diesem Abschnitt des Programms.

1. Aufträge werden nur geöffnet, wenn das Signal nicht früher als bei einer vorherigen Kerze aufgetreten ist. Diese Einschränkung kommt hinzu, weil der Expert Advisor historische Daten verarbeiten kann (z.B. während der Initialisierung oder nachdem das Terminal vom Server getrennt wurde). In diesem Fall kann ein Signal mit einer Verzögerung auftreten, und ein neuer Handel kann zu unkontrollierbaren Verlusten führen.

2. Der Expert Advisor eröffnet Aufträge mit einem Stop-Loss, während Take-Profit nicht spezifiziert ist. Wenn also ein Signal auftaucht, wird bei Bedarf eine Gegenposition geschlossen.

Zusätzlich werden im Code zwei Hilfsfunktionen SearchNewChannel und DeleteChannel verwendet.

Die Funktion SearchNewChannel initialisiert eine neue Instanz der Klasse von CChannel im Array der Kanäle. Zu Beginn der Funktion prüfen wir das Handle des Indikators. Wenn das Handle nicht korrekt ist, verlassen wir die Funktion mit dem Ergebnis "false".

bool SearchNewChannel(datetime time)
  {
   if(zz_handle==INVALID_HANDLE)
      return false;

Bei der Erstellung des Expert Advisor habe ich mich entschieden, mit den letzten fünf Kanälen zu arbeiten. Deshalb ist der nächste Schritt, die Anzahl der im Array gespeicherten Kanäle zu überprüfen und gegebenenfalls den ältesten Kanal zu löschen. Die restlichen vier Kanäle werden an den Anfang des Arrays verschoben.

   int total=ArraySize(ar_Channels);
   if(total>4)
     {
      for(int i=0;i<total-4;i++)
        {
         if(CheckPointer(ar_Channels[i])!=POINTER_INVALID)
            delete ar_Channels[i];
        }
      for(int i=0;i<4;i++)
         ar_Channels[i]=ar_Channels[total-4+i];
      if(total>5)
        {
         if(ArrayResize(ar_Channels,5)>0)
            total=5;
         else
            return false;
        }
     }

Wenn es weniger als fünf Kanäle gibt, wird das Array vergrößert.

   else
     {
      if(ArrayResize(ar_Channels,total+1)>0)
         total++;
      else
         return false;
     }

Am Ende der Funktion initialisieren wir eine neue Instanz der CChannel-Klasse in der letzten Zelle des Arrays.

   ar_Channels[total-1]=new CChannel(zz_handle,time,_Symbol,PERIOD_CURRENT);
   return (CheckPointer(ar_Channels[total-1])!=POINTER_INVALID);
  }

Die Funktion DeleteChannel löscht eine CChannel-Klasseninstanz mit dem angegebenen Index aus dem Array. Zu Beginn der Funktion wird geprüft, ob der Index innerhalb des vorhandenen Arrays liegt. Ist dies nicht der Fall, beenden Sie die Funktion mit dem Ergebnis "false".

bool DeleteChannel(int pos)
  {
   int total=ArraySize(ar_Channels);
   if(pos<0 || pos>=total)
      return false;

Dann wird das angegebene Objekt gelöscht und die restlichen Objekte werden um eine Zelle nach unten verschoben.

   delete ar_Channels[pos];
   for(int i=pos;i<total-1;i++)
      ar_Channels[i]=ar_Channels[i+1];

Wenn das Array vor dem Start der Funktion nur ein Objekt hatte, wird das Array freigegeben. Andernfalls wird es um ein Element reduziert.

   if(total==1)
     {
      ArrayFree(ar_Channels);
      return true;
     }
   return (ArrayResize(ar_Channels,total-1)>0);
  }

Der vollständige Code des Expert Advisors ist unten angefügt.

4. Testen des Expert Advisors

4.1. Der H1-Zeitrahmen

Man geht davon aus, dass solche Strategien in höheren Zeiträumen besser funktionieren, da diese Zeiträume statischer sind und weniger dem zufälligen Lärm ausgesetzt sind. Daher wurden die ersten Tests im H1-Zeitrahmen durchgeführt. Die Tests wurden mit EURUSD-Daten für 2017 ohne vorherige Optimierung der Parameter durchgeführt.

Expert Advisor, getestet im Zeitrahmen H1Die Parameter des Expert Advisor für die Tests.

Schon der erste Test hat gezeigt, dass die Strategie profitabel ist. Der EA führte nur 26 Transaktionen durch, die zu 10 geöffneten Positionen während des Testzeitraums führte. 80% der offenen Positionen wurden mit Gewinn geschlossen. Dies führte zu einem reibungslosen Wachstum des Saldos. Der Profit-Faktor nach den Testergebnissen betrug 4,06. Das ist ein gutes Ergebnis.

Testergebnisse im Zeitrahmen H1

Aber 10 Positionen pro Jahr reichen nicht aus. Um die Anzahl der Positionen zu erhöhen, entschied ich mich, den EA in einem kleineren Zeitrahmen zu testen, ohne seine Parameter zu ändern.

4.2. Der M1 Zeitrahmen

Der zweite Test wurde mit den gleichen Parametern auf dem Zeitrahmen M15 durchgeführt.

Expert Advisor, getestet im Zeitrahmen M15Die Parameter des Expert Advisor für die Tests.

Die Anzahl der Positionen nahm zu. Der EA eröffnete während des Testzeitraums 63 Positionen. Diese Steigerung führte jedoch nicht zu einem besseren Ergebnis. Der Gesamtgewinn aller Operationen betrug $130,60 im Vergleich zu $133,46 auf Н1. Der Anteil der profitablen Positionen sank auf fast die Hälfte, auf 41,27%. Die resultierende Bilanz ist gebrochener, und der Gewinnfaktor beträgt 1,44, was fast dreimal weniger ist als beim vorherigen Test.

Testing results on the M15 timeframe.

4.3. Testen mit anderen Symbolen

Die Testergebnisse zeigten, dass die Strategie im H1-Zeitrahmen besser abschneidet. Um den möglichen Einsatz dieser Strategie in anderen Zeiträumen zu evaluieren, habe ich zusätzlich drei Tests durchgeführt. Ich benutzte den H1-Zeitrahmen, die gleichen Parameter und die gleiche Testphase. Die vollständigen Testergebnisse sind im Anhang verfügbar, die wichtigsten Zahlen sind in der untenstehenden Tabelle aufgeführt.

Symbol Anzahl der Positionen Anzahl der Transaktionen Anzahl der profitablen Positionen in % Profit Factor Recovery Factor Durchschnittliche Haltezeit einer Position, in Stunden 
EURUSD 10 26 80 4.06 1.78 552 
GBPUSD 2 8 50 1.47 0.23 2072 
EURGBP 5 14 0 0.0 -0.71 976 
USDJPY 6 17 83 0.72 -0.19 875 

Die schlechtesten Ergebnisse wurden mit EURGBP erzielt. Keine der 5 Positionen wurde mit Gewinn abgeschlossen. Aber wenn wir den Chart analysieren, sehen wir verlorenes Gewinnpotenzial für Einträge in Übereinstimmung mit der Strategie. Wie im Screenshot unten zu sehen ist, generiert die Kanalausbruchstrategie gute Eröffnungssignale. Aber es braucht eine geeignete Exit-Strategie für einen stabileren Betrieb. Dies wird durch die Positionshaltezeit bestätigt. Tests haben gezeigt, dass die durchschnittliche Positionshaltezeit je nach Symbol zwischen 550 und 2100 Stunden liegt. Die Marktentwicklung kann sich über einen so langen Zeitraum mehrfach ändern.

Beispielhafte Positionen des EAs dargestellt im Chart von EURGBP.

Schlussfolgerungen

Ein Beispiel für einen Expert Advisor, der mit dem Muster eines Kanalausbruchs handelt, wurde in diesem Artikel beschrieben. Testergebnisse haben gezeigt, dass diese Strategie für Eröffnungssignale genutzt werden kann. Auch Tests haben bestätigt, dass die Strategie bei höheren Zeiträumen besser funktioniert. Um die Strategie zum Erfolg zu führen, sollten jedoch Exitsignale der Positionen hinzugefügt werden. Die Strategie generiert präzise, aber seltene Eröffnungssignale, aber diese Signale reichen nicht aus, um rechtzeitig Gewinne zu fixieren. Dies führt oft zum Verlust zwischenzeitlicher Gewinne und sogar der Einlage.

Der Expert Advisor verfügt über kein Money-Management-Modul und prüft auch nicht auf Fehler, die bei Berechnungen und Handelsgeschäften auftreten könnten. Daher wird der EA nicht für den Einsatz auf realen Konten empfohlen. Jedoch kann jeder die notwendigen Funktionen hinzufügen.

Referenzen

  1. The Flag Pattern
  2. Graphs and Diagrams in the HTML format
  3. Universal ZigZag

Die Programme dieses Artikels:

#
 Name
Typ 
Beschreibung 
1 Break_of_channel_DNG.mq5  Expert Advisor  Ein Expert Advisor zum Prüfen der Strategie
 2 Channel.mqh  Klassenbibliothek  Klasse für die Suche nach Preiskanälen und Eröffnungssignale
 3 Break_of_channel_DNG.mqproj    Datei mit der Projektbeschreibung
 4 iUniZigZagSW.ex5  Indikator  Universal ZigZag
 5 Reports.zip Zip  Testbericht des Expert Advisors