Erstellen eines EA, der automatisch funktioniert (Teil 02): Erste Schritte mit dem Code

Daniel Jose | 28 Februar, 2023

Einführung

Im vorangegangenen Artikel „Erstellen eines EA, der automatisch funktioniert (Teil 01): Konzepte und Strukturen“ haben wir die ersten Schritte besprochen, die jeder verstehen muss, bevor er mit der Erstellung eines Expert Advisors für den automatischen Handel beginnen kann. In diesem Artikel habe ich aufgezeigt, welche Konzepte berücksichtigt werden sollten und welche Struktur zu schaffen ist.

Aber wir haben nicht darüber gesprochen, wie man den Code schreibt. Mein Ziel war es, das Wissen der Leser, insbesondere der Anfänger, die keine praktischen Kenntnisse auf dem Gebiet der Programmierung haben, zu verbessern. Daher habe ich versucht, diesen Menschen die Welt von MQL5 näher zu bringen, indem ich ihnen gezeigt habe, wie man einen EA erstellt, der in einem vollautomatischen Modus arbeiten kann. Wenn Sie den vorherigen Artikel noch nicht gelesen haben, empfehle ich Ihnen dringend, ihn zu lesen, da es wichtig ist, den Kontext zu verstehen, in dem Sie bei der Erstellung eines automatisierten EA arbeiten werden.

Okay, fahren wir mit dem Schreiben des Codes fort. Wir beginnen mit dem Basissystem, dem Auftragssystem. Dies wird der Ausgangspunkt für jeden EA sein, den wir jemals erstellen wollen.


Planung

Die MetaTrader 5 Standardbibliothek bietet eine bequeme und geeignete Möglichkeit, mit dem Auftragssystem zu arbeiten. In diesem Artikel möchte ich jedoch noch weiter gehen und Ihnen zeigen, was sich hinter den Kulissen der Bibliothek TRADE von MetaTrader 5 verbirgt. Aber ich möchte Sie heute nicht davon abhalten, die Bibliothek zu nutzen. Vielmehr möchte ich beleuchten, was sich in dieser Blackbox befindet. Zu diesem Zweck werden wir eine eigene Bibliothek für die Übermittlung von Aufträgen entwickeln. Sie verfügt nicht über alle Ressourcen, die in der MetaTrader 5 Standardbibliothek enthalten sind, aber die Funktionen, die für die Erstellung und Pflege eines funktionalen, robusten und zuverlässigen Auftragssystems erforderlich sind.

Bei unseren Entwicklungen müssen wir eine bestimmte Art von Konstruktion verwenden. Ich entschuldige mich bei denjenigen, die neu in der Programmierung sind, da sie sich anstrengen müssen, um den Erklärungen zu folgen. Ich werde mein Bestes tun, um alles so einfach wie möglich zu halten, damit Sie dem Ideal und den Konzepten folgen und sie verstehen können. Ich fürchte, dass Sie ohne Anstrengung nur das bewundern werden, was wir bauen werden, aber nicht in der Lage sein werden, selbst in MQL5 zu entwickeln.


Erstellen der Klasse C_Orders

Um eine Klasse zu erstellen, müssen wir zunächst eine Datei erstellen, die unseren Klassencode enthält. Navigieren Sie im MetaEditor-Browserfenster zum Ordner „Include“ und klicken Sie mit der rechten Maustaste darauf. Wählen Sie „Neue Datei“ und folgen Sie den Anweisungen in den folgenden Abbildungen:

Abbildung 1

Abbildung 01. Hinzufügen einer Include-Datei

Abbildung 02

Abbildung 02. So wird die erforderliche Datei erstellt


Nachdem Sie die Schritte aus Abbildung 01 und 02 ausgeführt haben, wird eine Datei erstellt, die in MetaEditor geöffnet wird. Der Inhalt ist weiter unten zu finden. Bevor ich weitermache, möchte ich kurz etwas erklären. Sehen Sie sich Abbildung 01 an - sie zeigt, dass Sie eine Klasse direkt erstellen können. Sie fragen sich vielleicht, warum wir das nicht tun. Das ist die richtige Frage. Der Grund dafür ist, dass, wenn wir eine Klasse unter Verwendung des in Abbildung 01 dargestellten Punktes erstellen, die Klasse nicht von Grund auf neu erstellt wird, sondern bereits einige vordefinierte Informationen oder Formate enthält. Im Rahmen dieser Artikelserie ist es jedoch wichtig, dass wir die Klasse von Grund auf neu erstellen. Kommen wir auf den Code zurück.

#property copyright "Daniel Jose"
#property link      ""
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+

Alle Zeilen mit den hellgrauen Texten sind Kommentare (als Beispielcode), die auf Wunsch entfernt werden können. Was uns interessiert, beginnt hier. Sobald die Datei geöffnet ist, können wir damit beginnen, ihr Code hinzuzufügen. Hier fügen wir den Code ein, der uns helfen wird, mit unserem Auftragssystem zu arbeiten.

Aber um den Code sicher, zuverlässig und robust zu machen, werden wir das Werkzeug verwenden, das MQL5 von C++ mitgebracht hat: Klassen. Wenn Sie nicht wissen, was Klassen sind, empfehle ich Ihnen, dies herauszufinden. Sie müssen nicht direkt zur C++-Dokumentation über Klassen gehen. Sie können über Klassen in der MQL5-Dokumentation lesen, die Ihnen einen guten Ausgangspunkt bietet. Außerdem ist der Inhalt der MQL5-Dokumentation leichter zu verstehen als die ganze Verwirrung, die C++ bei Leuten stiften kann, die noch nie etwas von Klassen gehört haben.

Im Allgemeinen ist die Klasse bei weitem der sicherste und effizienteste Weg, um Code von anderen Teilen des Codes zu isolieren. Das bedeutet, dass die Klasse nicht als Code behandelt wird, sondern als ein spezieller Datentyp, mit dem Sie viel mehr tun können als nur primitive Typen wie integer, double, bool und andere. Mit anderen Worten: Für ein Programm ist eine Klasse ein multifunktionales Werkzeug. Es muss nicht wirklich etwas darüber wissen, wie die Klasse erstellt wurde und was sie enthält. Das Programm muss nur wissen, wie es zu nutzen ist. Stellen Sie sich eine Klasse wie ein Elektrowerkzeug vor: Sie müssen nicht wissen, wie es gebaut wurde oder welche Komponenten es hat. Alles, was Sie wissen müssen, ist, wie man es zusammensetzt und nutzt, die Art und Weise, wie es funktioniert, macht keinen Unterschied bei der Nutzung. Dies ist eine einfache Definition für eine Klasse.

Und jetzt lassen Sie uns weitermachen. Als erstes werden wir die folgenden Zeilen erstellen:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
class C_Orders
{
        private :
        public  :
};
//+------------------------------------------------------------------+

Sie sind der grundlegendste Teil unseres Unterrichts. In der Regel wird der Name der Klasse mit dem Namen der Datei, in der sie enthalten ist, identisch sein, dies ist jedoch nicht notwendig. Aber genau das kann uns später helfen, die Klasse zu finden. Innerhalb der Klasse gebe ich zwei reservierte Wörter an, die den Grad des Informationsaustauschs zwischen ihnen angeben. Wenn Sie diese Wörter nicht hinzufügen, werden alle Elemente der Klasse als öffentlich betrachtet, d. h. jeder kann sie lesen, schreiben oder darauf zugreifen. Dies bricht einige der Merkmale, die uns zur Verwendung von Klassen veranlassen - Sicherheit und Robustheit - da jeder Teil des Codes in der Lage ist, auf die Inhalte der Klasse zuzugreifen und sie zu ändern.

Kurz gesagt: Alles, was zwischen der Deklaration des Wortes private und des Wortes public steht, ist nur innerhalb der Klasse zugänglich. Sie können hier globale Variablen verwenden, auf die von außerhalb des Klassencodes nicht zugegriffen werden kann. Auf alles, was nach dem Wort public deklariert wird, kann von überall zugegriffen werden, egal ob es Teil einer Klasse ist oder nicht. Jeder kann auf alles zugreifen, was sich dort befindet.

Sobald wir diese Datei wie oben gezeigt erstellt haben, können wir sie zu unserem EA hinzufügen. Dann wird unser EA den folgenden Code haben:

#property copyright "Daniel Jose"
#property version   "1.00"
#property link      "https://www.mql5.com/pt/articles/11223"
//+------------------------------------------------------------------+
#include <Generic Auto Trader\C_Orders.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+

In diesem Schritt haben wir unsere Klasse mit der Kompilieranweisung Include in den EA-Code eingebunden. Wenn wir also diese Direktive verwenden, versteht der Compiler, dass von diesem Moment an die Header-Datei C_Orders.mqh aus dem Include-Verzeichnis des Ordners Generic Auto Trader in das System eingebunden und kompiliert werden muss. Dazu gibt es einige Tricks, aber ich werde nicht ins Detail gehen, weil weniger erfahrene Leute bei dem Versuch, manche Details zu verstehen, völlig verloren sein können. Aber es geschieht genau das, was ich gerade beschrieben habe.


Definition der ersten Funktionen der Klasse C_Orders

Im Gegensatz zu dem, was viele Leute denken, tippt ein Programmierer nicht ununterbrochen Code. Im Gegenteil, bevor man etwas beginnt, muss der Programmierer durchdenken und studieren, was zu tun ist.

Deshalb sollten Sie, die Sie gerade Ihre Karriere beginnen, das Gleiche tun: Bevor Sie eine Zeile Code hinzufügen, sollten Sie alle Momente durchdenken. Lassen Sie uns darüber nachdenken, was wir in der Klasse C_Orders wirklich brauchen, um so wenig Arbeit wie möglich zu haben und das Beste aus der MetaTrader 5 Plattform herauszuholen?

Das Einzige, was mir einfällt, ist die Art und Weise, wie die Aufträge übermittelt werden. Wir brauchen nicht wirklich ein Mittel zum Verschieben und Löschen von Aufträgen oder zum Schließen von Positionen, da uns die MetaTrader 5-Plattform alle diese Möglichkeiten bietet. Wenn wir einen Auftrag auf dem Chart platzieren, erscheint er auf der Registerkarte „Handel“ des Toolbox-Fensters, sodass wir hier nicht viel zu tun brauchen.

Eine weitere Sache, die wir nicht wirklich brauchen, zumindest in dieser ersten Phase, ist ein Mechanismus zur Anzeige von Orders oder Positionen auf dem Chart, da die Plattform dies für uns erledigt. Das Einzige, was wir also implementieren müssen, ist eine Möglichkeit, Aufträge direkt aus dem EA zu senden, ohne das Auftragssystem in MetaTrader 5 zu durchlaufen.

Ausgehend von dieser Idee können wir mit der Programmierung dessen beginnen, was wir in diesem frühen Stadium wirklich brauchen, nämlich eine Funktion oder eine Prozedur, die es dem EA ermöglicht, Aufträge zu senden. An diesem Punkt fangen viele Leute an, Fehler zu machen, vor allem diejenigen, die nicht viel Wissen oder Erfahrung im Programmieren haben.

Da wir bereits festgelegt haben, dass wir MetaTrader 5 verwenden werden, um Aufträge und Positionen über die Toolbox der Plattform zu verwalten, müssen wir ein System zum Senden von Aufträgen an den Server entwickeln, was der grundlegendste Teil von allen ist. Dazu müssen wir jedoch einige Dinge über den Vermögenswert wissen, mit dem der EA arbeiten wird, um zu vermeiden, dass wir ständig über Funktionsaufrufe der MQL5-Standardbibliothek nach diesen Informationen suchen müssen. Beginnen wir mit der Definition einiger globaler Variablen innerhalb unserer Klasse.

class C_Orders
{
        private :
//+------------------------------------------------------------------+
                MqlTradeRequest m_TradeRequest;
                struct st00
                {
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                        bool    PlotLast;
                }m_Infos;

In diesen Variablen werden die von uns benötigten Daten gespeichert und sie sind in der gesamten Klasse sichtbar. Auf sie kann außerhalb der Klasse nicht zugegriffen werden, da sie nach dem Wort private deklariert sind. Das bedeutet, dass sie nur innerhalb der Klasse gesehen und aufgerufen werden können.

Nun müssen wir diese Variablen irgendwie initialisieren. Es gibt verschiedene Methoden dafür, aber aus meiner Erfahrung weiß ich, dass ich es oft vergesse, dies zu tun. Damit wir das nicht vergessen, werden wir den Klassenkonstruktor wie unten gezeigt verwenden. Schauen wir uns ein Beispiel an:

                C_Orders()
                        {
                                m_Infos.nDigits         = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_Infos.VolMinimal      = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_Infos.VolStep         = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_Infos.PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_Infos.ValuePerPoint   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_Infos.AdjustToTrade   = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
                                m_Infos.PlotLast        = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);
                        };

Klassenkonstruktoren sind ein sicherer Weg, um eine Klasse zu initialisieren, bevor man mit ihr arbeitet. Beachten Sie, dass ich hier einen Standardkonstruktor verwende, d. h. er erhält keine Parameter. Es gibt Fälle, in denen der Konstruktor Parameter erhalten kann, um etwas innerhalb der Klasse zu initialisieren. Aber das ist hier nicht nötig.

Achten Sie nun darauf, dass jede der Variablen auf eine bestimmte Art und Weise initialisiert wird, sodass sie beim Zugriff auf einen der Werte, sei es für einen Test oder für eine bestimmte Berechnung, richtig initialisiert werden. Aus diesem Grund sollten Sie immer Klassenkonstruktoren verwenden, um Ihre globalen und internen Klassenvariablen zu initialisieren, bevor irgendetwas anderes im Code getan wird.

Da wir einen Konstruktor verwenden, müssen wir auch einen Destruktor für die Klasse deklarieren. Es kann so einfach sein wie folgt:

                ~C_Orders() { }

Sehen Sie sich die Syntax an. Der Name des Konstruktors und des Destruktors ist derselbe wie der Name der Klasse. Der Destruktor-Deklaration wird jedoch eine Tilde (~) vorangestellt. Ein weiterer Punkt, der zu beachten ist, ist, dass sowohl der Konstruktor als auch der Destruktor keinen Rückgabewert haben. Der Versuch, dies zu tun, wird als Fehler betrachtet und führt dazu, dass der Code nicht kompiliert werden kann.

Wenn Sie bei der Initialisierung oder Beendigung einer Klasse einen Wert zurückgeben müssen, müssen Sie einen regulären Prozeduraufruf verwenden. Weder Konstruktoren noch Destruktoren können zu diesem Zweck verwendet werden.

Wie genau kann diese Art der Kodierung, die Konstruktoren und Destruktoren verwendet, helfen? Wie bereits erwähnt, kommt es sehr häufig vor, dass Sie versuchen, auf eine globale Variable zuzugreifen oder sie zu verwenden, ohne sie zu initialisieren. Dies kann zu ungewöhnlichen Ergebnissen und Programmierfehlern führen, selbst für erfahrene Programmierer. Durch die Verwendung von Konstruktoren zur Initialisierung globaler Klassenvariablen stellen wir sicher, dass sie immer verfügbar sind, wenn wir sie brauchen.

Machen Sie sich daher nicht die schlechte Angewohnheit, objektorientiert zu programmieren, ohne Konstruktoren und Destruktoren zu verwenden. Andernfalls kann es Ihnen viel Kopfzerbrechen bereiten, wenn Sie versuchen, den Grund dafür herauszufinden, warum ein Programm nicht richtig funktioniert.

Bevor Sie fortfahren, beachten Sie bitte ein Detail, das im Klassenkonstruktor enthalten ist. Sie wird im Folgenden hervorgehoben:

        m_Infos.PlotLast = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);

Im Gegensatz zu dem, was viele Leute denken, gibt es Unterschiede zwischen den Märkten, aber nicht so, wie Sie vielleicht denken. Der Unterschied liegt in der Art und Weise, wie die Vermögenswerte im Chart dargestellt werden. Die vorherige Zeile bestimmt den Typ des für das Asset verwendeten Histogramms.

Aber warum ist sie so wichtig? Wenn wir einen EA erstellen wollen, der mit verschiedenen Märkten oder Vermögenswerten arbeitet, müssen wir bedenken, dass diese unterschiedliche Darstellungssysteme haben können. Es gibt im Wesentlichen zwei Methoden:

Auch wenn sie gleich aussehen, betrachten der EA und der Handelsserver sie als unterschiedliche Typen der Darstellung. Dies wird besonders deutlich und wichtig, wenn der EA anfängt, Aufträge zu senden — und zwar keine Marktaufträge, die zum besten Preis ausgeführt werden, sondern schwebende Aufträge oder pending Orders. In diesem Fall ist es für uns wichtig, eine Klasse zu schaffen, die in der Lage ist, solche Aufträge zu senden, die in der Tiefe des Marktes bleiben.

Aber warum muss ein EA wissen, welches grafische Darstellungssystem verwendet wird? Der Grund dafür ist einfach: Wenn ein Vermögenswert eine BID-Chart verwendet, ist der LAST-Preis immer Null. In diesem Fall ist der EA nicht in der Lage, einige Auftragstypen an den Server zu senden, da er nicht weiß, welcher Auftragstyp der richtige ist. Selbst wenn es gelingt, einen Auftrag zu senden und der Server ihn annimmt, wird der Auftrag nicht korrekt ausgeführt, da der Auftrag nicht korrekt ausgefüllt wurde.

Dieses Problem tritt auf, wenn Sie einen EA für den Aktienmarkt erstellen und versuchen, ihn auf dem Forex-Markt einzusetzen. Auch das Gegenteil ist der Fall: Wenn das System LAST-Kurse für die Darstellung verwendet, der EA aber für den Forex-Markt entwickelt wurde, wo Charts auf der Grundlage von BID-Kursen dargestellt werden, kann der EA die Order möglicherweise nicht zum richtigen Zeitpunkt senden, da sich der Last-Kurs ändern kann, während BID und ASK statisch bleiben.

Auf dem Aktienmarkt wird die Differenz zwischen BID und ASK jedoch immer größer als Null sein, was bedeutet, dass es immer eine Spanne zwischen ihnen geben wird. Das Problem besteht darin, dass der EA bei der Erstellung eines Auftrags, der an den Server gesendet werden soll, diesen möglicherweise falsch erstellt, insbesondere wenn er die BID- und ASK-Werte nicht berücksichtigt. Dies kann für den EA, der versucht, innerhalb der bestehenden Spanne zu handeln, fatal sein.

Aus diesem Grund prüft der EA, mit welchem Markt bzw. Chartsystem er arbeitet, sodass er ohne Änderungen oder Neukompilierung vom Forex- auf den Aktienmarkt oder zurück portiert werden kann.

Wir haben bereits gesehen, wie wichtig kleine Nuancen sind. Ein einfaches Detail kann alles in Frage stellen. Sehen wir uns nun an, wie man einen schwebenden Auftrag an einen Handelsserver sendet.


Senden eines schwebenden Auftrags an den Server

Beim Studium der MQL5-Sprachdokumentation in Bezug auf die Funktionsweise des Handelssystems fand ich die Funktion OrderSend. Sie müssen die Funktion nicht direkt verwenden, da Sie die MetaTrader 5 Standardbibliothek und ihre CTrade-Klasse nutzen können. Hier möchte ich jedoch zeigen, wie diese Funktion hinter den Kulissen funktioniert.

Um Vorgangsanforderungen an den Server zu senden, verwenden wir einfach die Funktion OrderSend, aber wir müssen vorsichtig sein, wenn wir diese Funktion direkt verwenden. Um sicherzustellen, dass sie nur innerhalb der Klasse verwendet wird, fügen wir sie in private ein. Auf diese Weise können wir die gesamte Komplexität im Zusammenhang mit dem Ausfüllen der Anfrage in die öffentlichen Funktionen der Klasse auslagern, auf die der EA zugreifen kann.

Es ist wichtig, sehr vorsichtig zu sein und die erforderlichen Tests durchzuführen, damit wir wissen, wann etwas schief läuft und wann es gut läuft. Da alle Anfragen die Funktion OrderSend durchlaufen, ist es zweckmäßig, eine eigene Prozedur dafür zu erstellen, wie unten gezeigt:

                ulong ToServer(void)
                        {
                                MqlTradeCheckResult TradeCheck;
                                MqlTradeResult      TradeResult;
                                bool bTmp;
                                
                                ResetLastError();
                                ZeroMemory(TradeCheck);
                                ZeroMemory(TradeResult);
                                bTmp = OrderCheck(m_TradeRequest, TradeCheck);
                                if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
                                if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK);
                
                                return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
                        }

Lassen Sie sich von dem obigen Code nicht abschrecken. Kurz gesagt, es garantiert, dass die Anfrage gesendet wird und informiert uns, ob sie korrekt gesendet wurde. Wenn der Versand fehlgeschlagen ist, werden wir sofort benachrichtigt. Schauen wir uns nun genauer an, wie es funktioniert. Zunächst setzen wir die interne Fehlervariable zurück, sodass Sie alle generierten Fehler an jeder Stelle des EA analysieren können.

Vergessen Sie nicht, immer die Rückgabewerte der kritischsten Prozeduren zu überprüfen. Sobald dies geschehen ist, setzen wir die internen Strukturen der Funktion zurück und senden eine Anfrage zur Überprüfung des Auftrags. Achtung: Anstatt zu prüfen, ob ein Fehler zurückgegeben wurde, um herauszufinden, welcher Fehler aufgetreten ist, sollte der Wert der Fehlervariablen geprüft werden. Wird ein anderer Wert als erwartet angezeigt, wird der Aufrag nicht an den Server gesendet. Wenn die Prüfung keine Fehler ergibt, wurde der Auftrag an den Handelsserver gesendet.

Das Detail, das den Unterschied ausmacht: Unabhängig davon, wer den Fehler erzeugt, wird der Fehlercode auf dem Chart angezeigt, wenn ein Fehler auftritt. Wenn alles einwandfrei ist, wird die Meldung nicht angezeigt. Auf diese Weise spielt es keine Rolle, welche Art von Anfrage wir an den Server senden. Wenn wir nur das oben gezeigte Verfahren anwenden, werden wir immer die gleiche Art der Handhabung haben. Wenn weitere Informationen oder Tests erforderlich sind, reicht es aus, diese in das obige Verfahren einzubauen. Auf diese Weise können Sie stets sicherstellen, dass das System so stabil und zuverlässig wie möglich ist.

Die Funktion gibt zwei mögliche Werte zurück: Die Ticketnummer des Auftrags, der im Erfolgsfall vom Server zurückgegeben wird, oder Null, was einen Fehler indiziert. Um den Fehlertyp zu ermitteln, prüfen wir den Wert der Variablen _LastError. In einem automatisierten EA würde sich diese Meldung an anderer Stelle befinden, oder sie wäre nur eine Protokollmeldung, je nach Zweck des EA. Da es aber darum geht, Ihnen zu zeigen, wie man einen EA erstellt, füge ich diese Information hinzu, damit Sie wissen, woher die Nachricht kommt.

Jetzt haben wir ein weiteres Problem zu lösen: Sie können nicht jeden beliebigen Preis an den Server senden, weil der Auftrag abgelehnt werden könnte. Sie müssen den richtigen Preis eingeben. Manche sagen, dass es ausreicht, den Wert zu normalisieren, aber in den meisten Fällen funktioniert das nicht. Tatsächlich müssen wir eine kleine Berechnung durchführen, um einen korrekten Preis zu verwenden. Wir werden hier die folgende Funktion verwenden:

inline double AdjustPrice(const double value)
                        {
                                return MathRound(value / m_Infos.PointPerTick) * m_Infos.PointPerTick;
                        }

Durch diese einfache Berechnung wird der Preis so angepasst, dass er unabhängig vom eingegebenen Wert auf einen angemessenen Wert normiert und vom Server akzeptiert wird.

Eine Sache weniger, über die man sich Sorgen machen muss. Die bloße Tatsache, dass wir die obige Funktion erstellt haben, führt jedoch nicht dazu, dass ein Auftrag erstellt oder an den Server gesendet wird. Dazu müssen wir die Struktur MqlTradeRequest ausfüllen, die wir bereits im vorherigen Code gesehen haben und auf die wir über eine private globale Variable m_TradeRequest zugreifen. Auch diese Struktur sollte korrekt ausgefüllt werden.

Für jeden Auftrag ist eine eigene Form zu wählen. Aber hier wollen wir nur einen Auftrag senden, damit der Server eine einen schwebenden Auftrag (eng. pending order) korrekt platziert. Sobald dies geschehen ist, zeigt die MetaTrader 5-Plattform den schwebenden Auftrag auf dem Chart und in der Toolbox an.

Das Ausfüllen dieser Struktur ist einer der Hauptgründe für Probleme bei einem Expert Advisor, sowohl im manuellen als auch im automatisierten Modus. Daher werden wir unsere Klasse verwenden, um eine Abstraktion zu schaffen und so das korrekte Ausfüllen zu erleichtern, damit unser Auftrag vom Handelsserver akzeptiert wird.

Aber bevor wir die Funktion erstellen, sollten wir ein wenig nachdenken. Wir müssen ein paar Fragen beantworten: