MetaTrader 5 herunterladen

Cross-Platform Expert Advisor: Signale

16 Juni 2017, 08:44
Enrico Lambino
0
225

Inhaltsverzeichnis

Einführung

Im vorangegangenen Artikel Cross-Plattform Expert Advisor: Order-Manager zeigten wir die Klasse COrderManager, und wie sie verwendet werden kann, um das Schließen und Öffnen von Handelspositionen zu automatisieren. In diesem Artikel verwenden wir in etwa die gleichen Prinzipien, um das Erstellen von Signalen zu automatisieren. Erreicht wird das über die Klasse CSignal, dessen Container und der Klasse CSignals. Dieser Artikel erläutert detailliert die Implementation dieser Klassenobjekte.

Ziele

Die Klassen CSignal und CSignal aus diesem Artikel verfolgen folgende Ziele:

  • Die Umsetzung muss kompatibel sein mit MQL4 und MQL5.
  • Automatisierung des Großteils der Prozesse, die das Handelssignal betreffen
  • Die Umsetzung sollte einfach sein

Handelssignale

Beide, die Klasse CSignal mit seinen Containern und CSignals, sind verantwortlich für die Auswertung des allgemeinen Signals einer aktuellen Marktsituation. Die Handelssignale werden in zwei Gruppen unterteilt: Eröffnen und Schließen. Damit ein Eröffnungssignal einen EA dazu bringt, eine Position zu eröffnen, sollten das Signal und mit allen anderen Signalen zur Eröffnung in dieselbe Richtung handeln (alle kaufen oder alle verkaufen). Andererseits müssen die Signale zum Schließen einer Position individuell bleiben, da sie das jeweilige Endergebnis beeinflussen. Die Signale zum Schließen werden aber auch insgesamt bewertet. Wenn zum Beispiel Signal 1 das Schließen der Verkaufspositionen und Signal 2 das Schließen aller Kaufpositionen sendet, werden schlussendlich alle Positionen geschlossen.

Signaltypen

Der EA hat vier verschiedene Signaltypen, die von den Signalobjekten ausgewertet werden (und damit auch vom EA), und zwar auf Basis der Art, wie sie angewendet werden (zum Eröffnen oder Schließen). Die folgende Tabelle zeigt die Signaltypen, ihre Werte und wie sie, abhängig von der angestrebten Verwendung, interpretiert werden:

 Signaltypen
 Wert  Eröffnen Schließen
CMD_VOID -1
Annullieren aller anderen Signale
Schließen aller Positionen
CMD_NEUTRAL 0
Ignorieren
Ignorieren
CMD_LONG 1
Kaufen
Schließen der Verkaufspositionen
CMD_SHORT 2
Verkaufen
Schließen der Kaufpositionen

Die Signaltypen CMD_LONG und CMD_SHORT sind selbsterklärend, wir konzentrieren uns daher auf die beiden anderen Signaltypen.

CMD_VOID steht für die ganze Zahl -1, und signalisiert ein stark mehrdeutiges Signal. Wenn ein Signal zum Eröffnen zu diesem Ergebnis führt, werden alle anderen Eröffnungssignale annulliert. Das heißt, dieses Signals muss gesendet werden, und die Bedingung für ein Handelsstopp durch dieses Signal führt zu einem Handelsstopp für alle anderen Signale, unabhängig von den aktuellen Ergebnissen und unabhängig davon, ob alle anderen Signale mit der Richtung des Handels übereinstimmen oder nicht. Betrachten wir als Beispiel folgende drei Signale, eine Position zu eröffnen:

Signal 1: CMD_VOID

Signal 2: CMD_LONG

Signal 3: CMD_SHORT

Ergebnis: CMD_VOID

In diesem Fall sehen wir wie Signal 1 sofort die beiden anderen annulliert und zum Ergebnis CMD_VOID führt. Wir erkennen aber auch, dass Signal 2 und 3 in der Richtung nicht übereinstimmen und daher sowie und unabhängig vom Signal 1 zu einem Handelsstopp des EAs führen würden.

Betrachten wir jetzt den etwas verändert Fall:

Signal 1: CMD_VOID

Signal 2: CMD_LONG

Signal 3: CMD_LONG

Ergebnis: CMD_VOID

In diesem Falle stimmen Signal 2 und 3 in der Richtung überein (Kaufen), aber Signal 1 ist ungültig. Daher ist das Endsingal ein Handelsstopp. Signal 1 hat mehr Gewicht, wenn es ein Ungültig sendet, auch wenn Signal 1 und 2 übereinstimmen.

Allerdings, bei einem Signal zum Schließen, würde der zweite Fall zum Schließen aller Positionen führen: alle drei signalisieren das Schließen von Kaufpositionen, wobei allerdings Signal 1 das Schließen von Kauf- und Verkaufspositionen sendete. Da alle Signale zum Schließen kumulativ behandelt werden, würde das Endsignal zum Schließe aller Positionen auffordern.

CMD_NEUTRAL entspricht der Zahl 0 und wird verwendet, um ein erhaltenes Signal zurück zuweisen. Das entspricht in etwa einer "Abstinenz" in einem Auswahlprozess. Ein neutrales Signale enthält sich, das Endsignal zu beeinflussen, und überlässt die Entscheidung den anderen Signalen. Wenn es jedoch nur ein Signal gibt und das ist neutral, dann führt das dazu, dass weder ein Kauf- noch ein Verkaussignal dem EA gesendet wird, welches auch der Situation entspricht, wenn mehrere Handelssignale in der Richtung nicht übereinstimmen.

Erstellen wir ein Beispiel für CMD_NEUTRAL durch das etwas veränderte, erste Beispiel in diesem Kapitel:

Signal 1: CMD_NEUTRAL

Signal 2: CMD_LONG

Signal 3: CMD_SHORT

Ergebnis: CMD_VOID

In unserem dritten Beispiel sendet das Signal 1 jetzt eine neutrale Position. In diesem Fall werden nur die Signale 2 und 3 für das Endsignal berücksichtigt. Aber, da Signal 2 und 3 in der Richtung nicht übereinstimmen, führt das zu Handelsstopp als Endsignal (CMD_NEUTRAL).

Anders wäre es, wenn die verbleibenden Signale in dieselbe Richtung weisen würden. In unserem vierten Beispiel (siehe unten) ist das erste Signal neutral, und die beiden verbleibenden zeigen in die gleiche Richtung. In diesem Fall wird das Signal 1 ignoriert, und die Signale 2 und 3 ausgewertet, welches insgesamt zu einem Kaufsignal führt.

Signal 1: CMD_NEUTRAL

Signal 2: CMD_LONG

Signal 3: CMD_LONG

Ergebnis: CMD_LONG

Beachten Sie, dass die Reihenfolge der Signale keine Rolle spielt, wie in der folgenden Signalsituation:

Signal 1: CMD_NEUTRAL

Signal 2: CMD_LONG

Signal 3: CMD_LONG

Signal 4: CMD_NEUTRAL

Signal 5: CMD_LONG

Signal 6: CMD_NEUTRAL

Sie endet mit einem Endsignal CMD_LONG, nicht CMD_NEUTRAL.

Ein neutrales Signal, CMD_NEUTRAL, zum Schließen beeinflusst das Endsignal in keinem Fall. Da ist das so, als ob dieses Signal zum Schließen überhaupt nicht existiert, soweit es das Endsignal betrifft.

Es ist auch bemerkenswert, dass der Wert von CMD_NEUTRAL 0 ist, und das wir diese eigene Enumeration als Ersatz für ENUM_ORDER_TYPE verwenden. Die Verwendung der eigenen Enumeration hat verschiedene Vorteile. Erstens ist die Interpretation der Signale leichter. Ein weiterer Vorteil ist, dass so versehentliche Ausführungen von Handelspositionen durch nicht initialisierte Variable verhindert werden. So hat zum Beispiel ORDER_TYPE_BUY den Wert 0. Wenn unser EA eine Ganzzahl direkt einer Methode, die die Handelsaufträge durchführt, übergeben würde und, wenn diese Variable nicht initialisiert wurde oder ihr keine neue Zahl zugewiesen wurde (wahrscheinlich unabsichtlich), dann würde ein Wert von 0 zu einen Kaufauftrag führen. Durch die eigene Enumeration kann solch ein Fehler nicht mehr passieren, da ein Wert Null nicht mehr zu einem Handelsauftrag führt.

Vergleich mit CExpertSignal

CExpertSignal wertet die Gesamtrichtung wie folgt aus:

  1. Berechnen der eigenen Richtung und diese in der Variablen m_direction sichern
  2. Für jeden seiner Filter
    1. Abfragen der Richtung
    2.  Addieren der Richtung zu m_direction (abziehen, wenn der jeweilige Filter umgekehrt wurde)
  3. Wenn der Endwert von m_direction den Schwellenwert überschreitet, sende ein Handelssignal

Mit dieser Methode erreichen wir, dass je größer der Wert von m_direction ist, desto höher ist die Chance, dass der Preis steigen wird (erhöht die Chance den Schwellenwert zu überschreiten). Genauso gilt, je negativer der Wert von m_direction, desto größer die Wahrscheinlichkeit für einen weiteren Preisverfall. Da der Schwellenwert immer positiv ist, verwenden wir der Absolutwert von m_direction für die Verhaufssignale.

Das Signalobjekt aus diesem Artikel kann als eine vereinfachte Version von CExpertSignal betrachtet werden. Aber an statt die Signale und die Filter gemeinsam mit arithmetischen Operationen auszuwerten, wird jedes Signal einzeln behandelt. Dieser Ansatz ist weniger flexible, aber er ermöglicht dem Händler oder Programmierer eine größere Kontrolle über jedes einzelne Signal, das das Endsignal beeinflusst.

Phasen

OnInit und OnDeinit

Die Initialisierung von jedem Signal umfasst häufig auch das Erstellen und Initialisieren des verwendeten Indikators, und der zusätzlichen Mitglieder der Klasse (wenn es sie gibt), die vielleicht von verschiedenen Methoden des Klassenobjektes benötigt werden. Bei der De-Initialisierung müssen die Instanzen des Indikators gelöscht werden.

OnTick

  1. Erste Phase (Berechnung) - in dieser Phase werden die für die Berechnung benötigten Werte (prüfen des Signals) aktualisiert.
  2. Hauptphase oder Signalprüfung - in der Hauptphase wird das aktuell zu sendende Signal bestimmt. Vorzugsweise hat der Hauptteil der Methode, um die Lesbarkeit des Codes zu verbessern, nur eine Zeile (auf einen Blick erkennen, was das Signal empfiehlt).
  3. Final- oder Aktualisierungsphase - es gibt Signale, die bestimmt Mitglieder haben, die nur aktualisiert werden können, nachdem die Signalprüfung erfolgt ist. Ein Beispiel dafür wäre das Verfolgen der vorherigen Bid-Preise, um ihn vielleicht mit dem aktuellen Bis-Preis zu vergleichen oder andere Werte (möglicherweise das eines Chartobjektes oder Indikatorwertes). Eine Aktualisierung in der ersten Phase von jener Variablen, die den vorherigen Bid-Preis sichert, würde keinen Sinn machen, da dessen Wert dadurch immer gleich dem aktuellen Wert des Bid beim Prüfen des Signals sein würde.

Es ist erwähnenswert, dass MQL5 im Gegensatz zu MQL4 über einen Array für die Ticks verfügt. Nichtsdestotrotz ist MQL4 der begrenzende Faktor und daher muss der Code so gestaltetet werden, dass er die Standards von MQL4 erfüllt, um die Cross-Plattform Kompatibilität zu gewährleisten, da bei einer getrennten Implementierung dies engere Option darstellt (was in diesem Fall zutrifft).

Umsetzung

Die Klasse CSignal

Vor dem Prüfen irgendeines Handelssignals, ist es üblich, erst einmal die benötigten Daten zu aktualisieren. Dies wird von der Methode Refresh der Klasse CSignal durchgeführt, die die Indikatoren (auch die Daten der Zeitreihen) auf den letzten Stand bringt. Der folgende Code-Ausschnitt zeigt die Methode Refresh aus CSignal:

bool CSignalBase::Refresh(void)
  {
   for(int i=0;i<m_indicators.Total();i++)
     {
      CSeries *indicator=m_indicators.At(i);
      if(indicator!=NULL)
         indicator.Refresh(OBJ_ALL_PERIODS);
     }
   return true;
  }

Der Aufruf der Methode Refresh aus CSignal geschieht innerhalb der Methode Check derselben Klasse. Wie der Code-Ausschnitt unten zeigt, würde die Methode dann eine Weiterarbeit verhindern, wenn es nicht möglich war, die Daten zu aktualisieren (es könnte ja zu einen falschen Signal führen).

void CSignalBase::Check(void)
  {
   if(!Active())
      return;
   if(!Refresh())
      return;
   if(!Calculate())
      return;
   int res=CMD_NEUTRAL;
   if(LongCondition())
     {
      if (Entry())
         m_signal_open=CMD_LONG;
      if (Exit())
         m_signal_close=CMD_LONG;
     }
   else if(ShortCondition())
     {
      if (Entry())
         m_signal_open=CMD_SHORT;
      if (Exit())
         m_signal_close=CMD_SHORT;
     }
   else
   {
      if (Entry())
         m_signal_open=CMD_NEUTRAL;
      if (Exit())
         m_signal_close=CMD_NEUTRAL;
   }
   if(m_invert)
     {
      SignalInvert(m_signal_open);
      SignalInvert(m_signal_close);
     }
   Update();
  }

In der Methode Check aus CSignal wird das aktuelle Signal durch den Aufruf der Methoden LongCondition und ShortCondition bestimmt, die praktisch gleich den Methoden aus CExpertSignal der Standardbibliothek von MQL5 sind.

Der Empfang des aktuellen Signals wird durch die Methoden CheckOpenLong und CheckOpenShort bewerkstelligt, die von ausserhalb der Klasse aufgerufen werden (entweder von einem anderen Klassenobjekt oder direkt in der Funktion OnTick):

bool CSignalBase::CheckOpenLong(void)
  {
   return m_signal_open==CMD_LONG;
  }
bool CSignalBase::CheckOpenShort(void)
  {
   return m_signal_open==CMD_SHORT;
  }

Selbst gibt CSignal kein Signal zu kaufen oder verkaufen. Daher sind die Methoden virtuell und erhalten eine aktuelle Umsetzung sobald CSignal erweitert wird..

virtual bool      LongCondition(void)=0;
virtual bool      ShortCondition(void)=0;
Wenn aber die reinen Daten der Zeitreihen und Indikatoren nicht genügen und weitere Berechnungen notwendig sind, muss vor den oben genannten Methoden zuerst die Methode Calculate implementiert werden. So, wie die Indikatoren, die von CSignal verwendet werden, so sollten auch die Variablen, die die Werte sichern, Mitglieder der Klasse sein, damit sie von den Methoden LongCondition und ShortCondition verwendet werden können.
virtual bool      Calculate(void)=0;
virtual void      Update(void)=0;

Beachte, die Methode Calculate ist vom Typ 'bool' während die Methode Update keinen Wert zurück gibt. Das bedeutet, dass ein EA so konfiguriert werden kann, dass er das Prüfen der Signale unterlassen kann, falls bestimmte Berechnungen nicht durchgeführt werden konnten. Die Methode Update hingegen ist von Typ 'void', da ein logischer Rückgabewert nicht länger benötigt wird, da sie erst nach dem Erhalt der aktuellen Signale ausgeführt wird.

Es können einzelne Instanzen von CSignal der Signale für das Eröffnen, das Schließen oder beider erstellt werden. Das kann durch das Umschalten zwischen den Methoden Entry() und Exit() der Klasse erfolgen. Das geschieht meistens während der Initialisierung des Expert Advisors.

Die Klasse CSignals

CSignals ist abgeleitet von CArrayObj. Das erlaubt das Sichern der Instanzen von CObject, in diesem Falle das Sichern der Instanzen von CSignal.

Die Initialisierung dieser Klasse umfasst auch die Übergabe eines Objektes (CSymbolManager), das in einem früheren Artikel besprochen wurde. Das erlaubt dem Signal die Möglichkeit, die benötigten Daten abzufragen, egal, ob sie das Symbol des Charts oder ein anderes Symbol betreffen. In dieser Methode wird auch für jedes Signal die Methode Init aufgerufen:

bool CSignalsBase::Init(CSymbolManager *symbol_man)
  {
   m_symbol_man= symbol_man;
   m_event_man = aggregator;
   if(!CheckPointer(m_symbol_man))
      return false;
   for(int i=0;i<Total();i++)
     {
      CSignal *signal=At(i);
      if(!signal.Init(symbol_man))
         return false;
     }
   return true;
  }

Die Methode Check der Klasse initialisiert das Signal als neutral und iteriert dann über alle Signale, um deren jeweiliges Ergebnis abzufragen. Falls die Methode ein Signal der Ausprägung 'void' erhält oder ein gültiges Signal (Kauf oder Verkauf), aber in anderer Richtung als das vorherige, dann wird 'void' als Endsignal zurückgegeben. 

CSignalsBase::Check(void)
  {
   if(m_signal_open>0)
      m_signal_open_last=m_signal_open;
   if(m_signal_close>0)
      m_signal_close_last=m_signal_close;
   m_signal_open=CMD_NEUTRAL;
   m_signal_close=CMD_NEUTRAL;
   for(int i=0;i<Total();i++)
     {
      CSignal *signal=At(i);      
      signal.Check();
      if(signal.Entry())
        {
         if(m_signal_open>CMD_VOID)
           {
            ENUM_CMD signal_open=signal.SignalOpen();
            if(m_signal_open==CMD_NEUTRAL)
              {    
               m_signal_open=signal_open;
              }
            else if(m_signal_open!=signal_open)
              {               
               m_signal_open=CMD_VOID;
              }
           }
        }
      if(signal.Exit())
        {
         if(m_signal_close>CMD_VOID)
           {
            ENUM_CMD signal_close=signal.SignalClose();
            if(m_signal_close==CMD_NEUTRAL)
              {
               m_signal_close=signal_close;
              }
            else if(m_signal_close!=signal_close)
              {
               m_signal_close=CMD_VOID;
              }
           }
        }
     }
   if(m_invert)
     {
      CSignal::SignalInvert(m_signal_open);
      CSignal::SignalInvert(m_signal_close);
     }
   if(m_new_signal)
     {
      if(m_signal_open==m_signal_open_last)
         m_signal_open = CMD_NEUTRAL;
      if(m_signal_close==m_signal_close_last)
         m_signal_close= CMD_NEUTRAL;
     }
  }

Instanzen eines Indikators

Jede Instanz von CSignal sollte ihren eigenen Satz von Instanzen der Indikatoren haben, die in m_indicators gesichert werden (eine Instanz von CIndicators). Idealerweise sollte jede Instanz eines Indikators, die einer Instanz von CSignal gehört, unabhängig von jeder anderen Instanz von CSignal sein. Dies ist anders als in der Methode der Standardbibliothek von MQL5, die alle Indikatoren, die von einem EA verwendet werden, gemeinsam in einer einzigen Instanz von CIndicators sichert, die eine Mitglied der Klasse CExpert ist. Obwohl der Ansatz anfällig für doppelte Objekte ist (z.B. ein Indikatorobjekt eines gleitenden Durchschnitts als Signal 1, und dann ein zweites Indikatorobjekt als Signal 2), die dann zu einer zweifachen Berechnung desselben führen würden, hat es gewisse Vorteile. Erstens werden die Signalobjekte so als eigenständige Einheiten behandelt. Das gibt jedem Signal mehr Freiheit in der Verwendung des Indikators, besonders bei der Auswahl von Symbol und Zeitrahmen. Zum Beispiel könnte es für einen Multi-Symbol Expert Advisor schwierig werden, unter Verwendung der Klassen der Experten aus der Standardbibliothek Indikatoren zu erstellen, die die Daten unterschiedlicher Symbole verwenden. Der wahrscheinlich viel einfachere Weg (statt CExpert zu ändern oder zu erweitern) wäre es, einfach einen neuen, eigenen Indikator zu schreiben (der die benötigten Daten abfragt und/oder verarbeitet), die von CExpert für seine Signale und/oder Filter verwendet werden kann.

Einschränkungen

  1. Verfügbarkeit von Indikatoren. Nicht alle Indikatoren sind für MetaTrader 4 und MetaTrader 5 verfügbar. Daher muss für einen Cross-Plattform Expert Advisor, der auf beiden Plattformen arbeiten kann, jeder Indikator für beide existieren. Sonst wird der EA auf einer der beiden Plattformen nicht arbeiten. Das betrifft in der Regel nicht die Standardindikatoren, außer ein paar (z.B. der Volume Indikator von MT4 unterscheidet sich von der Version für MT5). Nutzerindikatoren dagegen müssen eine Version für MT4 und eine für MT5 haben, damit der Cross-Plattform Expert Advisor sie verwenden und normal auf beiden Plattformen arbeiten kann.
  2. Verfügbarkeit bestimmter Daten. Manche Daten in Zeitreihen gibt es einfach nicht für MetaTrader 4. Daher könnten manche Strategien, die Daten benötigen, die nur vom MetaTrader 5 bereitgestellt werden (z.B. das Tick-Volumen), schwierig oder gar nicht für MQL4 übersetzt werden.

Beispiele

Beispiel #1: Order Manager Beispiel

In unserem vorherigen Artikel, Cross-Plattform Expert Advisor: Order-Manager, zeigten wir an einem Beispiel, wie ein Expert Advisor mit dem Order-Manager arbeitet. Die Methode des EA wechselt zu Beginn einer neuen Bar von Kaufen nach Verkaufen und umgekehrt. Der folgende Codeausschnitt zeigt die Funktion OnTick des erwähnten Expert Advisors:

void OnTick()
  {
//---
   static int bars = 0;
   static int direction = 0;
   int current_bars = 0;
   #ifdef __MQL5__
      current_bars = Bars(NULL,PERIOD_CURRENT);
   #else 
      current_bars = Bars;
   #endif
   if (bars<current_bars)
   {   
      symbol_info.RefreshRates();
      COrder *last = order_manager.LatestOrder();
      if (CheckPointer(last) && !last.IsClosed())
         order_manager.CloseOrder(last);
      if (direction<=0)
      {
         Print("Entering buy trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
         direction = 1;
      }
      else
      {
         Print("Entering sell trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
         direction = -1;
      }   
      bars = current_bars;
   }
  }

Wie man sieht, sind die Codezeilen, die für das Verhalten des EAs verantwortlich sind (soweit es die Signalerstellung betrifft), an verschiedenen Plätzen in der Funktion. Der Code eines einfachen EAs wie dieser ist leicht zu verstehen und handhabbar. Dennoch könnte die Wartung des Quellcodes zunehmend schwieriger werden, wenn die Komplexität des EAs zunimmt. Unser Ziel soll es sein, die Signalerstellung unter Verwendung der oben im Artikel beschriebenen Signalklassen besser zu organisieren.

Dafür müssen wir die Klasse CSignal an ihrem Beginn durch drei 'protected' Variablen erweitern: (1) die Anzahl der vorherigen Bars, (2) die vorherige Zahl der vorherigen Bars und (3) die aktuelle Richtung. So wie es das Folgende zeigt:

class SignalOrderManagerExample: public CSignal
  {
protected:
   int               m_bars_prev;
   int               m_bars;
   int               m_direction;
   //rest of the class

Wir müssen auch die Methoden der Klasse CSignal erweitern. Die Methode Calculate für die Berechnung verwendeten wir bereits in dem alten Beispiel:

bool SignalOrderManagerExample::Calculate(void)
  {
   #ifdef __MQL5__
      m_bars=Bars(NULL,PERIOD_CURRENT);
   #else
      m_bars=Bars;
   #endif
   return m_bars>0 && m_bars>m_bars_prev;
  }

Die Methoden, um die Anzahl der Bars des aktuellen Chart zu ermitteln, sind in beiden Plattformen unterschiedlich und wir müssen daher die Implementation teilen. Beachten Sie auch, dass die Methode Calculate der Klasse einen Wert vom Typ 'bool' zurückgibt. Wie bereits erwähnt, wird die Weiterverarbeitung der Signale eines bestimmten Tick angehalten, wenn die Methode Calculate 'false' zurückliefert. Hier definieren wir explizit zwei Regeln, wie die Weiterverarbeitung der Signale eines bestimmten Ticks durchgeführt werden soll: (1) die aktuelle Zahl der Bars ist größer als Null und (2) die aktuelle Zahl der Bars ist größer als die vorher gesicherte. 

Betrachten wir jetzt die Methode Update der Klasse, um sie zu erweitern, so wie es der folgende Codeausschnitt zeigt:

void SignalOrderManagerExample::Update(void)
  {
   m_bars_prev=m_bars;
   m_direction= m_direction<=0?1:-1;
  }

Nach der Prüfung der Signale aktualisieren wir die vorherige Zahl der Bars (m_bars_prev) mit der aktuellen Zahl (m_bars). Ebenso aktualisieren wir die Richtung. Ist der aktuelle Wert kleiner oder gleich Null (vorherige Richtung war verkaufen oder es wäre die erste Position), wird der Variablen der Wert 1 zugewiesen, andernfalls -1.

Zum Schluss behandeln wir die Signalerstellung selbst. Auf Basis der für die Erstellung der Signale des aktuellen Ticks benötigten Variablen, legen wir die Bedingungen fest, die bestimmen, ob das Endsignal ein Kauf oder Verkauf ist. Das wird durch die erweiterten Methoden LongCondition und ShortCondition aus CSignal erledigt:

bool SignalOrderManagerExample::LongCondition(void)
  {
   return m_direction<=0;
  }

bool SignalOrderManagerExample::ShortCondition(void)
  {
   return m_direction>0;
  }

Die Funktion Init dieses Beispiels ist dem früheren sehr ähnlich. Außer dass wir, für dieses Beispiel, auch die gerade definierte, abgeleitete Klasse von CSignal (SignalOrderManagerExample) instanziieren müssen, wie auch dessen Container (CSignals):

int OnInit()
  {
//---
   order_manager=new COrderManager();
   symbol_manager=new CSymbolManager();
   symbol_info=new CSymbolInfo();
   if(!symbol_info.Name(Symbol()))
      Print("symbol not set");
   symbol_manager.Add(GetPointer(symbol_info));
   order_manager.Init(symbol_manager,NULL);
   SignalOrderManagerExample *signal_ordermanager=new SignalOrderManagerExample();
   signals=new CSignals();
   signals.Add(GetPointer(signal_ordermanager));
//---
   return(INIT_SUCCEEDED);
  }

Hier deklarieren wir signal_ordermanager als Pointer auf ein neues Objekt des Typs SignalOrderManagerExample, das wir gerade definiert haben. Dann machen wir dasselbe über den Pointer für CSignals, und wir übergeben den Pointer auf SignalOrderManagerExample mit der Methode Add.

Die Verwendung von CSignal und CSignals in unserem Expert Advisor ergibt ein deutlich vereinfachtes Bild der Funktion OnTick:

void OnTick()
  {
//---
   symbol_info.RefreshRates();   
   signals.Check();
   if(signals.CheckOpenLong())
     {
      close_last();
      Print("Entering buy trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
     }
   else if(signals.CheckOpenShort())
     {
      close_last();
      Print("Entering sell trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
     }
  }

Alle anderen Berechnungen, die für das Berechnen des aktuellen Signals benötigt werden, werden in die Objekte von CSignal und CSignals verschoben. Alles was jetzt noch zu tun bleibt, ist die die Prüfung von CSignals und entsprechend dem Ergebnis die Methoden CheckOpenLong und CheckOpenShort aufzurufen. Der folgende Screenshot zeigt die Testergebnisse des Experten in MetaTrader 4 und MetaTrader 5:

(MT4)

signal_ordermanager (MT4)

(MT5)

signal_ordermanager (MT5)

Beispiel #2: MA Expert Advisor

Unser nächstes Beispiel verwendet einen gleitenden Durchschnitt als Indikator für die Handelssignale. Der MA ist ein Standardindikator im MetaTrader 4 und MetaTrader 5, und er ist einer der einfachsten Indikatoren, wenn es darum geht, einen Cross-Plattform Expert Advisor zu schreiben.

So im vorigen Beispiel erstellen wir ein eigenes Signal durch eine Erweiterung von CSignal:

class SignalMA: public CSignal
  {
protected:
   CiMA             *m_ma;
   CSymbolInfo      *m_symbol;
   string            m_symbol_name;
   ENUM_TIMEFRAMES   m_timeframe;
   int               m_signal_bar;
   double            m_close;   
   //rest of the class

Wie man sieht verfügen die Bibliotheken für MQL4 und MQL5 bereits über die Objektklasse des Moving-Average-Indikators. Das macht es einfacher, den Indikator in unserer eigenen Signalklasse zu verwenden. Obwohl es möglicherweise in diesem Beispiel nicht notwendig ist, speichern wir trotzdem das Handelssymbol über dessen Pointer auf das Objekt CSybmolInfo. Wir deklarieren auch eine Variable mit dem Namen m_close, in der wir den Schlusskurs der Signalbar sichern. Der Rest der 'protected' Klassenmitglieder sind die Parameter des gleitenden Durchschnittsindikators.

Das vorige Beispiel musste keine komplexen Datenstrukturen für die Verwendung vorbereiten. In diesem Beispiel sind sie jedoch notwendig (durch den Indikator), und daher müssen wir den Konstruktor der Klasse initialisieren:

void SignalMA::SignalMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int period,const int shift,const ENUM_MA_METHOD method,const ENUM_APPLIED_PRICE applied,const int bar)
  {
   m_symbol_name= symbol;
   m_timeframe = timeframe;
   m_signal_bar = bar;
   m_ma=new CiMA();
   m_ma.Create(symbol,timeframe,period,0,method,applied);
   m_indicators.Add(m_ma);   
  }

Der erhaltenen Wert des gleitenden Durchschnitts wird häufig mit einem bestimmten Preis des Charts verglichen. Es kann ein besonderer Preis des Charts sein, wie der 'Open' oder 'Close' oder die aktuellen Bid oder Ask. Letztere benötigen ein Symbolobjekt für die Berechnung. In diesem Beispiel erweitern wir auch die Methode Init, damit das richtige Symbol des Symbol Managers initialisiert wird für jene, die lieber Bid und Ask für die Vergleiche verwenden wollen, statt den OHCL-Daten (und ihre Varianten)

bool SignalMA::Init(CSymbolManager *symbol_man,CEventAggregator *event_man=NULL)
  {
   if(CSignal::Init(symbol_man,event_man))
     {
      if(CheckPointer(m_symbol_man))
        {
         m_symbol=m_symbol_man.Get();
         if(CheckPointer(m_symbol))
            return true;
        }
     }
   return false;
  }

Die nächste zu erweiternde Methode ist Calculate, siehe folgenden Codeausschnitt:

bool SignalMA::Calculate(void)
  {
   double close[];
   if(CopyClose(m_symbol_name,m_timeframe,signal_bar,1,close)>0)
     {
      m_close=close[0];
      return true;
     }   
   return false;
  }

Es gibt keine Notwendigkeit mehr, die Daten des Indikators zu aktualisieren, da das bereits durch die Methode Refresh aus CSignal erledigt wird. Alternativ könnten wir auch eine abgeleitete Klasse von CSignal implementieren, so dass wir den Schlusskurs der Signalbar über die Klasse CCloseBuffer erhalten. Sie ist eine Nachkomme von CSeries, damit können wir sie m_indicators hinzufügen, damit die Instanz von CCloseBuffer gleich mit den anderen Indikatoren aktualisiert wird. In diesem Fall ist eine Erweiterung der Methoden Refresh oder Calculate aus CSignal nicht mehr nötig. 

Für dieses besondere Signal gibt es keine Notwendigkeit die Methode Update zu erweitern, fahren wir also fort mit dem Erstellen des Signals selbst. Der folgende Codeausschnitt zeigt die Methoden LongCondition und ShortCondition:

bool SignalMA::LongCondition(void)
  {
   return m_close>m_ma.Main(m_signal_bar);
  }

bool SignalMA::ShortCondition(void)
  {
   return m_close<m_ma.Main(m_signal_bar);
  }

Die Bedingungen sind ganz einfach: Wenn der Schlusskurs der Signalbar größer ist als der gleitende Durchschnitt der Signalbar, führt das zu einem Kaufsignal. Umgekehrt, wenn der Schlusskurs kleiner ist, ergibt das ein Verkaufsignal.

Ähnlich wie im vorherigen Beispiel initialisieren wir einfach alle benötigten Pointer und fügen sie der Instanz von CSignal, dem eigenen Container (CSignals instance), hinzu. Das Folgende zeigt den zusätzlichen Code, der für die Initialisierung des Signals in OnInit notwendig ist:

SignalMA *signal_ma=new SignalMA(Symbol(),(ENUM_TIMEFRAMES) Period(),maperiod,0,mamethod,maapplied,signal_bar);
signals=new CSignals();
signals.Add(GetPointer(signal_ma));
signals.Init(GetPointer(symbol_manager),NULL);

Der folgende Code zeigt die Funktion OnTick, sie ist gleich dem vorigen Beispiel:

void OnTick()
  {
//---
   symbol_info.RefreshRates();
   signals.Check();
   if(signals.CheckOpenLong())
     {
      close_last();
      Print("Entering buy trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
     }
   else if(signals.CheckOpenShort())
     {
      close_last();
      Print("Entering sell trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
     }
  }

Die folgenden Screenshots sind die Testergebnisse des Expert Advisors auf MT4 und MT5. Und, wie man sieht, folgt der Expert Advisor in beiden Fällen der gleichen Logik:

(MT4)

signal_ma (MT4)

(MT5)

signal_ma (MT5)

Beispiel #3: HA Expert Advisor

In unserm nächsten Beispiel verwenden wir den Heiken Ashi Indikator in einem EA. Anders als der Indikator der gleitenden Durchschnitte ist der Heiken Ashi Indikator kein Standardindikator, und das macht den Code des Expert Advisors komplizierter als die vorherigen Beispiele, und daher müssen wir eine Klasse für den Heiken Ashi Indikator durch ein Erweitern von CiCustom deklarieren. Um anzufangen, zeigen wir die Klasse CiHA, es ist unsere Klasse für den HA Indikator:

class CiHA: public CiCustom
  {
public:
                     CiHA(void);
                    ~CiHA(void);
   bool              Create(const string symbol,const ENUM_TIMEFRAMES period,
                            const ENUM_INDICATOR type,const int num_params,const MqlParam &params[],const int buffers);
   double            GetData(const int buffer_num,const int index) const;
  };

Es gibt zwei Methoden, die wir erweitern müssen, Create und GetData. In der Methode Create muss der Konstruktor der Klasse neu definiert werden, da er jetzt andere Aufgaben zu erfüllen hat, um die Instanz des Indikators vorzubereiten (eine Initialisierung wie man sie bei Objekten von Standardindikatoren als Erweiterung von CIndicator sieht):

bool CiHA::Create(const string symbol,const ENUM_TIMEFRAMES period,const ENUM_INDICATOR type,const int num_params,const MqlParam &params[],const int buffers)
  {
   NumBuffers(buffers);
   if(CIndicator::Create(symbol,period,type,num_params,params))
      return Initialize(symbol,period,num_params,params);
   return false;
  }

Hier deklarieren wir die Zahl der Puffer, die der Indikator haben soll, und dann werden die übergebenen Parameter initialisiert. Die Parameter des Indikators werden in der Struktur (MqlParam) gesichert.

Die Methode GetData unterscheidet sich für beide Implementationen. In MQL4 wird die Funktion iCustom direkt aufgerufen, die den Wert des Indikators einer bestimmten Bar des Chart zurückgibt. In MQL5 ist der Prozess, den Indikator aufzurufen, anders. iCustom gibt ein Handle des Indikators zurück (in etwa vergleichbar mit dem Umgang mit Dateien). Um im MetaTrader 5 die Werte eines Indikators einer bestimmten Bar abzufragen, muss das Handle verwendet werden, nicht der Aufruf der Funktion iCustom. In diesem Fall teilen wir die Umsetzung:

double CiHA::GetData(const int buffer_num,const int index) const
  {
   #ifdef __MQL5__
      return CiCustom::GetData(buffer_num,index);
   #else
      return iCustom(m_symbol,m_period,m_params[0].string_value,buffer_num,index);
   #endif
  }

Beachten Sie, dass in dieser Methode für MQL5 einfach das Ergebnis eines Aufrufs der übergeordneten Methode (CiCustom) zurückgegeben wird. Andererseits, in MQL4, gibt die übergeordnete Methode (CiCustom) Null zurück, und wir müssen sie um den Aufruf der Funktion iCustom erweitern. Da die MQL4-Funktion keine Struktur (MqlParams) verwendet, um die Parameter des Indikators zu sichern, würde jeder Aufruf dieser Funktion für jeden Nutzerindikator fast immer anders sein.

In der Erweiterung von CSignal für diesen EA, gibt es keinen großen Unterschied zum vorherigen Beispiel. Im Konstruktor redefinieren wir einfach die Argumente der Methode, damit sie die Parameter des Indikators aufnehmen kann, die für die Berechnung benötigt werden. Für speziell dieses Signal brauchen wir nur einen Indikator:

void SignalHA::SignalHA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int numparams,const MqlParam &params[],const int bar)
  {
   m_symbol_name= symbol;
   m_signal_bar = bar;
   m_ha=new CiHA();
   m_ha.Create(symbol,timeframe,IND_CUSTOM,numparams,params,4);
   m_indicators.Add(m_ha);
  }

In der Methode Calculate müssen wir auch die Implementation teilen, da der Heiken Ashi Indikator für MetaTrader 4 und MetaTrader 5 sich in der Behandlung ihre Puffer unterscheiden. Früher wurden die Werte von Low/High, High/Low, Open, und Close im ersten, zweiten, dritten und vierten Puffer platziert. In MQL5 wäre dort Open, High, Low, und Close. Daher müssen wir spezielle Puffer verwenden, um die Werte des Indikators zu erhalten, je nach Plattform:

bool SignalHA::Calculate(void)
  {
   #ifdef __MQL5__
      m_open=m_ha.GetData(0,signal_bar);
   #else
      m_open=m_ha.GetData(2,signal_bar);
   #endif
      m_close=m_ha.GetData(3,signal_bar);
   return true;
  }

In der Version für MQL5 ist der Open-Preis der HA-Kerze aus dem ersten Puffer (Puffer 0), während in der Version für MQL4 ist er im 3. Puffer (Puffer 2). Der Schlusskurs der HA-Kerze findet sich bei beiden Versionen im 4. Puffer (Puffer 3), daher ist diese Codezeile außerhalb der versionsbedingten if-Struktur.

Für die Auswertung des Signals müssen wir die Methoden LongCondition und ShortCondition immer aktualisieren, abhängig von den speziellen Kriterien, die für die Auswertung der gesicherten Werte verwendet werden. Hier verwenden wir den Heiken Ashi in seiner typischen Weise durch die Prüfung, ob die Signalbar 'bullish' oder 'bearish' ist:

bool SignalHA::LongCondition(void)
  {
   return m_open<m_close;
  }

bool SignalHA::ShortCondition(void)
  {
   return m_open>m_close;
  }

Die Funktion OnTick des Expert Advisors ist ähnlich der der vorangegangenen Beispiele, schauen wir uns statt dessen die Funktion OnInit an:

int OnInit()
  {
//---
   order_manager=new COrderManager();
   symbol_manager=new CSymbolManager();
   symbol_info=new CSymbolInfo();
   if(!symbol_info.Name(Symbol()))
      Print("symbol not set");
   symbol_manager.Add(GetPointer(symbol_info));
   order_manager.Init(symbol_manager,NULL);

   MqlParam params[1];
   params[0].type=TYPE_STRING;
   #ifdef __MQL5__
      params[0].string_value="Examples\\Heiken_Ashi";
   #else
      params[0].string_value="Heiken Ashi";
   #endif
      SignalHA *signal_ha=new SignalHA(Symbol(),0,1,params,signal_bar);
   signals=new CSignals();
   signals.Add(GetPointer(signal_ha));
   signals.Init(GetPointer(symbol_manager),NULL);
//---
   return(INIT_SUCCEEDED);
  }

Hier erkennen wir, dass der Ort der ex4-Datei des Heiken Ashi Indikators sich bei beiden Plattform unterscheidet. Da MqlParams verlangt, dass der erste zu sichernde Parameter der Name des Nutzerindikators ist (ohne Postfix), müssen wir zur Angabe des ersten Parameters die Implementierung teilen. In MQL5 befindet sich der Indikator standardmäßig in "Indicators\Examples\Heiken Ashi", in MQL4 in "Indicators\Heiken Ashi".

Die folgenden Screenshots zeigen die Testergebnisse des Expert Advisors in MetaTrader 4 und MetaTrader 5. Wie man sieht, wird der Indikator unterschiedlich auf dem Chart dargestellt, trotzdem folgen beide der gleichen Logik und der Expert Advisor kann beide Versionen für seine Handelslogik verwenden:

(MT4)

signal_ha (MT4)

(MT5)

signal_ha (MT5)

Beispiel #4: Ein Expert Advisor auf Basis von HA und MA

Unser letztes Beispiel ist ein EA, der eine Kombination von MA und HA verwendet. Dies Beispiel weist keine wesentlichen Unterschiede auf. Wir ergänzen einfach die Definitionen der Klassen aus den Beispielen 2 und 3, dann noch die Pointer zu den Instanzen von CSignalMA und CSignalHA der Instanz von CSignals. Das Folgende zeigt das Testergebnis dieses Expert Advisors.

(MT4)

signal_ha_ma (MT4)

(MT5)

signal_ha_ma (MT5)

Schlussfolgerung

In diesem Artikel beschrieben wir die Klassen CSignal und CSignals. Das sind die Klassenobjekte, mit denen das Endsignal zu einem aktuellen Tick eines Cross-Plattform Expert Advisors ausgewertet wird. Das genannten Klassen wurden entwickelt, um die eine Auswertung der Signale, getrennt vom restlichen Code des Expert Advisor, zu ermöglichen.

Übersetzt aus dem Englischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/en/articles/3261

Beigefügte Dateien |
signals.zip (3907.49 KB)
Die Erstellung der benutzerdefinierten Indikatoren unter Verwendung der Klasse CCanvas Die Erstellung der benutzerdefinierten Indikatoren unter Verwendung der Klasse CCanvas

Im Artikel wurde das Beispiel der Erstellung der gezeichneten benutzerdefinierten Indikatoren mit Hilfe der graphischen Ringpuffer der Klasse CCanvas.

Cross-Plattform Expert Advisor: Order-Manager Cross-Plattform Expert Advisor: Order-Manager

Dieser Artikel behandelt das Erstellen eines Order-Managers für einen Cross-Plattform Expert Advisor. Der Order-Manager ist verantwortlich, für beide Versionen die Positionen eines Experten zu öffnen oder zu schließen, und die jeweiligen Datensätze für eine weitere Verwendung aktuell zu halten.

Das Beispiel eines Indikators, der die Linien Unterstützung / Widerstands zeichnet Das Beispiel eines Indikators, der die Linien Unterstützung / Widerstands zeichnet

Im Artikel wird das Realisierungsbeispiel des Indikators für den Aufbau der Linien Unterstützung/Widerstands aufgrund der formalisierten Bedingungen aufgeführt. Sie haben die Möglichkeit, den Indikator zu verwenden, aber verstehen auch nebenbei, wie einfach es ist, das zu realisieren. Nun können Sie selbst die Bedingungen für den Aufbau der Linien formulieren, die Sie nötig finden, dabei den Code des Indikators nach Ihren Wünschen ein wenig ändern.

Analyse der Grafiken Kontostand/Equity nach Symbolen und nach ORDER_MAGIC von Expert Advisors Analyse der Grafiken Kontostand/Equity nach Symbolen und nach ORDER_MAGIC von Expert Advisors

Die Einführung der Hedging-Option in MetaTrader 5 ermöglichte es, gleichzeitig mehrere Expert Advisors auf einem Handelskonto handeln zu lassen. Dabei ist die Situation möglich, dass eine Strategie profitabel ist, während die andere Verluste bringt. Als Ergebnis schwankt die Grafik um Null. Für diesen Fall ist es praktisch, Kontostand- und Equity-Grafiken für jede Handelsstrategie separat zu zeichnen.