English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
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)

MetaTrader 5Beispiele | 25 Oktober 2022, 09:38
361 0
Daniel Jose
Daniel Jose

Einführung

Im vorigen Artikel Herstellen eines robusten Systems (I) haben wir gesehen, wie man einige Teile des EA ändern kann, um das System zuverlässiger und robuster zu machen.

Dies war nur eine Einführung in das, was wir in diesem Artikel tun werden. Vergessen Sie alles, was Sie wussten, geplant oder sich gewünscht haben. Das Schwierigste dabei ist, die Dinge zu trennen. Seit Beginn dieser Serie hat sich der EA fast ständig weiterentwickelt: Wir haben Dinge hinzugefügt, geändert und sogar entfernt. Dieses Mal werden wir mit dem, was wir bisher getan haben, bis zum Äußersten gehen.

Im Gegensatz zu dem, was es scheinen mag, gibt es ein großes Problem: Ein gut gestalteter EA enthält keinen und wird keinen Indikator enthalten. Er wird lediglich darauf achten und sicherstellen, dass die angegebenen Auftragspositionen eingehalten werden. Der perfekte EA ist im Grunde nur ein Assistent, der einen echten Einblick in die Kursentwicklung bietet. Die Indikatoren werden nicht berücksichtigt, sondern nur die Positionen oder Aufträge, die sich auf dem Chart befinden.

Sie könnten denken, dass ich Unsinn rede und nicht weiß, wovon ich spreche. Aber haben Sie schon einmal darüber nachgedacht, warum MetaTrader 5 verschiedene Klassen für verschiedene Dinge anbietet? Warum hat die Plattform Indikatoren, Dienste, Skripte und Expert Advisors separat und nicht in einem Block? Also...

Das ist der springende Punkt. Wenn Dinge getrennt werden, dann gerade deshalb, weil sie besser getrennt bearbeitet werden können.

Indikatoren werden für einen allgemeinen Zweck verwendet, was auch immer das sein mag. Es ist gut, wenn das Design der Indikatoren gut durchdacht ist, um die Gesamtleistung nicht zu beeinträchtigen — ich meine nicht die MetaTrader 5 Plattform, sondern andere Indikatoren. Da sie auf einem anderen Thread laufen, können sie Aufgaben sehr effizient parallel ausführen.

Die Dienste helfen auf unterschiedliche Weise. In den Artikeln Zugang zu Daten im Internet (II) und Zugang zu Daten im Internet (III) in dieser Reihe haben wir beispielsweise Dienste für den Datenzugriff auf eine sehr interessante Weise genutzt. Eigentlich könnten wir das direkt im EA machen, aber das ist nicht die geeignetste Methode, wie ich schon in anderen Artikeln erklärt habe.

Skripte helfen uns auf ganz besondere Weise, da sie nur für eine bestimmte Zeit existieren, etwas ganz Bestimmtes tun und dann aus dem Chart verschwinden. Oder sie bleiben dort, bis wir eine Diagrammeinstellung wie z. B. den Zeitrahmen ändern.

Das schränkt die Möglichkeiten ein wenig ein, aber das ist ein Teil dessen, was wir akzeptieren müssen, wie es ist. Expert Advisors (EAs) hingegen sind spezifisch für die Arbeit mit einem Handelssystem. Obwohl wir in EAs Funktionen und Codes hinzufügen können, die nicht Teil des Handelssystems sind, ist dies für hochleistungsfähige oder hochzuverlässige Systeme nicht sehr sinnvoll. Der Grund dafür ist, dass alles, was nicht Teil des Handelssystems ist, nicht im EA sein sollte: Dinge sollten an den richtigen Stellen platziert und korrekt gehandhabt werden.

Um die Zuverlässigkeit zu verbessern, sollte man daher als Erstes alles aus dem Code entfernen, was nicht Teil des Handelssystems ist, und diese Dinge in Indikatoren oder Ähnliches umwandeln. Das einzige, was im EA-Code verbleibt, sind die Teile, die für die Verwaltung, Analyse und Verarbeitung von Aufträgen oder Positionen zuständig sind. Alle anderen Dinge werden entfernt.

Also, fangen wir an.


2.0. Umsetzung

2.0.1. Entfernen des EA-Hintergrunds

Dies schadet dem EA zwar nicht und verursacht auch keine Probleme, aber manche Leute möchten, dass ihr Bildschirm leer ist und nur bestimmte Elemente angezeigt werden. Wir werden also diesen Teil aus dem EA entfernen und ihn in einen Indikator umwandeln. Es ist sehr einfach zu implementieren. Wir werden keine der Klassen anfassen, sondern den folgenden Code erstellen:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
//+------------------------------------------------------------------+
input string                    user10 = "Wallpaper_01";        //Used BitMap
input char                      user11 = 60;                    //Transparency (from 0 to 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Background image type
//+------------------------------------------------------------------+
C_Terminal      Terminal;
C_WallPaper WallPaper;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper");
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
        break;
        }
        ChartRedraw();
}
//+------------------------------------------------------------------+

Wie Sie sehen können, ist alles ganz natürlich und verständlich. Wir haben einfach den Code aus dem EA gelöscht und ihn in einen Indikator umgewandelt, der dem Chart hinzugefügt werden kann. Und jede Änderung, sei es der Hintergrund, der Grad der Transparenz oder sogar das Entfernen aus dem Diagramm, hat keine Auswirkungen auf die EA-Aktionen.

Und jetzt werden wir damit beginnen, die Dinge zu löschen, die wirklich eine Verschlechterung der EA-Leistung verursachen. Dies sind die Dinge, die von Zeit zu Zeit oder bei jeder Kursbewegung funktionieren und daher manchmal dazu führen können, dass der EA langsamer wird, was ihn daran hindert, seine eigentliche Aufgabe zu erfüllen — zu beobachten, was mit den Aufträgen oder Positionen auf dem Chart passiert.


2.0.2. Volumen zum Preis in einen Indikator umwandeln

Auch wenn es nicht so aussieht, braucht das System Volume-At-Price Zeit, die für einen EA oft entscheidend ist. Ich meine die Momente hoher Volatilität, in denen die Preise wild und ohne große Richtung schwanken. In diesen Zeiten benötigt der EA jeden verfügbaren Maschinenzyklus, um seine Aufgabe zu erfüllen. Es wäre ärgerlich, eine gute Gelegenheit zu verpassen, weil ein Indikator beschließt, den Job zu übernehmen. Entfernen wir ihn also aus dem EA und verwandeln ihn in einen echten Indikator, indem wir den folgenden Code erstellen:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
//+------------------------------------------------------------------+
input color             user0   = clrBlack;                     //Bar color
input   char            user1   = 20;                                   //Transparency (from 0 to 100 )
input color     user2 = clrForestGreen; //Buying
input color     user3 = clrFireBrick;   //Selling
//+------------------------------------------------------------------+
C_Terminal                      Terminal;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        Terminal.Init();
        VolumeAtPrice.Init(user2, user3, user0, user1);
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        VolumeAtPrice.Update();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Das war der einfachste Teil. Wir haben den Code aus dem EA entfernt und ihn in den Indikator eingefügt. Wenn Sie den Code wieder in den EA einfügen möchten, brauchen wir nur den Indikatorcode zu kopieren und ihn wieder in den EA einzufügen.

Wir haben also mit etwas Einfachem begonnen. Aber jetzt wird es komplizierter — wir werden „Times & Trade“ aus dem EA entfernen.


2.0.3. Umwandlung von „Times & Trade“ in einen Indikator

Das ist nicht so einfach, wenn wir einen Code erstellen wollen, der sowohl in einem EA als auch in einem Indikator verwendet werden kann. Da es sich um einen Indikator handelt, der in einem Unterfenster arbeitet, wäre es einfach, ihn in einen Indikator zu konvertieren. Aber es ist nicht ganz einfach, weil es in einem Unterfenster funktioniert. Das Hauptproblem ist, dass wir, wenn wir alles wie in den vorherigen Fällen machen, das folgende Ergebnis im Indikatorfenster haben werden:

Es ist nicht empfehlenswert, solche Dinge im Indikatorfenster zu platzieren, da dies den Nutzer verwirren würde, wenn er den Indikator vom Bildschirm entfernen möchte. Dies sollte also auf eine andere Weise geschehen. Am Ende dieses Weges, der recht verwirrend erscheinen mag, aber in Wirklichkeit nur aus einer Reihe von Direktiven und einer gewissen Bearbeitung besteht, erhalten wir das folgende Ergebnis im Indikatorfenster.

Das ist genau das, was der Nutzer erwartet — und nicht das Durcheinander, das auf dem Bild oben zu sehen ist.

Nachstehend finden Sie den vollständigen Code des Indikators Times & Trade:

#property copyright "Daniel Jose"
#property version   "1.00"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
//+------------------------------------------------------------------+
C_Terminal        Terminal;
C_TimesAndTrade   TimesAndTrade;
//+------------------------------------------------------------------+
input int     user1 = 2;      //Scale
//+------------------------------------------------------------------+
bool isConnecting = false;
int SubWin;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade");
        SubWin = ChartWindowFind();
        Terminal.Init();
        TimesAndTrade.Init(user1);
        EventSetTimer(1);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        if (isConnecting)
                TimesAndTrade.Update();
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        if (TimesAndTrade.Connect())
        {
                isConnecting = true;
                EventKillTimer();
        }
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        TimesAndTrade.Resize();
        break;
        }
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Der Code scheint dem im EA verwendeten ähnlich zu sein, mit Ausnahme der hervorgehobenen Zeile, die im EA-Code nicht vorhanden ist. Wo ist dann der Haken? Oder gibt es keine? Allerdings gibt es einen Haken: Der Code ist nicht genau derselbe, es gibt einen Unterschied, der nicht im Indikator- oder EA-Code, sondern im Klassencode liegt. Doch bevor wir den Unterschied betrachten, sollten wir über Folgendes nachdenken: Wie sagen wir dem Compiler, was er kompilieren soll und was nicht? Vielleicht machen Sie sich beim Programmieren überhaupt keine Gedanken darüber — vielleicht erstellen Sie einfach Code und wenn Ihnen etwas nicht gefällt, löschen Sie es einfach.

Erfahrene Programmierer haben eine Regel: Entferne nur etwas, wenn es definitiv nicht funktioniert, ansonsten behalte Fragmente, auch wenn sie nicht wirklich kompiliert werden. Aber wie macht man das in einem linearen Code, wenn man will, dass die geschriebenen Funktionen immer funktionieren? Hier ist die Frage: Wissen Sie, wie Sie dem Compiler mitteilen können, was er kompilieren soll und was nicht? Wenn die Antwort „Nein“ lautet, ist das in Ordnung. Als ich anfing, wusste ich persönlich nicht, wie man das macht. Aber es hilft sehr. Finden wir heraus, wie man das macht.

Einige Sprachen haben Kompilierrichtlinien, die je nach Autor auch als Präprozessor bezeichnet werden können. Aber die Idee ist dieselbe: dem Compiler mitteilen, was er kompilieren soll und wie er die Kompilierung durchführen soll. Es gibt eine ganz bestimmte Art von Direktive, die verwendet werden kann, um Code absichtlich zu isolieren, damit wir bestimmte Dinge testen können. Dies sind bedingte Kompilierungsanweisungen. Wenn sie richtig eingesetzt werden, können wir denselben Code auf unterschiedliche Weise kompilieren. Genau das wird hier mit dem Beispiel von Times & Trade gemacht. Wir wählen aus, wer für die Erstellung der bedingten Zusammenstellung verantwortlich sein soll: entweder der EA oder der Indikator. Nachdem wir diesen Parameter definiert haben, erstellen wir die #define-Direktive und verwenden dann die bedingte Direktive #ifdef #else #endif, um dem Compiler mitzuteilen, wie der Code kompiliert werden soll.

Das kann schwierig zu verstehen sein, also sehen wir uns an, wie es funktioniert.

Wir definieren im EA-Code die unten hervorgehobenen Zeilen und fügen sie hinzu:

#define def_INTEGRATION_WITH_EA
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#ifdef def_INTEGRATION_WITH_EA
        #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
#endif
//+------------------------------------------------------------------+

Es geschieht Folgendes: Wenn Sie einen EA mit Klassen in MQH-Dateien kompilieren wollen, löschen Sie nicht die Direktive #ifdefine def_INTEGRATION_WIT_EA, die im Expert Advisor definiert ist. Dadurch wird der EA alle Klassen enthalten, die wir in die Indikatoren einfügen. Wenn Sie die Indikatoren löschen wollen, brauchen Sie den Code nicht zu löschen, sondern nur die Definition in einen Kommentar zu wandeln. Dies kann einfach dadurch erreicht werden, dass die Zeile, in der die Richtlinie deklariert wird, in eine Kommentarzeile umgewandelt wird. Der Compiler wird die Direktive nicht sehen, sie wird als nicht existent angegeben; da sie nicht existiert, wird sie jedes Mal, wenn die bedingte Direktive #ifdef def_INTEGRATION_WITH_EA gefunden wird, wird sie vollständig ignoriert, während Code zwischen ihr und dem #endif Teil im obigen Beispiel nicht kompiliert wird.

Das ist die Idee, die wir in der Klasse C_TimesAndTrade umsetzen. Hier sehen Sie, wie die neue Klasse aussieht. Ich werde nur einen Punkt aufzeigen, um Ihre Aufmerksamkeit zu erregen:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh>
#ifdef def_INTEGRATION_WITH_EA

#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>

class C_TimesAndTrade : private C_FnSubWin

#else

class C_TimesAndTrade

#endif
{
//+------------------------------------------------------------------+
#define def_SizeBuff 2048
#define macro_Limits(A) (A & 0xFF)
#define def_MaxInfos 257
#define def_ObjectName "TimesAndTrade"
//+------------------------------------------------------------------+
        private :
                string  m_szCustomSymbol;

// ... The rest of the class code....

}

Der Code mag jedem, der keine Kompilieranweisungen verwendet, seltsam erscheinen. Die Direktive def_INTEGRATION_WITH_EA wird im EA deklariert. Dann geschieht Folgendes. Wenn der Compiler aus dieser Datei den Objektcode erzeugt, geht er von folgender Beziehung aus: Wenn die zu kompilierende Datei ein EA ist und eine deklarierte Direktive hat, erzeugt der Compiler Objektcode mit Teilen, die sich zwischen den bedingten Direktiven #ifdef def_INTEGRATION_WITH_EA und #else liegen. Normalerweise verwenden wir in solchen Fällen die #else-Direktive. Falls eine andere Datei kompiliert wird, zum Beispiel der Indikator, dessen Direktive def_INTEGRATION_WITH_EA nicht definiert ist, wird alles zwischen den Direktiven #else und #endif kompiliert werden. So funktioniert es.

Wenn Sie einen EA oder einen Indikator kompilieren, sollten Sie sich den gesamten Code der Klasse C_TimesAndTrade ansehen, um jeden dieser Tests und die allgemeine Funktionsweise zu verstehen. So nimmt der MQL5-Compiler alle Einstellungen vor, was uns Zeit und Mühe erspart, die mit der Notwendigkeit verbunden sind, zwei verschiedene Dateien zu pflegen.


2.0.4. Den EA agiler machen

Wie bereits erwähnt, sollte der EA nur mit dem Auftragssystem arbeiten. Bislang wies es die Merkmale auf, die jetzt zu Indikatoren geworden sind. Der Grund dafür ist etwas sehr Persönliches, das mit den Dingen zu tun hat, die bei den Berechnungen, die der EA durchführen sollte, eine Rolle spielen. Dieses Berechnungssystem wurde jedoch geändert und auf eine andere Methode umgestellt. Dadurch habe ich festgestellt, dass das Auftragssystem durch einige Dinge, die der EA tat, anstatt sich um die Aufträge zu kümmern, beeinträchtigt wurde. Die schlimmsten Probleme traten bei OnTick auf:

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

Dieses Ereignis hat nun diese bedingte Richtlinie erhalten, sodass diejenigen, die nicht während Zeiten hoher Volatilität handeln, auf Wunsch einen EA mit allen ursprünglichen Indikatoren haben können. Doch bevor Sie dies für eine gute Idee halten, möchte ich Sie daran erinnern, wie die Aktualisierungsfunktion von Times & Trade funktioniert.

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;
        long lg1;
        static int nSwap = 0;
        static long lTime = 0;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {

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

        }
}

Der obige Code ist ein Teil der Aktualisierungsfunktion in der Klasse C_TimesAndTrade. Das Problem liegt in dem hervorgehobenen Teil. Jedes Mal, wenn er ausgeführt wird, wird eine Anfrage an den Server gesendet, um alle seit einem bestimmten Zeitpunkt getätigten Tauschtickets zurückzugeben, was übrigens nicht so problematisch ist. Das Problem ist, dass dieser Anruf gelegentlich mit zwei anderen Ereignissen zusammenfällt.

Das erste und offensichtlichste Ereignis ist die große Anzahl von Positionen, die stattfinden könnten, was dazu führt, dass die Funktion OnTick eine große Anzahl von Aufrufen erhält. Diese Funktion muss nicht nur den oben genannten Code in der Klasse C_TimesAndTrade ausführen, sondern auch ein weiteres Problem lösen: den Aufruf der Funktion SecureChannelPosition in der Klasse C_IndicatorTradeView. Es ist also ein weiteres kleines Problem, aber das ist noch nicht alles. Ich habe bereits gesagt, dass trotz der geringen Volatilität von Zeit zu Zeit zwei Ereignisse zusammentreffen werden, von denen das erste dieses Ereignis war.

Der zweite befindet sich in OnTime, das bereits aktualisiert wurde und wie folgt aussieht:

#ifdef def_INTEGRATION_WITH_EA
void OnTimer()
{
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
}
#endif 

Wenn Sie den EA so verwenden, wie er konzipiert wurde, auch weil er noch mehr Code erhält, kann es manchmal zu Problemen kommen, weil die Ereignisse zusammenfallen. Wenn dies geschieht, bleibt der EA (wenn auch nur für eine einzige Sekunde) und macht Dinge, die nichts mit dem Auftragssystem zu tun haben.

Im Gegensatz zu der Funktion in C_TimesAndTrade ist diese Funktion in der Klasse C_VolumeAtPrice vorhanden und kann die Leistung des EA bei der Verwaltung von Aufträgen stark beeinträchtigen. Dies ist der Grund dafür:

inline virtual void Update(void)
{
        MqlTick Tick[];
        int i1, p1;

        if (macroCheckUsing == false) return;
        if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0)
        {
                if (m_Infos.CountInfos == 0)
                {
                        macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time));
                        m_Infos.FirstPrice = Tick[0].last;
                }                                               
                for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++);
                for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
                if (p1 == i1) return;
                m_Infos.memTimeTick = Tick[i1 - 1].time_msc;
                m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time);
                Redraw();
        };      
};

Der Grund dafür ist in den hervorgehobenen Teilen zu finden, aber der schlimmste von ihnen ist REDRAW. Dies schadet der Leistung des EA erheblich, da bei jedem empfangenen Tick mit einem Volumen ÜBER den angegebenen Wert überschreitet, wird das gesamte Volumen-at-Price vom Bildschirm entfernt, neu berechnet und wieder an seinen Platz gesetzt. Dies geschieht etwa jede Sekunde. Dies kann mit anderen Dingen zusammenhängen, deshalb werden alle Indikatoren aus dem EA entfernt. Obwohl ich sie so belassen habe, dass Sie sie direkt im EA verwenden können, empfehle ich dies aus den oben erläuterten Gründen dennoch nicht.

Diese Änderungen waren notwendig. Aber es gibt noch eine andere, die symbolträchtiger ist und die durchgeführt werden muss. Dieses Mal betrifft die Änderung das Ereignis OnTradeTransaction. Die Verwendung dieses Ereignisses ist ein Versuch, das System so flexibel wie möglich zu gestalten. Viele derjenigen, die EAs zur Auftragsausführung programmieren, verwenden das Ereignis OnTrade, um zu überprüfen, welche Aufträge auf dem Server sind oder nicht, oder welche Positionen noch offen sind. Ich sage nicht, dass sie es falsch machen. Es ist nur nicht sehr effizient, da der Server uns darüber informiert, was vor sich geht. Das große Problem mit dem OnTrade-Ereignis ist jedoch die Tatsache, dass wir unnötigerweise immer wieder Dinge überprüfen müssen. Wenn wir das Ereignis OnTradeTransaction verwenden, haben wir ein System, das zumindest in Bezug auf die Bewegungsanalyse effizienter ist. Aber das ist hier nicht das Ziel. Jeder verwendet die Methode, die seinen Kriterien am besten entspricht.

Bei der Entwicklung dieses EA habe ich beschlossen, keine Speicherstruktur zu verwenden und somit die Anzahl der Aufträge oder Positionen, mit denen gearbeitet werden kann, nicht zu begrenzen. Diese Tatsache verkompliziert die Situation jedoch so sehr, dass eine Alternative zum OnTrade-Ereignis benötigt wird, die in der Verwendung des OnTradeTransaction-Ereignisses gefunden werden kann.

Dieses Ereignis ist sehr schwierig zu implementieren, was wahrscheinlich der Grund ist, warum es nicht von vielen genutzt wird. Aber ich hatte keine Wahl. Entweder es funktioniert oder es funktioniert nicht, sonst wäre die Sache zu kompliziert. In der vorherigen Version war der Code für dieses Ereignis jedoch sehr ineffizient, wie Sie unten sehen können:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                         if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                         else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                         if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
                
#undef def_IsBuy
}

Der obige Code funktioniert zwar, ist aber gelinde gesagt HORRIBLE. Die Anzahl der nutzlosen Anrufe, die durch den obigen Code erzeugt werden, ist wahnsinnig. Nichts kann den EA in Bezug auf Stabilität und Zuverlässigkeit verbessern, wenn der obige Code nicht behoben werden kann.

Aus diesem Grund habe ich auf einem Demokonto ein paar Dinge ausprobiert, um ein Muster in den Nachrichten zu finden, was eigentlich ziemlich schwierig ist. Ich habe kein Muster gefunden, aber ich habe etwas gefunden, das den Wahnsinn von nutzlosen Aufrufen vermeidet und den Code stabil, zuverlässig und gleichzeitig flexibel genug macht, um jederzeit auf dem Markt handeln zu können. Natürlich gibt es noch ein paar kleine Fehler zu beheben, aber der Code ist sehr gut:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
        if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
        {
                case TRADE_ACTION_PENDING:
                        TradeView.IndicatorAdd(request.order);
                        break;
                case TRADE_ACTION_SLTP:
                        TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
                case TRADE_ACTION_DEAL:
                        TradeView.RemoveIndicator(request.position);
                        break;
                case TRADE_ACTION_REMOVE:
                        TradeView.RemoveIndicator(request.order);
                        break;
                case TRADE_ACTION_MODIFY:
                        TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
        }
                        
#undef def_IsBuy
}

Versuchen Sie nicht, sofort herauszufinden, was los ist, sondern genießen Sie einfach die Schönheit dieser Funktion. Es ist fast die gelebte Perfektion. Ich sage das nicht, weil ich das mache, sondern weil es so robust und flexibel ist.

Auch wenn es kompliziert erscheinen mag, gibt es zwei Kontrollen in diesem Code. Sie werden im Folgenden hervorgehoben, um die Vorgänge besser zu erläutern.

if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
{

//... inner code ...

}

Die GRÜN markierte Zeile prüft jedes Mal, wenn ein Handel in der Historie auftritt, ob der Vermögenswert mit dem vom EA beobachteten Vermögenswert identisch ist. Ist dies der Fall, erhält die Klasse C_IndicatorTradeView den Befehl, den Indikator aus dem Chart zu löschen. Dies kann in zwei Fällen geschehen: wenn ein Auftrag zu einer Position wird und wenn eine Position geschlossen wird. Bitte beachten Sie, dass ich nur den Modus NETTING und nicht HEDGING verwende. Der Indikator wird also, egal was passiert, aus dem Chart entfernt.

Man könnte fragen: Wenn die Position geschlossen wird, ist das in Ordnung; was aber, wenn der Auftrag zu einer Position wird — bin ich dann hilflos? Nein. Aber das Problem wird nicht innerhalb des Fehlers gelöst, sondern innerhalb der Klasse C_IndicatorTradeView. Wir werden sie im nächsten Abschnitt des Artikels behandeln.

Die rote Linie hingegen reduziert auf absurde Weise die Menge der nutzlosen Nachrichten, die an die Klasse C_IndicatorTradeView weitergeleitet wurden. Dies geschieht durch die Überprüfung der vom Server zurückgegebenen Antwort auf die Anfrage. Wir müssen also eine Bestätigung erhalten, indem wir die Anfrage zusammen mit dem gleichen Namen des Vermögenswerts, den der EA verfolgt, stellen. Erst dann wird eine neue Runde von Aufrufen an die Klasse C_IndicatorTradeView gesendet.

Das ist alles, was ich über dieses System sagen kann. Aber die Geschichte ist noch nicht zu Ende. Wir haben noch viel Arbeit vor uns und werden uns von nun an nur noch auf die Klasse C_IndicatorTradeView konzentrieren. Wir werden nun mit einigen Änderungen beginnen, die vorgenommen werden müssen.


2.0.5. Verringerung der Anzahl der von C_IndicatorTradeView erstellten Objekte

In dem Artikel Entwicklung eines Expert Advisors von Grund auf (Teil 23) habe ich ein eher abstraktes, aber sehr interessantes Konzept der Verschiebung von Aufträgen oder Stop-Levels vorgestellt. Das Konzept bestand darin, (Positions-) Geister (ghosts) oder Schatten zu verwenden. Sie definieren und zeigen auf dem Chart an, was der Handelsserver sieht, und werden verwendet, bis die eigentliche Bewegung stattfindet. Dieses Modell hat ein kleines Problem: Es fügt Objekte hinzu, die von MetaTrader 5 verwaltet werden sollen, aber die hinzugefügten Objekte werden in den meisten Fällen nicht benötigt, sodass MetaTrader 5 eine Liste von Objekten erhält, die oft voll von nutzlosen oder selten verwendeten Dingen ist.

Wir wollen aber nicht, dass der EA ständig Objekte anlegt oder unnötige Objekte in der Liste behält, da dies die Leistung des EA beeinträchtigt. Da wir MetaTrader 5 zur Verwaltung von Aufträgen verwenden, sollten wir nutzlose Objekte, die das gesamte System stören, eliminieren.

Aber es gibt eine sehr einfache Lösung. Ganz so einfach ist es nicht. Wir werden noch einige Änderungen an der Klasse C_IndicatorTradeView vornehmen, um sie zu verbessern. Wir werden die Geister auf dem Bildschirm behalten, und wir werden eine sehr kuriose und wenig genutzte Methode anwenden.

Es wird lustig und interessant sein.

Als erstes werden wir die Selektionsstruktur ändern. Sie wird nun wie folgt aussehen:

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
			bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_Selection;

Ich werde Ihnen nicht genau sagen, was sich geändert hat — Sie sollten es selbst verstehen. Aber die Änderungen vereinfachten einige Momente der Kodierungslogik.

Unser Geisterindikator hat jetzt also einen eigenen Index:

#define def_IndicatorGhost      2

Aus diesem Grund hat sich auch die Namensgebung geändert:

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

Es scheint eine Kleinigkeit zu sein, aber es wird sich bald viel ändern. Fahren wir fort.

Jetzt sind die Preispositionsmakros immer direkt, es gibt keine Duplikate mehr, sodass unser Code jetzt wie unten aussieht:

#define macroSetLinePrice(ticket, it, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)

Diese Änderungen zwingen uns, zwei weitere Funktionen zu erstellen, von denen ich jetzt eine und dann eine weitere zeigen werde. Die erste ist die Ersetzung der Funktion, die die Indikatoren selbst erstellt. Sie machte buchstäblich deutlich, was einen Indikator wirklich von einem anderen unterscheidet. Er ist unten zu sehen.

#define macroCreateIndicator(A, B, C, D)        {                                                                       \       
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                             \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : 92), (A == IT_RESULT ? 34 : 22));                         \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                           \
                m_EditInfo1.Size(sz0, 60, 14);                                                                          \
                if (A != IT_RESULT)     {                                                                               \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);    \
                        m_BtnMove.Size(sz0, 21, 23);                                                                    \
                                        }else                   {                                                       \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);           \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                               \
                                                }

                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING : macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit); break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

Sie haben vielleicht bemerkt, dass ich in meinem Code gerne Präprozessoranweisungen verwende. Ich mache das fast immer. Wie Sie jedoch sehen können, ist es jetzt recht einfach, zwischen den Indikatoren zu unterscheiden. Wenn Sie dem Indikator die gewünschten Farben geben wollen, ändern Sie diesen Code. Da sie alle fast identisch sind, können wir mit einem Makro dafür sorgen, dass sie alle gleich funktionieren und die gleichen Elemente haben. Dies ist die ultimative Wiederverwendung von Code.

Es gibt eine weitere Funktion mit einem sehr ähnlichen Namen wie diese. Aber es macht etwas anderes, und ich werde am Ende ausführlich darüber sprechen.

Die Funktion IndicatorAdd wurde geändert — wir haben einige der Fragmente gelöscht.

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);
				UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } 

Eines der entfernten Fragmente wird durch das markierte Fragment ersetzt. Bedeutet dies, dass keine schwebenden Aufträge und keine Indikatoren 0 mehr erstellt werden? Sie werden immer noch erstellt, aber an einem anderen Ort. Es wird also eine weitere Funktion geben.

SO hier ist es — die Funktion, die Indikatoren für schwebende Aufträge und Indikator 0 erstellt. Der Code von UpdateIndicators lautet wie folgt:

#define macroUpdate(A, B) { if (B > 0) {                                                                \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

Die Funktion hat eine sehr interessante Prüfung, die im Code hervorgehoben wird. Sie hilft bei der Erstellung von Geisterindikatoren, sodass die Funktion IndicatorAdd nicht mehr in der Lage sein wird, Indikatoren für schwebende Aufträge und Indikator 0 zu erstellen. Diese Prüfung allein reicht jedoch nicht aus, um einen Geisterindikator zu erstellen.

Die Funktion DispatchMessage enthält jetzt einige Details. Das sind zwar nur kleine Änderungen, aber sie machen unser Leben viel einfacher. Ich werde die Teile zeigen, die sich geändert haben:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Code ....

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Code ....
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;

// ... Code ...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:

// ... Code ...

                                        break;
                                case EV_MOVE:
                                        CreateGhostIndicator(ticket, it);
                                        break;
                        }
                break;
        }
}

CHARTEVENT_MOUSE_MOVE hat einen geänderten Teil. Mit diesem Code wird geprüft, ob wir mit dem Geist arbeiten. Handelt es sich um einen Ghost, wird das Fragment blockiert. Ist dies jedoch nicht der Fall, ist die Bewegung möglich (vorausgesetzt, der Indikator selbst kann sich bewegen).

Sobald wir auf die neue Position des Indikators klicken, wird der Geist mit allen seinen Komponenten aus der Liste der Objekte entfernt. Ich denke, das sollte klar sein. Achten Sie nun auf den hervorgehobenen Punkt — es ist der Aufruf der CreateGhostndicator Funktion. Wir werden diesen Code im nächsten Abschnitt besprechen.


2.0.6. Wie CreateGhostIndicator funktioniert

CreateGhostIndicator scheint eine seltsame Funktion zu sein. Schauen wir uns den Code unten an:

CreateGhostIndicator

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

Es ist sehr interessant, dass bei dieser Funktion nichts erstellt wird. Wenn der EA jedoch kompiliert und ausgeführt wird, erstellt er Ghosts, die den Auftragsstatus auf dem Server anzeigen. Um dies zu verstehen, sehen Sie sich das folgende Video an. Dies ist ein Beispiel dafür, wie das System in der Realität funktioniert.



Geisterindikatoren werden tatsächlich auf dem Chart erstellt, aber wie geschieht das eigentlich? Wie haben wir es geschafft, Indikatoren zu erstellen, ohne sie irgendwo im Code zu erstellen?

Das sind Geister. Sie werden nicht sehen, wie sie erstellt werden. Es macht keinen Sinn, den Code zu lesen, um die Zeile zu finden, in der es heißt: „HIER... fand ich ... den Punkt, an dem die Geisterindikatoren erstellt werden...“ Die Wahrheit ist, dass sie einfach schon im Chart sind, aber sie werden nirgends angezeigt, bis wir mit der Manipulation des Auftrags oder der Position beginnen — erst dann werden sie sichtbar. Wie ist das möglich?

Um dies zu verstehen, betrachten wir den Ausführungsthread des EAs.

Nach der EA-Initialisierung sehen wir den folgenden Ausführungsthread:

Thread 1

init_ea  <<<< System initialization thread

Der orangefarbene Bereich ist Teil des EA und der grüne Bereich ist Teil der C_IndicatorTradeView-Klasse. Sehen Sie, was passiert, bevor der Indikator erstellt und auf dem Bildschirm angezeigt wird. Die schwarzen Pfeile sind der gemeinsame Weg für schwebende Aufträge und offene Positionen; der blaue Pfeil ist der Weg der Positionen, und die violetten Pfeile zeigen den Weg, den schwebende Aufträge zur Erstellung ihrer Indikatoren nehmen. Natürlich gibt es innerhalb der Funktionen Dinge, die den Thread in die eine oder andere Richtung lenken, aber das Chart hier soll zeigen, wie alles im Allgemeinen funktioniert.

Das vorherige Schema wird nur einmal und nur beim Systemstart verwendet. Jedes Mal, wenn wir eine schwebende Auftrag auf dem Chart platzieren, haben wir zwei verschiedene Ausführungsthreads: Der erste ist für die Erstellung des Indikators 0 verantwortlich und versucht, den Auftrag auf dem Chart zu platzieren. Dies ist in der nachstehenden Abbildung dargestellt:

Thread 2

     <<<< Indikator 0 Thread-Initialisierung

Bitte beachten Sie, dass die Reihenfolge, die im Chart erscheint, nicht wirklich von der Klasse bestimmt wird. Sie wird es nur versuchen. Wenn alles gut geht, wird die Funktion SetPriceSelection erfolgreich ausgeführt und ein neuer Thread erstellt, der den Auftrag im Chart darstellt. So erhalten wir den folgenden Thread. Der Auftrag wird tatsächlich an dem Ort platziert, den der Handelsserver meldet. Es macht also keinen Sinn, zu warten, bis der Auftrag tatsächlich an dem Ort landet, den wir ursprünglich angegeben haben. Wenn die Volatilität dazu führt, dass der Server den Auftrag an einem anderen als dem von uns angegebenen Punkt ausführt, wird der EA dies korrigieren und den Auftrag an der richtigen Stelle präsentieren. Sie brauchen also nur zu analysieren, ob die Bedingungen für Ihr Handelsmodell geeignet sind.

Thread 3

     <<< Thread über die Erteilung schwebender Aufträge

Dies ist nur der Teil, der für die Platzierung der Bestellung auf der Karte verantwortlich ist. Ich spreche hier von einem vollständigen Auftrag, d.h. er hat einen Einstiegspunkt, einen Take-Profit und einen Stop-Loss. Aber was passiert, wenn eine der Limit-Orders, sei es der Take-Profit oder der Stop-Loss, aus dem Auftrag entfernt wird? Diese Themen sind keine Antwort darauf. Der Thread wird sich zwar von dem hier beschriebenen unterscheiden, aber die Elemente werden fast die gleichen sein. Im Folgenden sehen Sie, wie der Ablauf aussieht, wenn Sie auf die Schaltfläche klicken, um einen der Limitaufträge zu schließen.

Das mag seltsam erscheinen.

Thread 4

     <<< Löschen einen Auftrag oder eines Stopplevels

Wir haben 2 Threads, einer neben dem anderen. Die mit einem violetten Pfeil markierte Aufgabe wird zuerst ausgeführt. Sobald es ausgeführt wird, erfasst das Ereignis OnTradeTransaction die Antwort des Servers und veranlasst das System, den Indikator vom Bildschirm zu entfernen. Es gibt nur einen Unterschied zwischen der Löschung von Stop-Orders und der Schließung einer Position oder eines Auftrags: In diesen Fällen wird die Funktion SetPriceSelection nicht ausgeführt, aber der Ereignisablauf von OnTradeTransaction bleibt bestehen.

All das ist wunderbar, aber es beantwortet noch nicht die Frage, wie die Geister erscheinen.

Um zu verstehen, wie Geister entstehen, müssen wir wissen, wie der Ausführungsstrang abläuft: wie der EA eine schwebenden Auftrag platziert oder wie die Erstellung des Indikators 0 in der Praxis abläuft. Dieser Fluss ist in der obigen Abbildung dargestellt. Wenn Sie die Ausführungsthreads verstehen, wird es Ihnen leichter fallen, die Geister zu verstehen.

Jetzt wollen wir endlich sehen, wie Geister entstehen. Sehen Sie sich noch einmal die Funktion CreateGhostIndicator an. Sie erzeugt nichts, sondern manipuliert lediglich einige Daten. Warum? Denn wenn wir versuchen, ein Objekt zu erstellen, wird es über die vorhandenen Objekte gelegt und über ihnen gerendert. Dadurch werden die gewünschten Objekte ausgeblendet. Für dieses Problem gibt es zwei Lösungen. Die erste besteht darin, ein Set zu erstellen, das allen anderen unterlegen ist. Es wird vor allen anderen Objekten, die Aufträge darstellen, erstellt. Aber diese Lösung hat ein Problem. Wir werden eine Menge nutzloser Gegenstände haben. Aber wir ändern den gesamten Code, um dies zu vermeiden. Die zweite Lösung besteht darin, einen Geist zu erstellen, dann den Zeiger zu löschen und ihn anschließend erneut zu erstellen. Beide Lösungen sind nicht sehr praktisch und zudem recht rechenintensiv.

Beim Studium der Dokumentation fand ich Informationen, die meine Aufmerksamkeit erregten: Mit der Funktion ObjectSetString kann die Objekteigenschaft manipuliert werden, was auf den ersten Blick keinen Sinn ergibt — OBJPROP_NAME. Ich war neugierig, warum dies erlaubt ist. Das macht keinen Sinn. Wenn das Objekt bereits erstellt wurde, warum sollte man dann seinen Namen ändern?

Der Punkt ist folgender. Wenn wir ein Objekt umbenennen, hört das alte Objekt auf zu existieren und erhält einen neuen Namen. Nach der Umbenennung nimmt das Objekt den Platz des ursprünglichen Objekts ein, sodass der EA das ursprüngliche Objekt problemlos erstellen kann und der Geist ohne Nebenwirkungen für die Grafik und ohne Spuren zu hinterlassen erscheinen und verschwinden kann. Das einzige Objekt, das entfernt werden muss, ist die Schaltfläche zum Schließen des Indikators. Dies geschieht in dieser Zeile:

ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));

Hier gibt es ein kleines Detail. Ein Blick in die Dokumentation der Funktion ObjectSetString zeigt eine Warnung über ihre Funktionsweise:

Beim Umbenennen eines grafischen Objekts werden zwei Ereignisse gleichzeitig erzeugt. Diese Ereignisse können im EA oder im Indikator mit der Funktion OnChartEvent() verarbeitet werden:

  • Ereignis des Löschens eines Objekts mit einem alten Namen
  • Ereignis der Erstellung eines Objekts mit einem neuen Namen

Dies ist wichtig, weil wir nicht wollen, dass das Objekt, das wir umbenennen wollen, einfach auftaucht, wenn wir nicht bereit dafür sind. Wir fügen also vor und nach der Namensänderung eine weitere Sache hinzu:

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);

// ... Secure code...

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);

Alles, was sich im Code befindet, löst keine Ereignisse zur Erstellung und Löschung von Objekten aus. Jetzt haben wir den vollständigen Code, durch den die Geister erscheinen und sich richtig verhalten.

Es ist vielleicht noch nicht klar, wie der Code tatsächlich Geister erzeugt, indem er den Indikator einfach umbenennt. Ich werde Sie hier lassen. Um Ihnen ein wenig zu helfen, zeige ich Ihnen, wie der Ausführungsthread eines Geists aussieht. Dies ist in der nachstehenden Abbildung dargestellt:

Thread 5

    <<<< Geister-Thread

Beachten Sie, dass es sich hierbei um einen nahezu perfekten Klon von Thread 2 handelt, sodass wir bereits wissen, wie Geister erzeugt und zerstört werden, ohne jedoch einen Erstellungscode schreiben zu müssen.


Schlussfolgerung

Als Autor fand ich diesen Artikel recht interessant und sogar spannend. Nun, wir mussten den EA-Code stark verändern. Aber all dies ist zum Besseren. Es gibt noch ein paar Dinge und Schritte, die unternommen werden müssen, um sie noch zuverlässiger zu machen. Die bereits vorgenommenen Änderungen werden jedoch dem gesamten System zugute kommen. Ich möchte betonen, dass ein gut konzipiertes Programm in der Regel bestimmte Schritte durchläuft, die hier umgesetzt wurden: Studium der Dokumentation, Analyse der Ausführungsthreads, Benchmarking des Systems, um zu sehen, ob es in kritischen Momenten überlastet ist, und vor allem Ruhe, um den Code nicht in ein echtes Monster zu verwandeln. Es ist sehr wichtig zu vermeiden, unseren Code in eine Kopie von Frankenstein zu verwandeln, da dies den Code nicht besser macht, sondern zukünftige Verbesserungen und vor allem Korrekturen nur erschwert.

Herzliche Umarmungen an alle, die diese Serie verfolgen. Ich hoffe, wir sehen uns im nächsten Artikel wieder, denn wir sind noch nicht fertig und es gibt noch mehr zu tun.



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

Beigefügte Dateien |
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.
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)
In diesem Artikel werden wir das System zuverlässiger machen, um eine robuste und sichere Nutzung zu gewährleisten. 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 der Möglichkeiten. Eine andere Möglichkeit ist die Verwendung von OOP.
Marktmathematik: Gewinn, Verlust und Kosten Marktmathematik: Gewinn, Verlust und Kosten
In diesem Artikel zeige ich Ihnen, wie Sie den Gesamtgewinn oder -verlust eines Handels einschließlich Provision und Swap berechnen können. Ich werde das genaueste mathematische Modell zur Verfügung stellen und es verwenden, um den Code zu schreiben und ihn mit der Norm zu vergleichen. Außerdem werde ich versuchen, in die Hauptfunktion von MQL5 zur Berechnung des Gewinns einzudringen und alle erforderlichen Werte aus der Spezifikation zu ermitteln.
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.