Rezepte MQL5 - Handelssignale der gleitenden Kanäle

Denis Kirichenko | 16 September, 2016


Einführung

Der vorherige Artikel «Rezepte MQL5 - Programmierung der gleitenden Kanäle» beschreibt eine Methode zum Zeichnen äquidistanter Kanäle, auch benannt als gleitende Kanälen. Um das Aufgabe zu lösen, verwenden wir «äquidistanter Kanal» und die Möglichkeiten des OOP.

Dieser Artikel konzentriert sich auf Signale, die bei der Verwendung dieser Kanäle identifiziert werden können. Versuchen wir also eine Handelsstrategie aus diesen Signalen zu entwerfen.

Es gibt mehrere Artikel für MQL5, die das Erzeugen von Handelssignalen unter Verwendung der Mittel der Standardbibliothek beschreiben. Hoffentlich ergänzt dieser Artikel die Materialien und erweitert des Nutzers Wissen über die Standardklassen.

Anfänger sind eingeladen, mit dieser Strategie das Vorgehen vom Einfachen zum Komplexen zu lernen. Zuerst eine einfache Strategie erstellen, die dann durch zusätzliche Elemente komplexer wird.


1. Der Indikator eines äquidistanten Kanals

Im vorigen Artikel über gleitende Kanäle zeichnete der Expert Advisor die Kanäle selbst als grafische Objekte auf den Chart. Dessen Ansatz erleichtert auf der einen Seite die Aufgabe für Programmierer, aber auf der anderen Seite macht er einige Dinge unmöglich. Zum Beispiel, wenn der EA optimiert wird, kann er keine grafische Objekte erkennen, da es dann keinen Chart gibt. Gemäß den Beschränkungen während des Testens:


Grafische Objekte beim Testen

Während des Testens/Optimierung wird die Konstruktion von grafischen Objekten nicht ausgeführt. Wenn man auf die Eigenschaften des geschaffenen Objektes während des Testens/Optimierung zugreift, erhält der Expert Advisor eine Null.

Diese Einschränkung gilt nicht für das Testen im visuellen Modus.


Daher wird ein anderer Ansatz verwendet, um einen Indikator zu erstellen, der sowohl Fraktale wie den Kanal berücksichtigt.

Dieser Indikator heißt EquidistantChannels. Er besteht im Wesentlichen aus zwei Blöcken. Der Erste berechnet den Puffer der Fraktale, der Zweite den des Kanals.

Hier ist der Code der Funktion OnCalculate.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Wenn es noch keine Bars eines vorherigen Aufrufs gibt
   if(prev_calculated==0)
     {
      //--- Initialisieren der Puffer
      ArrayInitialize(gUpFractalsBuffer,0.);
      ArrayInitialize(gDnFractalsBuffer,0.);
      ArrayInitialize(gUpperBuffer,0.);
      ArrayInitialize(gLowerBuffer,0.);
      ArrayInitialize(gNewChannelBuffer,0.);
     }
//--- Berechnung der Fraktale [Start]
   int startBar,lastBar;
//---
   if(rates_total<gMinRequiredBars)
     {
      Print("Not enough data for calculation");
      return 0;
     }
//---
   if(prev_calculated<gMinRequiredBars)
      startBar=gLeftSide;
   else
      startBar=rates_total-gMinRequiredBars;
//---
   lastBar=rates_total-gRightSide;
   for(int bar_idx=startBar; bar_idx<lastBar && !IsStopped(); bar_idx++)
     {
      //---
      if(isUpFractal(bar_idx,gMaxSide,high))
         gUpFractalsBuffer[bar_idx]=high[bar_idx];
      else
         gUpFractalsBuffer[bar_idx]=0.0;
      //---
      if(isDnFractal(bar_idx,gMaxSide,low))
         gDnFractalsBuffer[bar_idx]=low[bar_idx];
      else
         gDnFractalsBuffer[bar_idx]=0.0;
     }
//--- Berechnung der Fraktale [Ende]

//--- Berechnung der Kanalgrenzen [Start]
   if(prev_calculated>0)
     {
      //--- Falls noch nicht initialisiert wurde
      if(!gFracSet.IsInit())
         if(!gFracSet.Init(
            InpPrevFracNum,
            InpBarsBeside,
            InpBarsBetween,
            InpRelevantPoint,
            InpLineWidth,
            InpToLog
            ))
           {
            Print("Fractal set initialization error!");
            return 0;
           }
      //--- Berechnung
      gFracSet.Calculate(gUpFractalsBuffer,gDnFractalsBuffer,time,
                         gUpperBuffer,gLowerBuffer,
                         gNewChannelBuffer
                         );
     }
//--- Berechnung der Kanalgrenzen [Ende]

//--- Rückgabe des Wertes von prev_calculated für den nächsten Aufruf
   return rates_total;
  }

Der Block mit der Berechnung des Fraktalpuffers ist gelb hervorgehoben und der Block mit der Berechnung des Kanalpuffers grün. Es ist leicht zu bemerken, dass der zweite Block nicht sofort durchlaufen wird, sondern erst beim nächsten Aufruf der Funktion. Diese Umsetzung des zweiten Blocks ermöglicht die Abfrage eines gefüllten Fraktalpuffers.

Jetzt ein paar Worte über Fraktale — das CFractalSet Objekt. Wegen der Änderungen der Anzeige des Kanals, war es auch notwendig die Klasse CFractalSet anzupassen. Die wichtigste Methode ist CFractalSet::Calculate, die die Werte des Kanalpuffers berechnet. Der Code befindet sich in der Datei CFractalPoint.mqh.



Jetzt haben wir die Basis — einen Absender der Signale des äquidistanten Kanals. Das Funktionieren des Indikators wird im Video gezeigt.


2. Basisstrategie

Beginnen wir also mit etwas Einfachem, das mit der Hilfe von OOP verbessert und überarbeitet werden kann. Nehmen wir eine ganz einfach Strategie.

Sie hat nur ein paar simple Handelsregeln. Der Markteintritt wird durch die Kanalgrenzen bestimmt. Berührt der Preis die untere Grenze, eröffnen wir eine Kaufposition, die obere - eine Verkaufposition. Fig. 1 zeigt die Berührung der unteren Grenze und der EA kauft eine bestimmte Menge. Die Handelsebenen (StopLoss und TakeProfit) sind fix und werden automatisch platziert. Gibt es eine offene Position, wird ein erneutes Signal ignoriert.

Fig.1 Eröffnungssignal

Fig.1Eröffnungssignal

Ich möchte darauf hinweisen, dass die Standardbibliothek stark gewachsen ist. Es gibt bereits viele fertige, verwendbare Klassen. Versuchen wir zuerst eine «Verbindung» zur Signalklasse CExpertSignal. Gemäß der Dokumentation ist es eine Basisklasse zur Erstellung eines Handelssignal Generators.

Diese Klasse trägt ihren Namen zurecht. Es nicht CTradeSignal und nicht CSignal, sondern die Signalklasse, die zur Verwendung in einem EA bestimmt ist — CExpertSignal.

Ich werde ihren Inhalt nicht besprechen. Der Artikel «Der MQL5 Assistent: Wie man ein Modul an Handelssignalen erzeugt» beschreibt die Methoden dieser Signalklassen.


2.1 Die Signalklasse CSignalEquidChannelsignal

Daher ist die abgeleitete Signalklasse wie folgt:

//+------------------------------------------------------------------+
//| Class CSignalEquidChannel                                        |
//| Zweck: Klasse der Handelssignale auf Basis äquidistanter         |
//|        Kanäle.                                                   |
//| Abgeleitet aus der Klasse CExpertSignal.                         |
//+------------------------------------------------------------------+
class CSignalEquidChannel : public CExpertSignal
  {
protected:
   CiCustom          m_equi_chs;          // Indikatorobjekt "EquidistantChannels"   
   //--- anpassbare Parameter
   int               m_prev_frac_num;     // vorheriges Fraktal
   bool              m_to_plot_fracs;     // Fraktal anzeigen?
   int               m_bars_beside;       // Bars rechts/links des Fraktals
   int               m_bars_between;      // Zwischenbars
   ENUM_RELEVANT_EXTREMUM m_relevant_pnt; // relevanter Punkt
   int               m_line_width;        // Linienstärke
   bool              m_to_log;            // Logeinträge?
   double            m_pnt_in;            // interne Toleranz, Points
   double            m_pnt_out;           // externe Toleranz, Points
   bool              m_on_start;          // Signal für den Start
   //--- Berechnung
   double            m_base_low_price;    // Basis Tiefstpreis
   double            m_base_high_price;   // Basis Höchstpreis
   double            m_upper_zone[2];     // obere Zone: [0]-interne Toleranz, [1]-externe  
   double            m_lower_zone[2];     // untere Zone
   datetime          m_last_ch_time;      // Zeitpunkt des Auftretens des letzten Kanals
   //--- "Wichtungen" des Modells (0-100)
   int               m_pattern_0;         //  "Berühren des unteren Kanals - kaufen, des oberen - verkaufen"

   //--- === Methods === --- 
public:
   //--- Constructor/Destructor
   void              CSignalEquidChannel(void);
   void             ~CSignalEquidChannel(void){};
   //--- Methoden für die regelbaren Parametereinstellungen
   void              PrevFracNum(int _prev_frac_num)   {m_prev_frac_num=_prev_frac_num;}
   void              ToPlotFracs(bool _to_plot)        {m_to_plot_fracs=_to_plot;}
   void              BarsBeside(int _bars_beside)      {m_bars_beside=_bars_beside;}
   void              BarsBetween(int _bars_between)    {m_bars_between=_bars_between;}
   void              RelevantPoint(ENUM_RELEVANT_EXTREMUM _pnt) {m_relevant_pnt=_pnt;}
   void              LineWidth(int _line_wid)          {m_line_width=_line_wid;}
   void              ToLog(bool _to_log)               {m_to_log=_to_log;}
   void              PointsOutside(double _out_pnt)    {m_pnt_out=_out_pnt;}
   void              PointsInside(double _in_pnt)      {m_pnt_in=_in_pnt;}
   void              SignalOnStart(bool _on_start)     {m_on_start=_on_start;}
   //--- Methode zur Anpassung der "Wichtungen" im Marktmodell
   void              Pattern_0(int _val) {m_pattern_0=_val;}
   //--- Überprüfung der Einstellung
   virtual bool      ValidationSettings(void);
   //--- Methode zum Erstellender des Indikators und der Zeitreihen
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- Prüfmethoden des Marktmodells
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);
   virtual double    Direction(void);
   //---
protected:
   //--- Methode zur Initialisierung des Indikators
   bool              InitCustomIndicator(CIndicators *indicators);
   //- Abfrage der oberen Kanalgrenze
   double            Upper(int ind) {return(m_equi_chs.GetData(2,ind));}
   //- Abfrage der unteren Kanalgrenze
   double            Lower(int ind) {return(m_equi_chs.GetData(3,ind));}
   //- Abfrage nach der Existenz des Kanals
   double            NewChannel(int ind) {return(m_equi_chs.GetData(4,ind));}
  };
//+------------------------------------------------------------------+

Ein paar Kleinigkeiten noch.

Das Hauptsignal ist der äquidistante Kanal. Und der ist augenblicklich der Einzige. Weitere gibt es nicht. Hier verfügt diese Klasse über eine Klasse, die mit dem technischen Indikator des TypsCiCustom arbeitet.

Das Basismodell wird als Signalmodell verwendet: "Berühren des unteren Kanals - kaufen, des oberen - verkaufen". Da eine punktgenaue Präzision praktisch unerreichbar ist, wird eine regelbare Toleranz verwendet. Der Parameter der externe Toleranz m_pnt_out bestimmt, wie weit der Preis die Kanalgrenze überschreiten darf und der der internen Toleranz m_pnt_in — wie weit der Preis innerhalb Grenze liegen darf. Die Logik ist ganz einfach. Angenommen der Preis erreicht die Grenze nicht ganz oder ist nur etwas darüber. Fig.2 zeigt schematisch diese Toleranz. Gelangt der Preis von unten dahin, wird das Signal ausgelöst.

Fig.2 Auslösung des Signals

Fig.2 Auslösung des Signals


Das Array m_upper_zone[2] enthält die obere und m_lower_zone[2] die untere Toleranzgrenzen.

Der Preis von $1,11552 ist ein Beispiel für eine obere Kanalgrenze (rote Linie). Der Preise $1,11452 ist das untere Limit der Toleranz und $1,11702 das der oberen. Also ist die Größe der externen Toleranz 150 Points und die der internen 100 Points. Der Kurs ist als blaue Linie angezeigt.

Der Parameter m_on_start erlaubt es, das aller erste Signal zu ignorieren, wenn der EA auf dem Chart läuft und der Kanal bereits gezeichnet worden ist. Wenn dieses Signal zurückgesetzt wird, wird der EA nur mit den nächsten Kanal arbeiten und nicht mit den augenblicklichen Handelssignalen.

Die Parameter m_base_low_price und m_base_high_price sichern die Werte des Hochs und Tiefs der aktuellen Bar. Das ist die Bar-Null, wenn der EA auf jedem Tick läuft, oder die vorherige Bar, wenn der Handel nur beim Erscheinen einer neuen Bar erlaubt ist.

Jetzt ein paar Worte über die Methoden. Es sollte angemerkt werden, dass der Entwickler genügend Freiraum für eine Weiterentwicklung ließ, da etwa die Hälfte virtuelle Methoden sind. Das bedeutet, dass das Verhalten der abgeleiteten Klassen ganz nach eigenen Wünschen bestimmt werden kann.

Beginnen wir mit der Methode Direction(), die ein qualitative Abschätzung einer möglichen Handelsrichtung liefert:

//+------------------------------------------------------------------+
//| Bestimmung der "Wichtungen" der Richtung                         |
//+------------------------------------------------------------------+
double CSignalEquidChannel::Direction(void)
  {
   double result=0.;
//--- Erscheinen eines neuen Kanals
   datetime last_bar_time=this.Time(0);
   bool is_new_channel=(this.NewChannel(0)>0.);
//--- Wenn das Signal des ersten Kanals ignoriert wird
   if(!m_on_start)
      //--- Wenn der erste Kanal ganz normal bei der Initialisierung angezeigt wird
      if(m_prev_frac_num==3)
        {
         static datetime last_ch_time=0;
         //--- Wenn ein neuer Kanal erscheint
         if(is_new_channel)
           {
            last_ch_time=last_bar_time;
            //--- Wenn das der erste Start ist
            if(m_last_ch_time==0)
               //--- Sichere den Zeitpunkt der Bar des Erscheinens des ersten Kanals
               m_last_ch_time=last_ch_time;
           }
         //--- Wenn die Zeit passt
         if(m_last_ch_time==last_ch_time)
            return 0.;
         else
         //--- Lösche das Signal
            m_on_start=true;
        }
//--- Index der aktuellen Bar
   int actual_bar_idx=this.StartIndex();
//--- Grenzsetzung
   double upper_vals[2],lower_vals[2]; // [0]-bar preceding the actual, [1]-actual bar
   ArrayInitialize(upper_vals,0.);
   ArrayInitialize(lower_vals,0.);
   for(int idx=ArraySize(upper_vals)-1,jdx=0;idx>=0;idx--,jdx++)
     {
      upper_vals[jdx]=this.Upper(actual_bar_idx+idx);
      lower_vals[jdx]=this.Lower(actual_bar_idx+idx);
      if((upper_vals[jdx]==0.) || (lower_vals[jdx]==0.))
         return 0.;
     }
//--- Preisabfrage
   double curr_high_pr,curr_low_pr;
   curr_high_pr=this.High(actual_bar_idx);
   curr_low_pr=this.Low(actual_bar_idx);
//--- Nach Erhalt des Preises
   if(curr_high_pr!=EMPTY_VALUE)
      if(curr_low_pr!=EMPTY_VALUE)
        {
         //--- sichern des Preises
         m_base_low_price=curr_low_pr;
         m_base_high_price=curr_high_pr;
         //--- Bestimme Preise der Toleranzzone
         //--- obere Zone: [0]-interne Toleranz, [1]-externe 
         this.m_upper_zone[0]=upper_vals[1]-m_pnt_in;
         this.m_upper_zone[1]=upper_vals[1]+m_pnt_out;
         //--- untere Zone: [0]-interne Toleranz, [1]-externe 
         this.m_lower_zone[0]=lower_vals[1]+m_pnt_in;
         this.m_lower_zone[1]=lower_vals[1]-m_pnt_out;
         //--- Normalisierung
         for(int jdx=0;jdx<ArraySize(m_lower_zone);jdx++)
           {
            this.m_lower_zone[jdx]=m_symbol.NormalizePrice(m_lower_zone[jdx]);
            this.m_upper_zone[jdx]=m_symbol.NormalizePrice(m_upper_zone[jdx]);
           }
         //--- Prüfung ob die Zonen konvergieren
         if(this.m_upper_zone[0]<=this.m_lower_zone[0])
            return 0.;
         //--- Ergebnis
         result=m_weight*(this.LongCondition()-this.ShortCondition());
        }
//---
   return result;
  }
//+------------------------------------------------------------------+

Der erste Block der Methode prüft, ob der erste Kanal auf dem Chart, wenn es ihn denn gibt, ignoriert werden muss.

Der zweit Block fragt nach den Preisen und bestimmt die Toleranzzonen. Das ist die Prüfung der Konvergierung der Zonen. Wenn der Kanal zu eng oder die Toleranz zu groß ist, ist es wahrscheinlich, dass die Preise in beide Zonen gelangen. Daher müssen wir das auch berücksichtigen.

Die Ziellinie ist markiert in blau. Hier machen wir eine quantitative Schätzung der Handelsrichtung, wenn es denn eine gibt.

Betrachten wir jetzt mal die Methode LongCondition().

//+------------------------------------------------------------------+
//| Prüfe die Kaufbedingung                                          |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
//--- Haben wir einen Tiefstpreis
   if(m_base_low_price>0.)
      //--- Wenn der Tiefstpreis nahe der unteren Grenze ist
      if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
        {
         if(IS_PATTERN_USAGE(0))
            result=m_pattern_0;
        }
//---
   return result;
  }
//+------------------------------------------------------------------+

Für einen Kauf, prüfe, ob der Preis in die untere Toleranzzone reicht. Ist das der Fall, prüfe die Erlaubnis das Marktmodell zu aktivieren. Weitere Details über die Struktur des Typs "IS_PATTERN_USAGE(k)" findet sich im Artikel «Handelssignal-Generator auf Grundlage eines angepassten Indikators».

Die Methode ShortCondition() arbeitet ähnlich der obigen. Der Fokus liegt diesmal nur auf der oberen Zone.

//+------------------------------------------------------------------+
//| Prüfe die Verkaufbedingung                                       |
//+------------------------------------------------------------------+
int CSignalEquidistantChannel::ShortCondition(void)
  {
   int result=0;
//--- Haben wir einen Höchstpreis
   if(m_base_high_price>0.)
      //--- Wenn der Höchstpreis nahe der oberen Grenze ist
      if((m_base_high_price>=m_upper_zone[0]) && (m_base_high_price<=m_upper_zone[1]))
        {
         if(IS_PATTERN_USAGE(0))
            result=m_pattern_0;       
        }
//---
   return result;
  }
//+------------------------------------------------------------------+

Diese Klasse initialisiert einen Nutzerindikator unter Verwendung der Methode InitCustomIndicator():

//+------------------------------------------------------------------+
//| Initialisierung des Nutzerindikators                             |
//+------------------------------------------------------------------+
bool CSignalEquidChannel::InitCustomIndicator(CIndicators *indicators)
  {
//--- füge ein Objekt hinzu
   if(!indicators.Add(GetPointer(m_equi_chs)))
     {
      PrintFormat(__FUNCTION__+": error adding object");
      return false;
     }
//--- bestimme die Parameter des Indikators
   MqlParam parameters[8];
   parameters[0].type=TYPE_STRING;
   parameters[0].string_value="EquidistantChannels.ex5";
   parameters[1].type=TYPE_INT;
   parameters[1].integer_value=m_prev_frac_num;   // 1) previous fractals
   parameters[2].type=TYPE_BOOL;
   parameters[2].integer_value=m_to_plot_fracs;   // 2) display fractals?
   parameters[3].type=TYPE_INT;
   parameters[3].integer_value=m_bars_beside;     // 3) bars on the left/right of fractal
   parameters[4].type=TYPE_INT;
   parameters[4].integer_value=m_bars_between;    // 4) intermediate bars
   parameters[5].type=TYPE_INT;
   parameters[5].integer_value=m_relevant_pnt;    // 5) relevant point
   parameters[6].type=TYPE_INT;
   parameters[6].integer_value=m_line_width;    // 6) line width
   parameters[7].type=TYPE_BOOL;
   parameters[7].integer_value=m_to_log;          // 7) keep the log?

//---Initialisierung des Objektes
   if(!m_equi_chs.Create(m_symbol.Name(),_Period,IND_CUSTOM,8,parameters))
     {
      PrintFormat(__FUNCTION__+": error initializing object");
      return false;
     }
//--- Anzahl der Puffer
   if(!m_equi_chs.NumBuffers(5))
      return false;
//--- ok
   return true;
  }
//+------------------------------------------------------------------+

Der erste Wert des Arrays der Parameter muss der Name des Indikators als Zeichenkette sein.

Die Klasse hat auch eine virtuelle Methode ValidationSettings(). Diese ruft eine ähnliche Methode des Vorfahren auf und prüft, ob die Parameter des Kanalindikators korrekt sind. Es gibt auch eine Methode, die die Werte des entsprechenden Puffers des Nutzerindikators zurück liefert.

Soweit ist das alles in Bezug zur abgeleiteten Signalklasse.


2.2 Klasse der Handelsstrategie CEquidChannelExpert

Umsetzung der Basisidee verlangt eine von der Standardklasse CExpert abgeleitete Klasse. Im Augenblick ist der Code so kompakt wie möglich, weil nur das Verhalten der Hauptmethode Processing() geändert werden muss. Sie ist virtuell und garantiert die Möglichkeit, damit jede Strategie schreiben zu können.

//+------------------------------------------------------------------+
//| Klasse CEquidChannelExpert.                                      |
//| Zweck: Klasse eines EA der den äquidistanten Kanal handelt.      |
//|        Abgeleitet von der Klasse CExper.                         |
//+------------------------------------------------------------------+
class CEquidChannelExpert : public CExpert
  {
   //--- === Data members === --- 
private:


   //--- === Methods === --- 
public:
   //--- Constructor/Destructor
   void              CEquidChannelExpert(void){};
   void             ~CEquidChannelExpert(void){};

protected:
   virtual bool      Processing(void);
  };
//+------------------------------------------------------------------+

Hier ist die Methode selbst:

//+------------------------------------------------------------------+
//| Hauptmodul                                                       |
//+------------------------------------------------------------------+
bool CEquidChannelExpert::Processing(void)
  {
//--- Berechnung der Richtung
   m_signal.SetDirection();
//--- Prüfe auf offene Positionen
   if(!this.SelectPosition())
     {
      //--- Positionseröffnung
      if(this.CheckOpen())
         return true;
     }
//--- wenn es keine Handelsoperationen gibt
   return false;
  }

Ist alles ganz einfach. Zuerst schätzt das Signalobjekt die mögliche Handelsrichtung, dann wird die Existenz offener Positionen geprüft. Gibt es keine, ist es möglich eine zu öffnen. Gibt es eine offene Position, Ende der Methode.

Der Code dieser Basisstrategie ist umgesetzt in der Datei BaseChannelsTrader.mq5.




Beispiele dieser Basisstrategie zeigt das folgende Video.


Fig.3 Ergebnisse der Basisstrategie für 2013-2015.

Fig.3 Ergebnisse der Basisstrategie für 2013-2015.

Der EA lief im Strategietester auf EURUSD im Zeitrahmen 1 Stunde. Auf dem Guthabenchart wird deutlich, dass in bestimmten Intervallen die Basisstrategie nach dem Prinzip "Sägezahn" funktioniert: auf einen Verlierer folgt ein Gewinner. Die Parameter dieses Tests wurden in der Datei base_signal.set gesichert. Enthalten sind auch die Parameter des Kanals, die in allen Versionen dieser Strategie unverändert bleiben.

Hier und weiter unten wird die Testmethode "Jeder Tick" verwendet.

Im Wesentlichen, gibt es 2 Möglichkeiten zur Verbesserung der Handelsergebnisse dieser Strategie. Zuerst die Optimierung, die jene Parameterauswahl findet, die den Gewinn maximiert, etc. Als zweites können wir Faktoren finden, die den Erfolg des EA beeinflussen. Die erste Methode belässt die Handelslogik unverändert, die ändert sie.

Im nächsten Teil wird die Basisstrategie verändert, um einen größeren Erfolg zu erreichen.


3. Erfolgsfaktoren

Ein paar Worte zum Ansatz. Es könnte bequem sein, alle Dateien der Strategie in einem einzelnen, eindeutigen Projektverzeichnis zu speichern. Daher ist alles in einem Unterverzeichnis des Basisverzeichnisses eingeordnet (Fig.4) und so weiter.

Fig.4 Beispiel der Verzeichnisstruktur des Projektes der Kanalstrategie

Fig.4 Beispiel der Verzeichnisstruktur des Projektes der Kanalstrategie

Weiters nehmen wir an, jeder neue Faktor bedeutet eine neue Stufe der veränderten Quelldateien, die den EA ausmachen.

3.1 Verwendung von Trailingstopps

Vor dem Start empfehlen wir, das Trailing der Strategie hinzuzufügen. Es ist ein Objekt der Klasse CTrailingFixedPips, die es erlaubt, die offenen Positionen mit einem fixen "Abstand" (in Points) zu kontrollieren. Die wird sowohl den StopLoss wie den TakeProfit nachziehen. Um das Nachziehen des TakeProfit zu deaktivieren, muss der entsprechende Parameter (InpProfitLevelPips) auf Null gesetzt werden.

Ändern wir den Code wie folgt:

Ergänzen wir eine Reihe von Parameter zum Experten der Datei ChannelsTrader1.mq5:

//---
sinput string Info_trailing="+===-- Trailing --====+"; // +===-- Trailing --====+
input int InpStopLevelPips=30;          // Abstand des StopLoss, Pips
input int InpProfitLevelPips=50;        // Abstand des TakeProfit, Pips

Im Initialisierungsblock erstellen wir das Objekt CTrailingFixedPips und integrieren es in die Strategie und setzen die Parameter des Trailings.

//--- Trailing Objekt
   CTrailingFixedPips *trailing=new CTrailingFixedPips;
   if(trailing==NULL)
     {
      //--- Fehler
      printf(__FUNCTION__+": error creating trailing");
      myChannelExpert.Deinit();
      return(INIT_FAILED);
     }
//--- Hinzufügen des Trailing Objektes
   if(!myChannelExpert.InitTrailing(trailing))
     {
      //--- Fehler
      PrintFormat(__FUNCTION__+": error initializing trailing");
      myChannelExpert.Deinit();
      return INIT_FAILED;
     }
//--- Trailing Parameter
   trailing.StopLevel(InpStopLevelPips);
   trailing.ProfitLevel(InpProfitLevelPips);

Da das Trailing verwendet wird, müssen wir auch die Hauptmethode CEquidChannelExpert::Processing() in der Datei EquidistantChannelExpert1.mqh ändern.

//+------------------------------------------------------------------+
//| Hauptmodul                                                       |
//+------------------------------------------------------------------+
bool CEquidChannelExpert::Processing(void)
  {
//--- Berechnung der Richtung
   m_signal.SetDirection();
//--- Keine Position
   if(!this.SelectPosition())
     {
      //--- Positionseröffnung
      if(this.CheckOpen())
         return true;
     }
//--- Es gibt eine Position
   else
     {
      //--- Prüfung auf Änderbarkeit der Position
      if(this.CheckTrailingStop())
         return true;
     }
//--- wenn es keine Handelsoperationen gibt
   return false;
  }

Das war's Das Trailing wurde ergänzt. Die Dateien der aktualisierten Strategie werden in einem eigenen Unterverzeichnis ChannelsTrader1 gespeichert.

Überprüfen wir, ob die Änderung die Effektivität beeinflussten.

Daher wurden mehrere Optimierungen im Strategietester durchgeführt, über dasselbe Zeitintervall und den Parameterwerten der Basisstrategie. StopLoss und TakeProfit wurden angepasst:

 Variable StartStep
Stop
Abstand des StopLoss, Pips
0
10
100
Abstand des TakeProfit, Pips
0
10
150

Die Ergebnisse der Optimierung finden sich in der Datei ReportOptimizer-signal1.xml. Das beste Ergebnis zeigt Fig.5, mit StopLoss = 0, und TakeProfit = 150.

Fig.5 Ergebnisse der Strategie mit Trailing für 2013-2015.

Fig.5 Ergebnisse der Strategie mit Trailing für 2013-2015.

Es ist leicht zu sehen, dass das letzte Bild dem von Fig 3 ähnelt. Daher kann gesagt werden, die Verwendung von Trailing bringt keine Verbesserung.


3.2 Kanaltypen

Nehmen wir an, dass der Kanaltyp den Erfolg beeinflusst. Die allgemeine Idee ist: Es ist besser in einem absteigenden Kanal zu verkaufen und in einem steigenden zu kaufen. Ist der Kanal flach (keine Steigung), ist es möglich auf beiden Grenzen zu handeln.

Die Enumeration ENUM_CHANNEL_TYPE definiert den Kanaltyp:

//+------------------------------------------------------------------+
//| Kanaltyp                                                         |
//+------------------------------------------------------------------+
enum ENUM_CHANNEL_TYPE
  {
   CHANNEL_TYPE_ASCENDING=0,  // steigend
   CHANNEL_TYPE_DESCENDING=1, // fallend
   CHANNEL_TYPE_FLAT=2,       // flach
  };
//+------------------------------------------------------------------+

Bestimmen wir Toleranzparameter für die Suche nach Kanaltypen im Initialisierungsblock der Quelldatei ChannelsTrader2.mq5 des EA.

//--- Filter Parameter
   filter0.PointsInside(_Point*InpPipsInside);
   filter0.PointsOutside(_Point*InpPipsOutside);
   filter0.TypeTolerance(_Point*InpTypePips);
   filter0.PrevFracNum(InpPrevFracNum);
   ...

Diese Parameter kontrollieren das Ausmaß der Preisänderung in Points. Angenommen das wären 7 Pips. Und wächst dann der Kanal um 6 Pip mit jeder Bar, reicht das nicht für einen steigenden Kanal. Er wird daher als flach klassifiziert (ohne Steigung).

Wir ergänzen diese Identifikation zur Methode Direction() des Quellcodes der Datei SignalEquidChannel2.mqh.

//--- Ist der Kanal neu
   if(is_new_channel)
     {
      m_ch_type=CHANNEL_TYPE_FLAT;                // flat (not inclined) channel
      //--- Wenn die Toleranz gesetzt ist
      if(m_ch_type_tol!=EMPTY_VALUE)
        {
         //--- Kanaltyp
         //--- Ausmaß der Änderung
         double pr_speed_pnt=m_symbol.NormalizePrice(upper_vals[1]-upper_vals[0]);
         //--- Ist das Ausmaß groß genug
         if(MathAbs(pr_speed_pnt)>m_ch_type_tol)
           {
            if(pr_speed_pnt>0.)
               m_ch_type=CHANNEL_TYPE_ASCENDING;  // ascending channel
            else
               m_ch_type=CHANNEL_TYPE_DESCENDING; // descending channel             
           }
        }
     }

Zunächst wird ein Kanal als flach klassifiziert - weder steigend noch fallend. Wenn der Wert des Toleranz Parameters zur Identifizierung des Kanaltyps nicht bestimmt wurde, können wir auch das Ausmaß der Änderung nicht bestimmen.

 Die Bedingung für einen Kauf beinhaltet auch die Prüfung, dass der Kanal nicht fällt.

//+------------------------------------------------------------------+
//| Prüfe die Kaufbedingung                                          |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
//--- Haben wir einen Tiefstpreis
   if(m_base_low_price>0.)
      //--- Wenn der Kanal nicht fällt
      if(m_ch_type!=CHANNEL_TYPE_DESCENDING)
         //--- Wenn der Tiefstpreis nahe der unteren Grenze ist
         if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
           {
            if(IS_PATTERN_USAGE(0))
               result=m_pattern_0;
           }
//---
   return result;
  }
//+------------------------------------------------------------------+

Eine vergleichbare Prüfung wird für einen Verkauf durchgeführt, der Kanal steigt nicht.

Die Hauptmethode CEquidChannelExpert::Processing() der Datei EquidistantChannelExpert2.mqh ist die gleiche wie in der Basisversion, da ja kein Trailing stattfindet.

Prüfung der Effektivität dieses Faktors. Nur ein Parameter wird optimiert.

 Variable StartStep
Stop
Toleranz des Typs, Pips
0
5
150

Die Ergebnisse der Optimierung finden sich in der Datei ReportOptimizer-signal2.xml. Das beste Ergebnis zeigt Fig.6.

Fig.6 Ergebnisse der Strategie unter Verwendung des Kanaltyps für 2013-2015.

Fig.6 Results of the strategy with the use of channel type for 2013-2015.


Leicht zu sehen, dass die Ergebnisse der Strategie etwas besser sind als die der Basisstrategie. Es zeigt sich, dass bei einem bestimmten Parameterwert, ein Filter wie der Kanaltyp das Endergebnis beeinflussen kann. 


3.3 Kanalbreite

Es könnte sein, dass die Kanalbreite die Strategie selbst beeinflussen könnte. Wenn der Kanal eng ist, dann könnte man vielleicht in die Richtung der Preisbewegung handeln und nicht gegen sie. Das Ergebnis wäre eine Ausbruchsstrategie. Ist die Kanalbreite groß, könnte man wie gehabt handeln. Das ist dann die Abprallstrategie. Das ist die augenblickliche Strategie — Handel auf der Basis der Kanalgrenzen.

Natürlich benötige wir ein Kriterium, um breite oder enge Kanäle zu bestimmen. Um nicht die Extrema definieren zu müssen, bestimmen wir den Kanal, der weder zu breit noch zu eng ist. Wir benötigen dazu 2 Kriterien:

  1. ausreichende Breite des engen Kanals;
  2. ausreichende Breite des breiten Kanals.

Ist der Kanal keines von beiden, sollte man vielleicht keine Position eröffnen.

Fig.7 Kanalbreite, Diagramm

Fig.7 Kanalbreite, Diagramm

Es muss auf das geometrische Problem der Bestimmung der Kanalbreite hingewiesen werden. Die Achsen des Charts haben unterschiedliche Dimensionen. Es ist sehr einfach die Längen der Senkrechten AB und CD zu messen. Aber die Berechnung der Länge von CE macht Probleme (Fig.7).

Entscheiden wir uns für die einfachste Methode, durchaus diskussionswürdig, weil nicht die Allergenaueste. Die Formel ist wie folgt:

Länge von CE ≃ Länge von CD / (1.0 + Ausmaß der Änderung des Kanals)

Kanalbreite gemessen unter Verwendung der Enumeration ENUM_CHANNEL_WIDTH_TYPE:

//+------------------------------------------------------------------+
//| Kanalbreite                                                      |
//+------------------------------------------------------------------+
enum ENUM_CHANNEL_WIDTH_TYPE
  {
   CHANNEL_WIDTH_NARROW=0,   // eng
   CHANNEL_WIDTH_MID=1,      // mittel
   CHANNEL_WIDTH_BROAD=2,    // weit
  };

Ergänzen wir das Kriterium der Kanalbreite zur Gruppe der Parameter des Quellcodes des Experten in der Datei ChannelsTrader3.mq5.

//---
sinput string Info_channels="+===-- Channels --====+"; // +===-- Channels --====+
input int InpPipsInside=100;            // interne Toleranz, Pips
input int InpPipsOutside=150;           // externe Toleranz, Pips
input int InpNarrowPips=250;            // enger Kanal, Pips
input int InpBroadPips=1200;            // breiter Kanal, Pips
...

Wenn das Kriteriums eines schmalen Kanals größer ist als der Wert eines breiten Kanals, gibt es einen Fehler bei der Initialisierung.

//--- Filter Parameter
   filter0.PointsInside(_Point*InpPipsInside);
   filter0.PointsOutside(_Point*InpPipsOutside);
   if(InpNarrowPips>=InpBroadPips)
     {
      PrintFormat(__FUNCTION__+": error specifying narrow and broad values");
      return INIT_FAILED;
     }
   filter0.NarrowTolerance(_Point*InpNarrowPips);
   filter0.BroadTolerance(_Point*InpBroadPips);

Die Bestimmung des Ausmaßes der Kanalbreite geschieht in der Methode Direction().

//--- Kanalbreite 
   m_ch_width=CHANNEL_WIDTH_MID;               // mittel
   double ch_width_pnt=((upper_vals[1]-lower_vals[1])/(1.0+pr_speed_pnt));
//--- das Kriterium eines engen Kanals
   if(m_ch_narrow_tol!=EMPTY_VALUE)
      if(ch_width_pnt<=m_ch_narrow_tol)
         m_ch_width=CHANNEL_WIDTH_NARROW;      // eng      
//--- das Kriterium eines bereiten Kanals
   if(m_ch_narrow_tol!=EMPTY_VALUE)
      if(ch_width_pnt>=m_ch_broad_tol)
         m_ch_width=CHANNEL_WIDTH_BROAD;       // breit 

Anfänglich wird eine mittlere Kanalbreite angenommen. Danach wird auf eng oder breit geprüft.

Es müssen aber auch die Methoden zur Bestimmung der Handelsrichtung geändert werden. Daher, die Bedingung zum Kauf ist jetzt wie folgt:

//+------------------------------------------------------------------+
//| Prüfe die Kaufbedingung                                          |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
//--- Ist der Kanal eng - handele den Ausbruch aus der oberen Grenze
   if(m_ch_width==CHANNEL_WIDTH_NARROW)
     {
      //--- Haben wir einen Höchstpreis
      if(m_base_high_price>0.)
         //--- Wenn der Höchstpreis nahe der oberen Grenze ist
         if(m_base_high_price>=m_upper_zone[1])
           {
            if(IS_PATTERN_USAGE(0))
               result=m_pattern_0;
           }
     }
//--- oder ist der Kanal breit - handele den Abprall von der unteren Grenze
   else if(m_ch_width==CHANNEL_WIDTH_BROAD)
     {
      //--- Haben wir einen Tiefstpreis
      if(m_base_low_price>0.)
         //--- Wenn der Tiefstpreis nahe der unteren Grenze ist
         if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
           {
            if(IS_PATTERN_USAGE(0))
               result=m_pattern_0;
           }
     }
//---
   return result;
  }
//+------------------------------------------------------------------+

Die Methode hat zwei Blöcke. Der Ersteprüft die Möglichkeit den Ausbruch im engen Kanal zu handeln. Beachten Sie, dass die aktuelle Variante den Ausbruch beim Erreichen der Preise der oberen Toleranzzone handelt. Der zweite Block prüft, ob der Preis bereits in der unteren Toleranzzone ist, um in der Folge den Abprall zu handeln.

Die Methode, die die Gelegenheit zum Verkauf prüft, — ShortCondition() — ist analog.

Die Hauptmethode CEquidChannelExpert::Processing() in der Datei EquidistantChannelExpert3.mqh bleibt unverändert.

Es gibt 2 Parameter zu optimieren.

 Variable StartStep
Stop
Enger Kanal, Pips
100
20
250
Breiter Kanal, Pips
350
50
1250

Die Ergebnisse der Optimierung finden sich in der Datei ReportOptimizer-signal3.xml. Das beste Ergebnis sieht man in Fig.8.

Fig.8 Ergebnisse der Strategie mit Kanalbreite für 2013-2015.

Fig.8 Ergebnisse der Strategie mit Kanalbreite für 2013-2015.


Vielleicht ist das der Faktor mit dem größten Einfluss von allen oben beschriebenen. Die Guthabenskurve hat jetzt eine deutlichere Richtung.


3.4 StopLoss und TakeProfit relativ zu den Kanalgrenzen

Wenn die Ausstiegsziele der Positionen ursprünglich in Form von StopLoss und TakeProfit gegeben waren, sollte es möglich sein, diese Werte von der augenblicklichen Strategie beeinflussen zu lassen. Einfach ausgedrückt, wenn der Kanal einen gewissen Winkel aufweist, sollten sich StopLoss und TakeProfit gemäß der Kanalgrenzen bewegen.

Ein paar Modelle wurden außerdem hinzugefügt. Nun Sie sehen so aus:

//--- "Wichtungen" des Modells (0-100)
   int               m_pattern_0;         //  Modell "Abprall von der Kanalgrenze"
   int               m_pattern_1;         //  Modell "Ausbruch aus der Kanalgrenze"
   int               m_pattern_2;         //  Modell "Neuer Kanal"

Die bisherigen Versionen hatten jeweils nur ein Modell, die in Relation zu den Kanalgrenzen handelten. Jetzt unterscheiden wir zwischen den Abprall und Ausbruch. Und jetzt gibt es auch ein drittes Modell — der neue Kanal. Das ist notwendig für den Fall, dass es einen neuen Kanal gibt und noch Positionen des alten Kanals offen sind. Wenn das Modell ausgelöst wurde, wird so eine Position geschlossen.

Die Bedingungen für den Kauf sind die Folgenden:

//+------------------------------------------------------------------+
//| Prüfe die Kaufbedingung                                          |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
   bool is_position=PositionSelect(m_symbol.Name());
//--- Ist der Kanal eng - handele den Ausbruch aus der oberen Grenze
   if(m_ch_width_type==CHANNEL_WIDTH_NARROW)
     {
      //--- Haben wir einen Höchstpreis
      if(m_base_high_price>0.)
         //--- Wenn der Höchstpreis nahe der oberen Grenze ist
         if(m_base_high_price>=m_upper_zone[1])
           {
            if(IS_PATTERN_USAGE(1))
              {
               result=m_pattern_1;
               //--- Keine Position
               if(!is_position)
                  //--- Eintrag im Log
                  if(m_to_log)
                    {
                     Print("\nTriggered the \"Breakout of channel border\" model for buying.");
                     PrintFormat("High price: %0."+IntegerToString(m_symbol.Digits())+"f",m_base_high_price);
                     PrintFormat("Trigger price: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[1]);
                    }
              }
           }
     }
//--- oder wenn der Kanal breit oder mittel ist - handele den Abprall von der unteren Grenze
   else
     {
      //--- Haben wir einen Tiefstpreis
      if(m_base_low_price>0.)
         //--- Wenn der Tiefstpreis nahe der unteren Grenze ist
         if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
           {
            if(IS_PATTERN_USAGE(0))
              {
               result=m_pattern_0;
               //--- Keine Position
               if(!is_position)
                  //--- Eintrag im Log
                  if(m_to_log)
                    {
                     Print("\nTriggered the \"Rebound of channel border\" model for buying.");
                     PrintFormat("Low price: %0."+IntegerToString(m_symbol.Digits())+"f",m_base_low_price);
                     PrintFormat("Zone up: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[0]);
                     PrintFormat("Zone down: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[1]);
                    }
              }
           }
     }
//---
   return result;
  }
//+------------------------------------------------------------------+

Dazu die Prüfung für einen Verkauf:

//+------------------------------------------------------------------+
//| Prüfe die Bedingung zum Schließen einer Kaufposition             |
//+------------------------------------------------------------------+
bool CSignalEquidChannel::CheckCloseLong(double &price) const
  {
   bool to_close_long=true;
   int result=0;
   if(IS_PATTERN_USAGE(2))
      result=m_pattern_2;
   if(result>=m_threshold_close)
     {
      if(m_is_new_channel)
         //--- Wenn eine Kaufposition zu schließen ist
         if(to_close_long)
           {
            price=NormalizeDouble(m_symbol.Bid(),m_symbol.Digits());
            //--- Eintrag im Log
            if(m_to_log)
              {
               Print("\nTriggered the \"New channel\" model for closing buy.");
               PrintFormat("Close price: %0."+IntegerToString(m_symbol.Digits())+"f",price);
              }
           }
     }
//---
   return to_close_long;
  }
//+------------------------------------------------------------------+

Für Verkaufspositionen sind die Bedingungen identisch.


Jetzt noch ein paar Worte zum Trailing. Eine eigene Klasse CTrailingEquidChannel wurde dafür geschrieben, abgeleitet von der Klasse CExpertTrailing.

//+------------------------------------------------------------------+
//| Class CTrailingEquidChannel.                                     |
//| Zweck: Klasse der Trailingstopps auf Basis des äquidist. Kanals. |
//|        Abgleitet von der Klasse CExpertTrailing.                 |
//+------------------------------------------------------------------+
class CTrailingEquidChannel : public CExpertTrailing
  {
protected:
   double            m_sl_distance;       // distance to stop loss
   double            m_tp_distance;       // distance to take profit
   double            m_upper_val;         // upper border
   double            m_lower_val;         // lower border
   ENUM_CHANNEL_WIDTH_TYPE m_ch_wid_type; // channel type by width
   //---
public:
   void              CTrailingEquidChannel(void);
   void             ~CTrailingEquidChannel(void){};
   //--- Methoden zur Initialisierung der "protected" Daten
   void              SetTradeLevels(double _sl_distance,double _tp_distance);
   //---
   virtual bool      CheckTrailingStopLong(CPositionInfo *position,double &sl,double &tp);
   virtual bool      CheckTrailingStopShort(CPositionInfo *position,double &sl,double &tp);
   //---
   bool              RefreshData(const CSignalEquidChannel *_ptr_ch_signal);
  };
//+------------------------------------------------------------------+

Die Methode für die Signale von den Kanälen ist rot markiert.

Die Methoden zur Prüfung des Trailings von Kauf- oder Verkaufpositionen des Vorfahren wurde mittels des Polymorphismus neu definiert - das Grundprinzip der OOP.

Damit die Trailing-Klasse Zeit und Preise der Ziele von dem aktuellen Kanal erhalten kann, muss sie mit der Klasse für die Signale CSignalEquidChannel verbunden werden. Es wurde durch einen "constant" Pointer in der Klasse CEquidChannelExpert realisiert. Dieser Ansatz erlaubt es, alle notwendigen Informationen von den Signalen zu erhalten, ohne dass die Parameter der Signale selbst verändert werden.

//+------------------------------------------------------------------+
//| Klasse CEquidChannelExpert.                                      |
//| Zweck: Klasse eines EA der den äquidistanten Kanal handelt.      |
//|        Abgeleitet von der Klasse CExper.                         |
//+------------------------------------------------------------------+
class CEquidChannelExpert : public CExpert
  {
   //--- === Data members === --- 
private:
   const CSignalEquidChannel *m_ptr_ch_signal;

   //--- === Methods === --- 
public:
   //--- Constructor/Destructor
   void              CEquidChannelExpert(void);
   void             ~CEquidChannelExpert(void);
   //--- Pointer auf das Objekt der Kanalsignale
   void              EquidChannelSignal(const CSignalEquidChannel *_ptr_ch_signal){m_ptr_ch_signal=_ptr_ch_signal;};
   const CSignalEquidChannel *EquidChannelSignal(void) const {return m_ptr_ch_signal;};

protected:
   virtual bool      Processing(void);
   //--- trade close positions check
   virtual bool      CheckClose(void);
   virtual bool      CheckCloseLong(void);
   virtual bool      CheckCloseShort(void);
   //--- trailing stop check
   virtual bool      CheckTrailingStop(void);
   virtual bool      CheckTrailingStopLong(void);
   virtual bool      CheckTrailingStopShort(void);
  };
//+------------------------------------------------------------------+

Auch die Methoden für das Schließen und Trailing von Positionen wurden in der Expertklasse angepasst.

Die Hauptmethode CEquidChannelExpert::Processing() in der Datei EquidistantChannelExpert4.mqh schaut jetzt wie folgt aus:

//+------------------------------------------------------------------+
//| Hauptmodul                                                       |
//+------------------------------------------------------------------+
bool CEquidChannelExpert::Processing(void)
  {
//--- Berechnung der Richtung
   m_signal.SetDirection();
//--- Keine Position
   if(!this.SelectPosition())
     {
      //--- Positionseröffnung
      if(this.CheckOpen())
         return true;
     }
//--- Es gibt eine Position
   else
     {
      if(!this.CheckClose())
        {
         //--- Prüfung auf Änderbarkeit der Position
         if(this.CheckTrailingStop())
            return true;
         //---
         return false;
        }
      else
        {
         return true;
        }
     }
//--- wenn es keine Handelsoperationen gibt
   return false;
  }
//+------------------------------------------------------------------+

Diese Parameter werden optimiert:
 Variable StartStep
Stop
StopLoss, Points
25
5
75
TakeProfit, Points50
5
200

Die Ergebnisse der Optimierung finden sich in der Datei ReportOptimizer-signal4.xml. Das beste Resultat zeigt Fig.9.

Fig.9 Ergebnisse der Strategie unter Berücksichtigung der Kanalgrenzen für 2013-2015.

Fig.9 Ergebnisse der Strategie unter Berücksichtigung der Kanalgrenzen für 2013-2015.

Es ist ganz deutlich, dass dieser Faktor — grenzbezogene Preislevel — keine Verbesserung der Ergebnisse darstellt.


Schlussfolgerung

Der Artikel erläutert den Prozess der Entwicklung und Umsetzung einer Klasse der Signale eines gleitenden Kanals. Jede Version der Signale wurde in einer Handelsstrategie realisiert und getestet.

Ich möchte betonen, dass für alles in diesem Artikel die verwendeten Werte des äquidistanten Kanals fix waren. Daher müssen alle Schlussfolgerungen, ob dieser oder jener Faktor entscheidend ist, unter Berücksichtigung dieses Faktums erfolgen.

Es bleiben noch andere Wege die Ergebnisse weiter zu verbessern. Dieser Artikel beschäftigt sich mit einem Teilaspekt dieser Möglichkeiten.