Cross-Plattform Expert Advisor: Einführung

Enrico Lambino | 5 September, 2016


Inhaltsverzeichnis


Einführung

Die Gründe für diesen Cross-Plattform Expert Advisor sind wie folgt:

  • Sie wollen Ihren Expert Advisor mit anderen teilen, egal, welche Plattform benützt wird.
  • Sie wollen den Unterschied zwischen MQL4 und MQL5 verstehen.
  • Sie wollen Entwicklungszeit reduzieren.
  • Wenn MetaTrader 4 wird plötzlich veraltet, haben Sie weniger Probleme Ihren Handelsroboter auf MetaTrader 5 zu migrieren.
  • Sie verwenden bereits den MetaTrader 5, aber, aus irgendwelchen Gründen, wollen Sie Ihren Expert Advisor auf MetaTrader 4 testen.
  • Sie verwenden immer noch MetaTrader 4, aber Sie wollen den MQL5 "Cloud Service" testen um Ihren Handelsroboter zu optimieren.

Bei der Entwicklung von Expert Advisors oder auch Indikatoren oder Skripts unternimmt der Entwickler typischerweise folgende Schritte:

  1. Entwickeln der Software in einer Sprache (MQL4 oder MQL5)
  2. Gründliche Prüfung der entwickelten Software
  3. Re-implementierung der selben Software für die andere Sprache

Das hat mehrere Nachteile:

  1. Alle Teile der Software müsste neu implementiert werden, darunter auch jene, die beide Versionen teilen
  2. Debugging und Wartung kann schwierig sein
  3. Das senkt die Produktivität

Eine eigene, parallele Umsetzung würde fast die doppelte Menge Code benötigt: eine für MQL4, und ein zweite für MQL5. Debuggen und Wartung kann noch schwieriger sein. Wenn die Version aktualisiert werden muss, müssten die gleichen Änderung auch für die andere Version durchgeführt werden. Und aufgrund der Unterschiede zwischen MQL4 und MQL5, würden beide Versionen der gleichen Software immer weiter auseinander laufen. Dies birgt jedoch die Gefahr weiterer Probleme, da die Abweichungen der beiden Implementierungen oft nicht deutlich gemacht wurden.


Hello World EA Beispiel

Beginnen wir mit einem einfachen Expert Advisor in MQL5: Ein Hello World Expert Advisor. In der o.a. MQL Version schreiben wir wie folgt:

(HelloWorld.mq5)

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }

CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting();
   ExpertRemove();
  }

Für MQL4 schreiben wir es in gleicher Weise:

HelloWorld.mq4

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }

CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting();
   ExpertRemove();
  }


Quell- und Header-Dateien

Beachten Sie, beiden Quelldateien von oben sind identisch. Eine einzige Quelldatei kann aber nicht cross-plattform-kompatibel sein. Das ist bedingt durch die Art, wie Quelldateien kompiliert werden:

  • Das Kompilieren einer MQ4 Quelldatei führt zu einer EX4-Datei
  • Das Kompilieren einer MQ5 Quelldatei führt zu einer EX5-Datei

Daher kann eine einzige Quelldatei nicht auf beiden Plattformen funktionieren. Es ist jedoch möglich, dass beide Quelldateien auf die dieselbe Headerdatei zugreifen, wie das im Folgenden gezeigt wird:

Quell- und Header-Dateien


Idealerweise würden wir jetzt gerne alles in die Headerdatei kopieren und dann mit einer einzigen Zeile im Qellcode die Verknüpfung herstellen. Wir könnte dann den Hello World Expert Advisor wie folgt neu schreiben:

HelloWorld_SingleHeader.mqh

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   Print(StringConcatenate(str,str1,str2));
  }
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Die Quellcodes für MQL4 und MQL5 enthalten beide eine einzige Zeile, die die obige Headerdatei mit einer #include Direktive lädt:

HelloWorld_SingleHeader.mq4 und HelloWorld_SingleHeader.mq5

#include <HelloWorld_SingleHeader.mqh>

Dieser Ansatz hat einige Vorteile. Zuerst können wir wahrscheinlich den Umfang des Quellcodes für beide Plattformen um bis zu 50% verringern (zumindest in diesem Beispiel). Der zweite Vorteil ist, dass dieses Vorgehen uns erlaubt, nur mit einer Umsetzung arbeiten zu müssen, statt mit zwei verschiedenen. Da es nur eine Version gibt, wird jede Änderung der MQL4-Version auch die von MQL5 betreffen und umgekehrt.

Bei normalem Vorgehen müsste man erst die eine Version ändern und dann die der andere Plattform. Expert Advisor sind aber selten so simpel wie dieses Beispiel. Sie sind viel komplexer. Und wenn ein Expert Advisor immer komplexer wird, würde es auch immer schwieriger werden zwei, verschieden Version zu warten.


Bedingte Kompilierung

MQL4 und MQL5 haben zwar einiges gemeinsam, sie unterscheiden sich aber auch in vielerlei Hinsicht von einander. Teil der Unterschiedlichkeiten ist zum Beispiel die Funktion StringConcatenate. In MQL4 ist sie wie folgt definiert:

string  StringConcatenate( 
   void argument1,        // erster Parameter ohne festem Typ
   void argument2,        // zweiter Parameter ohne festem Typ
   ...                    // weitere Parameter ohne festem Typ 
   );

In MQL5 ist diese Funktion etwas anders:

int  StringConcatenate( 
   string&  string_var,   // Zeichenkette, die sich bildet
   void argument1         // erster Parameter ohne festem Typ
   void argument2         // zweiter Parameter ohne festem Typ 
   ...                    // weitere Parameter ohne festem Typ 
   );

Wir können diese Funktion in unserer Hello-World-Anwendung durch Überladung der Methode Greeting() in unserer Klasse verwenden. Die neue Methode akzeptiert zwei string Argumente und gibt deren verkettetes Ergebnis auf dem Terminal aus. Wir aktualisieren unsere Headerdatei wie folgt:

(HelloWorld_SingleHeader.mqh)

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CObject
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   Print(StringConcatenate(str,str1,str2));
  }
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Mit dieser neuen Version würde in MetaTrader 4 folgendes ausgegeben werden:

Hello World!

In MetaTrader 5 würden wir stattdessen etwas anders als das Beabsichtigte sehen:

12

In MQL4 wird die verkettete Zeichenkette zurückgegeben. In MQL5 aber eine Zahl, die die Länge der Zeichenkette angibt. Damit beide Anwendungen das gleiche Verhalten zeigen auf beiden Plattformen zeigen, ohne den Code zu ändern, müssen wir nur die bedingten Kompilierung verwenden, wie nachfolgend gezeigt:

CHelloWorld::Greeting(const string str1,const string str2)
  {
   #ifdef __MQL5__
      string str=NULL;
      StringConcatenate(str,str1,str2);
      Print(str);
   #else
      Print(StringConcatenate(str1,str2));
   #endif
  }


Das ist die Direktive des Preprozessors. Das könnte zu einem zusätzlichen Aufwand für den Compiler führen, aber nicht während der Ausführung. In MQL4 wird aus dem Obigen folgender Code kompiliert:

CHelloWorld::Greeting(const string str1,const string str2)
  {
      Print(StringConcatenate(str1,str2));
}


Und der MQL5 Kompiler sieht nur dies:

CHelloWorld::Greeting(const string str1,const string str2)
  {
      string str=NULL;
      StringConcatenate(str,str1,str2);
      Print(str);
}


Getrennte Umsetzung

An diesem Punkt verstehen wir bereits, mit welcher Arten von Code ein kompatibler Cross-Plattform Expert Advisor zu erstellen ist:

  1. Kompatibel
    • Gemeinsame Funktionen
    • Berechnung
  2. Inkompatibel
    • Funktionen mit anderem Verhalten
    • Funktionen, die es nicht für beide gibt
    • Andere Form der Ausführung

Zwischen MQL4 und MQL5 gibt es eine Reihe von Funktionen mit identischem Verhalten. Die Funktion Print() ist ein Beispiel. Sie verhält sich auf beiden Plattformen gleich. Kompatiblen Quellcode existiert auch bei reinem Berechnungen. Das Ergebnis von 1+1 ist in MQL4 und MQL5 gleich, ebenso wie in allen anderen, existierenden Programmiersprachen. In beiden Fällen ist eine getrennte Umsetzung nicht notwendig.

In Fällen, in denen von der jeweils anderen Plattform entweder ein bestimmter Teil des Quellcodes nicht kompiliert oder anders ausgeführt wird, ist eine getrennte Umsetzung notwendig. Die Funktion StringConcatenate ist ein Beispiel der ersten Form eines inkompatiblen Codes. Trotz gleichen Namens verhalten sie sich unterschiedlich in MQL4 und MQL5. Es gibt auch Funktionen ohne direkte Entsprechung in der anderen Sprache. Ein Beispiel dafür ist die Funktion OrderCalcMargin, die, zumindest bis zum Zeitpunkt des Schreibens dieses Artikels, keine Entsprechung in der Programmiersprache MQL4 hat. Der dritte Fall ist wahrscheinlich der schwierigste für die Cross-Plattform-Entwicklung, da hier die Umsetzung von einem Entwickler zum anderen variieren könnte. Es gilt, einen gemeinsamen Nenner zwischen den beiden Plattformen zu finden, um den Codeumfang so klein wie möglich zu halten, und dann die Trennung umzusetzen.

Nun, sich allein auf die bedingte Kompilierung zu verlassen, könnte keine gute Idee sein. Sobald der Code durch zu viele derartige Anweisungen zu lang wird, wird die Fehlersuche sehr schwierig. In der objektorientierten Programmierung können wir eine getrennte Umsetzung dreiteilen: (1) die allgemeine, (2) die MQL4-spezifisch und (3) die MQL5-spezifische Umsetzung.

Die Basisklasse beinhaltet den Code für beide Versionen. In Fällen, in denen Inkompatibilitäten auftreten, kann man von diesem Ansatz abweichen oder die gemeinsame Basisklasse leer lassen und alles in die separaten Implementationen schreiben.

Für den Hello World Expert Advisor deklarieren wir die Basisklasse unter dem Namen CHelloWorldBase, und sie beinhaltet den gemeinsamen Code für MQL4 and MQL5. Dies umfasst die anfängliche Methode Greeting() vom Anfang dieses Artikels.

HelloWorld_SingleHeader.mqh

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+


Jetzt kommen wir zu den spezifischen Plattform- oder Sprachklassen, die die Basisklasse laden und übernehmen und in Folge die unterschiedlichen Umsetzungen realisieren, um dasselbe Ziel zu erreichen:

HelloWorld_SingleHeader_MQL4.mqh

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   Print(StringConcatenate(str1,str2));
  }
//+------------------------------------------------------------------+

HelloWorld_SingleHeader_MQL5.mqh

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   StringConcatenate(str,str1,str2);
   Print(str);
  }
//+------------------------------------------------------------------+


Jetzt können wir die Grußfunktion dort aufrufen, wo man sie normalerweise erwartet, in der Hauptquelldatei:

HelloWorld_SingleHeader.mq5

#include <HelloWorld_SingleHeader_MQL5.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


HelloWorld_SingleHeader.mq4

#include <HelloWorld_SingleHeader_MQL4.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+

In diesem besonderen Beispiel ist es praktischer, eine einzige Headerdatei mit der Basisklasse zu verwenden, und die Trennung durch eine bedingte Kompilierung die beiden abgeleiteten Klassen zu erreichen. In vielen Fällen jedoch ist es notwendig, die Klassen in eigenen Dateien zu speichern, insbesondere, wenn der Quellcode zu lang wird.


Include-Dateien

Es ist natürlich für Entwickler die Verwendung der Headerdateien mit den aktuellen Klassendefinitionen für das Programm zu vereinfachen. In der MQL5 Version des HelloWorld Expert Advisor können wir erkennen, dass beide Versionen (HelloWorld_SingleHeader.mq4 und HelloWorld_SingleHeader.mq5) praktisch gleich sind, bis auf die spezifische Headerdatei, die sie laden.

#include <HelloWorld_SingleHeader_MQL4.mqh>
#include <HelloWorld_SingleHeader_MQL5.mqh>
Ein weiterer Ansatz ist der Verweis auf die Headerdatei mit der Basis. Und dann, am Ende der Headerdatei, können wir mit der bedingten Kompilierung die spezifische Headerdatei des verwendeten Kompilers laden:
#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "HelloWorld_SingleHeader_MQL5.mqh"
#else
   #include "HelloWorld_SingleHeader_MQL4.mqh"
#endif
//+------------------------------------------------------------------+


Damit müssen wir nur diese Headerdatei laden an Stelle jeweils einer sprachspezifischen:

#include <HelloWorld_SingleHeader.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Danach löschen wir die kompilerspezifische #include Direktive (der durchgestrichene Text zeigt die gelöschte Zeile):

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   Print(StringConcatenate(str1,str2));
  }
//+------------------------------------------------------------------+

#include "HelloWorld_SingleHeader.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorld : public CHelloWorldBase
  {
public:
                     CHelloWorld(void);
                    ~CHelloWorld(void);
   virtual void      Greeting(const string str1,const string str2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::~CHelloWorld(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorld::Greeting(const string str1,const string str2)
  {
   string str=NULL;
   StringConcatenate(str,str1,str2);
   Print(str);
  }
//+------------------------------------------------------------------+


Dieser Ansatz wird empfohlen, er hat mehrere Vorteile. Zuerst gibt es nur eine include Direktive für beide Hauptquelldateien von MQL4 und MQL5. Zum zweiten erspart es den geistigen Aufwand, bei den include Direktiven an die spezifischen Headerdateien mit ihren spezifischen Pfaden zu denken (z.B. include MQL4/ oder MQL5/). Der dritte Vorteil ist, dass die Basisteile nur in der Basis Headerdatei sind. Wenn jemand die include Direktiven in einer sprachspezifischen Headerdatei verwendet, würden sie ausschließlich in dieser Version (MQL4 oder MQL5) verwendet.


Aufteilung von Verzeichnissen und Dateien

Bei der Entwicklung von Expert Advisor in OOP, ist es höchst unwahrscheinlich, dass der Entwickler mit nur einer einzigen Klasse auskommt. Ein Beweis dafür sind die Klassen der Handelsstrategien der Standardbibliothek von MQL5. Sobald die Zahl der Codezeilen wächst, erscheint es vielleicht praktischer den Code auf mehrere Headerdateien aufzuteilen. Dieser Artikel empfiehlt folgende Verzeichnisstruktur:

|-Include

|-Base

|-MQL4

|-MQL5

Die drei Verzeichnisse können direkt im Include-Verzeichnis des Datenordners oder einem seiner Unterverzeichnisse platziert werden.

Für unser Beispielcode verwenden wir folgende Verzeichnisstruktur:

|-Include

|-MQLx-Intro

|-Base

HelloWorldBase.mqh

|-MQL4

HelloWorld.mqh

|-MQL5

HelloWorld.mqh

Mit dieser Verzeichnisstruktur ist unser Code besser organisiert. Es beseitigt auch das Problem gleicher Namen, das wir früher hatten.

Aufgrund der geänderten Speicherorte der Headerdateien, müssen wir in der Hauptheaderdatei die neuen Pfade eintragen:

#include <Object.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CHelloWorldBase : public CObject
  {
public:
                     CHelloWorldBase(void);
                    ~CHelloWorldBase(void);
   virtual void      Greeting(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::~CHelloWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CHelloWorldBase::Greeting(void)
  {
   Print("Hello World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "..\MQL5\HelloWorld.mqh"
#else
   #include "..\MQL4\HelloWorld.mqh"
#endif
//+------------------------------------------------------------------+

Wir müssen auch in die Datei des Hauptquellcodes den Ort der Basisklasse eintragen. Für beide Versionen sind die Quelldateien bereits jetzt schon identisch:

HelloWorld_Sample.mq4 und HelloWorld_Sample.mq5

#include <MQLx-Intro\Base\HelloWorldBase.mqh>
CHelloWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting("Hello ","World!");
   ExpertRemove();
  }
//+------------------------------------------------------------------+


Vererbung

Nehmen wir an, wir wollen die Klasse CHelloWorld von oben erweitern durch eine Klasse mit Namen CGoodByeWorld. Diese Klasse verwendet die Methode Greeting() aus CHelloWorld für die Nachricht "Goodbye World!". Der empfohlene Weg, das umzusetzen, ist auf die Basisklasse der Eltern zu verweisen und die ist CHelloWorldBase. Dann, ähnlich wie in CHelloWorldBase, laden wir mittel bedingter Kompilierung am Ende der Datei den richtigen Nachkommen. Die Struktur der Vererbung schaut jetzt so aus:

Inheritance Hierarchy

Die Art jedoch, wie die Headerdateien geladen werden, ist jetzt etwas anders:

Include Struktur

Das Diagramm der Klassen ist unten gezeigt. Die erste Greeting-Funktion ist in der Klasse CHelloWorldBase, und diese Methode wird von allen anderen abgeleiteten Klassen verwendet (ererbt). Das gleiche gilt für die Klasse CGoodByeWorld mit der neue Methode GoodBye. Es ist auch möglich, die Methode der Klasse CHelloWorldBase zu erweitern und den Gruß "Auf Wiedersehen" durch "Hallo" zu ersetzen.

goodbye-world-uml



Wir laden nur die Headerdateien der Basisklasse. Wenn nur eine einzige Klassenhierarchie eingebunden ist, laden wir nur die Headerdatei der Basisklasse mit der größtmöglichen Abstraktion (GoodByeWorldBase.mqh), da diese automatisch die anderen, benötigten Headerdateien lädt. Beachten Sie, dass wir kein #include verwenden, um die spezifischen Headerdateien der jew. Plattform zu laden, da das in der Verantwortung der Headerdatei der Basis liegt.

Unsere Verzeichnisstruktur würde auch aktualisiert werden und dann schon die neuen Headerdateien beinhaltet:

|-Include

|-MQLx-Intro

|-Base

HelloWorldBase.mqh

GoodByeWorldBase.mqh

|-MQL4

HelloWorld.mqh

GoodByeWorld.mqh

|-MQL5

HelloWorld.mqh

GoodByeWorld.mqh


Es folgt die Umsetzung der Klasse CGoodByeWorldBase:

#include "HelloWorldBase.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CGoodByeWorldBase : public CHelloWorld
  {
public:
                     CGoodByeWorldBase(void);
                    ~CGoodByeWorldBase(void);
   virtual void      GoodBye(void);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::CGoodByeWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::~CGoodByeWorldBase(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CGoodByeWorldBase::GoodBye(void)
  {
   Greeting("Goodbye ","World!");
  }
//+------------------------------------------------------------------+
#ifdef __MQL5__
   #include "..\MQL5\GoodByeWorld.mqh"
#else
   #include "..\MQL4\GoodByeWorld.mqh"
#endif
//+------------------------------------------------------------------+

Beachten Sie, dass, obwohl die Datei "HelloWorldBase.mqh" geladen wird, die KlasseCGoodByeWorldBase von CHelloWorld erbt, nicht von CHelloWorldBase. Die Version von CHelloWorld hängt letztendlich vom verwendeten MQL-Kompiler ab. Die erweiterte CHelloWorldBase funktioniert auch in einem anderen Fall. Da jedoch in diesem Beispiel die Methode Goodbye() die Methode Greeting() verwendet, muss CGoodByeWorldBase direkt von einer plattformspezifischen Umsetzung erben.

Und da die Methode GoodBye() zwischen den beiden Versionen geteilt werden kann, wäre es ideal, sie in der Basisumsetzung zu halten. Und da es auch keine weitere Methode dieses Klassenobjektes gibt, können auch Nachkommen über keine weiteren Methoden verfügen. Wir können die Nachkommen wie folgt umsetzen:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CGoodByeWorld : public CGoodByeWorldBase
  {
  };
//+------------------------------------------------------------------+

Die Hauptquelldatei müsste auch aktualiesiert werden, diesmal mit einem Objekt auf Basis von CGoodByeWorld und dem Aufruf der Methode GoodBye() in der Funktion OnTick().

HelloWorld_Sample.mq4 und HelloWorld_Sample.mq5

#include <MQLx-Intro\Base\GoodByeWorldBase.mqh>
CGoodByeWorld hello;
//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Experten                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Experten                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Experten Funktion OnTick                                         |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   hello.Greeting("Hello ","World!");
   hello.GoodBye();
   ExpertRemove();
  }
//+------------------------------------------------------------------+

Läuft der Expert Advisor, wird er das auf dem Terminal ausgeben:

Hello World!
Goodbye World!
ExpertRemove() function called


Einschränkungen

In den meisten Fällen kann ein Programmierer mit diesem Ansatz Cross-Plattform Expert Advisor einfacher und schneller entwickeln. Aber der Leser sollte sich bestimmter Einschränkungen bewusst sein, die eine Anwendung, wie in diesem Artikel gezeigt, schwierig oder gar unmöglich machen könnten:

1. Einschränkungen des MetaTrader 4

2. Größere Unterschiede in Ausführung oder Konventionen zwischen beiden Plattformen

MetaTrader 4, die älteren Handelsplattform, fehlen einige Eigenschaften, die es in MetaTrader 5 gibt. In Fällen, in denen aber ein Expert Advisor solche Eigenschaften, die einer Plattform fehlen, erfordert, muss man maßgeschneiderte Lösung entwickeln, ausschließlich für jeweils eine Plattform. Dies ist vor allem ein Problem bei reinen MetaTrader 5 Expert Advisor, von denen eine Version für MetaTrader 4 entstehen soll. Nutzer des MetaTrader 4 müssen sich da weniger sorgen, da die meisten Eigenschaften des MetaTrader 4, wenn nicht alle, ein Gegenstück oder zumindest eine einfache Behelfslösung in MetaTrader 5 haben.

Die beiden Plattformen unterscheiden sich stark in einigen Operationen. Dies gilt insbesondere für den Handel. In diesem Fall muss der Entwickler entscheiden, welcher Konvention er folgen will. Er kann zum Beispiel die Art und Weise von MetaTrader 4 verwenden und sie an den MetaTrader 5 anpassen, um das gleiche Verhalten am Ende zu erreichen. Oder, anders herum, er gleicht den übliche Ansatz des Handelns in MetaTrader 5 an den eines MetaTrader 4 Expert Advisors an.


Schlussfolgerung

In diesem Artikel haben wir eine Methode gezeigt, durch die Cross-Plattform Expert Advisor möglicherweise entwickelt werden können. Die vorgeschlagene Methode ist die Verwendung einer Basisklasse mit den Umsetzungen für beide Plattformen. Dort, wo sich beide Sprachen sich unterscheiden, kann die getrennte Umsetzung durch Klassen der Nachkommen realisiert werden, die die Basisklasse erben. Das gleiche Verfahren wiederholt sich für Klassen, die im Weiteren in den Klassen definiert werden müssen. Dieses Vorgehen kann sich als hilfreich bei einer schnelleren Entwicklung von Cross-Plattform-Anwendungen erweisen und macht die Wartung des Code durch die Vermeidung sonst notwendiger Umsetzung separater, paralleler Implementierungen einfacher.