English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 24): Herstellen eines robusten Systems (I)

Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 24): Herstellen eines robusten Systems (I)

MetaTrader 5Handel | 24 Oktober 2022, 10:11
421 0
Daniel Jose
Daniel Jose

Einführung

Manche Dinge sind nicht so einfach, auch wenn manche Leute das denken mögen. Das Auftragssystem ist eines dieser Dinge. Sie können sogar ein bescheideneres System erstellen, das Ihnen sehr gute Dienste leistet, wie wir es in dem Artikel Entwicklung eines Expert Advisors für den Handel von Grund auf getan haben, in dem wir ein Basissystem erstellt haben, das für viele Menschen nützlich sein kann, aber für andere nicht ausreicht. Damit war der Zeitpunkt gekommen, an dem sich alles zu ändern begann — dies ist die Geburtsstunde des ersten Teils dieser Serie über das neue Ordnungssystem. Im vorherigen Artikel Entwicklung eines Expert Advisors von Grund auf (Teil 18). An dieser Stelle haben wir mit der Entwicklung eines Systems begonnen, das vom EA verwaltet werden kann und gleichzeitig von MetaTrader 5 unterstützt wird. Die Idee des Systems war, dass es keine Begrenzung für Aufträge auf dem Chart gibt. Zunächst erschien mir das System recht kühn, und ich muss zugeben, dass mir allein die Tatsache, ein System zu schaffen, in dem die Objekte nicht vom EA, sondern von MetaTrader 5 verwaltet werden, ziemlich sinnlos und ineffizient erschien.

Das System befand sich jedoch noch in der Entwicklung, und in dem Artikel Einen Expert Advisor für den Handel von Grund auf entwickeln (Teil 23) haben wir ein Ghost-System entwickelt, das die Verwaltung von Aufträgen, Positionen oder Stop-Levels (Take-Profit und Stop-Loss) erleichtert. Es war sehr interessant zu entwickeln, aber es gab ein Problem. Wenn Sie die Anzahl der verwendeten und sichtbaren Objekte mit der Anzahl der von MetaTrader 5 unterstützten Objekte vergleichen, werden Sie sicherlich überrascht sein, denn die Anzahl der unterstützten Objekte wird immer höher sein.

In vielen Fällen ist das Problem nicht so ernst, man kann sogar mit einigen Momenten leben. Es gibt jedoch zwei Probleme, die das System in Zeiten hoher Marktvolatilität nicht sehr stabil machen. In manchen Situationen zwangen sie den Nutzer zu einem falschen Verhalten. Dies liegt daran, dass das System, wenn der Händler einen schwebenden Auftrag hinzufügt, diesen an den Server sendet, und der Server manchmal mehr Zeit als üblich benötigt, um zu reagieren. Und das System zeigt in manchen Momenten an, dass es einen Auftrag gibt, und in anderen Momenten zeigt es an, dass es keinen Auftrag gibt. Und wenn es in Positionen gemacht wurde (siehe die Dokumentation zum Unterschied zwischen Aufträgen und Positionen) durchgeführt wurde, erwies sich dies als noch mühsamer, da nicht bekannt war, ob der Server den Befehl wie erwartet ausführte.

Es gibt mehrere Möglichkeiten, dieses Problem zu lösen. Einige von ihnen sind einfacher, andere komplexer. Wie auch immer, wir müssen dem EA vertrauen, sonst sollten wir ihn unter keinen Umständen verwenden.


1.0. Planung

Das große Problem besteht darin, ein System zu entwickeln, das zwei Eigenschaften aufweist: Schnelligkeit und Zuverlässigkeit. In manchen Systemen ist es ziemlich schwierig oder sogar unmöglich, beides zu erreichen. In vielen Fällen versuchen wir also, ein Gleichgewicht zu schaffen. Aber da es um Geld geht, UNSER Geld, wollen wir es nicht riskieren, indem wir ein System erwerben, das diese Qualitäten nicht hat. Man darf nicht vergessen, dass wir es mit einem System zu tun haben, das in ECHTZEIT arbeitet, und das ist das schwierigste Szenario, in das ein Entwickler geraten kann, da wir immer versuchen sollten, ein System zu haben, das extrem schnell ist: Es muss sofort auf Ereignisse reagieren und gleichzeitig genug Zuverlässigkeit aufweisen, um nicht zusammenzubrechen, wenn wir versuchen, es zu verbessern. Es ist also klar, dass die Aufgabe ziemlich schwierig ist.

Schnelligkeit lässt sich erreichen, indem sichergestellt wird, dass die Funktionen auf die am besten geeignete Weise aufgerufen und ausgeführt werden, sodass unnötige Aufrufe zu noch unnötigeren Zeitpunkten vermieden werden. Dadurch wird das System so schnell wie möglich in der Sprache bereitgestellt. Wenn wir jedoch etwas noch Schnelleres wollen, dann müssen wir auf die Ebene der Maschinensprache hinuntergehen, und in diesem Fall meinen wir Assembly. Aber das ist oft unnötig, wir können die Sprache C verwenden und erhalten ebenso gute Ergebnisse.

Eine der Möglichkeiten, die gewünschte Robustheit zu erreichen, besteht darin, den Code so oft wie möglich wiederzuverwenden, damit er ständig in verschiedenen Fällen getestet wird. Aber das ist nur eine Möglichkeit. Eine andere Möglichkeit ist die Verwendung der OOP (Object Oriented Programming). Wenn dies korrekt und ordnungsgemäß durchgeführt wird, sodass jede Objektklasse die Daten der Objektklasse nicht direkt manipuliert, außer im Falle der Vererbung, dann reicht das aus, um ein sehr robustes System zu haben. Dies kann zu einer Verringerung der Ausführungsgeschwindigkeit führen, die jedoch so gering ist, dass sie aufgrund des exponentiellen Anstiegs, der durch die von der Klasse bereitgestellte Kapselung entsteht, vernachlässigt werden kann. Diese Kapselung gibt uns die nötige Robustheit.

Wie Sie sehen, ist es nicht so einfach, sowohl Geschwindigkeit als auch Robustheit zu erreichen. Aber das Tolle ist, dass wir gar nicht so viel opfern müssen, wie man auf den ersten Blick meinen könnte. Wir können einfach in der Systemdokumentation nachsehen, was geändert werden kann, um die Dinge zu verbessern. Die einfache Tatsache, dass wir nicht versuchen, das Rad neu zu erfinden, ist bereits ein guter Anfang. Aber denken Sie daran, dass Programme und Systeme ständig verbessert werden. Wir sollten also immer versuchen, das Vorhandene so weit wie möglich zu nutzen und erst dann, im letzten Fall, das Rad wirklich neu zu erfinden.

Bevor einige es für unnötig halten, die in diesem Artikel vorgenommenen Änderungen vorzustellen, oder denken, dass ich viel am Code ändere, ohne ihn tatsächlich zu bewegen, lassen Sie mich das erklären: Wenn wir etwas codieren, haben wir keine Ahnung, wie der endgültige Code funktionieren wird. Alles, was wir haben, sind die zu erreichenden Ziele. Sobald dieses Ziel erreicht ist, untersuchen wir, wie wir dieses Ziel erreicht haben, und versuchen, die Dinge zu verbessern, um sie besser zu machen.

Im Falle eines kommerziellen Systems, sei es eine ausführbare Datei oder eine Bibliothek, nehmen wir die Änderungen vor und veröffentlichen sie als Update. Da es sich um ein kommerzielles System handelt, braucht der Nutzer die Wege zum Ziel nicht zu kennen. Es ist gut, dass er es nicht wirklich kennt. Aber da es sich um ein offenes System handelt, möchte ich Ihnen nicht weismachen, dass Sie sofort ein extrem effizientes System entwickeln können, also von Anfang an. Diese Denkweise ist nicht angemessen, sie ist sogar eine Beleidigung, denn unabhängig davon, wie gut ein Programmierer oder Entwickler die zu verwendende Sprache beherrscht, wird es immer Dinge geben, die im Laufe der Zeit verbessert werden können.

Verstehen Sie diese Sequenz also nicht als etwas, das man in 3 oder 4 Artikeln zusammenfassen könnte, denn wenn das der Fall wäre, wäre es besser, den Code einfach so zu erstellen, wie ich es für richtig hielt, und ihn kommerziell zu veröffentlichen. Das ist nicht meine Absicht. Ich habe das Programmieren gelernt, indem ich mir den Code anderer, erfahrenerer Programmierer angeschaut habe, und ich weiß, wie wertvoll das ist. Es ist viel wichtiger zu wissen, wie sich die Sache im Laufe der Zeit entwickelt, als einfach die fertige Lösung zu nehmen und zu versuchen zu verstehen, wie sie funktioniert.

Nach diesen Überlegungen wollen wir uns nun der Entwicklung zuwenden.


2.0. Umsetzung

2.0.1. Neue Modellierung von Positionsanzeigern

Das erste, was im neuen Codeformat zu beachten ist, ist die Änderung einer Funktion, die zu einem Makro geworden ist.

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false)
{
        return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal));
}

Auch wenn der Compiler diesen Code dank des reservierten Wortes „inline“ an jeder Stelle, an der er referenziert wird, verwendet, sollten Sie dies nicht als selbstverständlich ansehen, da diese Funktion im Code sehr oft aufgerufen wird. Wir müssen sicherstellen, dass es tatsächlich so schnell wie möglich läuft, also wird unser neuer Code wie folgt aussehen:

#define macroMountName(ticket, it, ev, Ghost) 								 \
		StringFormat("%s%c%llu%c%c%c%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,          \                                                                                                                                                                                                                                                                                                
                                                       ticket, def_SeparatorInfo,                        \                                                                                                                                                                                                                                        
                                                       (char)it, def_SeparatorInfo,                      \ 
                                                       (char)(Ghost ? ev + 32 : ev), def_SeparatorInfo,  \ 
                                                       (Ghost ? def_IndicatorGhost : def_IndicatorReal))

Achten Sie darauf, dass die Daten in der alten Version des Makros und die Daten in dieser Version unterschiedlich sind. Für diese Änderung gibt es einen Grund, den wir später in diesem Artikel erörtern werden.

Aber wegen dieser Änderung müssen wir auch eine kleine Änderung am Code einer anderen Funktion vornehmen.

inline bool GetIndicatorInfos(const string sparam, ulong &ticket, eIndicatorTrade &it, eEventType &ev)
                        {
                                string szRet[];
                                char szInfo[];
                                
                                if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
                                if (szRet[0] != def_NameObjectsTrade) return false;
                                ticket = (ulong) StringToInteger(szRet[1]);
                                StringToCharArray(szRet[2], szInfo);
                                it = (eIndicatorTrade)szInfo[0];
                                StringToCharArray(szRet[3], szInfo);
                                ev = (eEventType)szInfo[0];

                                return true;
                        }

Die Änderung betrifft nur den Index, der verwendet wird, um anzuzeigen, was ein Ticket und was ein Indikator ist. Nichts Kompliziertes. Es ist nur ein einfaches Detail, das erledigt werden muss, da wir sonst bei der Verwendung dieser Funktion inkonsistente Daten haben werden.

Sie fragen sich vielleicht: „Warum brauchen wir diese Änderungen? Hat das System nicht perfekt funktioniert?“ Ja, es hat. Aber es gibt Dinge, die wir nicht kontrollieren können. Zum Beispiel, wenn der MetaTrader 5-Entwickler einige Funktionen verbessert, die im EA nicht verwendet werden und daher für uns nicht von Nutzen sein können. Die Regel lautet, das Rad nicht neu erfinden, sondern stattdessen die vorhandenen Ressourcen nutzen. Daher sollten wir immer versuchen, die von den Sprachen bereitgestellten Funktionen zu verwenden, in unserem Fall MQL5, und die Erstellung eigener Funktionen vermeiden. Das mag absurd erscheinen, aber wenn Sie einmal innehalten und nachdenken, werden Sie feststellen, dass die Plattform von Zeit zu Zeit Verbesserungen in einigen Funktionen bereitstellt, und wenn Sie dieselben Funktionen verwenden, werden Sie eine bessere Leistung und eine höhere Sicherheit in Ihren Programmen haben, ohne dass Sie zusätzliche Anstrengungen unternehmen müssen.

Der Zweck heiligt also die Mittel. Werden die oben genannten Änderungen jedoch dazu beitragen, dass der EA von den Verbesserungen in der MQL5-Bibliothek profitiert? Die Antwort auf diese Frage liegt bei Ihnen. Die oben genannten Änderungen sind notwendig, um sicherzustellen, dass die Modellierung der Objektnamen korrekt ist, sodass wir mögliche zukünftige Verbesserungen, die von den MQL5- und MetaTrader 5-Entwicklern kommen, effektiv nutzen können. Nachfolgend finden Sie einen der Punkte, die nützlich sein könnten:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));
        else ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)it));
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        m_InfoSelection.bIsMovingSelect = false;
        ChartRedraw();
}

Die frühere Version desselben Codes ist unten abgebildet, für diejenigen, die sich nicht daran erinnern oder ihn noch nie gesehen haben. Der Code sieht wie folgt aus:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
#define macroDestroy(A, B)      {                                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B));                            \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B));                              \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B));                             \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B));                              \
                if (A != IT_RESULT)     ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B));      \
                else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B));                       \
                                }

        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        {
                macroDestroy(IT_RESULT, true);
                macroDestroy(IT_RESULT, false);
                macroDestroy(IT_PENDING, true);
                macroDestroy(IT_PENDING, false);
                macroDestroy(IT_TAKE, true);
                macroDestroy(IT_TAKE, false);
                macroDestroy(IT_STOP, true);
                macroDestroy(IT_STOP, false);
        } else
        {
                macroDestroy(it, true);
                macroDestroy(it, false);
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
#undef macroDestroy
}

Es mag den Anschein haben, dass der Code einfach kompakter geworden ist. Aber es ist nicht nur das. Die Reduzierung des Codes ist eine offensichtliche Sache, aber die Wahrheit liegt viel tiefer. Der alte Code wurde durch einen neuen ersetzt, der die Ressourcen der Plattform besser nutzt. Da aber das bisher verwendete Modell der Objektnamen diese Verbesserung nicht zuließ, ändern wir die Modellierung so, dass wir erwarten können, von den MQL5-Funktionen zu profitieren. Sollte diese Funktion jemals aus irgendeinem Grund verbessert werden, wird der EA von dieser Änderung profitieren, ohne dass wir irgendwelche Änderungen an der EA-Struktur vornehmen müssen. Ich spreche von der Funktion ObjectsDeleteAll. Wenn wir sie richtig einsetzen, wird MetaTrader 5 die Bereinigung vornehmen. Wir brauchen nicht zu viele Details anzugeben, wir geben nur den Namen des Objekts oder der Objekte an und lassen MetaTrader 5 den Rest erledigen. Die Stellen, an denen diese Funktion verwendet wird, sind im neuen Code hervorgehoben. Beachten Sie, wie wir die Modellierung vorgenommen haben, um über das zu verwendende Präfix zu informieren. Dies war vor der Änderung der Modellierung von Objektnamen nicht möglich.

Ich möchte Ihre Aufmerksamkeit auf ein Detail in dem neuen Codefragment lenken, das im Folgenden hervorgehoben wird.

if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));

Was meinen Sie, warum ich den hervorgehobenen Teil hinzugefügt habe?

Denn wenn das System ein Ticket erstellt, das mit einem Wert von 1 beginnt, werden alle Objekte vom Bildschirm entfernt, sobald der schwebenden Auftrag (Pending Order) erteilt wird. Ist das nicht klar? Die Eingabe, die zur Platzierung eines schwebenden Auftrags verwendet wird, hat den Wert 1, d.h. der Indikator 0 hat tatsächlich den Wert 1 und nicht 0, da 0 zur Durchführung anderer Tests im EA verwendet wird. Aus diesem Grund ist der Ausgangswert 1. Jetzt haben wir ein Problem: Angenommen, das Handelssystem erstellt ein Ticket 1221766803. Das Objekt, das diese Ticket darstellt, hat dann den folgenden Wert als Präfix: SMD_OT#1221766803. Wenn der EA die Funktion ObjectsDeleteAll ausführt, um Indikator 0 zu löschen, lautet der Objektname SMD_OT#1 und es werden alle Objekte gelöscht, die mit diesem Wert beginnen, einschließlich des neu erstellten Systems. Um dieses Problem zu lösen, nehmen wir eine kleine Anpassung des Namens vor, um die ObjectsDeleteAll-Funktionen zu informieren, indem wir ein zusätzliches Zeichen am Ende des Namens hinzufügen, damit die Funktion weiß, ob wir den Indikator 0 oder einen anderen löschen.

Wenn also der Indikator 0 gelöscht werden soll, erhält die Funktion den Wert SMD_OT#1#. Dadurch wird das Problem vermieden. Gleichzeitig erhält die Funktion im Falle des obigen Beispiels den Namen SMD_OT#1221766803*. Es scheint etwas Einfaches zu sein, aber deshalb kann man sich fragen, warum der EA die Indikatorobjekte einer neu platzierten Order immer wieder löscht.

Lassen Sie uns nun über ein kurioses Detail sprechen. Am Ende der Funktion erfolgt ein Aufruf von ChartRedraw. Wofür wird es hier verwendet? Aktualisiert MetaTrader 5 den Chart nicht selbst? Das tut er. Aber wir wissen nicht genau, wann es soweit sein wird. Es gibt noch ein weiteres Problem: Alle Aufrufe zum Platzieren oder Löschen von Objekten auf dem Chart sind synchron, d. h. sie werden zu einem bestimmten Zeitpunkt ausgeführt, auch, wenn es nicht der von uns erwartete ist. Unser Auftragssystem wird jedoch Objekte verwenden, um Aufträge anzuzeigen oder zu verwalten, und wir müssen sicher sein, dass sich das Objekt im Chart befindet. Wir können nicht davon ausgehen, dass MetaTrader 5 bereits Objekte im Chart platziert oder entfernt hat, denn wir müssen uns dessen sicher sein, weshalb wir die Plattform zwingen, diese Aktualisierung vorzunehmen.

Wenn wir also ChartRedraw aufrufen, zwingen wir die Plattform, die Liste der Objekte im Chart zu aktualisieren, sodass wir sicher sein können, dass ein bestimmtes Objekt im Chart vorhanden ist oder nicht. Wenn das immer noch nicht klar ist, lassen Sie uns zum nächsten Thema übergehen.


2.0.2. Weniger Objekte — höhere Geschwindigkeit

Die Initialisierungsfunktion in der Vorgängerversion war umständlich. Es gab viele sich wiederholende Prüfungen, und einige Dinge wurden doppelt ausgeführt. Abgesehen von einigen geringfügigen Problemen nutzte das System nur sehr wenig der bereits vorhandenen Kapazität. Um die Vorteile der neuen Modellierung zu nutzen, habe ich beschlossen, die Anzahl der Objekte, die während der Initialisierung erstellt werden, zu reduzieren. SO, jetzt sieht das System folgendermaßen aus:

void Initilize(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_OBJECT_DESCR, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false);
        for (int c0 = OrdersTotal(); c0 >= 0; c0--) IndicatorInfosAdd(OrderGetTicket(c0));
        for (int c0 = PositionsTotal(); c0 >= 0; c0--) IndicatorInfosAdd(PositionGetTicket(c0));
}

Es schien, als wäre alles anders, und das war es tatsächlich. Jetzt verwenden wir die Funktion wieder, die nicht oft genug genutzt wurde — dies ist die Funktion, die dem Chart Indikatoren hinzufügt. Werfen wir einen Blick auf dieses besondere Merkmal.

inline void IndicatorAdd(ulong ticket)
{
        char ret;
                                
        if (ticket == def_IndicatorTicket0) ret = -1; else
        {
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if ((ret = GetInfosTradeServer(ticket)) == 0) return;
        }
        switch (ret)
        {
                case  1:
                        CreateIndicatorTrade(ticket, IT_RESULT);
                        PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                        break;
                case -1:
                        CreateIndicatorTrade(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                        break;
        }
        ChartRedraw();
        UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
}

Sehen Sie sich den obigen Code genau an. Es mag den Anschein haben, dass der Code unnötige Prüfungen enthält. Aber es gibt sie aus einem ganz einfachen Grund. Diese Funktion ist die einzige Möglichkeit, einen schwebenden Auftrag oder einen Positionsindikator zu erstellen. In den beiden hervorgehobenen Zeilen wird geprüft, ob der Indikator vorhanden ist. Dazu wird geprüft, ob in dem Objekt, das als Linie darstellt, ein Wert gespeichert ist. Dabei handelt es sich um den Wert des Preises, zu dem sich das Objekt befindet. Dieser Wert muss ungleich Null sein, wenn sich das anzeigende Objekt im Chart befindet. In allen anderen Fällen ist sie gleich Null, entweder weil das Objekt nicht existiert oder aus einem anderen Grund, der keine Rolle spielt. Ist jetzt klar, warum wir die Aktualisierung des Charts erzwingen müssen? Andernfalls würde der EA unnötigerweise Objekte hinzufügen, sodass wir nicht darauf warten können, dass die Plattform diese Aktion zu einem unbekannten Zeitpunkt durchführt. Wir müssen sicher sein, dass das Chart aktualisiert wurde. Andernfalls werden bei diesen Prüfungen Dinge gemeldet, die nicht mit dem aktuellen Zustand der Objekte übereinstimmen, wodurch das System weniger zuverlässig wird.

Obwohl es den Anschein hat, dass diese Prüfungen die Geschwindigkeit des EA verlangsamen, ist dies ein konzeptioneller Fehler. Wenn wir solche Prüfungen durchführen und nicht versuchen, die Plattform zu zwingen, ein Objekt zu erstellen, das sich möglicherweise bereits in der Erstellungswarteschlange befindet, sagen wir der Plattform „UPDATE NOW“. Wenn wir es dann brauchen, prüfen wir, ob das Objekt bereits erstellt wurde, und falls es bereits erstellt wurde, verwenden wir es nach Bedarf. Das nennt man „richtig programmieren“. Da wir auf diese Weise die Plattform entlasten und unnötige Überprüfungen vermeiden, ob das Objekt erstellt wurde oder nicht, machen wir den EA zuverlässiger, weil wir wissen, dass wir Daten haben, mit denen wir arbeiten wollen.

Da die Prüfung ergibt, dass es kein Objekt gibt, das dem angegebenen Ticket entspricht, wird das Objekt erstellt. Achten Sie darauf, dass am Anfang noch einmal geprüft wird, ob wir den Indikator 0 oder einen anderen erstellen. Dadurch wird sichergestellt, dass wir keine unnötigen Objekte haben, die von MetaTrader 5 unterstützt werden; wir haben nur die Objekte, die wir tatsächlich im Chart verwenden. Wenn wir den Indikator 0 erstellen, sind keine weiteren Tests erforderlich, da wir ihn unter sehr speziellen und spezifischen Bedingungen erstellen werden. Das Objekt 0 dient zur Positionierung von Aufträgen mit SHIFT oder CTRL + Maus. Keine Sorge, wir werden bald sehen, wie es funktioniert.

Es gibt ein wichtiges Detail im obigen Code: Warum aktualisieren wir das Chart, bevor wir die Funktion Update aufrufen? Es ist sinnlos. Um dies zu verstehen, sehen wir uns die Funktion UpdateIndicators an.

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0 = false;
                                
        pr = macroGetLinePrice(ticket, IT_RESULT);
        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
        SetTextValue(ticket, IT_PENDING, vol);
        if (tp > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_TAKE, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_TAKE);
                PositionAxlePrice(ticket, IT_TAKE, tp);
                SetTextValue(ticket, IT_TAKE, vol, (isBuy ? tp - pr : pr - tp));
        }
        if (sl > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_STOP, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_STOP);
                PositionAxlePrice(ticket, IT_STOP, sl);
                SetTextValue(ticket, IT_STOP, vol, (isBuy ? sl - pr : pr - sl));
        }
        if (b0) ChartRedraw();
}

Diese Funktion kümmert sich im Wesentlichen um die Indikatoren, die auf die Grenzwerte hinweisen. Schauen Sie sich nun die beiden hervorgehobenen Zeilen an: Wenn der Chart nicht aktualisiert wird, werden diese Zeilen nicht ausgelöst und geben den Wert 0 zurück, und wenn doch, funktioniert der Rest des Codes nicht, und die Grenzwertindikatoren werden nicht korrekt auf dem Bildschirm angezeigt.

Bevor wir die Limit-Indikatoren erstellen, müssen wir jedoch prüfen, ob sie wirklich erstellt werden müssen oder ob sie nur angepasst werden müssen. Dies geschieht auf die gleiche Weise wie bei der Erstellung des Basisobjekts. Und auch hier werden wir bei der Erstellung der Objekte die Aktualisierung des Charts erzwingen, damit das Chart immer aktuell ist.

Sie fragen sich vielleicht: „Warum gibt es so viele Zwangsaktualisierungen, sind sie wirklich notwendig?“ Und die Antwort darauf ist ein GROSSES und LAUTES JA... und der Grund dafür ist die untenstehende Funktion:

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};

Man könnte meinen, dass diese Funktion nichts Besonderes ist. Sind Sie sicher? FALSCH! Diese Funktion enthält einen wichtigen Punkt: Wir müssen sicherstellen, dass sich das Objekt im Chart befindet, da sonst der gesamte Code zur Erstellung des Objekts mehrmals aufgerufen wird, wodurch eine große Warteschlange entsteht, die von MetaTrader 5 verwaltet werden muss, und einige Daten könnten verloren gehen oder veralten. All dies macht das System instabil, weniger sicher und damit unzuverlässig. Der Aufruf der Funktion, die das Objekt erstellt, ist hervorgehoben. Wenn wir MetaTrader 5 nicht dazu zwingen würden, das Chart zu strategischen Zeitpunkten zu aktualisieren, könnten wir Probleme bekommen, da die obige Funktion durch das Ereignis OnTick aufgerufen wird, und in Zeiten hoher Volatilität ist die Anzahl der Aufrufe von OnTick ziemlich groß, was zu einem Überschuss an Objekten in der Warteschlange führen kann, was überhaupt nicht gut ist. Die Daten müssen also über den Aufruf von ChartRedraw aktualisiert und über ObjectGetDouble validiert werden, wodurch die Wahrscheinlichkeit verringert wird, dass sich zu viele Objekte in der Warteschlange befinden.

Auch ohne sich anzusehen, wie das System funktioniert, könnte man meinen: „Es ist gut, dass jetzt, im Falle eines versehentlichen Löschens des TradeLine-Objekts, der EA dies bemerkt, und wenn die Prüfung durch ObjectGetDouble fehlschlägt und der Indikator nicht funktioniert, wird der Indikator neu erstellt.“ Das ist die Idee. Es ist jedoch nicht empfehlenswert, Objekte zu löschen, die in der Objektliste vorhanden sind, ohne wirklich zu wissen, um welches Objekt es sich handelt, denn wenn Sie ein beliebiges Objekt (außer TradeLine) löschen, bemerkt der EA möglicherweise nicht, dass es keinen Indikator mehr gibt, und kann nicht mehr darauf zugreifen, da er keine andere Zugriffsmöglichkeit hat als die Schaltflächen auf dem Objekt.

Das obige Skript wäre ein echter Alptraum, wenn es nicht die Funktion gäbe, die direkt danach kommt und für die Aufrechterhaltung des gesamten Nachrichtenflusses innerhalb der Klasse verantwortlich ist. Es ist jedoch nicht der einzige Einstiegspunkt. Ich spreche von der Funktion DispatchMessage, schauen wir sie uns einmal an.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,
                bKeySell,
                bEClick;
        datetime        dt;
        uint            mKeys;
        char            cRet;
        eIndicatorTrade it;
        eEventType      ev;
                                
        static bool bMounting = false, bIsDT = false;
        static double valueTp = 0, valueSl = 0, memLocal = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        m_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.pr = price;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
                                        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
                                        m_InfoSelection.bIsMovingSelect = bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        RemoveIndicator(def_IndicatorTicket0);
                                        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
                                }
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetIndicatorInfos(sparam, ticket, it, ev))
                        {
                                if (GetInfosTradeServer(ticket) == 0) break;
                                CreateIndicatorTrade(ticket, it);
                                if ((it == IT_PENDING) || (it == IT_RESULT))
                                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                                ChartRedraw();
				m_TradeLine.SpotLight();
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:
                                        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                        {
                                                case IT_PENDING:
                                                case IT_RESULT:
                                                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                                        break;
                                                case IT_TAKE:
                                                case IT_STOP:
							m_InfoSelection.ticket = ticket;
							m_InfoSelection.it = it;
                                                        m_InfoSelection.bIsMovingSelect = true;
                                                        SetPriceSelection(0);
                                                        break;
                                        }
                                        break;
                                case EV_MOVE:
                                        if (m_InfoSelection.bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_InfoSelection.bIsMovingSelect = false;
                                        }else
                                        {
                                                m_InfoSelection.ticket = ticket;
                                                m_InfoSelection.it = it;
                                                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                                                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
                                        }
                                        break;
                        }
                        break;
        }
}

Diese Funktion hat so viele Änderungen erfahren, dass ich sie in kleine Teile zerlegen muss, um zu erklären, was in ihr vor sich geht. Wenn Sie bereits über Programmiererfahrung verfügen, wird es Ihnen nicht schwer fallen, zu verstehen, was es tut. Wenn Sie jedoch nur ein Enthusiast oder ein Anfänger in der MQL5-Programmierung sind, kann das Verständnis dieser Funktion ein wenig schwierig sein, daher werde ich sie im nächsten Thema in aller Ruhe erklären.


2.0.3. Analyse der Funktion DispatchMessage

In diesem Thema wird erläutert, was in der Funktion DispatchMessage geschieht. Wenn Sie verstehen, wie es funktioniert, indem Sie sich einfach den Code ansehen, dann wird Ihnen dieses Thema nichts Neues bieten.

Nach den lokalen Variablen gibt es als erstes die statischen Variablen.

static bool bMounting = false, bIsDT = false;
static double valueTp = 0, valueSl = 0, memLocal = 0;

Sie könnten als private Variablen in der Klasse deklariert werden, aber da sie nur an dieser Stelle des Codes verwendet werden, ist es nicht sinnvoll, dass andere Funktionen in der Klasse diese Variablen sehen. Sie sollten als statisch deklariert werden, da sie sich ihre Werte merken müssen, wenn die Funktion erneut aufgerufen wird. Wenn wir das Schlüsselwort „static“ nicht hinzufügen, verlieren sie ihren Wert, sobald die Funktion verlassen wird. Sobald dies geschehen ist, beginnen wir mit der Verarbeitung der Ereignisse, die MetaTrader 5 an den EA übermittelt.

Das erste Ereignis ist unten zu sehen:

case CHARTEVENT_MOUSE_MOVE:
        Mouse.GetPositionDP(dt, price);
        mKeys   = Mouse.GetButtonStatus();
        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed

Hier sammeln und isolieren wir Daten von der Maus und einigen Tasten (von der Tastatur), die mit der Maus verbunden sind. Danach folgt ein langer Code, der mit einem Test beginnt.

if (bKeyBuy != bKeySell)

Wenn Sie die UMSCHALT- oder die STRG-Taste drücken, aber nicht beide gleichzeitig, wird dies dem EA zu verstehen geben, dass Sie einen Auftrag zu einem bestimmten Preis erteilen möchten. Wenn ja, folgen weitere Prüfungen.

if (!bMounting)
{
        Mouse.Hide();
        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        m_InfoSelection.it = IT_PENDING;
        m_InfoSelection.pr = price;
}

Falls der Indikator 0 noch nicht gesetzt wurde, ist dieser Test erfolgreich. Die Maus wird ausgeblendet, dann werden die Werte im Chart-Trade erfasst. Diese Werte werden dann in Punkte umgewandelt, basierend auf dem Hebelniveau, das der Händler über Chart Trade angibt. Der Ausgangswert, bei dem der Auftrag aufgegeben wird, wird angezeigt. Diese Sequenz sollte nur einmal pro Nutzungszyklus auftreten.

Der nächste Schritt besteht darin, das Take-Profit- und das Stop-Loss-Niveau festzulegen und anzugeben, ob wir kaufen oder verkaufen wollen.

m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
m_InfoSelection.bIsBuy = bKeyBuy;

Sie werden außerhalb des Zyklus erstellt, denn wenn wir die Maus in einen anderen Preisbereich bewegen, müssen wir auch den Take-Profit und den Stop-Loss verschieben. Aber warum ist der obige Code nicht im Test enthalten? Der Grund dafür ist, dass, wenn Sie die SHIFT-Taste loslassen und die STRG-Taste drücken oder umgekehrt, ohne die Maus zu bewegen, während Indikatoren auf dem Bildschirm angezeigt werden, die Werte der Indikatoren Take-Profit und Stop-Loss ausgetauscht werden. Um dies zu vermeiden, muss das Fragment aus dem Test herausgehalten werden. Dies zwingt uns jedoch dazu, einen neuen Montagetest durchzuführen, der unten zu sehen ist:

if (!bMounting)
{
        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
        m_InfoSelection.bIsMovingSelect = bMounting = true;
}

Warum haben wir zwei Tests? Können wir nur einen haben? Das wäre ideal, aber die im obigen Code hervorgehobene Funktion lässt dies nicht zu. Wir müssen uns IndicatorAdd ansehen, um diese Tatsache zu verstehen. Nachdem wir die Anzeige 0 erstellt haben, setzen wir sie als ausgewählt und zeigen an, dass sie bereits läuft und gebaut ist. Daher können Sie sie mit der nächsten Zeile verschieben.

MoveSelection(price);

Aber selbst bei den gleichen Kriterien, wie dem Drücken von SHIFT oder CTRL, um einen schwebenden Auftrag zu platzieren, gibt es einen letzten Schritt.

if ((bEClick) && (memLocal == 0))
{
        RemoveIndicator(def_IndicatorTicket0);
        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
}

Dadurch wird ein schwebender Auftrag genau zu dem Punkt hinzugefügt, den wir anvisieren. Zwei Bedingungen müssen erfüllt sein. Der erste ist der Klick mit der linken Maustaste und der zweite ist, dass wir es nicht zum gleichen Preis in einem Zug gemacht haben. Das heißt, um zwei oder mehr Aufträge zum gleichen Preis zu platzieren, müssen wir diesen neuen Auftrag mit einer anderen Aufforderung platzieren, da dies nicht mit der gleichen Aufforderung geschehen wird. 

Gleichzeitig mit dem Entfernen des Indikators 0 aus dem Chart wird ein Auftrag mit korrekt ausgefüllten Parametern an den Handelsserver gesendet.

Kommen wir nun zum nächsten Schritt...

if (bKeyBuy != bKeySell)
{

// ... code described so far ....

}else if (bMounting)
{
        RemoveIndicator(def_IndicatorTicket0);
        Mouse.Show();
        memLocal = 0;
        bMounting = false;
}

Wenn der Indikator 0 gesetzt war, aber die Bedingung nicht erfüllt wurde, weil nur SHIFT oder CTRL gedrückt wurde, dann wird der hervorgehobene Code ausgeführt, um den Indikator 0 aus der Liste der Objekte zu entfernen, wobei gleichzeitig die Maus zurückgesetzt wird und die statischen Variablen in ihrem Ausgangszustand belassen werden. Mit anderen Worten: Das System wird sauber sein.

Der nächste und letzte Schritt innerhalb der Behandlung von Mausereignissen ist unten dargestellt:

if (bKeyBuy != bKeySell)
{

// ... previously described code ...

}else if (bMounting)
{

// ... previously described code ...

}else if ((!bMounting) && (bKeyBuy == bKeySell))
{
        if (bEClick) SetPriceSelection(price); else MoveSelection(price);
}


Der hervorgehobene Code ist der letzte Schritt der Maus in der Nachrichtenverarbeitung. Wenn wir weder den Indikator 0 noch die SHIFT- oder STRG-Tasten in einen anderen Zustand versetzt haben, d.h. sie können gleichzeitig gedrückt oder losgelassen werden, ergibt sich folgendes Verhalten: Wenn wir mit der linken Maustaste klicken, wird der Preis an den Indikator gesendet, und wenn wir nur die Maus bewegen, wird der Preis zur Bewegung des Indikators verwendet. Aber dann stellt sich die Frage: welcher Indikator? Keine Sorge, wir werden gleich sehen, um welchen Indikator es sich handelt, aber falls Sie sich wundern: Indikator 0 verwendet diese Auswahl nicht. Wenn Sie das nicht verstehen, gehen Sie zum Anfang dieses Abschnitts zurück und lesen Sie, wie die Nachrichtenverarbeitung funktioniert.

Es folgt die nächste Nachricht:

case CHARTEVENT_OBJECT_DELETE:
        if (GetIndicatorInfos(sparam, ticket, it, ev))
        {
                if (GetInfosTradeServer(ticket) == 0) break;
                CreateIndicatorTrade(ticket, it);
                if ((it == IT_PENDING) || (it == IT_RESULT))
                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                ChartRedraw();
		m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
        }
        break;

Erinnern Sie sich, dass ich oben gesagt habe, dass der EA ein kleines Sicherheitssystem hat, um das falsche Entfernen von Indikatoren zu verhindern? Dieses System ist im Code für die Verarbeitung von Nachrichten über Ereignisse enthalten, die von MetaTrader 5 gesendet werden, wenn ein Objekt gelöscht wird.

In diesem Fall meldet MetaTrader 5 mit dem Parameter sparam den Namen des gelöschten Objekts, anhand dessen überprüft wird, ob es sich um einen Indikator handelt, und wenn ja, um welchen. Es spielt keine Rolle, welches Objekt betroffen ist. Wir wollen wissen, welcher Indikator betroffen war. Danach werden wir prüfen, ob eine schwebende oder eine Markt-Position mit dem Indikator verbunden ist, und wenn ja, werden wir den gesamten Indikator neu erstellen. Im Extremfall, wenn es sich bei dem betroffenen Indikator um den Basisindikator handelt, positionieren wir ihn sofort neu und zwingen MetaTrader 5, den Indikator sofort auf dem Chart zu platzieren, unabhängig davon, um welchen Indikator es sich handelt. Wir entfernen die Selektionsmarkierung und platzieren einen Auftrag zur Aktualisierung der Indikatorschwellendaten. 

Das nächste zu behandelnde Ereignis ist sehr einfach, es stellt lediglich eine Anfrage zur Größenänderung aller Indikatoren auf dem Bildschirm, sein Code ist unten dargestellt.

case CHARTEVENT_CHART_CHANGE:
        ReDrawAllsIndicator();
        break;

Hier ist das Objekt-Klick-Ereignis.

case CHARTEVENT_OBJECT_CLICK:
        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
        {
//....
        }
        break;

Es beginnt wie oben gezeigt: MetaTrader 5 teilt uns mit, welches Objekt angeklickt wurde, sodass der EA prüfen kann, welche Art von Ereignis zu behandeln ist. Bisher haben wir 2 Ereignisse CLOSE und MOVE. Betrachten wir zunächst das Ereignis CLOSE, das den Indikator auf dem Bildschirm schließt und sein Ende definiert.

case EV_CLOSE:
        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
        {
                case IT_PENDING:
                case IT_RESULT:
                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                        break;
                case IT_TAKE:
                case IT_STOP:
			m_InfoSelection.ticket = ticket;
			m_InfoSelection.it = it;
                        m_InfoSelection.bIsMovingSelect = true;
                        SetPriceSelection(0);
                        break;
        }
        break;

Das Ereignis „Schließen“ macht nun Folgendes: Es verwendet das Ticket, um auf dem Server zu suchen, was geschlossen werden soll, und um zu prüfen, ob es etwas zu schließen gibt, denn es kann vorkommen, dass der Server dies zu diesem Zeitpunkt bereits getan hat, der EA aber noch nicht davon weiß. Da wir etwas zu schließen haben, sollten wir es richtig machen, damit wir die erforderlichen Prüfungen durchführen und die Klasse richtig informieren können, um einen Indikator aus dem Chart zu schließen oder zu entfernen.

Damit sind wir beim letzten Schritt in diesem Thema angelangt, der unten dargestellt ist.

case EV_MOVE:
        if (m_InfoSelection.bIsMovingSelect)
        {
                m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
        }else
        {
                m_InfoSelection.ticket = ticket;
                m_InfoSelection.it = it;
                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
        }
        break;

MOVE ist ein Ereignis, das genau dies tut — es wählt den zu verschiebenden Indikator aus. Es wird also nur ausgewählt, aber die Bewegung selbst wird während eines Mausbewegungsereignisses ausgeführt. Erinnern Sie sich, dass ich zu Beginn des Themas gesagt habe, dass es eine Bedingung gibt, unter der wir es nicht mit dem Indikator 0 zu tun haben, und selbst dann wird sich noch etwas bewegen. Dies wird an dieser Stelle, im Ereignis „move“, angezeigt. Hier wird geprüft, ob etwas zum Verschieben ausgewählt wurde. Wenn dies der Fall ist, wird der ausgewählte Indikator nicht mehr ausgewählt und empfängt keine Mausbewegungsereignisse, und der neue Indikator wird nicht ausgewählt. In diesem Fall werden die Daten des neuen Indikators, der die Mausdaten erhalten soll, in einer Struktur gespeichert und dieser Indikator erhält eine Änderung, die anzeigt, dass er ausgewählt ist. Diese Veränderung zeigt sich in der Linienstärke.


2.0.4. Eine neue Objektklasse Maus

Zusätzlich zu den oben erwähnten Verbesserungen gibt es noch weitere, die es verdienen, erwähnt zu werden.

Während die meisten Händler kein mausbasiertes Indikatorsystem in einem EA benötigen, möchten andere, dass das System perfekt funktioniert. Allerdings kann es vorkommen, dass der Händler einige der Objekte, aus denen der Mausindikator besteht, versehentlich löscht, was zu seinem Scheitern führt. Glücklicherweise können wir dies durch die Verwendung des Ereignissystems EVENT vermeiden. Sobald das Ereignis einer Objektlöschung erkannt und an den EA gesendet wird, kann die Klasse, zu der das Objekt gehört, das Objekt erneut erstellen, was dem System Stabilität verleiht. Aber es ist gut, die Liste der Punkte so klein wie möglich zu halten, sie nach Bedarf anzulegen und sie zu löschen, wenn sie nicht mehr benötigt werden. So haben wir es bisher gemacht, aber die Klasse der Maus fehlte.

Beginnen wir damit, einige Definitionen zu erstellen, die das System zur Erstellung von Konstantennamen ersetzen.

#define def_MousePrefixName "MOUSE "
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TMPV"
#define def_NameObjectLineT def_MousePrefixName + "TMPT"
#define def_NameObjectBitMp def_MousePrefixName + "TMPB"
#define def_NameObjectText  def_MousePrefixName + "TMPI"

Danach sieht die neue Initialisierungsfunktion wie folgt aus:

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        Show();
}

Bitte beachten Sie, dass sie viel einfacher ist als die vorherige Version. An diesem Punkt haben wir den Aufruf, der das Maussystem anzeigt. Der Aufruf erfolgt an der markierten Stelle im vorherigen Code. Er ruft den Code auf, der tatsächlich ein Anzeigesystem auf der Preisachse erstellt.

inline void Show(void)
{
        if (ObjectGetDouble(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_PRICE) == 0)
        {
                ObjectCreate(Terminal.Get_ID(), def_NameObjectLineH, OBJ_HLINE, 0, 0, 0);
                ObjectSetString(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_TOOLTIP, "\n");
                ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_BACK, false);
        }
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, m_Infos.cor01);
}

Dieser Code ist sehr interessant: Er prüft, ob das Mauszeigerobjekt im Preis existiert oder nicht. Wenn die Prüfung erfolgreich ist, bedeutet dies, dass eine Linie im Chart vorhanden ist oder etwas mit der Maus zu tun hat. Warum führen wir diese Prüfung durch? Um dies zu verstehen, werfen Sie einen Blick auf die Funktion, die für das Ausblenden bzw. Entfernen der mit der Maus verbundenen Objekte verantwortlich ist. Siehe die Funktion unten:

inline void Hide(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(Terminal.Get_ID(), def_MousePrefixName + "T");
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
}

Dies ist ein interessanter Arbeitsstil. Alle Objekte, die mit der Maus verbunden sind und den angegebenen Namen tragen, werden aus dem MetaTrader 5-Chart gelöscht, sodass die Liste der Objekte immer klein ist. Die horizontale Linie wird jedoch nicht gelöscht, nur ihre Farbe ändert sich. Daher führt die Funktion, die die Maus zeigt, eine Prüfung durch, bevor das Objekt erstellt wird, denn es wird nicht wirklich aus der Liste der Objekte ausgeschlossen, sondern nur ausgeblendet. Alle anderen Objekte werden jedoch aus der Liste der Objekte gelöscht. Aber wie sollen wir dann diese anderen Objekte im Studium verwenden? Da es sich bei den Studien um kurze Momente handelt, in denen wir nur einige Details herausfinden wollen, macht es keinen Sinn, die Objekte in der Liste zu behalten, nur um sie 1-2 Mal zu verwenden. Es ist besser, sie zu erstellen, die Studie durchzuführen und sie dann von der Liste zu streichen, damit wir ein zuverlässigeres System erhalten.

Das mag albern erscheinen, aber das von uns gezeigte Ordnungssystem basiert auf der Verwendung von Objekten, und je mehr Objekte in der Liste sind, desto mehr Arbeit muss MetaTrader 5 leisten, um die Liste zu durchsuchen, wenn wir auf ein bestimmtes Objekt zugreifen wollen. Wir werden also keine zusätzlichen Objekte auf dem Chart oder in der Liste der Objekte hinterlassen, um das System so einfach wie möglich zu halten.

Achten Sie nun auf die Funktion DispatchMessage, die wie folgt beginnt:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;

Direkt danach folgt der Code, der das erste Ereignis behandelt.

switch (id)
{
        case CHARTEVENT_MOUSE_MOVE:
                Position.X = (int)lparam;
                Position.Y = (int)dparam;
                ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                ObjectMove(Terminal.Get_ID(), def_NameObjectLineH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                if (b1 > 0) ObjectMove(Terminal.Get_ID(), def_NameObjectLineV, 0, Position.dt, 0);
                key = (uint) sparam;
                if ((key & 0x10) == 0x10)    //Middle button....
                {
                        CreateObjectsIntern();
                        b1 = 1;
                }

Wenn wir die mittlere Maustaste drücken, erzeugen wir einen Anruf. Aber das ist jetzt nicht mehr der Fall. Dann werden wir sehen, was diese Funktion bewirkt. Beachten Sie, dass wir versuchen, ein Objekt zu verschieben, das nicht existiert, weil es nicht in der Liste der von MetaTrader 5 unterstützten Objekte enthalten ist. Dieser Aufruf erfolgt nur, wenn die mittlere Maustaste gedrückt wird. Beachten Sie, die Variable b1 steuert, zu welchem Zeitpunkt sich der Händler innerhalb der Gruppe befindet, die an der Erstellung der Studie beteiligt ist.

Sobald der Nutzer auf die linke Maustaste klickt und der erste Schritt abgeschlossen ist, wird der folgende Code ausgeführt:

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

Sie positioniert die Trendlinie und ruft den nächsten Schritt auf, in dem der Wert der Variablen b1 geändert wird. An dieser Stelle können wir zum nächsten Fragment übergehen.

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), def_NameObjectBitMp, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), def_NameObjectText, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), def_NameObjectText, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}

Das obige Fragment zeigt die Studie auf dem Bildschirm an. All diese Objekte, die sich in diesem Fragment befinden, werden nicht mehr existieren, wenn die Studie beendet ist, sie werden innerhalb dieser Routine erstellt und zerstört. Obwohl dies nicht sehr effizient zu sein scheint, habe ich während der Studienphase weder eine Verringerung noch eine Erhöhung der Bearbeitungszeit festgestellt. In der Tat habe ich eine leichte Verbesserung des Auftragssystems festgestellt, etwas sehr Subtiles, das praktisch innerhalb der Fehlermarge der vergleichenden Schätzung liegt. Ich kann also nicht sagen, dass diese Änderungen tatsächlich Verbesserungen in Bezug auf die Verarbeitung gebracht haben.

Beachten Sie jedoch, dass die Studie durchgeführt wird, solange die linke Maustaste gedrückt ist; sobald wir sie loslassen, wird das nächste Fragment ausgeführt.

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}
Position.ButtonsStatus = (b1 == 0 ? key : 0);

Hier werden alle Objekte, die zur Erstellung der Studie verwendet wurden, aus der Liste der Objekte entfernt. Zeigen wir die Mauslinie noch einmal auf dem Bildschirm. Der hervorgehobene Code ist eine großartige Idee, da er verhindert, dass eine Funktion oder ein Unterprogramm innerhalb des EA falsche Messwerte erhält, wenn wir die Maustasten erfassen. Wenn eine Studie durchgeführt wird, sollte der EA die Schaltflächenzustände ignorieren. Zu diesem Zweck verwenden wir die hervorgehobenen Zeilen. Es ist keine perfekte Lösung, aber besser als nichts.

Wir haben den Code, der die Objekte für die Durchführung der Studie erstellt, nicht berücksichtigt. Da es sich hierbei jedoch um eine recht einfache Funktion handelt, werde ich mich in diesem Artikel nicht darauf konzentrieren.


Schlussfolgerung

Auch wenn die Änderungen geringfügig erscheinen mögen, machen sie doch einen großen Unterschied im System selbst aus. Dabei ist eines zu bedenken: Unser Befehlssystem basiert auf grafischen Objekten auf dem Bildschirm. Je mehr Objekte der EA also verarbeitet, desto geringer wird seine Leistung sein, wenn wir ein bestimmtes Objekt anfordern. Erschwerend kommt hinzu, dass das System in Echtzeit arbeitet, d. h. je schneller das System unseres EA ist, desto besser wird seine Leistung sein. Deshalb gilt: Je weniger der EA zu tun hat, desto besser. Im Idealfall sollte es nur mit dem Auftragssystem funktionieren, alles andere sollten wir auf eine andere Ebene verlagern, und MetaTrader 5 sollte sich darum kümmern. Dies werden wir natürlich schrittweise tun, da wir viele kleine Änderungen vornehmen müssen, die aber nicht zu kompliziert sind. Dies wird in den nächsten Artikeln geschehen, die ausschließlich der Verbesserung der Zuverlässigkeit des EA gewidmet sind.

Eines kann ich mit Sicherheit sagen: In Zukunft wird der EA nur noch für das Bestellsystem zuständig sein. Im nächsten Artikel werden wir dem EA einen sehr interessanten letzten Schliff geben: Wir werden die Anzahl der Objekte, die während des Betriebs des EAs in der Liste vorhanden sind, weiter reduzieren, da das Auftragssystem ein großer Objektgenerator ist, und wir werden sehen, wie man dieses System so verändern kann, dass die Last, die es auf dem MetaTrader 5 erzeugt, minimiert wird.

Aus diesem Grund füge ich diesem Artikel keine Änderungen bei, da der Code selbst noch geändert werden kann. Aber keine Sorge, es lohnt sich, auf den nächsten Artikel zu warten. Diese Änderungen werden die Gesamtleistung unseres Expert Advisors erheblich steigern. Wir sehen uns also im nächsten Artikel dieser Reihe.


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

Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 25): Herstellen eines robusten Systems (II) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 25): Herstellen eines robusten Systems (II)
In diesem Artikel werden wir den letzten Schritt zu einem schnellen EA machen. Machen Sie sich also auf eine längere Lektüre gefasst. Um unseren Expert Advisor zuverlässig zu machen, werden wir zunächst alles aus dem Code entfernen, was nicht Teil des Handelssystems ist.
Algorithmen zur Populationsoptimierung Algorithmen zur Populationsoptimierung
Dies ist ein einführender Artikel über die Klassifizierung von Optimierungsalgorithmen (OA). In dem Artikel wird versucht, einen Prüfstand (eine Reihe von Funktionen) zu erstellen, der zum Vergleich von OAs und vielleicht zur Ermittlung des universellsten Algorithmus unter allen bekannten Algorithmen verwendet werden soll.
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 26): Der Zukunft entgegen (I) Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 26): Der Zukunft entgegen (I)
Heute werden wir unser Auftragssystem auf die nächste Stufe bringen. Aber vorher müssen wir noch einige Probleme lösen. Jetzt haben wir einige Fragen, die sich darauf beziehen, wie wir arbeiten wollen und welche Dinge wir während des Handelstages tun.
Risiko- und Kapitalmanagement durch Expert Advisor Risiko- und Kapitalmanagement durch Expert Advisor
In diesem Artikel geht es darum, was Sie in einem Backtest-Bericht nicht sehen können, was Sie erwarten sollten, wenn Sie automatisierte Handelssoftware verwenden, wie Sie Ihr Geld verwalten, wenn Sie Expert Advisors verwenden, und wie Sie einen erheblichen Verlust ausgleichen können, um in der Handelsaktivität zu bleiben, wenn Sie automatisierte Verfahren verwenden.