English Русский 中文 Español 日本語 Português
preview
Erstellen eines EA, der automatisch funktioniert (Teil 02): Erste Schritte mit dem Code

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

MetaTrader 5Handel | 28 Februar 2023, 08:44
1 016 0
Daniel Jose
Daniel Jose

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:

  • BID-Charts, die auf dem Devisenmarkt zu sehen sind;
  • LAST-Charts, die wir häufig auf dem Aktienmarkt sehen.

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:

  • Welche Art von Operation werden wir durchführen (Kauf oder Verkauf)?
  • Zu welchem Preis wollen wir das Handelsgeschäft abschließen?
  • Wie hoch wird das Volumen sein?
  • Welcher Zeitraum ist für die Operation vorgesehen?
  • Welche Art von Aufträgen werden wir verwenden: Kauf-Limit, Kauf-Stopp, Verkaufs-Limit oder Verkaufs-Stopp (wir sprechen jetzt noch nicht von Verkauf-Stopplimit oder Kauf-Stopplimit)?
  • Welcher Markt wird gehandelt: Devisenmarkt oder Aktienmarkt?
  • Welchen Stop-Loss (Verlustbegrenzung) soll ich setzen?
  • Welchen Take-Profit (Gewinnziel) will ich erreichen?
  • Wie Sie sehen, gibt es mehrere Fragen, von denen einige nicht direkt in der vom Server benötigten Struktur verwendet werden können. Daher werden wir eine Abstraktion schaffen, um eine praktischere Modellierung zu implementieren, damit unser EA ohne Probleme funktionieren kann. Alle Anpassungen und Korrekturen werden von der Klasse vorgenommen. Daraus ergibt sich die folgende Funktion:

                    ulong CreateOrder(const ENUM_ORDER_TYPE type, double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                            {
                                            double  bid, ask, Desloc;                      
    					
                                    	Price = AdjustPrice(Price);
                                            bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID));
                                            ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                            ZeroMemory(m_TradeRequest);
                                            m_TradeRequest.action           = TRADE_ACTION_PENDING;
                                            m_TradeRequest.symbol           = _Symbol;
                                            m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                            m_TradeRequest.type             = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                                        (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
                                            m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                            Desloc = FinanceToPoints(FinanceStop, Leverage);
                                            m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                            Desloc = FinanceToPoints(FinanceTake, Leverage);
                                            m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                            m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                            m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                            m_TradeRequest.deviation        = 1000;
                                            m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                                    
                                            return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                    };
    
    

    Diese einfache Funktion hat eine wunderbare Wirkung. Es nimmt die Daten, die wir handeln wollen, und wandelt sie in die Form um, die der Server erwartet, unabhängig davon, ob wir mit Devisen oder Aktien handeln.

    Um diese Funktion zu nutzen, teilen wir ihr einfach mit, was wir kaufen oder verkaufen möchten, den gewünschten Preis, den gewünschten Stop-Loss, den gewünschten Take-Profit, den gewünschten Hebel und ob der Handel intraday oder langfristig ist. Die Funktion berechnet den Rest, um sicherzustellen, dass der Auftrag korrekt erstellt wird. Dazu müssen wir jedoch einige Berechnungen und Anpassungen vornehmen, um angemessene Werte zu erhalten. Bei der Verwendung der oben genannten Funktion müssen zwei Punkte beachtet werden, um Probleme bei der Erstellung von Aufträgen durch den Server zu vermeiden.

    Aufgrund dieser Probleme werden Aufträge häufig vom Server abgelehnt. Diese sind Stop-Loss und Take-Profit. Wenn wir hier bei einem Finanzwert Null zuweisen, wird die Funktion keinen Take-Profit und Stop-Loss generieren. Sie müssen also vorsichtig sein, wenn Sie Null angeben, wenn die Strategie dies erfordert, da die Stopp-Levels nicht erstellt werden (Sie können dies später tun, aber zu diesem Zeitpunkt werden sie nicht hinzugefügt). Ein weiterer Punkt ist, dass negative Werte für Stop-Loss bei Kaufgeschäften und ein negativer Wert für Take-Profit-Werte bei Verkaufsgeschäften keinen Sinn machen. Die Funktion wird diese Dinge ignorieren. Sie können einfach mit positiven Werten arbeiten, und alles wird gut.

    Wenn man sich diese Funktion ansieht, kann man verwirrt sein und denken, sie sei zu kompliziert. Aber wenn wir sehen, wie es funktioniert, werden Sie Ihre Meinung ändern. Doch bevor wir sehen, wie es funktioniert, wollen wir die Funktion FinanceToPoints analysieren, die in dem obigen Verfahren erwähnt wird. Der Code der Funktion FinanceToPoints ist unten dargestellt:

    inline double FinanceToPoints(const double Finance, const uint Leverage)
                            {
                                    double volume = m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1));
                                    
                                    return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
                            };
    
    

    Versuchen wir herauszufinden, was hier vor sich geht, denn das kann ein wenig knifflig sein. Wenn Sie die Funktion nicht verstehen, wird es für Sie schwierig sein zu verstehen, was passiert, wenn die Klasse eine Order an den Server sendet und der Server Take-Profit- und Stop-Loss-Aufträge mit einem bestimmten Abstand zum Einstiegskurs platziert. Daher ist es wichtig zu verstehen, wie das System funktioniert, um diese Funktion zu verstehen.

    Jeder Vermögenswert verfügt über Informationen, die zur Umwandlung eines finanziellen Wertes in einen Punkt verwendet werden können. Um dies zu verstehen, sehen Sie sich die folgenden Bilder an:

    Abbildung 05 Abbildung 06 Abbildung 07


    In allen oben genannten Zahlen sind Punkte hervorgehoben, die für die Umrechnung eines finanziellen Wertes in Punkte verwendet werden. Bei Forex-Symbolen werden die Ticketgröße und der Ticketwert nicht angezeigt, aber sie sind 1,0 für den Ticketwert und 0,00001 für die Größe (Anzahl der Punkte). Vergessen Sie diese Werte nicht.

    Betrachten wir nun Folgendes: Der finanzielle Wert ist das Ergebnis der Division des Handelsvolumens durch die Anzahl der Punkte, multipliziert mit dem Wert jedes Punktes. Nehmen wir zum Beispiel an, dass wir einen Vermögenswert mit einem Mindestvolumen von 100 und einer Auftragsgröße von 2x, also dem doppelten Mindestvolumen, handeln. In diesem Fall beträgt das Volumen 100 x 2, d. h. 200. Kümmern Sie sich vorerst nicht um diesen Wert, machen wir weiter. Um den Wert eines jeden Punktes zu ermitteln, können wir die folgende Berechnung durchführen:

    Der Wert eines Punktes ist gleich dem Wert pro Punkt geteilt durch die Anzahl der Punkte. Viele Menschen gehen davon aus, dass die Anzahl der Punkte 1 ist, aber das ist ein Irrtum. In der obigen Abbildung können Sie sehen, dass die Anzahl der Punkte unterschiedlich sein kann. Für den Devisenhandel beträgt er 0,00001. Deshalb sollten Sie auf den Punktwert achten. Sorgen Sie dafür, dass das Programm den richtigen Wert erfasst und verwendet. Nehmen wir zum Beispiel an, die Anzahl der Punkte ist 0,01 und der Wert jedes Punktes ist 0,01. In diesem Fall ergibt die Division des einen Wertes durch den anderen den Wert 1. Aber dieser Wert kann unterschiedlich sein. Während der Wert auf dem Devisenmarkt beispielsweise 0,00001 beträgt, liegt er für den Dollar, der auf der B3 (Bolsa do Brasil) gehandelt wird, bei 10.

    Kommen wir nun zu einer weiteren Sache. Um es einfacher zu machen, nehmen wir an, dass der Nutzer einen Handel für einen Vermögenswert mit einem Mindestvolumen von 100 mit einem Volumen von x3 ausführen möchte. Die Größe des Tickets ist 0,01 und der Wert ist 0,01. Dennoch will der Nutzer ein finanzielles Risiko von 250 Euro eingehen. Um wie viele Punkte sollte der Einstiegskurs erhöht werden, um diesen Wert von 250 zu erreichen? Dies ist die Aufgabe des oben beschriebenen Verfahrens. Es berechnet den Wert und passt ihn an, damit wir je nach Fall eine positive oder negative Verschiebung haben, sodass die finanziellen Werte 250 betragen werden. In diesem Fall haben wir 2,5 oder 250 Punkte.

    Dieses Beispiel scheint einfach zu sein. Aber versuchen Sie, es schnell zu tun, während der Handel der Markt, wenn die Volatilität ist hoch, und Sie haben zu entscheiden, welche Größe zu verwenden, sehr schnell, sodass Sie nicht überschreiten die akzeptierten Risiko Grenzen und nicht bedauern, dass Sie eröffnet weniger als könnte. An diesem Punkt werden Sie verstehen, wie wichtig es ist, einen gut programmierten EA an Ihrer Seite zu haben.

    Mit dieser Klasse kann der EA einen korrekt konfigurierten Auftrag an den Server senden. Um jedoch zu sehen, wie es gemacht wird, wollen wir unser Auftragssystem testen.


    Testen des Auftragssystem

    Ich möchte betonen, dass der Zweck dieser Serie nicht darin besteht, einen EA zu erstellen, der manuell gesteuert werden kann. Die Idee ist, zu zeigen, wie ein Anfängerprogrammierer mit möglichst wenig Aufwand einen automatisch handelnden EA erstellen kann. Wenn Sie wissen wollen, wie man einen EA für den manuellen Handel erstellt, lesen Sie bitte eine andere Serie „Entwicklung eines EA von Grund auf“. In dieser Serie zeige ich, wie man einen EA für den manuellen Handel erstellt. Allerdings kann diese Serie ein wenig veraltet sein, und bald werden Sie sehen, warum. Im Moment müssen wir uns auf unser Hauptziel konzentrieren.

    Um das Auftragssystem des EA zu testen und zu prüfen, ob es Aufträge an den Handelsserver sendet, werden wir einige Tricks anwenden. Schauen wir uns an, wie das geht. Normalerweise füge ich dem Chart eine horizontale Linie hinzu, aber in diesem Fall wollen wir nur überprüfen, ob das Auftragssystem funktioniert. Um das Testen zu erleichtern, verwenden wir daher Folgendes:

    #property copyright "Daniel Jose"
    #property description "This one is an automatic Expert Advisor"
    #property description "for demonstration. To understand how to"
    #property description "develop yours in order to use a particular"
    #property description "operational, see the articles where there"
    #property description "is an explanation of how to proceed."
    #property version   "1.03"
    #property link      "https://www.mql5.com/pt/articles/11223"
    //+------------------------------------------------------------------+
    #include <Generic Auto Trader\C_Orders.mqh>
    //+------------------------------------------------------------------+
    C_Orders *orders;
    ulong m_ticket;
    //+------------------------------------------------------------------+
    input int       user01   = 1;           //Lot increase
    input int       user02   = 100;         //Take Profit (financial)
    input int       user03   = 75;          //Stop Loss (financial)
    input bool      user04   = true;        //Day Trade ?
    input double    user05   = 84.00;       //Entry price...
    //+------------------------------------------------------------------+
    int OnInit()
    {
            orders = new C_Orders();
            
            return INIT_SUCCEEDED;
    }
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
            delete orders;
    }
    //+------------------------------------------------------------------+
    void OnTick()
    {
    }
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
    {
    #define KEY_UP          38
    #define KEY_DOWN        40
    
            switch (id)
            {
                    case CHARTEVENT_KEYDOWN:
                            switch ((int)lparam)
                            {
                                    case KEY_UP:
                                            m_ticket = orders.CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                            break;
                                    case KEY_DOWN:
                                            m_ticket = orders.CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                            break;
                            }
                            break;
            }
    #undef KEY_DOWN
    #undef KEY_UP
    }
    //+------------------------------------------------------------------+
    
    

    Trotz seiner Einfachheit wird dieser Code es uns ermöglichen, das Auftragssystem zu testen. Gehen Sie dazu folgendermaßen vor, um einen Auftrag zum gewünschten Preis aufzugeben:

    1. Geben Sie den Preis an, zu dem der Auftrag erteilt werden soll. Beachten Sie, dass Sie nicht den aktuellen Preis verwenden können;
    2. Verwenden Sie den Pfeil nach oben, wenn Sie glauben, dass der Preis steigen wird, oder den Pfeil nach unten, wenn Sie glauben, dass der Preis fallen wird;
    3. Gehen Sie dann auf die Registerkarte „Handel“ im Werkzeugkastenfenster. Es sollte ein Auftrag mit den vom EA festgelegten Bedingungen erscheinen.

    Die Daten werden wie in dem Feld angegeben ausgefüllt, in dem der EA mit dem Nutzer interagieren kann. Achten Sie darauf, wie die Klasse mittels des Konstruktors initialisiert wird. Achten Sie auch auf den Code des Destruktors. Auf diese Weise können wir sicher sein, dass die Klasse immer initialisiert wird, bevor wir eine ihrer internen Prozeduren aufrufen.


    Schlussfolgerung

    Obwohl dieser EA sehr einfach ist, können Sie das Auftragssystem testen und Aufträge damit versenden. Denken Sie jedoch daran, nicht den aktuellen Kurs zu verwenden, da Sie dann nicht erkennen können, ob das System die Aufträge an der richtigen Stelle und mit den richtigen Werten platziert.

    In dem folgenden Video sehen Sie eine Demonstration der korrekten Funktionsweise des Systems. Die angehängte Datei enthält die vollständige Version des Codes, den wir in diesem Artikel behandelt haben. Nutzen Sie es zum Experimentieren und Studieren.

    Aber wir stehen noch ganz am Anfang. Im nächsten Artikel werden die Dinge noch ein wenig interessanter. In dieser Phase ist es jedoch wichtig, dass Sie wissen, wie Aufträge in das Auftragsbuch, dem order book, gelangen.


    Demo-Video


Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/11223

Beigefügte Dateien |
Erstellen eines EA, der automatisch funktioniert (Teil 03): Neue Funktionen Erstellen eines EA, der automatisch funktioniert (Teil 03): Neue Funktionen
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet. Im vorherigen Artikel haben wir begonnen, ein Auftragssystem zu entwickeln, das wir in unserem automatisierten EA verwenden werden. Wir haben jedoch nur eine der benötigten Funktionen geschaffen.
DoEasy. Steuerung (Teil 31): Scrollen des Inhalts des ScrollBar-Steuerelements DoEasy. Steuerung (Teil 31): Scrollen des Inhalts des ScrollBar-Steuerelements
In diesem Artikel werde ich die Funktionsweise des Scrollens des Inhalts des Containers mithilfe der Schaltflächen der horizontalen Bildlaufleiste implementieren.
Erstellen eines EA, der automatisch funktioniert (Teil 04): Manuelle Auslöser (I) Erstellen eines EA, der automatisch funktioniert (Teil 04): Manuelle Auslöser (I)
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet.
Erstellen eines EA, der automatisch funktioniert (Teil 01): Konzepte und Strukturen Erstellen eines EA, der automatisch funktioniert (Teil 01): Konzepte und Strukturen
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet.