English Русский 中文 Español 日本語 Português
preview
Erstellen eines EA, der automatisch funktioniert (Teil 10): Automatisierung (II)

Erstellen eines EA, der automatisch funktioniert (Teil 10): Automatisierung (II)

MetaTrader 5Handel | 4 Mai 2023, 12:54
315 0
Daniel Jose
Daniel Jose

Einführung

In dem vorherigen Artikel „Erstellen eines EA, der automatisch funktioniert (Teil 9): Automatisierung (I)“ haben wir uns angesehen, wie man ein Breakeven- und Trailing-Stop-System erstellt, das zwei verschiedene Modi verwendet. Eine davon verwendet eine Stop-Linie für OCO-Positionen, während die andere einen schwebenden Auftrag als Stop-Level verwendet. Wie ich bereits erläutert habe, hat jede dieser Methoden ihre Vor- und Nachteile.

Auch wenn der EA ein recht einfaches Automatisierungssystem verwendet und detaillierte Anweisungen für die Berechnungen enthält, ist er nicht wirklich automatisiert. Obwohl sie bereits über eine erste Automatisierungsstufe verfügt, wird sie immer noch manuell oder genauer gesagt „halb-manuell“ bedient. Dies liegt daran, dass der Teil, der für das Verschieben der Linie oder der Stop-Order verantwortlich ist, vom EA selbst auf der Grundlage der vom Nutzer festgelegten Einstellungen ausgeführt wird.

In diesem Artikel werden wir uns ansehen, wie man dem EA eine weitere Automatisierungsebene hinzufügt, sodass er die ganze Zeit auf dem Chart bleiben kann, auf dem er verwendet wird. Aber keine Sorge, das bedeutet nicht, dass er 24 Stunden am Tag kontinuierlich Aufträge sendet oder Positionen eröffnet. Wir werden sehen, wie man die Handelsvolumina des Systems kontrolliert, um ein Überhandeln des EA zu verhindern und ihn nicht mehr als das maximal erlaubte Volumen handeln zu lassen.

Die Art der Automatisierung, die ich hier zeigen werde, ist das, was viele Plattformen als Möglichkeit für einen Händler anbieten, nicht die ganze Zeit zu handeln. Sie legen die Zeit fest, in der das Senden von Aufträgen oder die Eröffnung von Positionen erlaubt ist, und während dieser vordefinierten Zeit können Sie die Plattform grundsätzlich und theoretisch nutzen.

Ich behaupte, dass die Nutzung der Plattform zu diesem Zeitpunkt theoretisch sein wird, weil man das System abschalten kann, sodass die gesamte Methodik den Bach runtergeht. Jeder Mensch weiß, wann er eine Pause einlegen muss, aber im Gegensatz zu einem menschlichen Händler verhält sich ein EA nicht so.

Ich muss darauf hinweisen, dass Sie alles, was wir dem EA hinzufügen oder aus ihm löschen, doppelt überprüfen müssen, damit er im Rahmen der vordefinierten Methodik funktionieren kann. Wir sollten den EA jedoch niemals unbeaufsichtigt laufen lassen. Denken Sie immer daran: LASSEN SIE EINEN EA NIEMALS UNBEAUFSICHTIGT LAUFEN.

Wie kann man eine Zeitplankontrolle durchführen?

Es gibt verschiedene Möglichkeiten, dies zu tun. Die Implementierung dieser Art von Steuerung hängt mehr davon ab, wie der Programmierer sie im EA-Code platzieren möchte, als davon, welche Art von Programmierung erforderlich ist. Da der Zweck dieser Artikel darin besteht, zu zeigen, wie man einen automatisierten EA auf möglichst einfache Weise erstellt, werden wir Code erstellen, der leicht entfernt werden kann. Gleichzeitig können wir es in einem manuellen EA verwenden, wenn wir unsere eigene Handelsmethode kontrollieren wollen, aber das sollte vom Nutzer entschieden werden. 


Planung

Das Entwicklungssystem ist insofern interessant, als sich jeder Händler andere Mittel und Wege ausdenkt, um ein bestimmtes Konzept zu fördern, oder sogar, wie das System selbst funktionieren soll. Selbst wenn die Ergebnisse sehr ähnlich oder nahe beieinander liegen, kann die Art und Weise, wie sie umgesetzt werden, unterschiedlich sein.

Was ich hier in MQL5 jedoch vermisse, ist die in C++ vorhandene Form der objektorientierten Programmierung, nämlich die so genannte Mehrfachvererbung. Wird diese Methodik jedoch falsch angewandt, kann es zu ernsthaften Problemen kommen. Bei der Verwendung von Mehrfachvererbung sollten Sie daher beim Programmieren sehr vorsichtig sein. Aber auch ohne diese C++-Funktion können wir einige Arten von Code generieren, die sich innerhalb des Vererbungssystems bewegen.

Um den Unterschied zwischen der Verwendung von Mehrfachvererbung und der Nichtvererbung zu verstehen, sehen Sie sich die folgenden Bilder an. Bitte beachten Sie, dass C_ControlOfTime der Name der Klasse ist, die wir verwenden werden, um das Zeitintervall für die Ausführung des EA zu steuern.

Abbildung 01

Abbildung 01. Modellierung mit Mehrfachvererbung

Abbildung 02

Abbildung 02. Modellierung ohne Mehrfachvererbung

Der Unterschied zwischen Abbildung 01 und Abbildung 02 besteht darin, dass in der ersten Abbildung die Klasse C_Manager die in den Klassen C_Orders und C_ControlOfTime implementierten Methoden durch Vererbung ableitet, wodurch die Klasse C_Manager schnell wächst. Da dies in MQL5 nicht möglich ist, werden wir einen anderen Ansatz verwenden, der in Abbildung 02 dargestellt ist. Die Klasse C_ControlOfTime wird von C_Orders geerbt.

Aber warum tun wir nicht das Gegenteil? Das liegt daran, dass ich nicht möchte, dass der EA direkten Zugriff auf die Klasse C_Orders hat. Wir benötigen jedoch Zugriff auf die Implementierung der Klasse C_ControlOfTime. Das Beste am Programmieren ist, dass wir oft unterschiedliche Ansätze wählen können, aber am Ende genau die gleiche Funktionalität erhalten, die auch ein anderer Programmierer erstellen könnte.

Was ich hier zeige, ist nur eine von vielen Möglichkeiten, um das gleiche Ergebnis zu erzielen. Was wirklich zählt, ist das Ergebnis. Es ist eigentlich egal, wie Sie dies erreichen, solange die Integrität Ihres Codes erhalten bleibt. Sie können Ihre eigenen Techniken und Möglichkeiten zur Umsetzung bestimmter Dinge entwickeln, da die Programmierung dies ermöglicht.


Einige weitere Details, die vor der Umsetzung zu beachten sind

Nach der Definition der Idee, die in Abbildung 02 gezeigte Klassenmodellierung zu verwenden, gingen wir zur zweiten Planungsphase über, in der wir die Klasse für die Zeitfenstersteuerung erstellen werden.

Nun müssen wir festlegen, wie der Arbeitszeitbereich bestimmt wird, d.h. wie ein Händler diesen Zeitplan einfach festlegen kann. Eine Möglichkeit wäre die Verwendung einer Datei mit Zeitbereichsdaten, an die sich der EA hält.

Allerdings ist die Verwendung einer Datei für diese Art von Dingen eher umstritten. Die Verwendung einer Datei gibt dem Händler mehr Freiheit, mehrere Zeitintervalle innerhalb eines Tages zu definieren. Das mag in manchen Fällen sinnvoll sein, kann aber eine einfache Aufgabe erheblich erschweren. Der EA kann in bestimmten Zeitintervallen schlecht abschneiden, was uns viel Stress bereitet.

Andererseits kann die Art und Weise, wie der Zeitplan in einer Datei definiert ist, eine so einfache Aufgabe für die meisten Händler verkomplizieren. Dies ist darauf zurückzuführen, dass der EA in einigen Fällen nur innerhalb eines Zeitintervalls funktioniert.

In der überwiegenden Mehrheit der Fälle ist dies eine viel häufigere Tatsache, als es den Anschein haben mag. Wir können also etwas Besseres tun, etwas, das es uns ermöglicht, in der Mitte zu bleiben. Die MetaTrader 5-Plattform ermöglicht es uns, die gewünschten Einstellungen in der Datei zu speichern und zu laden. Sie müssten also nur eine Konfiguration für einen bestimmten Zeitraum erstellen. Sie können zum Beispiel eine Konfiguration für den Vormittag und eine andere für den Nachmittag verwenden. Um den EA durch das Zeitkontrollsystem zu blockieren, z.B. wenn der Händler sich ein wenig ausruhen möchte, kann der Händler eine Einstellungsdatei direkt auf die MetaTrader 5 Plattform hochladen, und die Konfiguration wird von der Plattform selbst gepflegt. Dies ist meines Erachtens eine große Hilfe, da es uns die Mühe erspart, zusätzliche Konfigurationsdateien nur für diesen Zweck zu erstellen.

Abbildung 03

Abbildung 03. EA-Einstellungen

Abbildung 03 zeigt das EA-Einrichtungssystem. Nachdem Sie den EA konfiguriert haben, können Sie die Einstellungen mit der Schaltfläche <SPEICHERN> (Salvar) speichern. Wenn Sie die gespeicherte Konfiguration hochladen möchten, verwenden Sie die Schaltfläche <ÖFFNEN> (Abrir). Auf diese Weise erhalten wir ein System, das viel weniger Code erfordert und gleichzeitig den gesamten Code sehr zuverlässig macht. Ein Teil der Arbeit wird von der MetaTrader 5-Plattform selbst übernommen, wodurch wir uns die Tests sparen können, um sicherzustellen, dass alles korrekt funktioniert.

Das letzte zu bestimmende Detail betrifft Aufträge oder Positionen außerhalb des Zeitintervalls. Was können wir mit ihnen machen? Der EA ist nicht in der Lage, außerhalb des zulässigen Zeitintervalls Aufträge zur Eröffnung einer Position zu senden oder einen Auftrag zu platzieren. Außerhalb des Arbeitsbereichs ist der EA jedoch in der Lage, den Auftrag zu verwalten oder die Position zu schließen, die sich bereits auf dem Server befindet. Sie können die von mir verwendete Richtlinie deaktivieren oder ändern, wenn Sie möchten. Ich überlasse Ihnen die Wahl, dies entsprechend Ihrer eigenen Handelspolitik festzulegen.


Die Geburt der Klasse C_ControlOfTime

Als erstes muss in der Header-Datei C_ControlOfTime.mqh der folgende Code erstellt werden:

#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_ControlOfTime : protected C_Orders
{
        private :
                struct st_00
                {
                        datetime Init,
                                 End;
                }m_InfoCtrl[SATURDAY + 1];
//+------------------------------------------------------------------+
        public  :

//... Class functions ...

};

Wir fügen die Header-Datei C_Orders.mqh hinzu, um Zugriff auf die Klasse C_Orders zu haben. C_ControlOfTime wird also von der Klasse C_Orders geerbt, wobei die geschützte Methode verwendet wird. In einem anderen Artikel dieser Reihe habe ich bereits die Folgen dieser Art der Vererbung erläutert: „Erstellen eines EA, der automatisch funktioniert (Teil 05): Manuelle Auslöser (II)“.

Nun fügen wir im privaten Teil des Codes der Klasse eine Struktur hinzu, die als Array mit 7 Elementen verwendet wird. Aber warum nicht 7 definieren, anstatt diese verrückte Aussage zu verwenden? Das liegt daran, dass der Wert von SATURDAY in der Sprache MQL5 intern als Wert der Enumeration ENUM_DAY_OF_WEEK definiert ist. Es wird also klarer, warum wir die Wochentage für den Zugriff auf das Array verwenden.

Dies ist die Anreicherung auf sprachlicher Ebene, denn für diejenigen, die den Code lesen, ist ein Wort aussagekräftiger als ein numerischer Wert. Diese Struktur hat nur zwei Elemente: Eines gibt den Startpunkt der EA-Operation an, das andere den Punkt, nach dem die EA-Operation nicht mehr funktionieren kann; beide sind vom Typ datetime.

Sobald das Array definiert ist, können wir zu unserem ersten Code in der Klasse übergehen, nämlich dem unten gezeigten Klassenkonstruktor:

                C_ControlOfTime(const ulong magic)
                        :C_Orders(magic)
                        {
                                ResetLastError();
                                for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) ZeroMemory(m_InfoCtrl[c0]);
                        }

Vielen mag der folgende Code seltsam vorkommen, aber es handelt sich dabei um eine etwas fortschrittlichere Form der Programmierung, als wir sie gewohnt sind. Die Tatsache, dass ich sage, dass es sich um High-Level-Programmierung handelt, hat nichts mit meiner Erfahrung in der Programmierung zu tun. Wie ich schon sagte:

„Es ist einfacher, Code zu verstehen, der eine natürliche Sprache verwendet, als Code, der numerische Werte verwendet.“

Die Schleife in diesem Konstruktor ist selbsterklärend. Solche Dinge bestimmen, ob es sich um High-Level- oder Low-Level-Code handelt.

Ich glaube, dass es für Sie recht einfach sein wird, diesen Konstruktor zu verstehen. Zuvor habe ich bereits erklärt, wie der Code im Konstruktor funktioniert. Wenn Sie sich nicht sicher sind, lesen Sie bitte die vorherigen Artikel dieser Reihe. Der einzige Unterschied besteht in der Schleife, in der wir die Variable angeben, die mit dem Wert SONNTAG beginnt und mit SAMSTAG endet. Sonst ist hier nichts kompliziert.

Die Schleife funktioniert nur deshalb korrekt, weil in der Enumeration der Sonntag als erster Tag der Woche definiert ist und der Samstag der letzte ist. Wenn jedoch MONTAG als erster Tag der Woche festgelegt wurde, würde die Schleife bei der Ausführung des Codes in der MetaTrader 5-Plattform fehlschlagen und einen Fehler auslösen. Daher ist es wichtig, bei der Verwendung von High-Level-Code vorsichtig zu sein, denn bei falscher Konfiguration kann der Code mehrere Laufzeitfehler erzeugen.

Sobald dies erledigt ist, können wir zur nächsten Funktion in unserer Klasse übergehen:

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

Dieser Teil erfordert eine genauere Analyse, da er für Personen mit wenig Erfahrung nicht so einfach ist. Und ich weiß das, weil ich an Ihrer Stelle war, liebe Leserin, lieber Leser. Um den obigen Code genauer zu erklären, müssen wir ihn in kleinere Teile zerlegen.

if (_LastError != ERR_SUCCESS) return;
if ((index > SATURDAY) || (index < SUNDAY)) return;

Wann immer möglich, sollten Sie das System auf Fehler überprüfen, da dieser Code sehr empfindlich und aufgrund der Natur der Sache, mit der wir arbeiten, fehleranfällig ist. Zunächst wird geprüft, ob ein Fehler vorliegt, bevor diese spezielle Funktion aufgerufen wird. Wenn ein Fehler auftritt, wird die Funktion sofort beendet.

Die zweite Prüfung ist auch notwendig, um zufällige Fehler zu vermeiden. Wenn wir aus irgendeinem Grund einen Aufruf machen, der vom Wochentag abhängt, der eine Zahl angibt, und der Prozessor behandelt diese Zahl nur als Zahl, weil sie nicht im Bereich zwischen SAMSTAG und SONNTAG liegt, werden wir nicht mit der Ausführung weiterer Zeilen innerhalb der Funktion fortfahren.

Nachdem dieser erste Schritt getan wurde, er akzeptiert wurde und der Code die ersten Tests bestanden hat, werden wir eine Übersetzung des vom Aufrufer übergebenen Inhalts vornehmen:

if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
{
        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
}

Hier haben wir viel interessanteren Code, vor allem für diejenigen, die mit dem Programmieren anfangen. Hier verwenden wir die Funktion StringSplit, um die Informationen, die wir vom Anrufer erhalten, in zwei Teile zu „teilen“. Das Zeichen, das angibt, wo die Information unterbrochen wird, ist das Minuszeichen ( - ). Was wir sehen wollen und erwarten, sind zwei Informationen. Wenn wir eine andere Zahl erhalten, wird dies als Fehler gewertet, wie in den folgenden Beispielen zu sehen ist:

12:34 - - 18:34 <-- This is an error
12:32  -  18:34 <-- Information is correct
12:32     18:34 <-- This is an error
       -  18:34 <-- This is an error
12:34  -        <-- This is an error

Der interne Inhalt der Informationen spielt für die Funktion StringSplit keine Rolle. Die Daten werden anhand des angegebenen Trennzeichens „aufgeteilt“. Diese Funktion ist in bestimmten Fällen sehr nützlich, daher ist es wichtig, sie genau zu studieren. Denn es ist sehr hilfreich, um Informationen innerhalb einer Zeichenkette zu trennen.

Nachdem wir diese beiden Informationen erhalten haben, verwenden wir die Funktion StringToTime, um sie in einen Datumsformatcode zu übersetzen.

Es gibt noch ein weiteres wichtiges Detail: Die Informationen, die wir zur Verfügung stellen werden, enthalten nur Stunden und Minuten. Wir sind nicht an einem bestimmten Datum interessiert. Aber nichts hindert uns daran, ein bestimmtes Datum festzulegen. Je nach Umsetzung wird das Datum jedoch ignoriert. Sie müssen nur den Namen eingeben. Die Funktion StringToTime fügt automatisch das aktuelle Datum hinzu. Das ist eigentlich kein Problem, aber es gibt eine Sache, auf die wir später noch eingehen werden.

Um den aktuellen Datumswert zu eliminieren, der von der Funktion StringToTime hinzugefügt wurde, verwenden wir die Faktorisierung, bei der das Ergebnis nur der vom Aufrufer angegebene Zeitwert ist. Falls Sie das von der Funktion StringToTime hinzugefügte Datum, das immer das aktuelle Datum ist, wirklich nicht entfernen wollen, entfernen Sie einfach diese angegebene Faktorisierung.

Wir haben den ersten Test, nachdem der Wert umgewandelt wurde. Das ist sehr wichtig, um spätere Probleme zu vermeiden. Es wird geprüft, ob die Startzeit kleiner oder gleich der angegebenen Endzeit ist. Wenn diese Prüfung erfolgreich ist, müssen wir uns später nicht um dieses Problem kümmern. Wenn die Prüfung fehlschlägt, wird der Nutzer darauf hingewiesen, dass die Werte nicht geeignet sind.

Es gibt noch eine weitere Prüfung, denn da wir das Datum nicht angeben, wird die Plattform einen Laufzeitfehler erzeugen. Diese Art von Fehlern ist sehr ärgerlich, aber wir gehen sehr korrekt damit um. Wenn ein solcher Fehler entdeckt wird, entfernen wir einfach den Hinweis auf den Fehler. Da wir im Voraus wissen, dass ein Termin nicht mitgeteilt wird, führt dies zu diesem Fehler.

Wichtiger Hinweis: Wann immer Sie beim Testen des EA feststellen, dass ein Laufzeitfehler ausgelöst wird, der die Integrität des EA nicht verletzt und ihn nicht instabil oder unsicher macht, fügen Sie diese Art von Test nach dem Code ein, der den Fehler erzeugt, um die Fehlererzeugung in Zukunft zu minimieren. Einige Laufzeitfehler können ignoriert werden, da sie nicht so kritisch sind und den EA-Betrieb in keiner Weise beeinträchtigen. Einige Fehler müssen jedoch anders behandelt werden, da sie dazu führen, dass der EA die Bewegung auf dem Diagramm anhält. Dies wird als auferlegte Robustheit bezeichnet, denn wir wissen, dass ein Fehler auftreten kann, aber wir wissen auch, dass er das System nicht gefährden wird.

Nach der Implementierung der Wertumwandlung ergibt sich der folgende Code:

if ((_LastError != ERR_SUCCESS) || (!bLocal))
{
        Print("Error in the declaration of the time of day: ", EnumToString(index));
        ExpertRemove();
}

Hier ist etwas Wichtiges zu beachten: Wenn das System während der Wertumwandlung schwerwiegende Fehler erzeugt, hat diese konstante Variable einen anderen Wert als ERR_SUCCESS. Dies bedeutet, dass wir den vom Nutzer verwendeten oder eingegebenen Daten nicht vertrauen können. Dasselbe gilt, wenn die Variable hier den Wert „false“ hat. Das bedeutet, dass es an irgendeiner Stelle ein Fehler aufgetreten ist. In jedem Fall werden wir den Händler informieren, indem wir eine Meldung im Terminal ausgeben. Außerdem wird eine Aufforderung zum Schließen des EA in der MetaTrader 5-Plattform generiert.

Ich hoffe, dass die Funktionsweise der Funktion klar ist, da sie ausführlich erklärt wurde. Aber das ist noch nicht alles, denn es gibt noch eine weitere Funktion zu besprechen:

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), mdt);
                                dt = (mdt.hour * 3600) + (mdt.min * 60);
                                return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
                        }

Es ist wichtig, etwas nicht als selbstverständlich hinzunehmen, bevor man nicht alle möglichen Varianten ausprobiert hat, die zwar zu den gleichen Ergebnissen führen können, aber auf eine andere, viel einfachere Weise. Das mag entmutigend erscheinen, aber es ist ein wertvoller Ansatz zur Problemlösung und zur Suche nach besseren Lösungen.

Bevor wir zur Sache kommen, sollten wir die oben beschriebene Funktion verstehen. Es erfasst die Ortszeit und das Datum und wandelt diese Werte in eine Struktur um, um die Daten aufzuteilen. Dann legen wir einen Wert fest, der nur Stunden und Minuten enthält - das sind die Werte, die wir in der vorherigen Funktion verwendet haben. Das ist das Problem, das ich bereits erwähnt habe. Wenn Sie den Datumswert in der Konvertierungsfunktion nicht entfernt haben, müssen Sie hier den Datumswert hinzufügen. Sobald wir diese Werte haben, prüfen wir, ob sie innerhalb des Bereichs liegen. Wenn dies der Fall ist, wird true zurückgegeben, wenn sie außerhalb des definierten Bereichs für den Tag liegen, wird false zurückgegeben.

Jetzt kommt eine weitere Frage. Warum müssen wir die ganze Arbeit des Erfassens und Wiederherstellens machen, um zu prüfen, ob die Ortszeit in einem Bereich von Tageswerten liegt oder nicht? Wäre es nicht einfacher, einen Code zu verwenden, bei dem wir die Ortszeit erfassen und mit dem festgelegten Bereich vergleichen? Ja, das wäre in der Tat viel einfacher. Aber, und es gibt immer ein Aber, wir haben ein kleines Problem: den Wochentag.

Wenn Sie den EA-Operationsbereich nur auf den aktuellen Tag festlegen würden, wäre das in Ordnung. Es würde genügen, die Ortszeit zu kennen. Aber wir definieren Werte für sieben Tage in der Woche. Der einfachste Weg, den Wochentag zu ermitteln, besteht darin, genau die Arbeit zu verwenden, die in der obigen Funktion geleistet wurde.

Es hängt also alles davon ab, wie Sie das System implementieren. Wenn Sie eine einfache Implementierung vornehmen, werden die Funktionen und Prozeduren, die Sie erstellen und definieren müssen, wesentlich einfacher sein. Wenn Sie ein System für eine breitere Anwendung erstellen, werden die Funktionen und Verfahren wesentlich komplizierter sein.

Deshalb ist es wichtig, dass man nachdenkt und analysiert, bevor man mit der eigentlichen Programmierung beginnt. Andernfalls könnten Sie in einer Sackgasse landen, in der ein neu erstellter Code dazu führt, dass ein älterer Code die Anforderungen nicht mehr erfüllen kann. Sie müssen also ältere Codes ändern, und als Nächstes stehen Sie in einem solchen Durcheinander, dass Sie alles verwerfen und von vorne anfangen müssen.

Bevor ich das Thema wechsle, möchte ich ein Detail aus der obigen Funktion hervorheben. Wenn Sie möchten, dass der EA 24 Stunden am Tag in Betrieb ist und die Plattform während dieser Zeit läuft, könnten Sie befürchten, dass der EA zum Tageswechsel nicht weiß, wann er den Betrieb wieder aufnehmen soll. Dies wird in der Tat nicht passieren, da bei jedem Aufruf der Funktion die gesamte Situation neu bewertet wird und der aktuelle Wochentag verwendet wird, um zu überprüfen, ob der EA in der Lage ist, eine Operation durchzuführen oder nicht.

Nehmen wir zum Beispiel an, Sie teilen dem EA mit, dass er am Montag zwischen 04:15 und 22:50 Uhr und am Dienstag zwischen 3:15 und 20:45 Uhr handeln kann. Sie können ihn einfach am Montag einschalten und bis Dienstag laufen lassen. Sobald der Tag von Montag auf Dienstag wechselt, wird automatisch geprüft, welcher Zeitraum für den Betrieb am Dienstag erlaubt ist. Aus diesem Grund habe ich beschlossen, den Wochenmodus anstelle einer Definition auf der Grundlage des aktuellen Tages zu verwenden.

Vielleicht habe ich einige Details dieser Klasse übersehen, aber ich möchte die Erklärung der Funktionsweise nicht zu sehr verkomplizieren. Schauen wir uns ein sehr wichtiges Detail im Zusammenhang mit der Funktionsvererbung genauer an. Wenn Sie genau hinsehen, werden Sie feststellen, dass sowohl die Funktionen SetInfoCtrl als auch CtrlTimeIsPassed sehr seltsame Deklarationen haben. Warum dies? Was ist ihr Zweck? Diese Erklärungen werden im Folgenden hervorgehoben:

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
//+------------------------------------------------------------------+
virtual const bool CtrlTimeIsPassed(void) final
//+------------------------------------------------------------------+

Hier hat jedes einzelne Wort einen Grund. Hier wird nichts platziert, um den Code auszuschmücken, auch wenn manche Leute dies zu tun pflegen, aber das wäre eine andere Geschichte.

Entscheidend ist in diesen Erklärungen das reservierte Wort „final“. Dies ist die große Frage, die sich aus der Existenz des Wortes „virtual“ in der Erklärung. Wenn Sie eine Klasse erstellen, können Sie sie auf verschiedene Weise bearbeiten, indem Sie Methoden überschreiben, die Art und Weise, wie eine Funktion der übergeordneten Klasse in einer untergeordneten Klasse ausgeführt wird, ändern, neue Formen auf der Grundlage primitiverer Arbeit erstellen und vieles mehr. Wenn Sie einer Funktionsdeklaration in einer Klasse, wie oben gezeigt, das Wort „final“ hinzufügen, teilen Sie dem Compiler mit, dass eine abgeleitete Klasse die Funktion in keiner Weise ändern kann. Sie kann nicht einmal die in der übergeordneten Klasse geschriebene Funktion außer Kraft setzen, was sehr üblich ist.

Ich wiederhole es noch einmal, weil es wichtig ist: Durch die Verwendung des Wortes „final“ in der Deklaration teilen Sie dem Compiler mit, dass eine untergeordnete Klasse die geerbte Funktion, die in der übergeordneten Klasse, die das Wort „final“ in der Deklaration erhalten hat, geschrieben wurde, in keiner Weise verändern, nicht einmal überschreiben darf.

Damit garantieren wir, dass, wenn eine Klasse, die diese Klasse mit ihren Methoden und Variablen von hier erbt, versucht, diese Methoden zu ändern, dieser Versuch als Fehler betrachtet wird und das Programm nicht kompiliert wird. Das Wort „virtuell“ dient gerade dazu, die Möglichkeit einer Änderung zu fördern, aber das letzte Wort verhindert eine solche Änderung. Wer die Regeln wirklich macht, hat das letzte Wort. Wenn Sie also sicherstellen wollen, dass eine Funktion innerhalb einer untergeordneten Klasse nicht unzulässig geändert wird, müssen Sie diese Möglichkeit der Änderung durch Hinzufügen einer Deklaration, wie oben gezeigt, verhindern.

Dies erspart Ihnen eine Menge Kopfzerbrechen, wenn Sie mit vielen Klassen und einer tiefen Vererbungsebene arbeiten.


Einbinden von C_ControlOfTime mit C_Manager und Verwendung im EA

Jetzt können wir endlich die Klasse C_ControlOfTime mit der Klasse C_Manager verknüpfen, und der EA wird einen neuen Typ von Arbeitsparameter haben. Zu diesem Zweck fügen wir die folgende Änderung in den Code der Klasse C_Manager ein:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
class C_Manager : public C_ControlOfTime

Die durchgestrichenen Zeilen wurden aus dem ursprünglichen Code entfernt, und an ihrer Stelle erschienen neue Zeilen. Beachten Sie jedoch, dass die Klasse C_Manager die Klasse C_ControlOfTime öffentlich erben wird. Wir fügen einfach alles aus der Klasse C_ControlOfTime in die Klasse C_Manager ein. Dies erweitert die Möglichkeiten der Klasse C_Manager, ohne ihren Code zu erhöhen. Wenn wir die Fähigkeiten, die durch die Vererbung der Klasse C_ControlOfTime hinzugefügt wurden, nicht mehr benötigen, müssen wir nur diese Vererbung und alle möglichen Referenzpunkte aus der Klasse C_Manager entfernen. So einfach ist das.

Wir ändern also nicht den Zuverlässigkeitsgrad der Klasse C_Manager, denn sie wird weiterhin mit maximaler Sicherheit, Stabilität und Robustheit funktionieren, als ob nichts geschehen wäre. Wenn die Klasse C_ControlOfTime anfängt, Instabilitäten in der Klasse C_Manager zu verursachen, können wir die Klasse C_ControlOfTime einfach entfernen, und die Klasse C_Manager wird wieder stabil sein.

Ich denke, das erklärt, warum ich gerne alles in Form von Klassen und nicht in Form von verstreuten Funktionen erstelle. Die Dinge entwickeln und verbessern sich sehr schnell, und wir haben immer das höchstmögliche Maß an Stabilität und Zuverlässigkeit, das uns die Sprache bieten kann.

Da die Konstruktoren irgendwie referenziert werden müssen, sehen wir uns den Konstruktor der neuen Klasse unten an:

//+------------------------------------------------------------------+
                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_ControlOfTime(magic),
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;

// ... The rest of the constructor code....

                        }
//+------------------------------------------------------------------+

Hier gilt das Gleiche wie bei den Deklarationen. Die durchgestrichene Linie ist entfernt worden. An seine Stelle trat eine neue Zeile, die die Daten an den Konstruktor der Klasse C_ControlOfTime übergibt. Dieser Konstruktor verweist auf den Konstruktor der Klasse C_Orders, damit er die magische Zahl erhält, die zum Senden der Aufträgen erforderlich ist.

Um dieses Thema abzuschließen, folgen nun die Punkte, die den tatsächlichen Nutzen der Zeitkontrolle betreffen:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

Keine andere Funktion innerhalb des EA oder der Klasse C_Manager wird die Zeitbereiche verwenden. Aber wenn Sie möchten, können Sie diese Kontrolle hinzufügen, indem Sie genau die hervorgehobene Zeile zu den Funktionen hinzufügen, die kontrolliert werden müssen, wie z. B. den Trigger des Trailing-Stop. Wenn Sie das Steuerelement in einigen dieser Funktionen nicht verwenden möchten, entfernen Sie einfach die hinzugefügte Zeile. Hat Ihnen die Art und Weise, wie der Code geplant wurde, gefallen? Aber all dies funktioniert nicht wirklich ohne einen weiteren Punkt im Code des EA.

Die einzigen wirklich notwendigen Änderungen im EA-Code sind unten zu sehen:

#include <Generic Auto Trader\C_Manager.mqh>
#include <Generic Auto Trader\C_Mouse.mqh>
//+------------------------------------------------------------------+
C_Manager *manager;
C_Mouse  *mouse;
//+------------------------------------------------------------------+
input int       user01   = 1;                   //Leverage Factor
input double    user02   = 100;                 //Take Profit ( FINANCE )
input double    user03   = 75;                  //Stop Loss ( FINANCE )
input bool      user04   = true;                //Day Trade ?
input color     user05  = clrBlack;             //Price Line Color
input color     user06  = clrForestGreen;       //Take Line Color 
input color     user07  = clrFireBrick;         //Stop Line Color
input double    user08  = 35;                   //BreakEven ( FINANCE )
//+------------------------------------------------------------------+
input string    user90  = "00:00 - 00:00";      //Sunday
input string    user91  = "09:05 - 17:35";      //Monday
input string    user92  = "10:05 - 16:50";      //Tuesday
input string    user93  = "09:45 - 13:38";      //Wednesday
input string    user94  = "11:07 - 15:00";      //Thursday
input string    user95  = "12:55 - 16:25";      //Friday
input string    user96  = "00:00 - 00:00";      //Saturday
//+------------------------------------------------------------------+
#define def_MAGIC_NUMBER 987654321
//+------------------------------------------------------------------+
int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
        {
                switch (c0)
                {
                        case SUNDAY     : szInfo = user90; break;
                        case MONDAY     : szInfo = user91; break;
                        case TUESDAY    : szInfo = user92; break;
                        case WEDNESDAY  : szInfo = user93; break;
                        case THURSDAY   : szInfo = user94; break;
                        case FRIDAY     : szInfo = user95; break;
                        case SATURDAY   : szInfo = user96; break;
                }
                (*manager).SetInfoCtrl(c0, szInfo);
        }
        (*manager).CheckToleranceLevel();
        EventSetMillisecondTimer(100);

        return INIT_SUCCEEDED;
}

Es mussten nur die Punkte hinzugefügt werden, an denen der Nutzer mit dem Code interagiert. Diese Schleife erfasst die Daten und gibt sie an das System weiter, sodass sie von der Klasse C_Manager verwendet werden können, wodurch die tatsächliche EA-Betriebszeit gesteuert wird. In diesem Teil des Codes ist das Verhalten einfach zu verstehen und bedarf keiner weiteren Erklärung.


Schlussfolgerung

In diesem Artikel habe ich Ihnen gezeigt, wie man ein Steuerelement hinzufügt, damit der EA innerhalb einer bestimmten Zeitspanne arbeiten kann. Obwohl das System recht einfach ist, bedarf es einer letzten Erklärung.

Wenn Sie im Interaktionssystem eine Uhrzeit eingeben, die größer als 24 Stunden ist, wird sie auf die Uhrzeit korrigiert, die der 24-Stunden-Grenze am nächsten kommt. Das heißt, wenn Sie möchten, dass der EA bis 22:59 Uhr arbeitet (wenn Sie im Forexmarkt arbeiten), sollten Sie darauf achten, genau diesen Wert anzugeben. Wenn Sie 25:59 eingeben, ändert das System dies in 23:59. Obwohl dieser Tippfehler nicht häufig vorkommt, kann er dennoch passieren.

Ich habe keine zusätzliche Prüfung hinzugefügt, um diese Situation zu analysieren, weil sie selten vorkommt, aber ich wollte dies kommentieren und eine mögliche Prüfung für eine solche Bedingung zeigen. Er ist unten zu sehen. Der beigefügte Code enthält diese Änderungen bereits.

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[], sz1[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        for (char c0 = 0; (c0 <= 1) && (bLocal); c0++)
                                                if (bLocal = (StringSplit(szRes[0], ':', sz1) == 2))
                                                        bLocal = (StringToInteger(sz1[0]) <= 23) && (StringToInteger(sz1[1]) <= 59);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

Es war notwendig, eine neue Variable und den hervorgehobenen Code hinzuzufügen, der die Zeitdaten aufteilt, um zu prüfen, ob die eingegebene Zeit unter dem maximal möglichen Wert innerhalb des 24-Stunden-Intervalls liegt.


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

Beigefügte Dateien |
Erstellen eines EA, der automatisch funktioniert (Teil 11): Automatisierung (III) Erstellen eines EA, der automatisch funktioniert (Teil 11): Automatisierung (III)
Ein automatisiertes System wird ohne angemessene Sicherheit nicht erfolgreich sein. Die Sicherheit wird jedoch nicht gewährleistet sein, wenn man bestimmte Dinge nicht richtig versteht. In diesem Artikel werden wir untersuchen, warum es so schwierig ist, ein Maximum an Sicherheit in automatisierten Systemen zu erreichen.
Erstellen eines EA, der automatisch funktioniert (Teil 09): Automatisierung (I) Erstellen eines EA, der automatisch funktioniert (Teil 09): Automatisierung (I)
Obwohl die Erstellung eines automatisierten EA keine sehr schwierige Aufgabe ist, können ohne die notwendigen Kenntnisse viele Fehler gemacht werden. In diesem Artikel werden wir uns ansehen, wie man die erste Stufe der Automatisierung aufbaut, die darin besteht, einen Auslöser zu erstellen, um den Breakeven und einen Trailing-Stop zu aktivieren.
Indikatoren mit Hintergrund: Kanäle mit Transparenz Indikatoren mit Hintergrund: Kanäle mit Transparenz
In diesem Artikel stelle ich eine Methode zur Erstellung von nutzerdefinierten Indikatoren vor, deren Zeichnungen mit der Klasse CCanvas aus der Standardbibliothek erstellt werden, und zeige die Eigenschaften von Charts für die Koordinatenkonvertierung. Ich werde speziell auf Indikatoren eingehen, die den Bereich zwischen zwei Linien mit Transparenz füllen müssen.
Algorithmen zur Optimierung mit Populationen: Der Affen-Algorithmus (Monkey Algorithmus, MA) Algorithmen zur Optimierung mit Populationen: Der Affen-Algorithmus (Monkey Algorithmus, MA)
In diesem Artikel werde ich den Optimierungsalgorithmus Affen-Algorithmus (MA, Monkey Algorithmus) betrachten. Die Fähigkeit dieser Tiere, schwierige Hindernisse zu überwinden und die unzugänglichsten Baumkronen zu erreichen, bildete die Grundlage für die Idee des MA-Algorithmus.