Einen Expert Advisor mit Hilfe des MQL5 Objekt-orientierten Programmieransatzes schreiben

Samuel Olowoyo | 9 März, 2016

Einleitung

Im ersten Beitrag haben wir uns mit den Grundlagen zur Erzeugung, Fehlersuche und Tests eines Expert Advisors in MQL5 beschäftigt.

Jeder einzelne Schritt war sehr einfach und interessant, doch die neue MQL5-Programmiersprache hat noch eine Menge auf Lager. In diesem Beitrag soll es um den Objekt-orientierten Ansatz gehen, mit dessen Hilfe wir genau das tun, was im ersten Artikel bereits beschrieben wurde. Die meisten Menschen glauben, das sei schwer, doch ich darf Ihnen versichern: wenn Sie diesen Beitrag gelesen haben, dann können Sie Ihren eigenen Objekt-orientierten Expert Advisor schreiben.

Wir werden uns nicht mehr mit Punkten aufhalten, die wir bereits im ersten Beitrag besprochen haben, daher schlage ich vor, dass Sie sich zunächst den gesamten ersten Beitrag durchlesen, falls Sie das nicht sowieso schon getan haben.


1. Das Objekt-orientierte Paradigma

Eine der Tatsachen, die die neue MQL5 weitaus leistungsfähiger und stabiler machen als MQL4 ist ihr OOP Ansatz (Objekt-orientiertes Programmieren).

In OOP wird empfohlen, dass ein Objekt keines seiner Implementierungsdetails zeigt, denn dadurch kann seine Implementierung verändert werden, ohne dabei den Code, der das Objekt verwendet, ändern zu müssen. Das bedeutet, dass eine Klasse einem Programmierer erlaubt, zu verbergen (und auch Veränderungen daran zu vermeiden) wie die Klasse, die er geschrieben hat, implementiert wird.

Zum besseren Verständnis beschäftigen wir uns zunächst mit den gerade angesprochenen Begriffen "Klasse" und "Objekt".

1.1 EINE KLASSE DEKLARIEREN

Eine Klasse enthält im Grunde die Beschreibung der Mitglieder (Eigenschaften und Funktionen/Methoden) eines Objekts, das Sie aus der Klasse erzeugen wollen. Betrachten wir uns ein Beispiel…<

Wenn wir ein Objekt erzeugen wollen, das Türen, Sitze, Reifen, Gewicht, usw. haben soll und das auch starten, schalten, anhalten und hupen kann, müssen wir dafür eine Klasse schreiben. Die Türen, Sitze, Reifen, Gewicht, starten, schalten, anhalten und hupen stellen die Mitglieder der Klasse dar.

Natürlich haben Sie bemerkt, dass diese Mitglieder kategorisiert sind - einige stellen nur das dar, was unser Objekt besitzen wird (Eigenschaften), andere bezeichnen das, wie unser Objekt agieren wird (Handlungen – Funktionen/Methoden). Zur Deklarierrung unserer Klasse müssen wir uns einen sehr guten und beschreibenden Namen ausdenken. In unserem Beispiel nennen wir unsere Klasse daher AUTO. Unsere AUTO-Klasse besitzt all die oben angegebenen Eigenschaften und Funktionen als ihre Mitglieder.

Um eine Klasse zu deklarieren, tippen wir zunächst das Schlüsselwort Klasse , gefolgt vom Namen der Klasse, gefolgt von einem Klammernpaar, das die Mitglieder der Klasse enthält, ein.

So sieht das grundlegende Format einer Klasse aus:
class class_name 
{
  access_keyword_1:
    members1;

  access_keyword_2:
    members2;
  ...
};

Class_name ist hier ein gültiger Identifikator für die Klasse, die wir schreiben möchten;members1 und members2 sind die Datenmitglieder der Klasse.

Das access_keyword legt das Zugriffsrecht auf die Mitglieder unserer Klasse fest. Ein access_keyword kann private, geschützt oder public sein. Nicht vergessen: Wir wollen hier eine Klasse schreiben, die von uns selbst und anderen verwendet werden kann, ohne die Implementierungsdetails zu zeigen. Und deshalb sind Zugriffsrechte wichtig.

Denn auf einige Mitglieder unserer Klasse wollen wir vielleicht von außerhalb unserer Klasse gar nicht zugreifen. Sie werden innerhalb des privaten Zugriffsbereichs mit Hilfe des private oder geschütztSchlüsselworts deklariert. Andere Mitglieder, auf die wir von außerhalb unserer Klasse zugreifen wollen, werden dann im öffentlichen Zugriffsbereich mit Hilfe des public Schlüsselworts deklariert. Unser neues AUTO sollte also inzwischen so aussehen:

class CAR 
{
  private:
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

Unsere Auto-Klasse wird mit Hilfe des Schlüsselworts Klasse deklariert. Diese Klasse enthält acht Mitglieder, davon vier Mitglieder mit private Zugriff und vier Mitglieder mit public Zugriff. Die vier Mitglieder im private Bereich sind Datenmitglieder. Drei davon sind vom Typ ganzzahlige (int) Daten, ein Mitglied ist vom Typ double Daten. Auf diese Mitglieder kann mit keiner anderen Funktion, die außerhalb dieser Klasse deklariert wird, zugegriffen werden.

Die vier Mitglieder im public Bereich sind Funktionsmitglieder. Zwei liefern Daten vom Typ bool und zwei vom Typ void. Auf diese Mitglieder kann jedes Objekt dieser Klasse zugreifen, sobald es von jemandem, der unsere Klasse verwendet, erzeugt wird. Sobald ein Objekt unserer Klasse erzeugt ist, stehen diese Mitglieder bereits für uns zur Verfügung.

Und wie Sie sicher bemerkt haben, sind die Zugriffsschlüsselworte (private, public, geschützt) immer von einem Komma gefolgt. Die Deklarierung der Klasse endet mit einem Strichpunkt. Die Mitglieder werden mit Hilfe ihres korrekten Datentyps deklariert.

Hier möchte ich darauf hinweisen, dass, wenn Sie eine Klasse deklarieren, allen Mitgliedern dieser Klasse private Zugriffsrechte gegeben werden, es sei denn, etwas anderes ist explizit angegeben - so wie wir das vorhin getan haben. So haben z.B. in der unten stehenden Klassendeklarierung

class CAR 
{
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

alle vier, oberhalb des public Zugriffsschlüsselworts, deklarierten Mitglieder automatisch private Zugriff.

Damit ein Objekt unserer Klasse für die Klasse benutzt werden kann, muss es zuerst erzeugt werden. Erzeugen wir also ein Objekt, das ein Typ unserer Klasse ist. Dazu verwenden wir unseren Klassennamen, gefolgt vom Namen, den wir dem Objekt geben wollen.

AUTO Honda;

Oder wir erzeugen ein anderes Objekt

AUTO Toyota;

Honda oder Toyota sind nun der Typ des AUTOS und haben nun Zugriff auf alle Mitgliederfunktionen unserer AUTO-Klasse, vorausgesetzt, die Mitgliederfunktionen sind innerhalb des public Zugriffsbereichs deklariert. Wir kommen hierauf später noch einmal zurück.

Sie sehen, wir können zudem auch so viele Objekte dieser Klasse erzeugen, wie wir wollen. Das ist einer der Vorteile des Objekt-orientierten Programmierens.

Doch sehen wir uns jetzt detailliert das Format der Klasse in MQL5 an.

class class_name 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

Hier handelt es sich um die Deklarierung einer Klasse, wobei class_name den Namen der Klasse bezeichnet. Diese Klasse hat neun Mitglieder, von denen zwei allerdings spezielle Mitglieder sind.

Constructor:
Der Constructor (dargestellt als class_name()) ist eine spezielle Funktion, die automatisch aufgerufen wird, sobald ein neues Objekts der Typs der Klasse erzeugt wird. Wenn Sie also in diesem Fall ein Objekt des Typs dieser Klasse

class_nameobject erzeugen,

wird der Constructor, class_name(), automatisch aufgerufen. Der Name des Constructors muss mit dem Namen der Klasse übereinstimmen, daher haben wir ihn auch class_name() genannt. In MQL5 nimmt ein Constructor keine Eingabeparameter und ist nicht vom Typ 'liefern'. Memory-Zuweisungen und Initialisierung der Klassenmitglieder erfolgen normalerweise beim Aufruf des Constructors. Constructors können nicht explizit aufgerufen werden, so als wären sie reguläre Mitgliederfunktionen. Sie werden nur ausgeführt, wenn ein neues Objekt dieser Klasse erzeugt wird. Eine Klasse in MQL5 kann nur einen Constructor haben.

Destructor:
Das zweite, spezielle Mitglied, wird dargestellt als ~class_name(). Dies ist der Destructor der Klasse, der mit einer Tilde (~) vor dem Klassennamen geschrieben wird. Sobald ein Klassenobjekt vernichtet wird, wird er automatisch aufgerufen. Alle Mitglieder der Klasse, die de-initialisiert werden müssen, werden in dieser Phase de-initialisiert. Dabei spielt es keine Rolle, ob Sie den Destructor explizit deklariert haben oder nicht.

Datenmitglieder:
Mitglieder eine Klasse können jeden zulässigen dataTyp, classTyp oder struct Typ haben. Mit anderen Worten: Bei der Deklarierung der Mitgliedervariablen einer Klasse, können Sie jeden zulässigen Datentyp, (int, double, String, usw.), ein Objekt einer anderen Klasse oder einen Typ einer Struktur (z.B. die MQL5 MqlTradeRequest, usw.) verwenden.

Funktionsmitglieder:
Dies sind Mitglieder der Klasse, die zur Veränderung der DatenMitglieder und zur Ausführung der wichtigsten Funktionen/Methoden der Klasse benutzt werden. Der 'liefern' Typ für Funktionsmitglieder kann jeder zulässige 'liefern' Typ sein (bool, void, double, string, etc).

Private:
Auf in diesem Bereich deklarierte Mitglieder kann nur via Funktionsmitglieder der Klasse zugegriffen werden. Auf sie kann durch keine andere Funktion außerhalb der Klasse zugegriffen werden.

Geschützt:
Auf in diesem Bereich deklarierte Mitglieder kann nur via Funktionsmitglieder der Klasse zugegriffen werden sowie via den Mitgliederfunktionen anderer Klassen, die von dieser Klasse abgeleitet sind. Das bedeutet, dass wir aus dieser Klasse auch eine neue Klasse erzeugen können. In diesem Fall kann die neue, aus dieser Klasse abgeleitete, Klasse (die jetzt zur Basisklasse wird) auf die geschützten Mitglieder der Basisklasse zugreifen. Dies ist das Konzept der Vererbung in OOP. Dazu kommen wir gleich. Nur Geduld…

Public:
Innerhalb dieses Bereichs deklarierte Mitglieder können nur außerhalb der Klasse von jedem Objekt der Klasse verwendet werden. Hier müssen einige der Funktionen deklariert werden, die man für die Verwendung der Klasse in anderen Programmen braucht.

Da wir uns jetzt das grundlegende Format einer Klasse betrachtet haben, hoffe ich sehr, dass Ich Sie noch nicht langweile.... denn wir müssen uns noch einen anderen interessanten Aspekt der Klassen ansehen, bevor wir dann schließlich wirklich eine Klassenpackung für unseren Expert Advisor erzeugen.

1.2 VERERBUNG

Nehmen wir mal an, wir möchten aus der ursprünglichen base_class Klasse noch eine andere Klasse machen. Das Format zur Ableitung einer neuen Klasse aus einer ursprünglichen Klasse sieht so aus:

Die Basisklasse:

class base_class 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

Die abgeleitete Klasse:

class new_class : access_keyword base_class 
{
  private:
    members8;

  public:
    new_class()  //Constructor;
    ~new_class() //Destructor;
    Members9();
};

Lassen Sie mich hier zunächst einiges erklären, bevor wir ins Detail gehen. Die new_class Klasse wird von der base_class Klasse wie oben gezeigt abgeleitet, und zwar mittels des Doppelpunkts und einem access_keyword. Die aus der base_class abgeleitete/gemachte new_class kann sowohl auf die public als auch die geschützten Mitglieder der base_class zugreifen (oder sie erben), kann jedoch nicht auf die private Mitglieder base_class zugreifen. Die new_class kann zudem die neuen Mitgliederfunktionen/Methoden, die sich von der base_class unterscheiden, implementieren. Anders gesagt: die new_class kann also auch ihre eigenen Daten und Funktionsmitglieder jenseits derjenigen haben, die sie von der base_class geerbt hat.

Wird bei der Erzeugung der abgeleiteten Klasse das Schlüsselwort public benutzt, heißt das, dass die public und geschützten Mitglieder der Basisklasse als public und geschützte Mitglieder von der abgeleiteten Klasse geerbt werden. Wird das Schlüsselwort 'geschützt' benutzt, werden public und geschützte Mitglieder der Basisklasse als geschützte Mitglieder von der abgeleiteten Klasse geerbt. Wird das Schlüsselwort 'private' benutzt, werden public und geschützte Mitglieder der Basisklasse als private Mitglieder von der abgeleiteten Klasse geerbt.

Hierbei ist es wichtig, zu wissen, dass sobald ein neues Objekt der new_class (der abgeleiteten Klasse) erzeugt wird, der Constructor der base_class als erster, noch vor dem Constructor der new_class, aufgerufen wird. Wird das Objekt allerdings vernichtet, wird der Destructor der new_class (der abgeleiteten Klasse) als erster, noch vor dem Destructor derbase_class, aufgerufen.

Damit dieses Konzept klarer wird, nehmen wir wieder unsere ursprüngliche Klasse AUTO her.

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();

  private:
    int        tyres;
};

Aus dieser Klasse können wir eine weitere Klasse ableiten, nämlich LIMOUSINE. Beachten Sie, dass ich drei der Datenmitglieder der Klasse AUTO als geschützte Mitglieder deklariert habe Denn so kann unsere neue Klasse LIMOUSINE diese Mitglieder erben.

Des Weiteren möchte ich auch, dass Sie verstehen, dass die Reihenfolge, in der Sie die Schlüsselworte für den Zugriff platzieren, keine Rolle spielt. Worauf es ankommt ist, dass alle unter einem Schlüsselwort für Zugriff deklarierten Mitglieder zu diesem Schlüsselwort gehören.

class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void       runathighspeed();
};

Unsere abgeleitete Klasse LIMOUSINE besitzt zwei Mitglieder und erbt zugleich sieben Mitglieder (geschützte und public Mitglieder) von der Basisklasse AUTO. Sobald also ein Objekt der LIMOUSINE erzeugt ist kann es auf die public Mitgliederfunktionen von AUTO zugreifen, die starten(), schalten(), anhalten() und hupen() sind, sowie auf seine eigene public Mitgliederfunktion schnellfahren(). Das ist das Konzept der Vererbung.

Das ist ganz genauso wie bei uns Menschen: einige Merkmale/Verhaltensmuster (Methoden) unseres Vaters/Mutters (Basisklasse) zeigen sich in uns, den Kindern (abgeleitete Klasse), da wir diese Verhaltensmuster (Methoden/Funktionen) ja genetisch oder sonst irgendwie vererbt bekommen haben. Ich bin nun wirklich kein Genetiker, aber ich glaube es ist klar, was ich mit diesem Bild veranschaulichen wollte. Übrigens unterstützt MQL5 keine Mehrfachvererbung, also können wir das beiseite lassen.

Hmm!!! Ich hoffe, das sich der Schleier, unter dem dieses geheimnisvolle Ding namens OOP oder KLASSE verborgen liegt, so langsam lüftet. Nicht müde werden, wenn Sie an dieser Stelle immer noch nicht genau wissen, um was es eigentlich geht. Machen Sie eine Pause, trinken einen Kaffe und dann fangen Sie nochmal von vorne an. So ein großes Geheimnis ist das alles gar nicht…

Wenn Sie jetzt also wieder bei mir sind, gehe ich davon aus, dass Sie bis hierhin alles nachvollziehen konnten. Ich möchte nun von Ihnen wissen, wie viele weitere Klassen man noch aus unserer Basisklasse AUTO ableiten kann? Na? Ich warte auf Ihre Antwort. Ja, ernsthaft! Benennen Sie sie, schreiben ihre Deklarierung und mailen mir das Ganze. Wenn Sie sie alle nennen können, geht das Mittagessen auf mich... (ich mache nur Spaß?)

Sind Sie bereit für mehr Informationen? Ok, weiter geht's

! Stimmt schon, wenn ich schreibe, dann schreibe ich wie mein Papa. Seine Handschrift ist sehr sauber und klar und ziemlich stilvoll - so wie meine. Na, wahrscheinlich ist das ein Element, das ich ich von ihm geerbt habe. Doch halt... er hat mit links geschrieben und ich schreibe mit rechts. Doch wenn Sie beide Handschriften nebeneinander legen, merken Sie den Unterschied kaum. Worauf ich eigentlich hinaus will? Ich habe die gut leserliche Handschrift meines Vaters geerbt, doch ich schreibe nicht mit links wie mein Papa. Das heißt, obwohl ich seine Art zu schreiben geerbt habe und sich unsere Handschriften auch gleichen, schreibe ich mit einer anderen Hand als mein Vater. Können Sie mir noch folgen? Dieser Gedanke wird in OOP Polymorphismus genannt.

Eine abgeleitete Klasse (ich - wie im obigen Beispiel) erbt eine Mitgliedsfunktion (writefine() – für meine Handschrift) von einer Basisklasse (mein Vater), doch sie (Ich) implementiert die Funktion (writefine() ) auf andere Weise als in der Basisklasse (mein Vater).

Auf unsere AUTO-Klasse und die daraus abgeleitete Klasse LIMOUSINE übertragen, bedeutet das:

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool               start();
    virtual void       changegear(){return(0);}
    void               stop();
    bool               horn();

  private:
    int        tyres;
};
class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void               runathighspeed();
    virtual  void       changegear(){gear1=reverse; gear2=low; gear3=high;}
  };

class WAGON : public CAR 
{
  private:
    bool               hasliftback;

  public:
   virtual  void       changegear(){gear1=low; gear2=high; gear3=reverse;}
};

Sehen wir uns einige Änderungen an, die wir hier vorgenommen haben, Wir haben zunächst aus AUTO eine neue abgeleitete Klasse namens KOMBI mit zwei Mitgliedern deklariert. Wir haben zudem auch die Mitgliederfunktion schalten() verändert, sodass sie eine virtuelle Funktion in der Basisklasse wird. Warum haben wir schalten() zu einer virtuellen Funktion gemacht? Ganz einfach, weil wir möchten, das jede Klasse, die die Funktion von der Basisklasse erbt, diese auf ihre eigene Art und Weise implementieren kann.

Anders ausgedrückt:virtuelle Mitgliederfunktionen einer Klasse sind Mitgliederfunktionen, die in jeder abgeleiteten Klassen anders aufgehoben oder implementiert werden können als in der Basisklasse, in der sie deklariert werden. Der Korpus der Mitgliederfunktion kann dann durch eine neue Implementierungsreihe in der abgeleiteten Klasse ersetzt werden. Selbst wenn wir den Begriff 'virtuell' in der abgeleiteten Klasse nicht mehr verwenden werden, ist seine Verwendung in den abgeleiteten Klassen gängige und empfehlenswerte Programmierpraxis.

Auf Grundlage der obigen Beispiele, implementieren die Klassen LIMOUSINE und KOMBI die Funktion schalten() auf jeweils ihre eigene Art und Weise.

1.3 KLASSENMETHODEN FESTLEGEN (MITGLIEDER-FUNKTIONEN)

Da wir nun, zumindest ein wenig, wissen, wie man Klassen deklariert, besprechen wir nun wie man die Mitgliederfunktionen einer Klasse festlegt. Nach der Deklarierung der Klasse müssen wir als nächstes die Mitgliederfunktionen unserer Klasse festlegen. Nehmen wir dazu wieder unsere AUTO-Klasse her:

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    void       CAR() // Constructor
    bool       start();
    void       changegear();
    void       stop();
    bool       horn(){press horn;}

  private:
    int        tyres;
};

 void CAR::CAR()
{
 // initialize member variables here
}

bool CAR::start()
{
 // car start procedure here
}

void CAR::changegear()
{
// car changegear procedure here
}

void CAR::stop()
{
// car stop procedure here
}

Zur Festlegung der Mitgliederfunktionen haben wir einen Doppel-Doppelpunkt-Operator (::) verwendet, den sog. Bereichs-Operator. Dies wird genauso wie normale Funktionen geschrieben - mit dem einzigen Unterschied, dass hier der Klassenname und der Bereichs-Operator hinzugefügt werden. Des weiteren haben Sie auch bemerkt, dass eine der Funktionen innerhalb der Klasse bereits festgelegt war (Mitgliederfunktion hupen()). Eine Mitgliederfunktion kann entweder innerhalb oder außerhalb der Klassen-Deklarierung festgelegt werden - so wie ich es Ihnen hier gezeigt habe.

Bevor wir weiter machen, ist es vielleicht nicht schlecht, noch kurt auf das Konzept der Funktionen einzugehen.

1.4 FUNKTIONEN

Was ist eigentlich eine Funktion?

Vielleicht wohnen Sie in einem Haus und vielleicht haben Sie tatsächlich drei Kinder. Anstatt dass nur eines Ihrer Kinder alle Arbeiten verrichtet, haben Sie eins gebeten nach dem Essen abzuwaschen, das andere übernimmt das Fegen und das dritte ist jeden Morgen mit dem Bettenmachen dran.

In jedem Haus fallen Arbeiten an und anstatt dass eine Person alles übernimmt, haben wir in unserem Beispiel, die Tätigkeiten zwischen allen drei Kindern aufgeteilt. Das erleichtert jedem die Arbeit und verteilt alle Pflichten gleichermaßen, anstatt sie nur einen Person aufzubürden. Und wenn eines der Kinder seinen Aufgabenbereich nicht erfüllt hat, dann wissen wir auch sehr schnell, wenn wir zu rügen haben, Das ist der Hauptgedanke hinter Funktionen.

Meistens möchten wir einen Code schreiben, der viele Aufgaben ausführen kann. Und hier kommen die Funktionen ins Spiel. Wir können uns entscheiden, die Aufgabe in kleinere Unteraufgaben aufzuteilen und dann zur Ausführung jeder dieser kleineren Aufgabe eine Funktion schreiben. Eine Funktion ist ein Code-Block, der eine Reihe von Abläufen ausführt oder implementiert. Er ist eine Gruppe von Aussagen, die immer dann ausgeführt wird, sobald sie von irgendeinem Punkt in einem Programm aufgerufen wird.

Eine Funktion kann folgendermaßen definiert werden:

Return_type function_name (parameters1,parameters2,…)
{
  Expressions; //(actions to carry out by the function)
}

Beispiel einer Funktion:

int doaddition (int x, int y)
{
 return (x+y);
}

Der 'Lieferungs'typ der Funktion ist ganzzahlig (int), ihr Name ist Additionausführen, und int x und int y sind ihre Parameter. Diese Funktion addiert immer zwei eingegebene Eingabeparameter und liefert das Ergebnis. Füttern wir die Funktion also mit den zwei ganzzahligen Variablen 2 und 3, führt die Funktion die Addition aus und liefert uns 5 als Ergebnis.

int doaddition(2,3) // returns 5

Weitere Informationen zu Funktionen finden Sie im MQL5 Benutzerhandbuch.

So, genug Theorie! Steigen wir in die tatsächliche Arbeit ein.

Worum es in diesem Beitrag geht, ist Ihnen näher zu bringen, wie Sie eine Klasse für Ihren Expert Advisor mit Hilfe des Objekt-orientierten Ansatzes schreiben können, der in MQL5 vorgestellt wird.

Also an die Arbeit…


2. Einen Expert Advisor schreiben

Wir beziehen uns in diesem Abschnitt auf den Expert Advisor, den wir im ersten Beitrag beschrieben haben. Sollten Sie diesen Beitrag nicht gelesen haben, dann holen Sie dies bitte nach, damit Ihnen die meisten Punkte, die wir im Folgenden besprechen werden, nicht komplett fremd sind. Ich werde allerdings trotzdem einige Dinge, die notwendig sind, noch überarbeiten.

Bevor Sie Ihre Klasse schreiben können, müssen Sie sich zunächst eine Handelsstrategie überlegen und entwickeln. Das haben wir ja im ersten Beitrag bereits getan. Als nächsten Schritt müssen wir alle Funktionalitäten auswählen, die wir an unsere Klasse delegieren wollen. Sie legen die Mitgliedervariablen unserer Klasse fest. Rekapitulieren wir hier nochmal kurz unsere Handelsstrategie aus dem ersten Beitrag.

Was unser EA ausführen wird:

  • Er überprüft einen bestimmten Indikator. Wenn eine (oder mehrere) bestimmte Bedingung(en) erfüllt ist/sind, platziert er einen Handel (entweder einen Short/Sell oder Long/Buy), je nach der aktuellen, erfüllten Bedingung.

Das nennt man Handelsstrategie. Bevor man einen EA schreiben kann, muss man zunächst die Strategie entwickeln, die man im EA automatisieren möchte. In unserem Fall verändern wir die obige Aussage, sodass sie die Strategie, die wir in einen EA entwickeln wollen, auch widerspiegelt.

  • Dazu verwenden wir einen Indikator, den sog. gleitenden Mittelwert, mit einem Zeitraum von 8 (man kann jeden Zeitraum wählen, doch für unsere Strategie hier, nehmen wir 8).
  • Unser EA soll einen Long (Buy) platzieren, wenn der gleitende Mittelwert-8 (in diesem Beitrag nenne ich den gleitenden Mittelwert einfach GM-8) nach oben geht und der Kurs knapp darüber liegt - und einen Short (Sell), wenn der GM-8 nach unten geht und der Kurs knapp darunter liegt.
  • Wir verwenden hier aber auch noch einen anderen Indikator, den sog. ADX (Average Directional Movement) mit Zeitraum 8, der uns ebenfalls hilft, festzustellen, ob sich der Markt in eine Richtung bewegt oder nicht. Dies tun wir, weil wir den Handel nur eingeben wollen, wenn sich der Markt in eine Richtung bewegt und uns zurückhalten, wenn der Markt stagniert (sich also nicht bewegt). Dazu platzieren wir unseren Handel (Buy oder Sell) nur dann, wenn die o.g. Bedingungen erfüllt sind und der ADX-Wert größer als 22 ist. Ist der ADX größer als 22, doch geht nach unten, oder ist der ADX kleiner als 22, werden wir keinen Handel ausführen, selbst wenn Bedingung B erfüllt ist.
  • Zudem möchten wir uns auch durch Einrichtung eines Stop Loss von 30 Pips (kleinste mögliche Kursveränderung) schützen. Wir zielen hier wir auf einen Profit von 100 Pips.
  • Zudem soll unser EA auch nur dann nach Buy/Sell-Chancen Ausschau halten, wenn sich ein neuer Bar herausgebildet hat. Zudem möchten wir auch sicherstellen, dass wir eine Buy Position eröffnen, wenn die Buy-Bedingungen erfüllt sind und wir noch keine eröffnet haben. Und wir wollen eine Sell Position eröffnen, wenn die Sell-Bedingungen erfüllt sind und wir noch keine eröffnet haben.

Zusätzlich dazu möchten wir auch sicherstellen, dass wir die Prozente unserer freien Marge kontrollieren können, die wir zur Platzierung eines Handels verwenden können und zudem sicher sein, dass wir vor jeder Platzierung eines Handels zunächst die freie Marge prüfen. Unser EA platziert also nur dann einen Handel, wenn dafür genügend verfügbare Marge vorhanden ist.

Verstehen Sie jetzt, was wir vorhaben? Die Funktionen, die wir an unsere Klasse delegieren wollen, lauten:

Das st im Grunde alles, was unser EA ausführen soll. Diese zwei Funktionalitäten sind sind die beiden Hauptfunktionen, doch klar gibt es da noch mehr. So müssen z.B. beim Prüfen nach Buy/Sell Positions Indikatoren eingesetzt werden. D.h., es müssen die Werte der Indikatoren ebenfalls in unserer Klasse erhalten werden, und daher schließen wir folgendes mit ein:

Um die Indikatorwerte zu bekommen, muss unsere Klasse die MA- und ADX-Zeiträume, die Chartperiode und das Symbol (das Währungspaar mit dem wir arbeiten wollen) wissen, also müssen wir auch folgendes einschließen:

Und zur Prüfung der freien Marge vor der Platzierung eines Handels, schließen wir außerdem noch ein:

Und angesichts all dessen, bekommen wir schon eine gute Vorstellung davon, welche Variablen und Funktionen in unserer Klasse sein sollten.

So jetzt habe ich Ihnen wirklich alle notwendigen Schritte und Überlegungen mitgeteilt - Zeit endlich den Code zu schreiben.

2.1 Eine Klasse schreiben

Fahren wir als erstes den MetaEditor hoch (das kennen Sie schon, oder?). Sobald er geöffnet ist, legen wir ein neues MQL-Dokument an, indem wir die neue Werkzeugleiste anklicken oder Ctrl+N drücken. Im Assistent-Fester wählen wir dann "mit einschließen" und klicken auf WEITER.

Abb. 1 Ein neues MQL5-Dokument aufrufen

Abb. 1 Ein neues MQL5-Dokument aufrufen

Den Dateinamen, so wie unten gezeigt, eingeben und auf Fertigstellen klicken:

Abb. 2 Dem neuen Dokument einen Namen geben

Abb. 2 Dem neuen Dokument einen Namen geben

Wir haben 'mit einschließen' gewählt, da unsere Klasse eine einzuschließende Datei sein muss, die unser EA-Code aufnehmen muss, sobald wir ihn später verwenden. Deshalb haben Sie hier keinen Platz, um Eingabeparameter einzugeben.

Wie üblich liefert Ihnen der Editor ein Gerüst davon , was er glaubt, dass Sie tun möchten.

Zunächst müssen Sie alles unterhalb der "#property link …" Codezeile löschen, damit Sie dann ungefähr das erhalten:

//+------------------------------------------------------------------+
//|                                              my_expert_class.mqh |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"

Jetzt schreiben wir die Deklarierung unserer Klasse, die wir MyExpert nennen.

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{

Analysieren wir die Deklarierung kurz: Sie beginnt mit dem Namen der Klasse. Danach haben wir die private Mitglieder der Klasse deklariert.

Die Private Mitglieder:

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{
//--- private members
private:
   int               Magic_No;   // Expert Magic Number
   int               Chk_Margin; // Margin Check before placing trade? (1 or 0)
   double            LOTS;       // Lots or volume to Trade
   double            TradePct;   // Percentage of Account Free Margin to trade
   double            ADX_min;    // ADX Minimum value
   int               ADX_handle; // ADX Handle
   int               MA_handle;  // Moving Average Handle
   double            plus_DI[];  // array to hold ADX +DI values for each bars
   double            minus_DI[]; // array to hold ADX -DI values for each bars
   double            MA_val[];   // array to hold Moving Average values for each bars
   double            ADX_val[];  // array to hold ADX values for each bars
   double            Closeprice; // variable to hold the previous bar closed price 
   MqlTradeRequest   trequest;    // MQL5 trade request structure to be used for sending our trade requests
   MqlTradeResult    tresult;     // MQL5 trade result structure to be used to get our trade results
   string            symbol;     // variable to hold the current symbol name
   ENUM_TIMEFRAMES   period;      // variable to hold the current timeframe value
   string            Errormsg;   // variable to hold our error messages
   int               Errcode;    // variable to hold our error codes

Wie zuvor schon erklärt, kann keine Funktion außerhalb der Klasse auf diese privaten Mitglieder zugreifen. Die meisten Variablen sind in ihren Deklarierungen sehr klar, also halte ich mich nicht lange mit ihnen auf.

Doch vielleicht erinnern Sie sich noch, dass wir in unserer Erläuterung gesagt haben, dass Mitgliedervariablen jeden zulässigen Datentyp, Struktur oder Klasse haben können.

Und wie das in Aktion aussieht, erkennt man hier schön mit der Deklarierung der Typen MqlTradeRequest und MqlTradeResults.

Der Constructor

//--- Public member/functions
public:
   void              MyExpert();                                  //Class Constructor

Der Constructor nimmt keine Eingagebparameter auf. Bitte daran denken, wenn Sie Ihren Code schreiben.

Die Mitgliederfunktionen

//--- Public member/functions
public:
   void              MyExpert();                                 //Class Constructor
   void              setSymbol(string syb){symbol = syb;}         //function to set current symbol
   void              setPeriod(ENUM_TIMEFRAMES prd){period = prd;} //function to set current symbol timeframe/period
   void              setCloseprice(double prc){Closeprice=prc;}   //function to set prev bar closed price
   void              setchkMAG(int mag){Chk_Margin=mag;}          //function to set Margin Check value
   void              setLOTS(double lot){LOTS=lot;}               //function to set The Lot size to trade
   void              setTRpct(double trpct){TradePct=trpct/100;}   //function to set Percentage of Free margin to use for trading
   void              setMagic(int magic){Magic_No=magic;}         //function to set Expert Magic number
   void              setadxmin(double adx){ADX_min=adx;}          //function to set ADX Minimum values

Diese Mitgliederfunktionen haben wir so festgelegt, dass Sie uns die Einrichtung wichtiger Variablen erlauben, die unsere Klasse zur Ausführung ihrer Funktion braucht. Ohne diese Funktionen kann unsere Klasse diese Variablen nicht verwenden. Und Sie bemerken vielleicht auch, dass wir bereits eine entsprechende Variable in unserer Klasse deklariert hatten, die diese Werte aufnimmt, sobald sie von diesen Funktionen eingerichtet sind.

Ein weiterer Punkt, den man nicht vergessen sollte ist, dass wir diese Mitgliederfunktionen innerhalb der Klassendeklarierung festgelegt haben Dies ist zulässig, wie ich zuvor erklärt habe. Das bedeutet also, dass wir sie nicht noch einmal festlegen müssen, wenn wir andere Mitgliederfunktionen definieren - wie Sie bald sehen werden.

Sie haben, ganz genauso wie normale Funktionen, Parameter des richtigen Datentyps, je nach den 'Lieferungs'werten jeder Funktion. Auch das sollte Ihnen klar sein, nicht wahr?.

void              doInit(int adx_period,int ma_period);         //function to be used at our EA intialization
void              doUninit();                                  //function to be used at EA de-initializatio
bool              checkBuy();                                  //function to check for Buy conditions
bool              checkSell();                                 //function to check for Sell conditions
void              openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,
                         double TP,int dev,string comment="");   //function to open Buy positions
void              openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,
                          double TP,int dev,string comment="");  //function to open Sell positions

Wir haben diese Mitgliederfunktionen nur deklariert, aber nicht festgelegt. Denn dies tun wir erst später. Diese Funktionen werden den Großteil der Werte bearbeiten, die in den Mitgliedervariablen unserer Klasse abgelegt sind. Zugleich bilden sie auch die Funktionen für die wichtige Rolle unserer Klasse. Auch dazu kommen wir später noch.

Die geschützten Mitglieder

Diese Mitglieder werden von jeder Klasse, die von unserer Klasse abgeleitet wird, geerbt. Das ist nicht wirklich notwendig, wenn Sie sowieso keine andere Klasse von dieser Klasse ableiten wollen. Sie können sie genauso gut auch als private Mitglieder platzieren. Ich mache das hier nur deshalb, damit Sie die verschiedenen Punkte besser verstehen, die wir zuvor im Hinblick auf Klassen durchgegangen sind.

//--- Protected members
protected:
   void              showError(string msg, int ercode);   //function for use to display error messages
   void              getBuffers();                       //function for getting Indicator buffers
   bool              MarginOK();                         //function to check if margin required for lots is OK

Die drei Funktionen sind für unsere Klasse ebenfalls sehr wichtig, obwohl sie interne Funktionen der Klasse sind. showError zeigt unsere Fehler an, und mit getBuffers holen wir uns Indikator-Buffer. MarginOK prüft, ob genügend freie Marge zum Eröffnen eine Position vorhanden ist.

Wenn Sie mit der Deklarierung der Klasse fertig sind, vergessen Sie bitte den Stichpunkt nicht. Der ist wirklich wichtig.

};   // end of class declaration

Als nächstes müssen wir unmittelbar nach der Deklarierung der Klasse die Mitgliederfunktionen festlegen, die im Deklarierungsbereich noch nicht festgelegt worden sind

//+------------------------------------------------------------------+
// Definition of our Class/member functions
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|  This CLASS CONSTRUCTOR
//|  *Does not have any input parameters
//|  *Initilizes all the necessary variables                                          
//+------------------------------------------------------------------+
void MyExpert::MyExpert()
  {
//initialize all necessary variables
   ZeroMemory(trequest);
   ZeroMemory(tresult);
   ZeroMemory(ADX_val);
   ZeroMemory(MA_val);
   ZeroMemory(plus_DI);
   ZeroMemory(minus_DI);
   Errormsg="";
   Errcode=0;
  }

Dies ist unser Klassen-Constructor. Hier haben wir den Doppel-Doppelpunkt (::) (der Bereichs-Operator) zwischen dem Klassennamen und dem Namen der Mitgliederfunktion verwendet. Das heißt zusammengefasst:

Obwohl wir diese Mitgliederfunktion außerhalb der Deklarierung der Klasse festlegen, liegt sie dennoch noch im Bereich der Klasse. Sie ist ein Mitglied der Klasse, deren Namen vor den zwei Doppelpunkten steht (Bereichs-Operator) und

besitzt keine Eingabeparameter. Und an dieser Stelle initialisieren wir die notwendigen Mitgliedervariablen. Dies geschieht mit Hilfe der ZeroMemory Funktion.

void ZeroMemory(
void & variable // Variable zurücksetzen
);

Diese Funktion setzt die Werte der an sie übertragenen Variablen zurück. In diesem Fall verwenden wir sie zum Zurücksetzen unserer Strukturtypen (MqlTradeRequest aud MqlTradeResult) und unserer Arrays.

Die showError Funktion:

//+------------------------------------------------------------------+
//|  SHOWERROR FUNCTION
//|  *Input Parameters - Error Message, Error Code                                                               
//+------------------------------------------------------------------+
void MyExpert::showError(string msg,int ercode)
  {
   Alert(msg,"-error:",ercode,"!!"); // display error
  }

Dies ist eine geschützte Mitgliederfunktion, mit deren Hilfe alle Fehler angezeigt werden, die während der Abläufe irgendeines Objekts unserer Klasse auftauchen. Sie besitzt zwei Argumente/Parameter – Fehlerbeschreibung und Fehlercode

Die getBuffers Funktion:

//+------------------------------------------------------------------+
//|  GETBUFFERS FUNCTION                                                                
//|  *No input parameters
//|  *Uses the class data members to get indicator's buffers
//+------------------------------------------------------------------+
void MyExpert::getBuffers()
  {
   if(CopyBuffer(ADX_handle,0,0,3,ADX_val)<0 || CopyBuffer(ADX_handle,1,0,3,plus_DI)<0
      || CopyBuffer(ADX_handle,2,0,3,minus_DI)<0 || CopyBuffer(MA_handle,0,0,3,MA_val)<0)
     {
      Errormsg="Error copying indicator Buffers";
      Errcode = GetLastError();
      showError(Errormsg,Errcode);
     }
  }

Mit dieser Funktion werden alle unsere Indikator-Buffer in die Arrays, die wir in den Mitgliedervariablen mit Hilfe des entsprechenden Indikator-Handles festgelegt haben, kopiert.

Die CopyBuffer Funktion wurde bereits im ersten Beitrag erklärt. Die getBuffers Funktion besitzt keine Eingabeparameter, da wir die werte von den Mitgliedervariablen der Klasse verwenden.

Wir haben hier unsere interne Fehlerfunktion zur Anzeige aller Fehler verwendet, die während des Kopiervorgangs der Buffer auftreten könnten.

Die MarginOKFunktion:

//+------------------------------------------------------------------+
//|  MARGINOK FUNCTION
//| *No input parameters
//| *Uses the Class data members to check margin required to place a trade
//|  with the lot size is ok
//| *Returns TRUE on success and FALSE on failure
//+------------------------------------------------------------------+
bool MyExpert::MarginOK()
  {
   double one_lot_price;                                                        //Margin required for one lot
   double act_f_mag     = AccountInfoDouble(ACCOUNT_FREEMARGIN);                //Account free margin
   long   levrage       = AccountInfoInteger(ACCOUNT_LEVERAGE);                 //Leverage for this account
   double contract_size = SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);  //Total units for one lot
   string base_currency = SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);        //Base currency for currency pair
                                                                                //
   if(base_currency=="USD")
     {
      one_lot_price=contract_size/levrage;
     }
   else
     {
      double bprice= SymbolInfoDouble(symbol,SYMBOL_BID);
      one_lot_price=bprice*contract_size/levrage;
     }
// Check if margin required is okay based on setting
   if(MathFloor(LOTS*one_lot_price)>MathFloor(act_f_mag*TradePct))
     {
      return(false);
     }
   else
     {
      return(true);
     }
  }
  

Diese Funktion führt in der Tat zwei Arbeiten aus: Sie überprüft, ob wir auch wirklich ausreichend freie Marge zur Platzierung des Handels haben und prüft zugleich auch nach, dass wir nicht versehentlich mehr als den festgelegten Prozentsatz der für eine Platzierung des Handels verfügbaren freien Marge einsetzen. Auf diese Weise können wir kontrollieren, wie viel Geld wir für jeden Handel einsetzen.

Wir arbeiten mit der AccountInfoDouble() Funktion, zusammen mit dem ENUM_ACCOUNT_INFO_DOUBLE Identifikator , um die freie Marge für diesen Account zu erhalten. Wir arbeiten zudem auch mit der AccountInfoInteger() Funktion, zusammen mit dem ENUM_ACCOUNT_INFO_INTEGER Identifikator, um die Leverage für diesen Account zu erhalten. AccountInfoInteger() und AccountInfoDouble() sind Account-Funktionen, mit denen man die Details des aktuellen Accounts bekommt, der den EA verwendet.

double  AccountInfoDouble(
   int  property_id      // identifier of the property
   );

Wir haben auch die Funktionen der Symboleigenschaften SymbolInfoDouble() und SymbolInfoString() verwendet, um die Kontraktgröße bzw. die Basiswährung für das aktuelle Symbol (Währungspaar) zu bekommen. Die SymbolInfoDouble() Funktion nimmt den Symbolnamen und einen ENUM_SYMBOL_INFO_DOUBLEIdentifikator als Parameter, wohingegen die SymbolInfoString() Funktion den Symbolnamen und den ENUM_SYMBOL_INFO_STRING Identifikator als Parameter nimmt. Die Ergebnisse dieser Funktionen werden in den für jeden Datentyp deklarierten Variablen abgelegt.

double  SymbolInfoDouble(
   string  name,        // symbol
   int     prop_id      // identifier of the property
   );

Die Berechnung, die wir durchgeführt haben, ist sehr einfach.

Um die zur Platzierung eine Handels notwendige Marge zu erhalten, müssen wir zwei Situationen berücksichtigen:

  1. 1. Die Basiswährung ist USD (USD/CAD, USD/CHF, USD/JPY, usw.)

Erforderliche Marge = Kontraktgröße per Posten/Leverage

2. Die Basiswährung ist nicht USD (EUR/USD, usw.)

Erforderliche Marge = aktueller Preis des Symbols* Kontraktgröße per Posten/Leverage.

Wir entscheiden nun, ob die für den Handel der spezifizierten Postengröße oder Volumen erforderliche Marge größer ist als der Prozentsatz der freien Marge, den wir für einen Handel einsetzen wollen, Ist die erforderliche Marge geringer, dann liefert die Funktion TRUE und der Handel wird platziert. Andernfalls liefert sie FALSE und der Handel wird nicht platziert.

Die doInit Funktion:

//+-----------------------------------------------------------------------+
// OUR PUBLIC FUNCTIONS                                                   |
//+-----------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| DOINIT FUNCTION
//| *Takes the ADX indicator's Period and Moving Average indicator's 
//| period as input parameters 
//| *To be used in the OnInit() function of our EA                                                               
//+------------------------------------------------------------------+
void MyExpert::doInit(int adx_period,int ma_period)
  {
//--- Get handle for ADX indicator
   ADX_handle=iADX(symbol,period,adx_period);
//--- Get the handle for Moving Average indicator
   MA_handle=iMA(symbol,period,ma_period,0,MODE_EMA,PRICE_CLOSE);
//--- What if handle returns Invalid Handle
   if(ADX_handle<0 || MA_handle<0)
     {
      Errormsg="Error Creating Handles for indicators";
      Errcode=GetLastError();
      showError(Errormsg,Errcode);
     }
// Set Arrays as series
// the ADX values arrays
   ArraySetAsSeries(ADX_val,true);
// the +DI value arrays
   ArraySetAsSeries(plus_DI,true);
// the -DI value arrays
   ArraySetAsSeries(minus_DI,true);
// the MA values arrays
   ArraySetAsSeries(MA_val,true);
  }

Dies ist eine public Funktion, die wir in der OnInit() Funktion unserer EA verwenden wollen, den wir schon bald schreiben werden. Sie übernimmt zwei Aufgaben:

zunächst setzt sie die Handles für unsere Indikatoren und führt bei den Array-Variablen zudem die Handlung Array-als-Serie-setzen aus. Sie besitzt zwei Eingabeparameter, die aus dem EA-Code stammen.

Die doUninit Funktion:

//+------------------------------------------------------------------+
//|  DOUNINIT FUNCTION
//|  *No input parameters
//|  *Used to release ADX and MA indicators handleS                                                                |
//+------------------------------------------------------------------+
void MyExpert::doUninit()
  {
//--- Release our indicator handles
   IndicatorRelease(ADX_handle);
   IndicatorRelease(MA_handle);
  }
  

Diese Funktion ist ebenfalls eine public Funktion, die in der UnDeInit Funktion unseres EAs zur Freigabe aller Handles für die Indikatoren, die wir verwendet haben, benutzt wird. Sie besitzt keine Eingabeparameter.

Die checkBuy Funktion:

//+------------------------------------------------------------------+
//| CHECKBUY FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Buy setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Buy conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkBuy()
  {
/*
    Check for a Long/Buy Setup : MA increasing upwards, 
    previous price close above MA, ADX > ADX min, +DI > -DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Buy Conditions
   bool Buy_Condition_1=(MA_val[0]>MA_val[1]) && (MA_val[1]>MA_val[2]); // MA Increasing upwards
   bool Buy_Condition_2=(Closeprice>MA_val[1]);         // previous price closed above MA
   bool Buy_Condition_3=(ADX_val[0]>ADX_min);          // Current ADX value greater than minimum ADX value
   bool Buy_Condition_4=(plus_DI[0]>minus_DI[0]);       // +DI greater than -DI
//--- Putting all together   
   if(Buy_Condition_1 && Buy_Condition_2 && Buy_Condition_3 && Buy_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

Diese Funktion wird zur Prüfung verwendet, ob eine Buy-Bedingung eingerichtet wurde oder nicht. Daher ist der Typ ihrer 'Lieferung' bool, sodass sie nur TRUE oder FALSE liefert. Und hier haben wir unsere Buy-Handelsstrategie festgelegt. Tritt in der Strategie, die wir festgelegt haben, eine Buy-Bedingung ein, liefert uns die Funktion TRUE. Tritt diese Buy-Bedingung jedoch nicht ein, erhalten wir FALSE. Wann wir diese Funktion in unserem Code verwenden, platzieren wir natürlich nur einen Buy, wenn wir TRUE erhalten.

Das erste was wir hier gemacht haben, ist die interne Mitgliederfunktion getBuffers() aufzurufen die all die Arraywerte, die von der checkBuy Funktion benötigt werden, in die entsprechenden Array-Variablen kopiert.

Die hier codierten Bedingungen sind ebenfalls bereits im ersten Beitrag erklärt worden.

Die checkSell Funktion:

//+------------------------------------------------------------------+
//| CHECKSELL FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Sell setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Sell conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkSell()
  {
/*
    Check for a Short/Sell Setup : MA decreasing downwards, 
    previous price close below MA, ADX > ADX min, -DI > +DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Sell Conditions
   bool Sell_Condition_1=(MA_val[0]<MA_val[1]) && (MA_val[1]<MA_val[2]);  // MA decreasing downwards
   bool Sell_Condition_2=(Closeprice <MA_val[1]);                         // Previous price closed below MA
   bool Sell_Condition_3=(ADX_val[0]>ADX_min);                            // Current ADX value greater than minimum ADX
   bool Sell_Condition_4=(plus_DI[0]<minus_DI[0]);                        // -DI greater than +DI

//--- Putting all together
   if(Sell_Condition_1 && Sell_Condition_2 && Sell_Condition_3 && Sell_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

Ganz genauso wie die checkBuy Funktion, übernimmt diese Funktion die Prüfung, ob eine Sell-Bedingung eingerichtet wurde oder nicht. Daher ist der Typ ihrer 'Lieferung' bool, sodass sie nur TRUE oder FALSE liefert. Und hier haben wir eine Sell-Handelsstrategie festgelegt. Tritt in der Strategie, die wir festgelegt haben, eine Sell-Bedingung ein, liefert uns die Funktion TRUE. Tritt diese Sell-Bedingung jedoch nicht ein, erhalten wir FALSE.

Wann wir diese Funktion in unserem Code verwenden, platzieren wir natürlich nur einen Sell, wenn wir TRUE erhalten. Und ebenso wie bei checkBuy, haben wir die interne Funktion getBuffers() als erste aufgerufen. Die hier codierten Bedingungen sind ebenfalls bereits im ersten Beitrag erklärt worden.

Die openBuy Funktion:

//+------------------------------------------------------------------+
//| OPENBUY FUNCTION
//| *Has Input parameters - order type, Current ASK price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=askprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Buy order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=askprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Buy order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

Diese Funktion eröffnet eine Buy-Position, sobald sie in unserem EA aufgerufen wird. Ihre Eingabeparameter sind der Großteil der Variablen, die zur Platzierung eine Handels erforderlich sind; einige davon werden von unserem EA-Code zur Verfügung gestellt. Sie haben sicher bemerkt, das wir hier, wie im ersten Beitrag erläutert, Variablen vom Typ MqlTraderequest verwendet haben.

Wir müssen sie nicht in unserem EA-Code verwenden. Bevor ein Handel platziert wird, möchten wir eine Bestätigung, ob der Anwender die Marge prüfen möchte. Beläuft sich der Wert von Chk_Margin (die man aus dem EA erhält) auf 1, dann rufen wir die Funktion MarginOK() auf, dies für uns zu übernehmen. Das Ergebnis, das uns diese Funktion liefert, bestimmt den nächsten Schritt. Wenn der Anwender jedoch die Marge nicht prüfen möchte, fahren wir einfach fort und platzieren den Handel.

Die openSell Funktion:

//+------------------------------------------------------------------+
//| OPENSELL FUNCTION
//| *Has Input parameters - order type, Current BID price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=bidprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Sell order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=bidprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Sell order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

Ganz genauso wie die openBuy Funktion, eröffnet diese Funktion eine Sell-Position, sobald sie in unserem EA aufgerufen wird. Ihre Eingabeparameter sind der Großteil der Variablen, die zur Platzierung eine Handels erforderlich sind; einige davon werden von unserem EA-Code zur Verfügung gestellt.

Und ebenfalls wie bei der Eröffnung einer Buy-Position, möchten wir vor Eröffnung eines Handels eine Bestätigung, ob der Anwender die Marge prüfen möchte. Beläuft sich der Wert von Chk_Margin (die man aus dem EA erhält) auf 1, dann rufen wir die Funktion MarginOK() auf, dies für uns zu übernehmen.

Das Ergebnis, das uns diese Funktion liefert, bestimmt den nächsten Schritt. Wenn der Anwender jedoch die Marge nicht prüfen möchte, fahren wir einfach fort und platzieren den Handel.

Jetzt sind wir also mit der Deklarierung und Definition unserer Klasse und den Mitgliederfunktionen fertig. Wir müssen uns jedoch noch mit einigen anderen Aufgaben beschäftigen, die wir in unserem EA-Code ausführen lassen möchten. Dazu gehören die Prüfung nach vorhandenen Balken, die Prüfung nach neuen Balken und die Prüfung nach verfügbaren geöffneten Positions. Sie werden alle in unserem EA-Code ausgeführt.

Für eine Aufzählung aller Funktionen und Methoden unserer Klasse, klicken Sie im MetaEditor, wie unten gezeigt, auf den Befehl/Menü 'Funktionen'. Diese Funktion zeigt alle Mitgliederfunktionen, einschließlich des Destructors, die wir in unserem Code nicht explizit deklariert haben.

Die geschützten Mitglieder werden durch grüne Pfeile gekennzeichnet, während der Constructor und Destructor mit blauen Pfeilen markiert sind.

Mitgliederfunktionen der Klasse

Abb. 3 Unsere Mitgliederfunktionen der Klasse zeigen den Klassen-Destructor

Was kommt als nächstes?

Ach was, habe ich da gerade Fehlersuche gehört? Vielleicht haben Sie ja recht. Testen schadet nie, denn dann sehen Sie, ob Ihr Code Fehler hat und Sie nichtenttäuscht sind, wenn Sie ihn später dann für die Öffentlichkeit freigeben. Das Problem ist jedoch, dass es sich hier nur um eine einzuschließende Datei handelt und keinen Expert Advisor Code oder Skript oder Indikator-Code, der an ein Chart angehängt werden kann. Jetzt haben Sie zwei Möglichkeiten (aus meiner Erfahrung):

Abb. 4 .mqh Dateien können nicht kompiliert werden

2.2 DEN EXPERT ADVISOR SCHREIBEN

Ich nehme an, Ihr Editor ist immer noch offen? Rufen Sie ein neues Dokument auf, doch wählen diesmal den Expert Advisor. (Für Einzelheiten verweise ich auf den ersten Beitrag). Und nennen Ihren EA jetzt ‘my_oop_ea’ .

Sie sollten also jetzt hier sein:

Jetzt können wir unseren OOP-basierten EA schreiben.

Als ersten Schritt schließen wir die Klasse, die wir gerade geschrieben haben mit Hilfe des #include Präprozessor-Befehls ein. Schließen Sie die Klasse gleich unmittelbar nach dem letzten Präpozessor Eigenschaftsbefehl ein.

//+------------------------------------------------------------------+
//|                                                    my_oop_ea.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
// Include  our class
#include <my_expert_class.mqh>

Dies kann auf zweierlei Weise erfolgen:

// Include  using angle brackets
#include <my_expert_class.mqh>
// Include  using quotations
#include "my_expert_class.mqh"

Mit Hilfe einer runden Klammer und den < > Zeichen (< ... >), das bedeutet, dass die einzuschließende Datei aus dem Standard 'Include'-Directory genommen wird (also dem 'Include'-Ordner innerhalb des MQL5 Directory). Das aktuelle Directory (der Experts Ordner innerhalb des MQL5 Directory wird als möglicher Standort, um nach der Datei zu suchen, nicht berücksichtigt). Wird die Datei mit runden einer runden Klammer und den Anführungszeichen eingeschlossen (" ... "), heißt das, dass die Datei im aktuellen Directory gesucht wird (nämlich der Experts Ordner), und das Standard-Directory (der 'Include'-Ordner) nicht entsprechend geprüft wird.

Wird Ihre Klasse im 'Include'-Ordner gespeichert (Standard-Directory) und Sie verwenden die Anführungszeichen anstelle der runden Klammern plus <> oder umgekehrt, dann wird Ihnen bei der Kompilierung des Codes ein Fehler gemeldet.

Abb. 5 Eine Fehlermeldung wird angezeigt, wenn die einzuschließende Datei nicht gefunden wird

EA-EINGABEPARAMETER

//--- input parameters
input int      StopLoss=30;      // Stop Loss
input int      TakeProfit=100;   // Take Profit
input int      ADX_Period=14;    // ADX Period
input int      MA_Period=10;     // Moving Average Period
input int      EA_Magic=12345;   // EA Magic Number
input double   Adx_Min=22.0;     // Minimum ADX Value
input double   Lot=0.2;          // Lots to Trade
input int      Margin_Chk=0;     // Check Margin before placing trade(0=No, 1=Yes)
input double   Trd_percent=15.0; // Percentage of Free Margin To use for Trading

Die meisten Eingabeparameter hier sind bereits bekannt. Sehen wir uns kurz die neuen an.

Wir haben eine ganzzahlige Variable eingeführt, die einen Wert von 1 haben soll, wenn wir unsere Marge überprüfen wollen oder den Wert 0, wenn wir dies nicht tun wollen. Wir haben auch eine andere Variable deklariert, die den maximalen Prozentsatz der freien Marge enthalten soll, der bei Eröffnung einer Position verwendet werden soll. Diese Werte werden später von unserem Klassenobjekt, wenn es erzeugt ist, verwendet werden.

Unmittelbar nach den Eingabeparametern legen wir zwei andere Parameter fest (STP und TKP), die wir bearbeiten können wollen (um auf Kurse mit 5 und 3 Ziffern reagieren zu können), da wir die Werte der Eingabe-Variablen nicht verändern können. Dann erzeugen wir ein Objekt unserer Klasse, das wir innerhalb unseres EA Codes verwenden.

//--- Other parameters
int STP,TKP;   // To be used for Stop Loss & Take Profit values
// Create an object of our class
MyExpert Cexpert;

Wie bereits zuvor erklärt, verwendet man zur Erzeugung eines Objekts einer Klasse den Klassennamen, gefolgt vom Namen des Objekts, das man erzeugen möchte. Wir haben hier ein Objekt namens Cexpert erzeugt, das ein Typ der MyExpert Klasse ist. Mit Hilfe von Cexpert kann man nun auf alle public Mitgliederfunktionen der MyExpert Klasse zugreifen.

EA INITIALISIERUNGSABSCHNITT

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- Run Initialize function
   Cexpert.doInit(ADX_Period,MA_Period);
//--- Set all other necessary variables for our class object
   Cexpert.setPeriod(_Period);     // sets the chart period/timeframe
   Cexpert.setSymbol(_Symbol);     // sets the chart symbol/currency-pair
   Cexpert.setMagic(EA_Magic);    // sets the Magic Number
   Cexpert.setadxmin(Adx_Min);    // sets the ADX miniumm value
   Cexpert.setLOTS(Lot);          // set the Lots value
   Cexpert.setchkMAG(Margin_Chk); // set the margin check variable
   Cexpert.setTRpct(Trd_percent); // set the percentage of Free Margin for trade
//--- Let us handle brokers that offers 5 digit prices instead of 4
   STP = StopLoss;
   TKP = TakeProfit;
   if(_Digits==5 || _Digits==3)
     {
      STP = STP*10;
      TKP = TKP*10;
     }  
//---
   return(0);
  }

Jetzt haben wir die doInit Funktion unserer Klasse aufgerufen und übertragen ihr die Varaiblen für ADX- und MA-Zeitraum. Als nächstes richten wir alle anderen Variablen ein, die das Objekt, das wir gerade erzeugt haben, benötigt, damit sie in den Mitgliedervariablen des Objekts mit Hilfe der Funktionen, die wir während des Schreibens unserer Klasse, beschrieben haben, abgelegt werden.

Die nächste Codezeile sollte nicht merkwürdig sein - wir müssen nur unsere Stop Loss und Take Profit Werte für die Kurse mit 3 oder 5 Ziffern anpassen.

EA DE-INITIALISIERUNGSABSCHNITT

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Run UnIntilialize function
   Cexpert.doUninit();
  }

Wir haben die doUninit Funktion der Klasse aufgerufen, damit sie alle Indikator-Handles freigibt, die in der Initialisierungsfunktion des EAs erzeugt werden müssen.

EA ONTICK-ABSCHNITT

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Do we have enough bars to work with
   int Mybars=Bars(_Symbol,_Period);
   if(Mybars<60) // if total bars is less than 60 bars
     {
      Alert("We have less than 60 bars, EA will now exit!!");
      return;
     }

//--- Define some MQL5 Structures we will use for our trade
   MqlTick latest_price;      // To be used for getting recent/latest price quotes
   MqlRates mrate[];          // To be used to store the prices, volumes and spread of each bar
/*
     Let's make sure our arrays values for the Rates
     is store serially similar to the timeseries array
*/
// the rates arrays
   ArraySetAsSeries(mrate,true);

Der erste Schritt hier ist das Prüfen der insgesamt verfügbaren Balken. Reichen sie für einen Handel durch unseren EA aus, gut. Ansonsten wird der EA erst dann mit dem Handel beginnen, wenn genügend Balken vorhanden sind (d.h. 60 Balken). Danach haben wir zwei Variablen der MQL5-Struktur (MqlTick und MqlRates) deklariert. Und zuletzt verwenden wir die ArraySetAsSeries Funktion beim Arrays mit den Raten.

//--- Get the last price quote using the MQL5 MqlTick Structure
   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("Error getting the latest price quote - error:",GetLastError(),"!!");
      return;
     }

//--- Get the details of the latest 3 bars
   if(CopyRates(_Symbol,_Period,0,3,mrate)<0)
     {
      Alert("Error copying rates/history data - error:",GetLastError(),"!!");
      return;
     }

//--- EA should only check for new trade if we have a new bar
// lets declare a static datetime variable
   static datetime Prev_time;
// lest get the start time for the current bar (Bar 0)
   datetime Bar_time[1];
// copy time
   Bar_time[0] = mrate[0].time;
// We don't have a new bar when both times are the same
   if(Prev_time==Bar_time[0])
     {
      return;
     }
//copy time to static value, save
   Prev_time = Bar_time[0]; 
  

Hier haben wir die SymbolInfoTick Funktion eingesetzt, um die letzte Kursquote zu bekommen und mit Hilfe von CopyRates die letzten Raten für die letzten drei Balken (inkl. aktueller Balken). Die nächsten Codezeilen prüfen, ob wir einen neuen Balken haben. Wir haben zwei Datum/Uhrzeit Variablen deklariert - eine ist eine statische Variable (Prev_Time), die andere ist Bar_Time.

Sobald wir einen neuen Balken haben, wird die Uhrzeit des Balkens in der statischen Variable Prev_Time gespeichert, damit wir ihren Wert mit dem Wert der Bar_Time bei der nächsten Kursschwankung vergleichen können. Wenn bei der nächsten Kursschwankung Prev_Time = Bar_Time ist, dann ist es immer noch der gleiche Balken, dessen Uhrzeit gepeichert wurde. Unser EA entspannt sich also.

Ist Bar_Time allerdings nicht = Prev_Time, dann haben wir einen neuen Balken. Die Startzeit des neuen Balkens speichern wir in der statischen Datum/Uhrzeit-Variable, sodass Prev_Time und unser EA nun fortfahren können, und nach neuen BUY- oder SELL-Gelegenheiten Ausschau halten können.

//--- we have no errors, so continue
//--- Do we have positions opened already?
    bool Buy_opened = false, Sell_opened=false; // variables to hold the result of the opened position
    
    if (PositionSelect(_Symbol) ==true)  // we have an opened position
    {
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            Buy_opened = true;  //It is a Buy
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            Sell_opened = true; // It is a Sell
         }
    }

Wir legen fest, dass wir prüfen wollen, ob wir bereits eine geöffnete Position haben. Wir wollen ja sichergehen, nur dann einen Buy-Handel zu eröffnen, wenn ein solcher Buy nicht bereits offen ist - und analog eben auch für Sell-Handel.

// Copy the bar close price for the previous bar prior to the current bar, that is Bar 1
   Cexpert.setCloseprice(mrate[1].close);  // bar 1 close price
//--- Check for Buy position
   if(Cexpert.checkBuy()==true)
     {
      // Do we already have an opened buy position
      if(Buy_opened)
        {
         Alert("We already have a Buy Position!!!");

         return;    // Don't open a new Buy Position
        }
      double aprice = NormalizeDouble(latest_price.ask,_Digits);              // current Ask price
      double stl    = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss
      double tkp    = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take profit
      int    mdev   = 100;                                                    // Maximum deviation
      // place order
      Cexpert.openBuy(ORDER_TYPE_BUY,aprice,stl,tkp,mdev);
     }

So, jetzt sind wir wieder beim Objekt, das wir erzeugt haben. Warum das? Ganz einfach: wir konnten alle Prüfungen durchführen, die für unser Objekt erforderlich sind, um seine arbeit auszuführen.

Als erstes holen wir uns den Schlusskur für den vorigen Balken mit Hilfe unserer Objekt-Mitgliederfunktion setCloseprice.

Dann rufen wir die checkBuy Funktion auf, um herauszufinden, ob eine Buy-Bedingung eingerichtet ist. Liefert die Funktion TRUE, dann wollen wir sichergehen, dass wir nicht bereits eine Buy-Postioin offen haben. Ist dies nicht der Fall, bereiten wir die erforderlichen Variablen vor, die von unserer Order verwendet werden sollen (OrderTyp, aktueller BRIEFkurs, Stop Loss, Take Profit und maximale Abweichung) und rufen die openBuy Funktion auf. Sehen Sie wie einfach die Klasse, die wir geschrieben haben, verwendet werden kann?

//--- Check for any Sell position
   if(Cexpert.checkSell()==true)
     {
      // Do we already have an opened Sell position
      if(Sell_opened)
        {
         Alert("We already have a Sell position!!!");
         return;    // Don't open a new Sell Position
        }
      double bprice=NormalizeDouble(latest_price.bid,_Digits);                 // Current Bid price
      double bstl    = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss
      double btkp    = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit
      int    bdev=100;                                                         // Maximum deviation
      // place order
      Cexpert.openSell(ORDER_TYPE_SELL,bprice,bstl,btkp,bdev);
     }

Das ist das Gleiche, was wir oben vorhin schon gemacht haben. Da wir jetzt nach einem Sell prüfen wollen, rufen wir die checkSell Funktion auf, und wenn sie TRUE liefert, und wir noch keine Sell-Postioin offen haben, bereiten wir die erforderlichen Variablen für eine Platzierung unserer Order vor (OrderTyp, aktueller BRIEFkurs, Stop Loss, Take Profit und maximale Abweichung) und rufen danach die openSell Funktion auf.

Ziemlich einfach, oder? Und damit haben wir die Codes fertig geschrieben. Jetzt durchsuchen wir unseren Code auf Fehler. Wenn Sie nicht wissen, wie Sie das Fehlersuchprogramm verwenden sollen, dann lesen Sie bitte den ersten Beitrag, dort wird alles erklärt.

Wenn Sie F5 oder die Schaltfläche "Debug" drücken, wird die eingeschlossene Datei (unsere Klasse) eingeschlossen und geprüft. Besteht dort ein Fehler, wird er gemeldet. Sobald Sie also einen Fehler angezeigt bekommen, müssen Sie zum Code zurück und ihn dort bereinigen.

Abb. 6 Unsere einzuschließende Datei wird bei der Fehlersuche des Hauptcodes des EA mit eingeschlossen

Ist alles in Ordnung, haben Sie gut gearbeitet. Jetzt müssen wir unseren EA nur noch mit Hilfe des Strategie-Testers überprüfen. Vor einem Test mit dem Strategie-Tester müssen wir unseren EA kompilieren. Dazu entweder auf Compile oder auf F7 drücken.

Abb. 7 Zur Kompilierung unseres Codes auf die Menüschaltfläche Compile drücken

Von der Handelsterminal Menüleiste gehen Sie dann auf Ansicht --> Strategie-Tester oder drücken CONTROL+R und der Strategie-Tester wird gestartet. (Einzelheiten, wie man mit dem Strategie-Tester umgeht, stehen ebenfalls im ersten Beitrag).

Damit Sie Ihren EA mit dem Strategie-Tester testen können, müssen Sie unbedingt zuvor kompiliert haben. Haben Sie dies nicht getan, erhalten Sie eine Fehlermeldung, wenn Sie in der Einstellungsleiste des Strategie-Testers den Expert Advisor auswählen. (Ach, da sehe ich gerade, das ist ja die neue Version des Terminals.)

Abb. 8 Der Code des EAs sollte vor seiner Anwendung im Strategie-Tester kompiliert sein

Unten sehen Sie die Ergebnisse des Strategie-Testers für unseren OOP-basierten Expert Advisor.

Abb. 9 Die Handelsergebnisse für unseren Objekt-orientierten Expert Advisor

Der Graph:

Abb. 10 Die Graph-Ergebnisse für unseren Objekt-orientierten Expert Advisor

Der Bericht/Logbuch der Handelsaktivität

Abb. 11 Die Ergebnisse der Handelsaktivität für unseren Objekt-orientierten Expert Advisor

Das Chart für den Test:

Abb. 12 Die Handelschart-Ergebnisse für unseren Objekt-orientierten Expert Advisor


Fazit

In diesem Beitrag haben wir zu einem gewissen Grad die Grundlagen einer Klasse besprochen und wie man sie zum schreiben eine einfachen Expert Advisors anwendet. Wir sind dabei nicht sehr weit auf die fortgeschrittenen Bereiche von Klassen eingegangen, doch die Erläuterungen in diesem Artikel sind ausreichend, damit Sie sich auf eine Stufe brigen können auf der Sie problemlos Ihren eigenen Objekt-orientierten Expert Advisor Code schreiben können.

Wir haben des weiteren besprochen, wie man auf freie Marge prüft, damit unser EA keinen Handel ausführt, wenn die verfügbare freie Marge für die Position, die wir eröffnen wollen, nicht ausreicht.

Sicherlich stimmen Sie mir jetzt zu, dass die neue MQL5-Sprache eine ganze Menge anzubieten hat und dass Sie kein Programmier-Guru sein müssen, um von dieser neuen Programmiersprache eindeutig zu profitieren. Und das ist der Hauptgrund, warum diese schrittweisen Anleitungen verfasst werden.