Der MQL5-Assistent: Platzierung von Order, Stop-Losses und Take Profits auf berechneten Kursen. Erweiterung der Standard-Library

Andrey Shpilev | 2 Juni, 2016

Einleitung

Die MQL5 Standard Library ist eine sinnvolle Hilfe bei der Entwicklung großer Projekte, die eine strikte Architektur verlangen. Der MQL5-Assistent gestattet im Dialogmodus in wenigen Minuten den Einbau einsatzbereiter Teile in ein umfassendes Schema - ein unschätzbarer Vorteil. Der MQL5-Assistent automatisiert das Zusammenführen aller Teile des Experts und deklariert im Expert automatisch Modulparameter, je nach ihren Handles. Wenn eine große Anzahl unterschiedlicher Module beteiligt ist, spart eine derartige Automatisierung enorm viel Zeit und Routineabläufe.

Klingt ja alles wunderschön, doch leider gibt es einen klaren Nachteil - die Fähigkeiten der, mittels des Assistenten auf Basis von Standardklassen erzeugten Handelssysteme, sind beschränkt. Dieser Beitrag stellt eine universelle Methode vor, die eine erhebliche Erweiterung der Funktionalität von erzeugten Experts bietet. Wird diese Methode implementiert, bleiben Kompatibilität mit dem Assistenten und den Standard-Modulen unverändert.

Der Hintergedanke dieser Methode ist, sich der Mechanismen der Vererbung und des Polymorphismus im Objekt-orientierten Programmieren zu bedienen oder Klassen anzulegen, die die Standardklassen im Code erzeugter Experts ersetzen. So können nämlich alle Vorteile des Assistenten und der Standard Library ausgeschöpft werden, was letztendlich zur Entwicklung eines Experts mit den erforderlichen Fähigkeiten führt. Doch um dahin zu kommen, muss der Code ein bisschen verringert werden - nur um vier Strings.

Der praktische Zweck dieses Beitrags ist, erzeugte Experts durch eine Fähigkeit zu ergänzen, mit der man Order sowie Stop Losses und Take Profits bei geforderten Kursstufen platzieren kann, und nicht nur am festgelegten Abstand vom aktuellen Kurs.

Ein ähnlicher Gedanke war bereits Gegenstand des Beitrags "MQL5-Assistent: Wie man einem EA beibringt, Pending Order bei jedem Preis zu öffnen". Der eindeutige Nachteil der vorgeschlagenen Lösung ist die "erzwungene" Änderung des Parameters des Handelssignalmoduls vom untergeordneten Filter. Dieser Ansatz begünstigt nicht die Arbeit mit einer Menge Modulen, sodass der Einsatz des Assistenten zur Prozessoptimierung daher keinen Sinn macht.

Die Implementierung des Platzierens von Order sowie Stop Losses und Take Profits bei jeden Kursen in Klassen, die von den Standardklassen geerbt wurden, wird später in diesem Beitrag detailliert besprochen. Vor diesem Hintergrund sollte es zu keinen Konflikten mehr zwischen Modulen kommen. Ich hoffe, dieser Beitrag dienst als ein Beispiel und inspiriert Leser dazu, ihre eigenen Verbesserungen des Standard-Rahmenwerks zu schreiben und gestattet Anwendern, die entwickelte Library-Erweiterung auch zu implementieren.


1. Standard-Algorithmus der Entscheidungsfindung

Im MQL5-Assistenten erzeugte Experts beruhen auf der CExpert Klasseninstanz. Der Zeiger des Objekts der CExpertSignal Klasse wird in dieser Klasse deklariert. Aus Platzgründen nennen wird dieses Objekt ab sofort das Hauptsignal. Das Hauptsignal enthält Zeiger auf die untergeordneten Filter (Signalmodule sind die Erben der CExpertSignal Klasse).

Gibt es keine offenen Positions und Order, verweist der Expert auf das Hauptsignal, um nach einer Gelegenheit zu suchen, bei einer neuen Kursschwankung eine Position zu eröffnen. Das Hauptsignal fragt die untergeordneten Filter nacheinander ab und berechnet auf Basis der erhaltenen Prognose die gewichtete Prognose (Richtung). Übersteigt ihr Wert den Grenzwert (Wert des m_threshold_open Parameters im Hauptsignal), werden Order-Parameter und Ergebnisse der Prüfergebnisse vom Typ bool der Bedingungen an den Expert übertragen. Werden diese Bedingungen erfüllt, wird entweder eine Position auf dem Marktkurs geöffnet oder eine pending Order in einem gewissen Abstand von ihm (vgl. Abb. 1). Stop Losses können an einem festen Abstand platziert werden. Die Eröffnungskurseinzüge sowie Stop Loss und Take Profit vom Marktkurs werden in den Expert-Einstellungen festgelegt und im Hauptsignal gespeichert - in den m_price_level, m_stop_level bzw. m_take_level Variablen.

Also müssen derzeit zwei Bedingungen erfüllt sein, damit eine Order platziert wird:

  1. Keine offenen Positions für das aktuelle Symbol;
  2. Der absolute Wert der gewichteten Durchschnittsprognose übersteigt den Grenzwert (d.h. ein Trend ist eher stark).

Abb. 1 Entscheidungsfindungsmuster bei Markteinstieg

Abb. 1 Entscheidungsfindungsmuster bei Markteinstieg

Das aktuelle Entscheidungsfindungsmuster in Abb. 1 schränkt den Anwendungsbereich des MQL5-Assistenten deutlich ein. Strategien mit einem festen Stop Loss-Wert sind bei langfristigem Handel aufgrund sich verändernder Volatilität selten wirkungsvoll. Systeme, die pending Order einsetzen, verlangen in der Regel danach, diese auch auf dynamisch berechneten Stufen zu platzieren.


2. Veränderter Algorithmus der Entscheidungsfindung

Von der Perspektive der Berechnung von Stufen und Platzierung von Order aus gesehen, ist dies eine Sackgasse, da Signalmodule nichts anderes als einen Prognosewert erzeugen können und das Hauptsignal nicht für eine Arbeit mit Stufen gedacht war. In dieser Hinsicht wird folgendes vorgeschlagen:

Der veränderte Algorithmus (Abb. 2) ermöglicht die Arbeit mit anderen Anfragen jenseits von pending Order. Der Kern dieses Algorithmus ist, dass er den Einstiegspunkt (Preis) davon abtrennt, einen Trend festzulegen (gewichtete Durchschnittsprognose). Um also eine Entscheidung zu treffen, wird die bevorzugte Richtung des Handels mit Filtern definiert und ein Set von Order-Parametern mit der geeigneten Richtung, die man von den Preismodulen erhalten hat, ausgewählt. Sollten mehrere ähnliche Sets vorhanden sein, wird das vom Modul empfangene Set mit der größten Gewichtung (Parameterwert m_weight) ausgewählt. Ist die Richtung festgelegt worden, doch sind derzeit keine Einstiegspunkte vorhanden, bleibt der Expert inaktiv.

Abb. 2 Verändertes Entscheidungsfindungsmuster bei Markteinstieg

Abb. 2 Verändertes Entscheidungsfindungsmuster bei Markteinstieg

Um eine Order zu platzieren, müssen die folgenden Voraussetzungen erfüllt sein:

  1. Keine offenen Positions und Order für das aktuelle Symbol;
  2. Der absolute Wert der gewichteten Durchschnittsprognose übersteigt den Grenzwert;
  3. Mindestens ein Eröffnungskurs der Order wurde gefunden.

Der Algorithmus in Abb. 2 ermöglicht den Umgang mit vielen möglichen Einstiegspunkten, filtert sie nach Richtung und wählt den besten in einem Expert aus.


3. Die Entwicklung veränderter Klassen des Experts und des Signalmoduls

Die Erweiterung der Library beruht auf zwei Klassen: CExpertSignalAdvanced und CExpertAdvanced, die von CExpertSignal bzw. CExpert geerbt wurden.

Alle Maßnahmen zur Ergänzung mit neuen Fähigkeiten sollen die Schnittstellen verändern, die für einen Datenaustausch zwischen den verschiedenen Blocks des Expert eingesetzt werden. Um z.B. den Algorithmus nach dem Muster in Abb. 2 zu implementieren, muss die Interaktion des Hauptsignals (CExpertSignalAdvanced Klasse) mit Kursmodulen (Nachkommen dieser Klasse) organisiert werden. Die Aktualisierung bereits platzierter Order bei Veränderung der Daten beinhaltet die Interaktion des Experts (CExpertSignalAdvanced Klasse) mit dem Hauptsignal.

In der gegenwärtigen Phase werden wir also das Muster in Abb. 2 zur Eröffnung von Positions implementieren und die Aktualisierung bereits platzierter Order bei sich ändernden Parametern organisieren (z.B. wenn ein günstigerer Einstiegspunkt auftaucht). Betrachten wir uns zunächst die CExpertSignalAdvanced Klasse.

3.1 CExpertSignalAdvanced

Diese Klasse ersetzt ihre Vorgängerin in der Funktion als Hauptsignal und wird zum grundlegenden Signal für Kursmodule, ganz genauso wie ihr Vorgänger das Basissignal für Signalmodule ist.

class CExpertSignalAdvanced : public CExpertSignal
  {
protected:
   //---data members for storing parameters of the orders being placed
   double            m_order_open_long;         //opening price of the order to buy
   double            m_order_stop_long;         //Stop Loss of the order to buy
   double            m_order_take_long;         //Take Profit of the order to buy
   datetime          m_order_expiration_long;   //expiry time of the order to buy
   double            m_order_open_short;        //opening price of the order to sell
   double            m_order_stop_short;        //Stop Loss of the order to sell
   double            m_order_take_short;        //Take Profit of the order to sell
   datetime          m_order_expiration_short;  //expiry time of the order to sell
   //---             
   int               m_price_module;            //index of the first price module in the m_filters array
public:
                     CExpertSignalAdvanced();
                    ~CExpertSignalAdvanced();
   virtual void      CalcPriceModuleIndex() {m_price_module=m_filters.Total();}
   virtual bool      CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      CheckOpenShort(double &price,double &sl,double &tp,datetime &expiration);
   virtual double    Direction(void);		//calculating weighted average forecast based on the data received from signal modules
   virtual double    Prices(void);		//updating of parameters of the orders being placed according to the data received from price modules
   virtual bool      OpenLongParams(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      OpenShortParams(double &price,double &sl,double &tp,datetime &expiration);
   virtual bool      CheckUpdateOrderLong(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex);
   virtual bool      CheckUpdateOrderShort(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex);
   double            getOpenLong()              { return m_order_open_long;         }
   double            getOpenShort()             { return m_order_open_short;        }
   double            getStopLong()              { return m_order_stop_long;         }
   double            getStopShort()             { return m_order_stop_short;        }
   double            getTakeLong()              { return m_order_take_long;         }
   double            getTakeShort()             { return m_order_take_short;        }
   datetime          getExpLong()               { return m_order_expiration_long;   }
   datetime          getExpShort()              { return m_order_expiration_short;  }
   double            getWeight()                { return m_weight;                  }
  };

Datenmitglieder zur Speicherung von Order-Parametern sind in der CExpertSignalAdvanced Klasse deklariert worden. Die Werte dieser Variablen werden in der Prices() Methode aktualisiert. Diese Variablen agieren als Puffer.

Dann wird der Parameter m_price_module deklariert. Er speichert den Index des ersten Kursmoduls im m_filters Array, das in CExpertSignal deklariert wurde. Dieses Array enthält die Zeiger auf mit aufgenommenen Signalmodule. Zeiger auf Standardmodule (Filter) werden ganz zu Beginn des Arrays gespeichert. Dann kommen, beginnend mit dem m_price_module Index, die Kursmodule. Um nicht die Initialisierungsmethoden von Indikatoren und Zeitreihen ändern zu müssen, wurde beschlossen, alles in einem Array zu speichern. Darüber hinaus gibt es noch eine Möglichkeit, 64 Module durch ein Array mit aufzunehmen - und das sollte meistens genügen.

Zusätzlich wurden auch noch Hilfs-Methoden in der CExpertSignalAdvanced Klasse zum Erhalt der Werte von geschützten Datenmitgliedern deklariert. Ihre Namen beginnen mit get (vgl. Klassendeklarierung).

3.1.1 Konstruktor

Der Konstruktor CExpertSignalAdvanced initialisiert Variablen, die innerhalb der Klasse deklariert sind:

CExpertSignalAdvanced::CExpertSignalAdvanced()
  {
   m_order_open_long=EMPTY_VALUE;
   m_order_stop_long=EMPTY_VALUE;
   m_order_take_long=EMPTY_VALUE;
   m_order_expiration_long=0;
   m_order_open_short=EMPTY_VALUE;
   m_order_stop_short=EMPTY_VALUE;
   m_order_take_short=EMPTY_VALUE;
   m_order_expiration_short=0;
   m_price_module=-1;
  }

3.1.2 CalcPriceModuleIndex()

Die CalcPriceModuleIndex() Methode weist in m_price_module die aktuelle Anzahl an Array-Elementen zu, die gleich dem Index des folgenden, hinzugefügten Moduls ist. Diese Methode wir vor dem Hinzufügen des ersten Kursmoduls aufgerufen. Der Funktionskorpus befindet sich in der Klassendeklarierung.

virtual void      CalcPriceModuleIndex() {m_price_module=m_filters.Total();}

3.1.3 CheckOpenLong(...) und CheckOpenShort(...)

Die CheckOpenLong(...) Methode wird von der CExpert Klasseninstanz aufgerufen und arbeitet so wie hier beschrieben:

  1. Auf mit aufgenommene Kursmodule prüfen. Sind keine vorhanden, dann kann die gleichnamige Methode der übergeordneten Klasse aufgerufen werden;
  2. Die gewichtete Durchschnittsprognose (Richtung) von der Direction() Methode erhalten;
  3. Überprüfen, ob die Bedingungen für einen Einstieg erfüllt sind, indem die Richtung mit EMPTY_VALUE und dem Grenzwert von m_threshold_open verglichen wird;
  4. Mittels der Prices() Methode die Werte der Order-Parameter erneuern und sie an den Expert via der OpenLongParams(...) Funktion übertragen. Das Ergebnis dieser Funktion speichern;
  5. Das gespeicherte Ergebnis liefern.
bool CExpertSignalAdvanced::CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration)
  {
//--- if price modules were not found, call the method of the basic class CExpertSignal
   if(m_price_module<0)
      return(CExpertSignal::CheckOpenLong(price,sl,tp,expiration));

   bool   result   =false;
   double direction=Direction();
//--- prohibitive signal
   if(direction==EMPTY_VALUE)
      return(false);
//--- check for exceeding the threshold
   if(direction>=m_threshold_open)
     {
      Prices();
      result=OpenLongParams(price,sl,tp,expiration);//there's a signal if m_order_open_long!=EMPTY_VALUE
     }
//--- return the result
   return(result);
  }

CheckOpenShort(...) funktioniert nach dem gleichen Prinzip und wird auch genauso verwendet - deshalb gehen wir auf sie nicht näher ein.

3.1.4 Direction()

Die Direction() Methode fragt Filter ab und berechnet die gewichtete Durchschnittsprognose. Diese Methode ist der gleichnamigen Methode der übergeordneten CExpertSignal Klasse sehr ähnlich, mit allerdings einer Ausnahme: in der Schleife verweisen wir nicht auf alle Elemente des m_filters Arrays, sondern nur auf diejenigen mit einem Index, der von "0" zu dem weniger alsm_price_module variiert. Alles andere ist ähnlich zu CExpertSignal::Direction().

double CExpertSignalAdvanced::Direction(void)
  {
   long   mask;
   double direction;
   double result=m_weight*(LongCondition()-ShortCondition());
   int    number=(result==0.0)? 0 : 1;      // number of queried modules
//--- loop by filters
   for(int i=0;i<m_price_module;i++)
     {
      //--- mask for bitmaps (variables, containing flags)
      mask=((long)1)<<i;
      //--- checking for a flag of ignoring a filter signal
      if((m_ignore&mask)!=0)
         continue;
      CExpertSignal *filter=m_filters.At(i);
      //--- checking for a pointer
      if(filter==NULL)
         continue;
      direction=filter.Direction();
      //--- prohibitive signal
      if(direction==EMPTY_VALUE)
         return(EMPTY_VALUE);
      if((m_invert&mask)!=0)
         result-=direction;
      else
         result+=direction;
      number++;
     }
//--- averaging the sum of weighted forecasts
   if(number!=0)
      result/=number;
//--- return the result
   return(result);
  }

3.1.5 Prices()

Die Prices() Methode wiederholt sich über dem zweiten Teil des m_filters Arrays und beginnt dabei beim m_price_module Index, bis zum Ende. Sie fragt Kursmodule ab und erneuert die Werte der Klassenvariablen mit den Funktionen OpenLongParams(...) und OpenShortParams(...). Die Parameterwerte werden vor dem Zyklus geleert.

Während des Zyklus werden die Parameterwerte überschrieben, sollte die Gewichtung des aktuellen Kursmoduls (m_weight) größer sein als die der zuvor abgefragten Module, die die Werte lieferten Als Ergebnis bleiben entweder leere Parameter stehen (wenn nichts gefunden wurde) oder eben Parameter mit der zum Zeitpunkt des Aufrufs einer Methode besten verfügbaren Gewichtung.

double CExpertSignalAdvanced::Prices(void)
  {
   m_order_open_long=EMPTY_VALUE;
   m_order_stop_long=EMPTY_VALUE;
   m_order_take_long=EMPTY_VALUE;
   m_order_expiration_long=0;
   m_order_open_short=EMPTY_VALUE;
   m_order_stop_short=EMPTY_VALUE;
   m_order_take_short=EMPTY_VALUE;
   m_order_expiration_short=0;
   int    total=m_filters.Total();
   double last_weight_long=0;
   double last_weight_short=0;
//--- cycle for price modules
   for(int i=m_price_module;i<total;i++)
     {   
      CExpertSignalAdvanced *prm=m_filters.At(i);
      if(prm==NULL)
         continue;
//--- ignore the current module if it has returned EMPTY_VALUE
      if(prm.Prices()==EMPTY_VALUE)continue;
      double weight=prm.getWeight();
      if(weight==0.0)continue;
//--- select non-empty values from modules with the greatest weight
      if(weight>last_weight_long && prm.getExpLong()>TimeCurrent())
         if(prm.OpenLongParams(m_order_open_long,m_order_stop_long,m_order_take_long,m_order_expiration_long))
            last_weight_long=weight;
      if(weight>last_weight_short && prm.getExpShort()>TimeCurrent())
         if(prm.OpenShortParams(m_order_open_short,m_order_stop_short,m_order_take_short,m_order_expiration_short))
            last_weight_short=weight;
     }
   return(0);
  }

3.1.6 OpenLongParams(...) and OpenShortParams(...)

Innerhalb der CExpertSignalAdvanced Klasse überträgt die OpenLongParams(...) Methode Parameterwerte der Kauf-Order per Verweis von den Klassenvariablen an die Eingabeparameter

Die Funktion dieser Methode war in der übergeordneten Klasse leicht anders. Dort hat sie die erforderlichen Parameter auf Basis des Marktkurses berechnet und Kurseinrückungen im Hauptsignal spezifiziert. Jetzt überträgt sie fertige Parameter. Wenn der Eröffnungskurs korrekt ist (nicht = EMPTY_VALUE), liefert die Methode 'true'- wenn nicht liefert sie 'false'.

bool CExpertSignalAdvanced::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration)
  {
   if(m_order_open_long!=EMPTY_VALUE)
     {
      price=m_order_open_long;
      sl=m_order_stop_long;
      tp=m_order_take_long;
      expiration=m_order_expiration_long;
      return(true);
     }
   return(false);
  }

Wir gehen auch auf OpenShortParams(...) nicht näher ein, da ihr Funktionsprinzip das gleiche ist und sie auch ähnlich eingesetzt wird.

3.1.7 CheckUpdateOrderLong(...) und CheckUpdateOrderhort(...)

Die CheckUpdateOrderLong(...) und CheckUpdateOrderhort(...) Methoden werden in derCExpertAdvanced Klasse aufgerufen. Sie werden zur Aktualisierung einer bereits platzierten pending Order, gemäß der letzten Kusstufen verwendet.

Die CheckUpdateOrderLong(...) Methode jedoch, verdient es, genauer betrachtet zu werden. Zunächst werden Kursstufen aktualisiert, wenn die Prices(...) Methode aufgerufen wird, danach wird eine Prüfung auf Datenaktualisierungen ausgeführt, um mögliche Veränderungsfehler auszuschließen. Und schließlich wird die OpenLongParams(...) Methode zur Übertragung aktualisierter Daten und Lieferung des Ergebnisses aufgerufen.

bool CExpertSignalAdvanced::CheckUpdateOrderLong(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex)
  {
   Prices();   //update prices
//--- check for changes
   double point=m_symbol.Point();
   if(   MathAbs(order_ptr.PriceOpen() - m_order_open_long)>point
      || MathAbs(order_ptr.StopLoss()  - m_order_stop_long)>point
      || MathAbs(order_ptr.TakeProfit()- m_order_take_long)>point
      || order_ptr.TimeExpiration()!=m_order_expiration_long)
      return(OpenLongParams(open,sl,tp,ex));
//--- update is not required   
   return (false);
  }

Auf OpenShortParams(...) gehen wir ebenfalls nicht näher ein, da ihr Funktionsprinzip das gleiche ist und sie auch ähnlich eingesetzt wird.

3.2 CExpertAdvanced

Änderungen in der Klasse des Experts betreffen nur die Veränderung bereits platzierter Order gemäß der akutalisierten Daten zu Kursen im Hauptsignal. Die Deklarierung der CExpertAdvanced Klasse ist unten abgebildet.

class CExpertAdvanced : public CExpert
  {
protected:
   virtual bool      CheckTrailingOrderLong();
   virtual bool      CheckTrailingOrderShort();
   virtual bool      UpdateOrder(double price,double sl,double tp,datetime ex);
public:
                     CExpertAdvanced();
                    ~CExpertAdvanced();
  };

Wie wir sehen, sind die Methoden nur wenige, und der Konstruktor und Destruktor sind leer.

3.2.1 CheckTrailingOrderLong() and CheckTrailingOrderhort()

Die CheckTrailingOrderLong() Methode überschreibt eine gleichnamige Methode der Basisklasse und ruft die CheckUpdateOrderLong(...) Methode des Hauptsignals auf, um die Notwendigkeit für eine Veränderung der Order festzustellen. Falls eine Veränderung nötig ist, wird die UpdateOrder(...) Methode aufgerufen und das Ergebnis wird geliefert.

bool CExpertAdvanced::CheckTrailingOrderLong(void)
  {
   CExpertSignalAdvanced *signal_ptr=m_signal;
//--- check for the opportunity to modify the order to buy
   double price,sl,tp;
   datetime ex;
   if(signal_ptr.CheckUpdateOrderLong(GetPointer(m_order),price,sl,tp,ex))
      return(UpdateOrder(price,sl,tp,ex));
//--- return with no actions taken
   return(false);
  }

Die CheckTrailingOrderhort() Methode ist ähnlich und wird auch genauso benutzt.

3.2.2 UpdateOrder()

Die UpdateOrder() Funktion beginnt zunächst mit einer Prüfung nach einem relevanten Kurs (nicht EMPTY_VALUE). Gibt es keinen, wird die Order gelöscht; ansonsten wird sie gemäß der erhaltenen Parameter verändert.

bool CExpertAdvanced::UpdateOrder(double price,double sl,double tp,datetime ex)
  {
   ulong  ticket=m_order.Ticket();
   if(price==EMPTY_VALUE)
      return(m_trade.OrderDelete(ticket));
//--- modify the order, return the result
   return(m_trade.OrderModify(ticket,price,sl,tp,m_order.TypeTime(),ex));
  }

Die Entwicklung der Erben der Standardklassen ist damit abgeschlossen.


4. Entwicklung von Kursmodulen

Wir haben jetzt also bereits die Grundlage zur Erzeugung von Experts, die Order und Stop Losses an berechneten Stufen platzieren. Genauer gesagt, haben wir Klassen, die für eine Arbeit mit Kursstufen vollkommen einsatzbereit sind. Es müssen jetzt also nur noch die Module geschrieben werden, die diese Stufen generieren.

Der Vorgang zur Entwicklung von Kursmodulen ähnelt dem des Schreibens von Modulen von Handelssignalen. Ihr einzige Unterschied ist, dass Prices(), die für die Kursaktualisierung innerhalb eines Moduls verantwortliche Methode, aufgehoben werden muss, und nicht LongCondition(), ShortCondition() oder Direction() wie in den Signalmodulen. Idealerweise sollte der Leser ziemlich gut über die Entwicklung von Signalmodulen Bescheid wissen. Hierfür können die Beiträge "6 Schritten zum eigenen automatischen Handelssystem!" und "Generator von Handelssignalen auf Grundlage eines angepassten Indikators" u.U. sehr hilfreich sein.

Als Beispiel soll uns der Code mehrerer Kursmodule dienen.

4.1 Kursmodul auf Basis des "Delta ZigZag"-Indikators

Der Delta ZigZag Indikator zeichnet Stufen nach der angegebene Anzahl einiger jüngster Spitzen. Übersteigt der Kurs diese Stufen, weist dies auf eine mögliche Umkehrung des Trends hin.

Das Ziel des Kursmoduls ist, den Einstiegspunkt vom Indikatorpuffer zu holen, das nächste lokale Extrem zur Platzierung eines Stop Loss zu finden und Take Profit durch Multiplikation des Stop Loss mit dem in den Einstellungen angegeben Koeffizienten zu berechnen.

Abb. 3 Illustration der Arbeit des Kursmoduls auf dem Delta ZigZag-Indikator mit Hilfe der Kauf-Order als Beispiel

Abb. 3 Illustration der Arbeit des Kursmoduls auf dem Delta ZigZag-Indikator mit Hilfe der Kauf-Order als Beispiel

Abb. 3 bildet die vom Kursmodul generierten Stufen ab. Bevor die Order ausgelöst wird, verändern sich Stop Loss und Take Profit entsprechend, gemäß den Aktualisierungen des Minimums.

4.1.1 Modul-Deskriptor

// wizard description start
//+------------------------------------------------------------------+
//| Description of the class                                         |
//| Title=DeltaZZ Price Module                                       |
//| Type=SignalAdvanced                                              |
//| Name=DeltaZZ Price Module                                        |
//| ShortName=DeltaZZ_PM                                             |
//| Class=CPriceDeltaZZ                                              |
//| Page=not used                                                    |
//| Parameter=setAppPrice,int,1, Applied price: 0 - Close, 1 - H/L   |
//| Parameter=setRevMode,int,0, Reversal mode: 0 - Pips, 1 - Percent |
//| Parameter=setPips,int,300,Reverse in pips                        |
//| Parameter=setPercent,double,0.5,Reverse in percent               |
//| Parameter=setLevels,int,2,Peaks number                           |
//| Parameter=setTpRatio,double,1.6,TP:SL ratio                      |
//| Parameter=setExpBars,int,10,Expiration after bars number         |
//+------------------------------------------------------------------+
// wizard description end

Die ersten fünf Parameter im Deskriptor sind für die Einrichtung des benutzten Indikators notwendig. Dann folgt der Koeffizient zur Berechnung von Take Profit auf Basis von Stop Loss und der Ablaufzeit in den Bars für die Order vom aktuellen Kursmodul.

4.1.2 Klassen-Deklarierung

class CPriceDeltaZZ : public CExpertSignalAdvanced
  {
protected:
   CiCustom          m_deltazz;           //object of the DeltaZZ indicator
   //--- module settings
   int               m_app_price;
   int               m_rev_mode;
   int               m_pips;
   double            m_percent;
   int               m_levels;
   double            m_tp_ratio;          //tp:sl ratio
   int               m_exp_bars;          //lifetime of the orders in bars
   //--- method of indicator initialization
   bool              InitDeltaZZ(CIndicators *indicators);
   //--- helper methods
   datetime          calcExpiration() { return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); }
   double            getBuySL();          //function for searching latest minimum of ZZ for buy SL
   double            getSellSL();         //function for searching latest maximum of ZZ for sell SL
public:
                     CPriceDeltaZZ();
                    ~CPriceDeltaZZ();
   //--- methods of changing module settings
   void              setAppPrice(int ap)           { m_app_price=ap; }
   void              setRevMode(int rm)            { m_rev_mode=rm;  }
   void              setPips(int pips)             { m_pips=pips;    }
   void              setPercent(double perc)       { m_percent=perc; }
   void              setLevels(int rnum)           { m_levels=rnum;  }
   void              setTpRatio(double tpr)        { m_tp_ratio=tpr; }
   void              setExpBars(int bars)          { m_exp_bars=bars;}
   //--- method of checking correctness of settings
   virtual bool      ValidationSettings(void);
   //--- method of creating indicators
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- main method of price module updating the output data
   virtual double    Prices();
  }; 

Geschützte Daten - das Objekt des Indikators vom Typ CiCustom und die Parameter, gemäß der im Deskriptor angegebenen Typen, werden im Modul deklariert.

4.1.3 Konstruktor

Der Konstruktor initialisiert die Klassen-Parameter mit Standardwerten. Später werden diese Werte erneut initialisiert, wenn das Modul in das Hauptsignal, gemäß den Eingabeparametern des Experts mit eingeschlossen wird.

CPriceDeltaZZ::CPriceDeltaZZ() : m_app_price(1),
                                 m_rev_mode(0),
                                 m_pips(300),
                                 m_percent(0.5),
                                 m_levels(2),
                                 m_tp_ratio(1),
                                 m_exp_bars(10)
  {
  }

4.1.4 ValidationSettings()

ValidationSettings() ist eine wichtige Methode zur Prüfung der Eingabeparameter. Sind die Werte der Modulparameter ungültig, wird als Ergebnis 'false' geliefert und im Logbuch eine Fehlermeldung gedruckt.

bool CPriceDeltaZZ::ValidationSettings(void)
  {
//--- checking for settings of additional filters
   if(!CExpertSignal::ValidationSettings())
      return(false);
//--- initial data check
   if(m_app_price<0 || m_app_price>1)
     {
      printf(__FUNCTION__+": Applied price must be 0 or 1");
      return(false);
     }
   if(m_rev_mode<0 || m_rev_mode>1)
     {
      printf(__FUNCTION__+": Reversal mode must be 0 or 1");
      return(false);
     }
   if(m_pips<10)
     {
      printf(__FUNCTION__+": Number of pips in a ray must be at least 10");
      return(false);
     }
   if(m_percent<=0)
     {
      printf(__FUNCTION__+": Percent must be greater than 0");
      return(false);
     }
   if(m_levels<1)
     {
      printf(__FUNCTION__+": Ray Number must be at least 1");
      return(false);
     }
   if(m_tp_ratio<=0)
     {
      printf(__FUNCTION__+": TP Ratio must be greater than zero");
      return(false);
     }
   if(m_exp_bars<0)
     {
      printf(__FUNCTION__+": Expiration must be zero or positive value");
      return(false);
     }
//--- parameter check passed
   return(true);
  } 

4.1.5 InitIndicators(...)

Die InitIndicators(...) Methode ruft eine gleichnamige Methode der Basisklasse auf und initialisiert den Indikator des aktuellen Moduls mittels der InitDeltaZZ(...) Methode.

bool CPriceDeltaZZ::InitIndicators(CIndicators *indicators)
  {
//--- initialization of indicator filters
   if(!CExpertSignal::InitIndicators(indicators))
      return(false);
//--- creating and initializing of custom indicator
   if(!InitDeltaZZ(indicators))
      return(false);
//--- success
   return(true);
  }

4.1.6 InitDeltaZZ(...)

Die InitDeltaZZ(...) Methode fügt das Objekt der angepassten Indikators dieser Sammlung hinzu und erzeugt einen neuen Indikator namens "Delta ZigZag".

bool CPriceDeltaZZ::InitDeltaZZ(CIndicators *indicators)
  {
//--- adds to collection
   if(!indicators.Add(GetPointer(m_deltazz)))
     {
      printf(__FUNCTION__+": error adding object");
      return(false);
     }
//--- specifies indicator parameters
   MqlParam parameters[6];
//---
   parameters[0].type=TYPE_STRING;
   parameters[0].string_value="deltazigzag.ex5";
   parameters[1].type=TYPE_INT;
   parameters[1].integer_value=m_app_price;
   parameters[2].type=TYPE_INT;
   parameters[2].integer_value=m_rev_mode;
   parameters[3].type=TYPE_INT;
   parameters[3].integer_value=m_pips;
   parameters[4].type=TYPE_DOUBLE;
   parameters[4].double_value=m_percent;
   parameters[5].type=TYPE_INT;
   parameters[5].integer_value=m_levels;
//--- object initialization
   if(!m_deltazz.Create(m_symbol.Name(),m_period,IND_CUSTOM,6,parameters))
     {
      printf(__FUNCTION__+": error initializing object");
      return(false);
     }
//--- number of the indicator buffers
   if(!m_deltazz.NumBuffers(5)) return(false);
//--- ок
   return(true);
  }

Nach erfolgreichem Abschluss der ValidationSettings(), InitDeltaZZ(...) und InitIndicators(...)Methoden wird das Modul initialisiert und steht zur Verwendung bereit.

4.1.7 Prices()

Diese Methode ist ganz grundlegend für das Kursmodul. Denn hier werden Order-Parameter aktualisiert und ihre Werte dann an das Hauptsignal übertragen. Diese Methode liefert ein Ergebnis in Form des double-Typs. Dies wurde hauptsächlich für eine zukünftige Entwicklung implementiert. Das Ergebnis der Prices() Methode kann einige eigenartige Situationen und Ereignisse kodieren, damit das Hauptsignal entsprechend korrekt mit ihnen verfahren kann. Derzeit soll nur mit dem gelieferten Wert EMPTY_VALUE umgegangen werden können. Sobald dieses Ergebnis erhalten wird, ignoriert das Hauptsignal die von diesem Modul vorgeschlagenen Parameter.

Funktions-Algorithmus der Prices() Methode in diesem Modul:

  1. Nehmen Sie den Eröffnungskurs der Order für Kaufen bzw. Verkaufen aus den Indikatorpuffern 3 und 4;
  2. Nullen Sie die endgültigen Parameter der Order;
  3. Prüfen Sie, ob ein Kurs zum Kaufen vorhanden ist. Wenn ja, identifizieren Sie die Stufen für eine Platzierung von Stop Loss mittels der getBuySL() Methode, berechnen die Take Profit Stufe mittels des Stop Loss Wertes und des Eröffnungskurses und berechnen auch die Ablaufzeit der Order;
  4. Prüfen Sie, ob ein Kurs zum Verkaufen vorhanden ist. Wenn ja, finden Sie die Stufen für eine Platzierung von Stop Loss mittels der getSellSL() Methode, berechnen die Take Profit Stufe mittels des Stop Loss Wertes und des Eröffnungskurses und berechnen auch die Ablaufzeit der Order;
  5. Verlassen.
Aufgrund einiger Aspekte der Funktionsweise des "Delta ZigZag" Indikators, schließen sich die Bedingungen für eine Anwesenheit von Kursen zum Kaufen und Verkaufen gegenseitig aus. Puffer 3 und 4 werden standardmäßig als Punkte gezeichnet (vgl. Abb. 3).

double CPriceDeltaZZ::Prices(void)
  {
   double openbuy =m_deltazz.GetData(3,0);//receive the last value from buffer 3
   double opensell=m_deltazz.GetData(4,0);//receive the last value from buffer 4
//--- clear parameter values
   m_order_open_long=EMPTY_VALUE;
   m_order_stop_long=EMPTY_VALUE;
   m_order_take_long=EMPTY_VALUE;
   m_order_expiration_long=0;
   m_order_open_short=EMPTY_VALUE;
   m_order_stop_short=EMPTY_VALUE;
   m_order_take_short=EMPTY_VALUE;
   m_order_expiration_short=0;
   int digits=m_symbol.Digits();
//--- check for the prices to buy
   if(openbuy>0)//if buffer 3 is not empty
     {
      m_order_open_long=NormalizeDouble(openbuy,digits);
      m_order_stop_long=NormalizeDouble(getBuySL(),digits);
      m_order_take_long=NormalizeDouble(m_order_open_long + m_tp_ratio*(m_order_open_long - m_order_stop_long),digits);
      m_order_expiration_long=calcExpiration();
     }
//--- check for the prices to sell
   if(opensell>0)//if buffer 4 is not empty
     {
      m_order_open_short=NormalizeDouble(opensell,digits);
      m_order_stop_short=NormalizeDouble(getSellSL(),digits);
      m_order_take_short=NormalizeDouble(m_order_open_short - m_tp_ratio*(m_order_stop_short - m_order_open_short),digits);
      m_order_expiration_short=calcExpiration();
     }
   return(0);
  }

4.1.8 getBuySL() und getSellSL()

Die getBuySL() und getSellSL() Methoden suchen nach lokalen jeweiligen Minima und Maxima zur Platzierung von Stop Losses. Der letzte nicht-Null Werte ist ein relevanter Puffer  in jeder Methode wird nach der Kursstufe des letzten lokalen Extremums gefragt.

double CPriceDeltaZZ::getBuySL(void)
  {
   int i=0;
   double sl=0.0;
   while(sl==0.0)
     {
      sl=m_deltazz.GetData(0,i);
      i++;
     }
   return(sl);
  }
double CPriceDeltaZZ::getSellSL(void)
  {
   int i=0;
   double sl=0.0;
   while(sl==0.0)
     {
      sl=m_deltazz.GetData(1,i);
      i++;
     }
   return(sl);
  }

4.2 Kursmodul auf Basis des Inside-Bars

Der Inside-Bar ist einer der am häufigsten verwendeten Modelle oder Muster im Handel ohne Indikatoren, die sog. Kursaktion. Ein Inside-Bar ist ein Bar, der innerhalb der Extrema des vorangegangenen Bars Hoch und Tief umfasst. Ein Inside-Bar gibt den Anfang der Konsolidierung oder möglichen Umkehrung der Kursbewegung an.

In Abb. 4 sind Inside-Bars innerhalb der roten Ellipsen. Sobald ein Inside-Bar entdeckt wird, generiert das Kursmodul die Eröffnungskurs der Buystop- und Sellstop-Order auf den Extrema des Bars, der dem Inside-Bar vorausgeht.

Eröffnungskurse sind die Stop Loss Stufen für entgegengesetzte Order. Take Profit wird genauso berechnet, wie im oben gerade besprochenen Modul - durch Multiplikation der Stop Loss Stufe mit dem in den Einstellungen angegebenen Koeffizienten. Eröffnungskurse und Stop Losses sind durch rote waagrechte Linien gekennzeichnet; Take Profits druch grüne.

Abb. 4 Abbildung von Inside-Bars und Stufen des Kursmoduls

Abb. 4 Abbildung von Inside-Bars und Stufen des Kursmoduls

Sell-Order werden in Abb. 4 nicht platziert, da der Trend gerade ansteht. Dennoch erzeugt das Kursmodul die Einstiegspunkte in beiden Richtungen. Einige Take Profit Stufen liegen außerhalb der grafischen Darstellung.

Anders als beim vorherigen Modul, ist der Algorithmus dieses Moduls einfach und verlangt keine Initialisierung von Indikatoren. In dem Modul gibt es nur wenig Code. Er ist unten komplett dargestellt. Wir analysieren jedoch nicht jede Funktion separat.

#include <Expert\ExpertSignalAdvanced.mqh>
// wizard description start
//+------------------------------------------------------------------+
//| Description of the class                                         |
//| Title=Inside Bar Price Module                                    |
//| Type=SignalAdvanced                                              |
//| Name=Inside Bar Price Module                                     |
//| ShortName=IB_PM                                                  |
//| Class=CPriceInsideBar                                            |
//| Page=not used                                                    |
//| Parameter=setTpRatio,double,2,TP:SL ratio                        |
//| Parameter=setExpBars,int,10,Expiration after bars number         |
//| Parameter=setOrderOffset,int,5,Offset for open and stop loss     |
//+------------------------------------------------------------------+
// wizard description end
//+------------------------------------------------------------------+
//| Class CPriceInsideBar                                            |
//| Purpose: Class of the generator of price levels for orders based |
//|          on the "inside bar" pattern.                            |
//| Is derived from the CExpertSignalAdvanced class.                 |
//+------------------------------------------------------------------+
class CPriceInsideBar : public CExpertSignalAdvanced
  {
protected:
   double            m_tp_ratio;          //tp:sl ratio
   int               m_exp_bars;          //lifetime of the orders in bars
   double            m_order_offset;      //shift of the opening and Stop Loss levels
   datetime          calcExpiration()  { return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); }
public:
                     CPriceInsideBar();
                    ~CPriceInsideBar();
   void              setTpRatio(double ratio){ m_tp_ratio=ratio; }
   void              setExpBars(int bars)    { m_exp_bars=bars;}
   void              setOrderOffset(int pips){ m_order_offset=m_symbol.Point()*pips;}
   bool              ValidationSettings();
   double            Prices();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPriceInsideBar::CPriceInsideBar()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPriceInsideBar::~CPriceInsideBar()
  {
  }
//+------------------------------------------------------------------+
//| Validation of protected settings                                 |
//+------------------------------------------------------------------+
bool CPriceInsideBar::ValidationSettings(void)
  {
//--- verification of the filter parameters
   if(!CExpertSignal::ValidationSettings())
      return(false);
//--- initial data check
  if(m_tp_ratio<=0)
     {
      printf(__FUNCTION__+": TP Ratio must be greater than zero");
      return(false);
     }
   if(m_exp_bars<0)
     {
      printf(__FUNCTION__+": Expiration must be zero or positive value");
      return(false);
     }
//--- check passed
   return(true);
  }
//+------------------------------------------------------------------+
//| Price levels refreshing                                          |
//+------------------------------------------------------------------+
double CPriceInsideBar::Prices(void)
  {
   double h[2],l[2];
   if(CopyHigh(m_symbol.Name(),m_period,1,2,h)!=2 || CopyLow(m_symbol.Name(),m_period,1,2,l)!=2)
      return(EMPTY_VALUE);
//--- check for inside bar      
   if(h[0] >= h[1] && l[0] <= l[1])
     {
      m_order_open_long=h[0]+m_order_offset;
      m_order_stop_long=l[0]-m_order_offset;
      m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio;
      m_order_expiration_long=calcExpiration();
      
      m_order_open_short=m_order_stop_long;
      m_order_stop_short=m_order_open_long;
      m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio;
      m_order_expiration_short=m_order_expiration_long;
      return(0);
     }
   return(EMPTY_VALUE);
  }  
//+------------------------------------------------------------------+

In der Prices() Methode dieses Moduls, wird das gelieferte EMPTY_VALUE Ergebnis dazu genutzt, dem Hauptsignal zu zeigen, dass es keine vorhandenen Kursstufen gibt.

4.3 Kursmodul auf Basis des Outside-Bars

Der Outside-Bar ist ein weiteres, beliebtse Muster der Kursaktion und wird auch "Absorption" genannt. Der Outside-Bar heißt deshalb so, weil sein Hoch und Tief mit dem Hoch und Tief des vorherigen Bars überlappen. Das Auftreten eines Outside-Bars ist ein Zeichen eines Anstiegs der Volatilität, gefolgt von einer gerichteten Kursbewegung.

In Abb. 5 sind Outside-Bars durch rote Ellipsen gekennzeichnet. Sobald ein Outside-Bar entdeckt wird, generiert das Kursmodul die Eröffnungskurse der Buystop- und Sellstop-Order auf seinen Extrema.

Eröffnungskurse sind die Stop Loss Stufen für entgegengesetzte Order. Take Profit wird genauso berechnet, wie im oben gerade besprochenen Modul - durch Multiplikation der Stop Loss Stufe mit dem in den Einstellungen angegebenen Koeffizienten. Eröffnungskurse und Stop Losses sind durch rote waagrechte Linien gekennzeichnet; Take Profits druch grüne.

Abb. 5 Abbildung von Outside-Bars und Kursmodul-Stufen

Abb. 5 Abbildung von Outside-Bars und Kursmodul-Stufen

In Abb. 5 sind Outside-Bars durch rote Ellipsen gekennzeichnet. Die waagrechten Linien stellen die Eröffnungskurse der, vom Kursmodul generierten, pending Order dar.

In diesem Fall werden nur Sell-Order geöffnet, da ein Abwärtstrend vorherrscht. Dieses Modul funktioniert im Grunde genauso wie das oben beschriebene. Der einzige Unterschied zwischen beiden Codes liegt nur in der Prices() Methode; alle anderen Teile sind genau gleich und haben auch dieselben Namen. Unten steht der Code der Prices() Methode.

double CPriceOutsideBar::Prices(void)
{
   double h[2],l[2];
   if(CopyHigh(m_symbol.Name(),m_period,1,2,h)!=2 || CopyLow(m_symbol.Name(),m_period,1,2,l)!=2)
      return(EMPTY_VALUE);
//--- check of outside bar
   if(h[0] <= h[1] && l[0] >= l[1])
   {
      m_order_open_long=h[1]+m_order_offset;
      m_order_stop_long=l[1]-m_order_offset;
      m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio;
      m_order_expiration_long=calcExpiration();
      
      m_order_open_short=m_order_stop_long;
      m_order_stop_short=m_order_open_long;
      m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio;
      m_order_expiration_short=m_order_expiration_long;
      return(0);
   }
   return(EMPTY_VALUE);
} 

4.4 Leistungstest des Moduls

Im nächsten Abschnitt werden drei Modulbeispiele getestet, um zunächst ihre jeweils einzelne und dann gemeinsame Funktion in einem Expert zu testen und um sicherzustellen, dass das System auch so wie konzipiert wie gedacht. Infolgedessen werden vier Experts erzeugt. In jedem der erzeugten Experts wird Handelssignalmodule auf Basis des Awesome Oscillator Indikators, der auf dem H12 Zeitrahmen arbeitet, als ein Filter verwendet. Kursmodule arbeiten auf H6.

Geldverwaltung: fester Posten und Trailing Stop nicht angewendet. Alle Tests werden in EURUSD Notierungen vom Demo-Konto des MetaQuotes-Demoservers für den Zeitraum vom 01.01.2013. bis 01.06.2014 durchgeführt. Version des Terminals: 5.00, Bauart 965 (27. Juni 2014).

Aus Platzgründen werden die Testergebnisse nur durch grafische Darstellungen des Saldo abgebildet. Sie reichen aus, um das Verhalten des Experts in jedem speziellen Fall zu verstehen.

4.4.1 Test des Moduls auf Basis des Delta ZigZag-Indikators

Abb. 6 Grafische Darstellung des Saldos beim Test des Kursmoduls auf Basis von Delta ZigZag

Abb. 6 Grafische Darstellung des Saldos beim Test des Kursmoduls auf Basis von Delta ZigZag

4.4.2 Test des Moduls auf Basis des "Inside Bar"-Musters

Abb. 7 Grafische Darstellung des Saldos beim Test des Kursmoduls auf Basis des Inside Bars

Abb. 7 Grafische Darstellung des Saldos beim Test des Kursmoduls auf Basis des Inside Bars

Bei der Betrachtung von Abb. 7 sollte man nicht vergessen, dass der Zweck der Tests in dieser Phase darin besteht, die Leistung des Codes zu überprüfen und nicht eine gewinnbringende Strategie zu erhalten.

4.4.3 Test des Moduls auf Basis des "Outside Bar"-Musters

Abb. 8 Grafische Darstellung des Saldos beim Test des Kursmoduls auf Basis des Outside Bars

Abb. 8 Grafische Darstellung des Saldos beim Test des Kursmoduls auf Basis des Outside Bars

4.4.4 Test der gemeinsamen Arbeit der entwickelten Kursmodule

Unter Berücksichtigung der vorangegangenen Testergebnisse, haben Kursmodule auf Basis des DeltaZigZag sowie der Inside und Outside Bars Gewichtungskoeffizienten von 1, 0,5 bzw. 0,7 erhalten. Solche Gewichtungskoeffizienten legen die Prioritäten der Kursmodule fest.

Abb. 9. Grafische Darstellung des Saldos beim Test des Experts mit drei Kursmodulen

Abb. 9 Grafische Darstellung des Saldos beim Test des Experts mit drei Kursmodulen

In jeder Testphase wurden alle ausgeführten Abläufe auf den Kursplots sorgfältig analysiert. Es haben sich keine Fehler in der Strategie sowie Abweichungen gezeigt. Alle vorgeschlagenen Programme sind unten an diesen Beitrag angehängt, sodass Sie sie alle selbst noch einmal testen können.


5. Anwendungsanleitungen

Betrachten wir uns jetzt die einzelnen Etappen der Entwicklung des Experts mit Hilfe der entwickelten Erweiterung am Beispiel des Experts mit drei Kursmodulen und dem Awesome Oscillator als einen Filter.

Bevor Sie mit der Erzeugung beginnen, vergewissern Sie sich, dass sich die Header-Dateien "CExpertAdvanced.mqh" und "CExpertSignalAdvanced.mqh" im Katalog des MetaTrader 5 Terminals, im Ordner MQL5/Include/Expert befinden. Die Dateien der Kursmodule sind im Ordner MQL5/Include/Expert/MySignal. Bevor Sie den Expert starten, vergewissern Sie sich, dass die kompilierten Indikatoren im MQL5/Indicators Ordner sind - in unserem Fall die "DeltaZigZag.ex5"-Datei. Im unten angehängten Archiv sind alle Dateien am richtigen Platz. Sie müssen jetzt nur noch dieses Archiv in einem Ordner des MetaTrader 5 Terminal entpacken und das Zusammenführen der Kataloge bestätigen.

5.1 Erzeugung des Experts

Um mit der Erzeugung des Experts zu beginnen, im MetaEditor auf "Datei"->"Neu" gehen.

Abb. 10 Einen neuen Expert mit Hilfe des MQL5-Assistenten erzeugen

Abb. 10 Einen neuen Expert mit Hilfe des MQL5-Assistenten erzeugen

Im neuen Fenster "Expert Advisor (erzeugen)" auswählen und dann "Weiter" anklicken.


Abb. 11 Einen Expert mit Hilfe des MQL5-Assistenten generieren

Abb. 11 Einen Expert mit Hilfe des MQL5-Assistenten generieren

Nun geht ein Fenster auf, in dem Sie den Namen eingeben können. In unserem vorliegenden Beispiel lautet der Name des Experts "TEST_EA_AO_DZZ_IB_OB". Die Parameter 'Symbol' und 'TimeFrame' haben Standardwerte. Anschließend "Weiter" anklicken.

Abb. 12 Gemeinsame Parameter des Expert Advisors


Abb. 12 Gemeinsame Parameter des Expert Advisors

Im aufgegangenen Fenster nun jedes Modul nacheinander einfügen, indem Sie jedes Mal "Hinzufügen" anklicken. Der gesamte Ablauf ist unten dargestellt.

BITTE BEACHTEN! Wenn Sie Module hinzufügen, bitte zuerst alle Signalmodule mitaufnehmen (CExpertSignal Erben) und dann erst die Kursmodule (CExpertSignalAdvanced Erben). Wenn Sie diese Reihenfolge nicht einhalten, kann es zu unerwarteten Ergebnissen kommen.

Beginnen wir also mit der Aufnahme der Signalmodule vom Awesome Oscillator - in unserem Beispiel der einzige Filter.

Abb. 13 Aufnahme des Signalmoduls vom Awesome Oscillator

Abb. 13 Aufnahme des Signalmoduls vom Awesome Oscillator

Nachdem alle Signalmodule aufgenommen wurden, auch die erforderlichen Kursmodule in einer zufälligen Reihenfolge mit aufnehmen. In unserem Beispiel gibt es deren drei.

Abb. 14 Aufnahme des Signalmoduls vom DeltaZZ-Kursmodul

Abb. 14 Aufnahme des Signalmoduls vom DeltaZZ-Kursmodul


Abb. 15 Aufnahme des Signalmoduls vom Inside Bar -Kursmodul

Abb. 15 Aufnahme des Signalmoduls vom Inside Bar -Kursmodul


Abb. 16 Aufnahme des Signalmoduls vom Outside Bar -Kursmodul

Abb. 16 Aufnahme des Signalmoduls vom Outside Bar -Kursmodul

Sind alle Module hinzugefügt, sieht das Fenster folgendermaßen aus:

Abb. 17 Liste der aufgenommenen Module an Handelssignalen

Abb. 17 Liste der aufgenommenen Module an Handelssignalen

Die Priorität des Kursmoduls wird durch den Wert seines Parameters "Gewichtung" festgelegt.

Nachdem Sie dann "Weiter" angeklickt haben, schlägt Ihnen der Assistent vor, Module zur Geldverwaltung und zum Trailing Stop auszuwählen. Sie können nun eins oder beide Module auswählen oder auch alles so lassen wie es ist. Anschließend "Weiter" oder "Fertig" anklicken.

Durch Anklicken von "Fertig" erhalten wir den generierten Code des Experts Advisors.

5.2 Den generierten Code analysieren

OK, wir haben jetzt also unseren Code.

1. Den String ganz am Anfang finden

#include <Expert\Expert.mqh>

und so bearbeiten, dass er so aussieht

#include <Expert\ExpertAdvanced.mqh>

Die Dateien der entwickelten Klassen müssen mit aufgenommen werden.

2. Dann den String finden

CExpert ExtExpert;

und ändern zu

CExpertAdvanced ExtExpert;

Dadurch ändert sich die Standardklasse des Experts zu ihrem Nachkömmling mit den geforderten Funktionen.

3. Jetzt den String finden

CExpertSignal *signal=new CExpertSignal;

und ändern zu

CExpertSignalAdvanced *signal=new CExpertSignalAdvanced;

So wird die Standardklasse des Hauptsignals zu ihrem Nachkömmling mit den geforderten Funktionen geändert.

4. Anschließend den String finden, der die Ergänzung des m_filters Array des Hauptsignals durch das erste Kursmodul implementiert. In diesem Beispiel sieht sie so aus:

signal.AddFilter(filter1);

Zuvor jedoch fügen wir den String ein

signal.CalcPriceModuleIndex();

Das ist nötig, damit das Hauptsignal erkennt bis zu welchem Index im m_filters Array sich Signalmodul-Zeiger befinden und es von dort beginnen kann, wo sich Kursmodulzeiger befinden.

Die richtige Stelle zu finden, wo der spezifizierte String eingefügt werden muss, kann evtl. schwierig sein. Verwenden Sie als Referenzpunkt die Zahl nach dem Wort "Filter". Das erleichtert die Suche und verhindert, dass Sie die richtige Stelle verpassen. Der MQL5-Assistent benennt die mit aufgenommenen Module automatisch in ihrer Reihenfolge. Das erste Modul heißt Filter0, das zweite – Filter1, das dritte – Filter2, usw. In unserem Fall gibt es nur ein Signalmodul. Daher ist das erste, mit aufgenommene Kursmodul Nr. 2 und wir müssen nach dem String "signal.AddFilter(filter1);" suchen, um den Filter hinzuzufügen, das die Bezifferung im Code bei "0" beginnt. Abb. 18 veranschaulicht dies:

Abb. 18 Modulnamen im Code gemäß der Reihenfolge ihrer Aufnahme

Abb. 18 Modulnamen im Code gemäß der Reihenfolge ihrer Aufnahme

5. Dieser Teil ist nicht obligatorisch. Infolge der eingeführten Veränderungen, sind die Expert-Parameter, die zur Öffnung von Kurseinzügen, Stop Losses, Take Profits und Ablaufzeit der Order, verantwortlich waren, nutzlos geworden. Um den Code kompakter zu machen, können Sie die folgenden Codes einfach löschen:

input double Signal_PriceLevel            =0.0;                    // Price level to execute a deal
input double Signal_StopLevel             =50.0;                   // Stop Loss level (in points)
input double Signal_TakeLevel             =50.0;                   // Take Profit level (in points)
input int    Signal_Expiration            =4;                      // Expiration of pending orders (in bars)

Der nach dem Löschen der obigen Strings aufgetretene Kompilierungsfehler führt uns direkt zur nächsten Gruppe von Strings, die gelöscht werden müssen:

   signal.PriceLevel(Signal_PriceLevel);
   signal.StopLevel(Signal_StopLevel);
   signal.TakeLevel(Signal_TakeLevel);
   signal.Expiration(Signal_Expiration);

Sobald diese gelöscht sind, wird die Kompilierung erfolgreich sein.

Kommentare und Erklärungen zur Bearbeitung finden sich im angehängten Code des Experts "TEST_EA_AO_DZZ_IB_OB". Code-Strings, die gelöscht werden konnten, sind ebenfalls mit Kommentaren versehen.


Fazit

In diesem Beitrag haben wir den Anwendungsbereich des MQL5-Assistenten erheblich erweitert. Er kann jetzt auch zur Entwicklungsoptimierung von automatischen Handelssystemen genutzt werden, die die Platzierung von Order sowie von Stop Losses und Take Profits an unterschiedlichen Kursstufen verlangen - ungeachtet des aktuellen Kurses.

Generierte Experts können nun ein Set an Kursmodulen enthalten, die die Parameter der gesendeten Order berechnen. Und das am besten geeignete Parameterset wird dann aus allen vorhandenen ausgewählt. Präferenzen lassen sich in den Einstellungen festlegen. Damit kann man viele verschiedene Einstiegspunkte mit maximaler Effizienz nutzen. Durch diesen Ansatz können Experts selektiv arbeiten. Ist die Richtung bekannt, jedoch der Einstiegspunkt noch nicht definiert, dann wartet der Expert solange, bis er auftaucht.

Die Einführung von Standard-kompatiblen Modulen, die nach Kursstufen suchen ist ein erheblicher Vorteil und soll ganz klar die Entwicklung von Experts vereinfachen. Zwar gibt es im Moment nur drei Module, doch wird ihre Zahl in Zukunft sicherlich ansteigen. Sollten Sie diesen Beitrag hilfreich finden, dann schlagen Sie doch in den Kommentaren Algorithmen für Abläufe von Kursmodulen vor. Interessante Vorschläge werden in den Code implementiert.

Dieser Beitrag stellt zudem auch eine Methode für eine weitere Ausweitung der Fähigkeiten des Experts vor, der im Assistenten entwickelt wurde. Der beste Weg, Veränderungen einzuführen, ist per Vererbung.

Anwender ohne Erfahrungen im Programmieren, die jedoch den Code mit Hilfe der Anleitungen bearbeiten können, haben somit eine Möglichkeit zur Hand, noch fortschrittlichere Experts auf Basis der zur Verfügung stehenden Modelle zu erzeugen.