English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay Systems (Teil 40): Beginn der zweiten Phase (I)

Entwicklung eines Replay Systems (Teil 40): Beginn der zweiten Phase (I)

MetaTrader 5Beispiele | 11 Juli 2024, 13:23
133 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay Systems (Teil 39): Den Weg ebnen (III)“ haben wir gesehen, wie wir die Kommunikation zwischen Prozessen organisieren können, um bestimmte Aktionen zu ermöglichen. Im Moment verwenden wir einen EA und einen Indikator, aber bei Bedarf werden wir diese Tools erweitern können.

Der Hauptvorteil dieser Art von Kommunikation ist, dass wir unser System in Modulen aufbauen können. Sie verstehen vielleicht noch nicht, was wir wirklich tun können. Letztlich werden wir in der Lage sein, Informationen zwischen Prozessen über einen „sichereren“ Kanal auszutauschen als bei der Verwendung globaler Terminalvariablen.

Um dies umzusetzen und zu zeigen, wie wir unser Wiedergabe-/Simulatorsystem in ein modulares System integrieren können, gehen wir einen Schritt zurück und dann einen Schritt nach vorne. In diesem Artikel werden wir das Lernsystem entfernen, das die EA-Maus verwendet. Wir werden dasselbe System in einen Indikator umwandeln, damit es mit dem nächsten Schritt, den wir durchführen werden, kompatibel ist.

Wenn wir Erfolg haben, wird sich zeigen, dass wir danach noch viel mehr tun können. Es besteht eine hohe Wahrscheinlichkeit, dass der Indikator hier nicht endet, da wir später noch andere Dinge tun müssen, aber schließlich wird er fertiggestellt sein. Der große Vorteil ist, dass wir das Modul aktualisieren oder ändern können, ohne dass sich dies auf den Rest unseres Hauptsystems auswirkt.


Beginn der Kodierung

Unser Code wird auf dem aufbauen, was wir bereits haben. Wir werden minimale Änderungen vornehmen, damit der EA-Code zu einem Indikator wird.

Als Erstes sollten Sie den Code in der Header-Datei InterProcess.mqh überprüfen. Der vollständige Code ist unten zu sehen.

Code der Datei InterProcess.mqh:

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_SymbolReplay                    "RePlay"
04. #define def_GlobalVariableReplay            def_SymbolReplay + "_Infos"
05. #define def_GlobalVariableIdGraphics        def_SymbolReplay + "_ID"
06. #define def_GlobalVariableServerTime        def_SymbolReplay + "_Time"
07. #define def_MaxPosSlider                    400
08. //+------------------------------------------------------------------+
09. union u_Interprocess
10. {
11.     union u_0
12.     {
13.             double  df_Value;  // Value of the terminal global variable...
14.             ulong   IdGraphic; // Contains the Graph ID of the asset...
15.     }u_Value;
16.     struct st_0
17.     {
18.             bool    isPlay;     // Indicates whether we are in Play or Pause mode...
19.             bool    isWait;     // Tells the user to wait...
20.             bool    isHedging;  // If true we are in a Hedging account, if false the account is Netting...
21.             bool    isSync;     // If true indicates that the service is synchronized...
22.             ushort  iPosShift;  // Value between 0 and 400...
23.     }s_Infos;
24.     datetime        ServerTime;
25. };
26. //+------------------------------------------------------------------+
27. union uCast_Double
28. {
29.     double   dValue;
30.     long     _long;                    // 1 Information
31.     datetime _datetime;                // 1 Information
32.     int      _int[sizeof(double)];     // 2 Informations
33.     char     _char[sizeof(double)];    // 8 Informations
34. };
35. //+------------------------------------------------------------------+

Was uns in diesem Code wirklich interessiert, liegt zwischen den Zeilen 27 und 34. Hier handelt es sich um eine Referenz, die die Übertragung von Informationen zwischen Prozessen ermöglicht. Diese Verbindung enthält nun einige Daten, die wir von Anfang an benötigen und verwenden werden. Die Idee ist, den Datentransfer zu erleichtern, den wir bereits im vorigen Artikel besprochen haben, aber auf praktische und gezielte Weise.

Diese Zeilen wurden in die Datei InterProcess.mqh eingefügt. Machen wir weiter, aber jetzt werden wir andere Änderungen am Code vornehmen. Sie müssen in der Klassendatei C_Study.mqh implementiert werden.

Zunächst werden wir zu Beginn der Stunde ein paar Dinge ändern. Er ist unten zu sehen.

class C_Study : public C_Mouse
{
        protected:
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
        private :
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};

Die durchgestrichene Zeile wurde aus dem privaten Teil des Codes entfernt und befindet sich jetzt im geschützten Teil. Private Teile zeigen an, dass auf ihren Inhalt nicht von außerhalb der Klasse zugegriffen werden kann, aber das ist bei Enumerationen nicht der Fall: Sie werden immer als öffentlich behandelt, unabhängig vom definierenden Teil.

Nachfolgend finden Sie den Teil, in dem die Änderungen tatsächlich stattgefunden haben:

//+------------------------------------------------------------------+
void Update(const eStatusMarket arg)
void Update(void)
{
   datetime dt;
                                
   switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
   switch (m_Info.Status)
   {
      case eCloseMarket : m_Info.szInfo = "Closed Market";
         break;
      case eInReplay    :
      case eInTrading   :
         if ((dt = GetBarTime()) < ULONG_MAX)
         {
            m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
            break;
         }
      case eAuction     : m_Info.szInfo = "Auction";
         break;
      default           : m_Info.szInfo = "ERROR";
   }
   Draw();
}
//+------------------------------------------------------------------+
void Update(const MqlBookInfo &book[])
{
   m_Info.Status = (ArraySize(book) == 0 ? eCloseMarket : (def_InfoTerminal.szSymbol == def_SymbolReplay ? eInReplay : eInTrading));
   for (int c0 = 0; (c0 < ArraySize(book)) && (m_Info.Status != eAuction); c0++)
      if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Info.Status = eAuction;
   this.Update();
}
//+------------------------------------------------------------------+

Alle durchgestrichenen Zeilen wurden aus der Klasse entfernt und durch neue, hervorgehobene Zeilen ersetzt.

Warum ist das so? Um dies zu verstehen, müssen Sie wissen, dass die Umwandlung des Expert Advisor-Codes in einen Indikator zu einer Änderung der Aktionsmethode führt.

Der betreffende Code bezieht sich auf die Verwendung der Maus als Studienobjekt.

Wenn wir den Code so schreiben, dass der EA alle Aktionen ausführt, können wir auf eine bestimmte Weise handeln. Aber wenn wir das Gleiche mit einem Indikator tun, müssen wir uns auf eine andere Art und Weise darauf einstellen. Das liegt daran, dass der Indikator in einem Thread lebt, und wenn er in irgendeiner Weise betroffen ist, wirkt sich das auf alle anderen im Chart vorhandenen Indikatoren aus.

Wir wollen aber nicht, dass der Indikator irgendwann blockiert wird. Aus diesem Grund müssen wir die Dinge etwas anders angehen.

Schauen wir uns den Indikatorcode unten an. Dies ist der vollständige Code.

Quellcode des Indikators:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property description "This is an indicator for graphical studies using the mouse."
004. #property description "This is an integral part of the Replay / Simulator system."
005. #property description "However it can be used in the real market."
006. #property version "1.40"
007. #property icon "/Images/Market Replay/Icons/Indicators.ico"
008. #property link "https://www.mql5.com/en/articles/11624"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. #property indicator_buffers 1
012. //+------------------------------------------------------------------+
013. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
014. #include <Market Replay\Auxiliar\InterProcess.mqh>
015. //+------------------------------------------------------------------+
016. C_Terminal *Terminal  = NULL;
017. C_Study    *Study     = NULL;
018. //+------------------------------------------------------------------+
019. input C_Study::eStatusMarket user00 = C_Study::eAuction;   //Market Status
020. input color user01 = clrBlack;                             //Price Line
021. input color user02 = clrPaleGreen;                         //Positive Study
022. input color user03 = clrLightCoral;                        //Negative Study
023. //+------------------------------------------------------------------+
024. C_Study::eStatusMarket m_Status;
025. int m_posBuff = 0;
026. double m_Buff[];
027. //+------------------------------------------------------------------+
028. int OnInit()
029. {
030.    if (!CheckPass("Indicator Mouse Study")) return INIT_FAILED;
031.            
032.    Terminal = new C_Terminal();
033.    Study = new C_Study(Terminal, user01, user02, user03);
034.    if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
035.    {
036.            MarketBookAdd((*Terminal).GetInfoTerminal().szSymbol);
037.            OnBookEvent((*Terminal).GetInfoTerminal().szSymbol);
038.            m_Status = C_Study::eCloseMarket;
039.    }else
040.            m_Status = user00;
041.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
042.    ArrayInitialize(m_Buff, EMPTY_VALUE);
043.    
044.    return INIT_SUCCEEDED;
045. }
046. //+------------------------------------------------------------------+
047. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
048. {
049.    m_posBuff = rates_total - 4;
050.    (*Study).Update(m_Status);      
051.    
052.    return rates_total;
053. }
054. //+------------------------------------------------------------------+
055. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
056. {
057.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
058.    SetBuffer();
059.    
060.    ChartRedraw();
061. }
062. //+------------------------------------------------------------------+
063. void OnBookEvent(const string &symbol)
064. {
065.    MqlBookInfo book[];
066.    C_Study::eStatusMarket loc = m_Status;
067.    
068.    if (symbol != (*Terminal).GetInfoTerminal().szSymbol) return;
069.    MarketBookGet((*Terminal).GetInfoTerminal().szSymbol, book);
070.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
071.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
072.            if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
073.    if (loc != m_Status) (*Study).Update(m_Status);
074. }
075. //+------------------------------------------------------------------+
076. void OnDeinit(const int reason)
077. {
078.    if (reason != REASON_INITFAILED)
079.    {
080.            if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
081.                    MarketBookRelease((*Terminal).GetInfoTerminal().szSymbol);
082.            delete Study;
083.            delete Terminal;
084.    }
085. }
086. //+------------------------------------------------------------------+
087. bool CheckPass(const string szShortName)
088. {
089.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName + "_TMP");
090.    if (ChartWindowFind(ChartID(), szShortName) != -1)
091.    {
092.            ChartIndicatorDelete(ChartID(), 0, szShortName + "_TMP");
093.            Print("Only one instance is allowed...");
094.            
095.            return false;
096.    }
097.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
098.    
099.    return true;
100. }
101. //+------------------------------------------------------------------+
102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }
116. //+------------------------------------------------------------------+

Schauen Sie sich den Quellcode des Indikators an und vergleichen Sie ihn mit dem Code aus dem Artikel Entwicklung eines Replay Systems (Part 39): Den Weg ebenen (III), Sie können viele ähnliche Teile erkennen.

Wenn Sie diese Artikelserie über das Replay/Simulator-System verfolgt haben, sollten Sie die meisten der erwähnten Codes problemlos verstehen. Es gibt jedoch einige Dinge, die es wert sind, genauer erklärt zu werden, da viele Leute nicht in der Lage sein werden, den Code zu verstehen, wenn sie ihn sich ansehen. Ich möchte, dass das Motiv und die Idee dieses Ansatzes so klar wie möglich sind, da ein richtiges Verständnis dieser und anderer Quellcodes für diejenigen, die wirklich verstehen wollen, wie das System implementiert ist, von großer Bedeutung sein wird.

Zwischen den Zeilen 2 und 8 befinden sich Informationen, die sich im Laufe der Zeit ändern werden. Diese Information verbindet den Code mit dem Artikel, in dem er erstellt, geändert oder eingereicht wurde. Diese Informationen werden hier bereitgestellt, um weniger erfahrene Nutzer anzuleiten.

In den Zeilen 9 und 11 teilen wir dem Compiler genau mit, wie der Indikator projiziert werden soll. Alle Indikatorcodes müssen mindestens eine Zeile 9 und eine Zeile 10 enthalten. ALLE. Zeile 11 sagt uns, dass wir einen Puffer verwenden werden und dass jeder Prozess, der etwas über die Funktion CopyBuffer lesen möchte, dies tun kann, indem er auf den internen Puffer des Indikators zugreift.

Die Zeilen 13 und 14 sind Includes, die dem Compiler mitteilen, welche Header-Dateien er verwenden soll. Obwohl wir bereits einige Änderungen an diesen Dateien vorgenommen haben, werden wir weitere Änderungen vornehmen müssen. Im Moment können wir die mitgelieferten Header-Dateien problemlos verwenden.

In den Zeilen 16 und 17 deklarieren wir zwei interne globale Indikatorvariablen. Diese Zeiger werden uns helfen, einen kontrollierten Zugang zu den Klassen zu erhalten. Normalerweise wird eine globale Variable nicht standardmäßig initialisiert, da der Compiler dies automatisch tut. Aber hier möchte ich klarstellen, dass diese Variablen (Zeiger) zwar auf keinen bestimmten Speicherplatz zeigen, aber wenn wir sie explizit deklarieren, müssen wir daran denken, dass der Compiler dies standardmäßig implizit tut.

Zwischen den Zeilen 19 und 22 befinden sich die externen Konfigurationsvariablen. Ich schlage vor, dass Sie von nun an solche Variablen nicht mehr einfach als eine Möglichkeit für den Nutzer betrachten, auf den Indikator zuzugreifen oder ihn zu konfigurieren. Wenn wir so denken, haben wir eine sehr begrenzte Sichtweise und können uns nicht vorstellen, was mit ihrer Hilfe alles erreicht werden kann.

Kein Programm sollte als rein und einfach angesehen werden. Sie sollten sich ein Programm immer als Funktion vorstellen, da es Informationen empfängt, sie auf eine bestimmte Weise verarbeitet und eine Ausgabe produziert. Welches Element in der Programmierung hat das gleiche Verhalten? EINE FUNKTION. Beginnen wir also, die Dinge aus einer breiteren Perspektive zu betrachten. Hören wir auf, uns etwas vorzustellen oder von Möglichkeiten zu träumen, und fangen wir an, die Dinge als das zu sehen, was sie wirklich sind: Programme sind Funktionen, egal, was wir tun können. Programme sind Funktionen.

In Zeile 19 haben wir einen Parameter, der keine Bedeutung hat, wenn der Nutzer den Indikator im Chart platziert. Dieser Parameter soll nicht vom Nutzer konfiguriert werden; er ist vorhanden, damit ein anderes Programm diesen von uns erstellten Indikator verwenden kann. Wie ist das möglich? Ist das jetzt schwer zu verstehen 🤔? Machen Sie sich darüber vorerst keine Gedanken. Beachten Sie, dass dies der erste Artikel in der zweiten Phase der Entwicklung unseres Replay/Simulator-Systems ist. In den folgenden Artikeln finden Sie einige Informationen, die Ihnen helfen sollen, zu verstehen, warum es die Zeile 19 gibt. Also keine Panik.

Die Zeilen 20-22 hingegen sind Parameter, die tatsächlich sinnvoll sind, da sie dazu dienen, die von der Maus im Chart verwendete Farbe anzupassen.

Nun stellt sich die Frage: Wozu brauchen wir Zeile 24? Warum haben wir eine globale Variable des gleichen Typs wie in Zeile 19? Der Grund dafür ist, dass der Parameter in Zeile 19 eigentlich nicht als Variable, sondern als Konstante gilt. Daher müssen wir eine Variable deklarieren, die verwendet und gesetzt wird, während der Indikator läuft. Dies ist der Grund für die Erklärung in Zeile 24.

Die in Zeile 25 deklarierte Variable dient als eine Art Speicher, allerdings mit einem edleren Zweck als der gewöhnliche Speicher. Wir werden in Kürze auf diesen Teil zurückkommen. In Zeile 26 deklarieren wir unseren Puffer, der als Ausgabe für den Indikator dienen wird. Achtung! Wir haben einen Puffer deklariert, aber er funktioniert noch nicht. Wenn Sie versuchen, dem irgendetwas zuzuweisen, erscheint eine Laufzeitfehlerwarnung, und wenn Sie versuchen, etwas zu lesen, können Sie es auch nicht tun.

Von diesem Punkt an gehen wir zur Interaktion des Indikators mit MetaTrader 5 über. Denken Sie daran, dass der Indikator auf Ereignisse reagiert, die vom MetaTrader 5 gemeldet werden, unabhängig davon, wie unser Code strukturiert ist und welche Aufgabe er erfüllen soll. Sie wird immer auf bestimmte Ereignisse reagieren. Einige davon können Interaktionsereignisse sein, andere können Aktivitätsereignisse sein.

Dazu gehört das Ereignis Init, das, wenn es ausgelöst wird, den Funktionsaufruf OnInit erzeugt. Diese Funktion beginnt in Zeile 28 und nimmt keine Parameter entgegen, aber wir können die zwischen den Zeilen 19 und 22 deklarierten Interaktionsparameter als Parameter in der OnInit-Funktion verwenden.

Schauen wir uns an, wie die OnInit-Funktion funktioniert. Als Erstes werden wir einchecken. Dies geschieht in Zeile 30, wo wir die Funktion aufrufen. Der Ausführungs-Thread wird dann zur Zeile 87 weitergeleitet. Analysieren wir sie, um zu verstehen, was dieser Check-in ist und warum die Funktion OnInit, wenn sie nicht funktioniert, einen Fehler bei der Initialisierung des Indikators zurückgibt.

Die Funktion in Zeile 87 benötigt ein Argument - die Kurzbezeichnung des Indikators. Dieser Name muss immer definiert werden, um bestimmte Bedingungen zu erfüllen. Als erstes (und das geschieht in Zeile 89) geben wir dem Indikator einen vorläufigen Namen. Sobald der Indikator einen vorläufigen Namen erhalten hat, können wir mit dieser Check-in-Phase fortfahren. Als Nächstes suchen wir unseren Indikator im Chart. Dies geschieht in Zeile 90. Wenn der Indikator bereits auf dem Chart vorhanden ist, zeigt MetaTrader 5 genau an, wo. Wenn nicht, erhalten wir den Wert -1.

Die Prüfung in Zeile 90 sagt uns also, ob der Indikator im Chart vorhanden ist oder nicht. Wenn der Indikator vorhanden ist, wird Zeile 92 ausgeführt, die den temporären Indikator entfernt, d. h. den Indikator, den wir auf dem Chart zu platzieren versuchen. Anschließend wird eine Warnmeldung ausgegeben, und in Zeile 95 kehren wir zum Aufrufer zurück und teilen ihm mit, dass die Registrierung fehlgeschlagen ist.

Wenn der Test in Zeile 90 meldet, dass der Indikator fehlt, führen wir Zeile 97 aus, die MetaTrader 5 den echten Namen des Indikators mitteilt. In Zeile 99 kehren wir zum Aufrufer zurück und teilen ihm mit, dass der Indikator erfolgreich im Chart platziert werden kann. Dies bedeutet jedoch nicht, dass sie tatsächlich in die Karte eingetragen wurde. Dies ist nur die Information, dass MetaTrader 5 sie nicht auf dem Chart gefunden hat. Jetzt kann sie gestartet werden.

Dies bringt uns zurück zu Zeile 30, wo wir beginnen, den Indikator zum Laufen zu bringen. Wenn dies gelingt, gehen wir zu Zeile 32 über. Im Falle eines Fehlers löst der MetaTrader 5 ein Ereignis aus, um die Funktion in Zeile 76 aufzurufen. Da wir aber in Zeile 30 über einen Fehler informieren, wird der Code zwischen den Zeilen 80 und 83 nicht mehr ausgeführt, sobald der Code in Zeile 78 ausgeführt wird. Dies ist wichtig, um Ausfälle am Ausgang des Indikators zu vermeiden.

Aber gehen wir zurück zu Zeile 32, wo wir den Indikator wirklich einsetzen. In Zeile 32 beginnen wir mit einem Zeiger für den Zugriff auf die Klasse C_Terminal. Von nun an können wir die Funktionen der Klasse C_Terminal nutzen. Unmittelbar danach, in Zeile 33, führen wir einen Zeiger aus, um auf die Klasse C_Study zuzugreifen. In diesem Kurs können wir die Maus verwenden und grafische Analysen erstellen. Da wir Zugriff auf die Klasse C_Terminal benötigen, müssen wir die folgende Abfolge von Aktionen durchführen.

In Zeile 34 gibt es einen neuen Test, der feststellt, ob ein Replay/Simulator-Asset oder ein anderes Asset verwendet wird. Wenn es sich um ein Replay-/Simulator-Asset handelt, muss der Inhalt des in Zeile 19 deklarierten Indikator-Eingabekonfigurationsparameters in unsere globale Variable eingefügt werden. Dies geschieht in Zeile 40.

Wenn Zeile 34 anzeigt, dass wir an einer anderen Art von Anlage arbeiten, werden die Zeilen 36 bis 38 ausgeführt. Diese Zeilen initialisieren die Verwendung des Orderbuchs und teilen MetaTrader 5 mit, dass wir Ereignisse, die im Orderbuch aufgetreten sind, empfangen möchten. Außerdem geben wir in Zeile 38 an, dass der Markt geschlossen ist. Diese Bedingung ist vorübergehend, da wir vom Auftragsbuch eine Anleitung erhalten, wie die korrekten Informationen lauten.

Jetzt kommt Zeile 41, die für den Indikator sehr wichtig ist. Denken Sie daran, dass die Deklaration eines Puffers in den Zeilen 11 und 26 nicht garantiert, dass auf ihn zugegriffen werden kann, und dass der Versuch, auf ihn zuzugreifen, zu einem Laufzeitfehler führt. Ohne Zeile 41 ist der Indikator fast völlig nutzlos. In diesem Artikel haben wir über den Index, die Matrix und ihre Vorteile gesprochen. Normalerweise beginnen wir mit einem Nullwert im Indexfeld. Im Array-Feld geben wir an, welche Variable als Puffer verwendet wird. Für das dritte Feld, das angibt, wofür der Puffer verwendet wird, können wir einen der Enumerationswerte ENUM_INDEXBUFFER_TYPE verwenden. Da wir jedoch Daten speichern wollen, verwenden wir INDICATOR_DATA. Es ist jedoch möglich, jeden anderen Typ in diesem speziellen Code zu verwenden.

Sobald dies geschehen ist, gehen wir zu Zeile 42 über. Das mag zu diesem Zeitpunkt unnötig erscheinen, aber ich möchte den Puffer sauber halten. Genau das tun wir jetzt. Wir könnten hier noch etwas hinzufügen, aber ich sehe keine Notwendigkeit dafür. Wir wollen eine Art Zwischenspeicher für Mausdaten anlegen, wie Sie in den nächsten Artikeln sehen werden.

Dies war eine Erklärung, wie der Indikator initialisiert wird. Nun müssen wir wissen, wie wir auf Ereignisse reagieren können, die von MetaTrader 5 ausgelöst werden. Um diesen Teil zu erklären, schauen wir uns zunächst den Code des Orderbuch-Ereignisses in der Funktion OnBookEvent an. Diese Funktion beginnt in Zeile 63 und wird von MetaTrader 5 jedes Mal aufgerufen, wenn etwas im Orderbuch passiert.

Wenn Sie genau hinsehen, können Sie feststellen, dass sich ihr Inhalt zwischen den Zeilen 70 und 72 praktisch nicht vom Inhalt der Klasse C_Study unterscheidet. Warum haben wir dieses Verfahren nicht in der Klasse C_Study belassen? Es ist schwer zu erklären, warum das so ist, aber glauben Sie mir, es gibt einen Grund, der später noch deutlicher werden wird. Dieser Teil ist Ihnen wahrscheinlich schon bekannt, wenn Sie die Artikelserie verfolgt und studiert haben. Diese Funktion enthält zusätzlichen Code.

In Zeile 66 deklarieren wir eine lokale und temporäre Variable, die den Marktstatus vorübergehend speichert, da er sich in den Zeilen 70 und 72 ändern kann. In diesem Fall sollten wir Zeile 73 aufrufen, um die Chartinformationen bequem zu aktualisieren. Bevor wir uns mit der Aktualisierung des Charts befassen, sollten wir einen kurzen Blick auf die anderen Zeilen werfen.

In Zeile 68 filtern wir die BOOK-Ereignisse auf diejenigen, die tatsächlich Teil des Assets im Chart sind. MetaTrader 5 macht solche Unterscheidungen nicht, d.h. wenn ein Vermögenswert im Marktbeobachtungsfenster ein Orderbuchereignis auslöst, löst MetaTrader 5 ein Orderbuchereignis aus. Wir müssen also sofort eine Unterscheidung treffen.

Danach werden in Zeile 69 die im Auftragsbuch enthaltenen Daten erfasst, damit wir sie nach unseren Bedürfnissen auswerten können. In Zeile 70 teilen wir dem Indikator dann mit, ob der Markt geschlossen (oder geöffnet) ist, indem wir uns auf den physischen Markt beziehen, d. h. ob der Handelsserver innerhalb des zulässigen Handelsfensters liegt oder nicht. Um dies herauszufinden, prüfen wir, ob das Orderbuch-Array leer ist oder nicht.

In Zeile 71 wird eine Schleife eingefügt, die das Array Punkt für Punkt daraufhin überprüft, ob es Positionen gibt, die darauf hinweisen, dass der Vermögenswert versteigert wird. Eine Anlage befindet sich im Auktionsstatus, wenn eine der beiden Bedingungen in Zeile 72 erfüllt ist. Da wir nicht wissen, wo genau sich BID und ASK im Auftragsbuch befinden, verwenden wir eine Schleife in Zeile 71. Wenn das von uns verwendete Asset jedoch eine feste BID- und ASK-Position hat, können wir die Schleife überspringen, indem wir Zeile 71 entfernen und einfach den Punkt im Array angeben, an dem sich die BID und ASK befinden. Wenn einer von ihnen anzeigt, dass sich der Vermögenswert in einer Auktion befindet, wird der Status aktualisiert.

Kommen wir zurück zum Update-Teil. Diese Informationen werden nicht immer übermittelt. Die Aktualisierung des Marktzustands wird nur dann aufgerufen, wenn der in Zeile 66 gespeicherte Wert vom neuen Zustand abweicht.

Der Grund dafür, dies hier in Zeile 73 zu tun, ist, dass, wenn ein Vermögenswert in eine Auktion eintritt oder sich während des Handelsfensters in einem ausgesetzten Zustand befindet, nur das Orderbuchereignis diese Informationen und eine korrekte Ansicht der Zustandsänderung hat. Wenn wir darauf warten, dass etwas anderes passiert, können wir in eine Situation geraten, in der wir uns nicht bewusst sind, was passiert.

Der zweite Punkt, an dem der Status aktualisiert wird, ist genau das Ereignis, das wir uns ansehen werden: die Funktion OnCalculate.

Jedes Mal, wenn der Kurs aktualisiert wird oder ein neuer Balken auf dem Chart erscheint, löst MetaTrader 5 ein Ereignis aus. Dieses Ereignis ruft die Funktion OnCalculate im Code des Indikators auf. Natürlich wollen wir, dass dieser Code so schnell wie möglich ausgeführt wird, damit wir keine Ereignisse verpassen, die uns interessieren. Im Gegensatz zum Orderbuch-Ereignis, das eine Zustandsänderung meldet, sobald sie eintritt, wird das OnCalculate-Ereignis erst wirksam, wenn sich etwas auf den Preis auswirkt.

Wir können jedoch sehen, dass wir in Zeile 49 einen Wert speichern und anpassen, der dort nicht verwendet wird. Wenn wir eine Funktion aufrufen, müssen wir den angegebenen Wert verwenden, aber warum passiert das 🤔? Auch hier muss die Funktion OnCalculate so schnell wie möglich ausgeführt werden, und es macht keinen Sinn, den Puffer jedes Mal zu aktualisieren, wenn sich der Kurs bewegt oder ein neuer Balken erscheint. Vergessen Sie nicht, dass wir nicht mit dem Preis oder den Balken im Chart arbeiten, sondern mit der Maus.

Für die Aktualisierung des Puffers ist daher das nächste von MetaTrader 5 ausgelöste Ereignis verantwortlich: die Funktion OnChartEvent.

Diese Funktion OnChartEvent, die in Zeile 55 beginnt, ist recht einfach und überschaubar. Die Funktion in Zeile 57 enthält einen Aufruf, der sicherstellt, dass die Studienklasse Ereignisse korrekt behandelt. Diese wiederum übernimmt die interne Arbeit, die zur vollständigen Verarbeitung des Mausereignisses erforderlich ist. Weitere Einzelheiten finden Sie in dem Artikel „Entwicklung eines Replay Systems (Teil 31): Expert Advisor Projekt - C_Mouse Klasse (V)“. Unabhängig davon rufen wir die Prozedur auf, und der Ausführungsfaden wird in Zeile 102 angehalten.

Bevor wir zu Zeile 102 übergehen, sehen wir uns das Ereignis in Zeile 76 an, das bereits bei der Erläuterung des Initialisierungsprozesses erwähnt wurde. Schauen wir uns nun an, wie sich derselbe Code verhält, wenn der Indikator korrekt initialisiert ist. Wenn dies geschieht, werden die Zeilen 80 bis 83 ausgeführt. In Zeile 80 wird geprüft, für welche Anlage der Indikator verwendet wird. Wenn es sich um einen Vermögenswert handelt, der auf Orderbuch-Ereignisse überwacht werden kann, müssen wir MetaTrader 5 mitteilen, dass wir solche Ereignisse nicht mehr erhalten möchten; dies geschieht in Zeile 81. Die Zeilen 82 und 83 zerstören einfach die Klasse und sorgen dafür, dass der Indikator korrekt aus dem Chart entfernt wird.

Da der Code ab Zeile 102 im Wesentlichen das ist, was wir in diesem Artikel verstehen wollen und müssen, verdient er besondere Aufmerksamkeit in einem eigenen Thema. Sie beginnt direkt darunter:


SetBuffer: Hier passiert die Magie

Wenn Sie die Artikel in dieser Reihe verfolgt haben, ist Ihnen vielleicht aufgefallen, dass in dem Artikel „Entwicklung eines Wiedergabesystems (Teil 39): Den Weg ebnen (III)“ gibt es einen ganzen Abschnitt, der ausschließlich der Erläuterung gewidmet ist, wie man Werte puffert, um Indikatorinformationen an einen anderen Prozess zu senden.

In diesem Abschnitt habe ich erklärt, dass die Informationen an einem ganz bestimmten Punkt platziert werden müssen, aber vor allem habe ich betont, dass die Informationen unter doppelter Kontrolle stehen müssen. Sie haben in diesem Artikel vielleicht nicht erkannt, was wir tun können, aber der Punkt ist, dass wir viel weiter gehen können als das, was viele bisher getan haben.

Wenn Sie sich den Code zwischen den Zeilen 102 und 115 ansehen, werden Sie sehen, dass ich alles auf meine Weise mache. Aber warum mache ich es auf diese Weise?

Um zu vermeiden, dass Sie auf der Seite nach unten scrollen müssen, um der Erklärung zu folgen, werde ich die Lügen näher an die Erklärung stellen. Das macht es einfacher, der Idee zu folgen.

102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0  ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }

In Zeile 49 geben wir an, dass wir vier Double-Werte in den Puffer eingeben werden. Aber wo genau im Puffer? Dies wurde in dem oben erwähnten Artikel erörtert. Wir verwenden die Position rates_total, die genau vier Positionen von diesem Punkt entfernt ist. Wenn wir uns im Replay-/Simulationsmodus befinden, können wir bei Null anfangen, weil wir diejenigen sind, die den Prozess starten. Die Initialisierung wurde in Zeile 25 durchgeführt.

Um die erforderlichen Umrechnungen zu erleichtern, deklarieren wir in Zeile 104 eine lokale Variable. Beachten Sie nun die erforderliche Prüfung in Zeile 106. Sie verhindert anormale Situationen, die vor allem durch die Zeile 49 der Funktion OnCalculate entstehen können. Im Falle eines realen Vermögenswerts ist es unwahrscheinlich, dass m_posBuff einen negativen Wert enthält. Aber in einem Replay- oder Simulations-Asset können wir einen negativen Wert erhalten. Zeile 106 behebt dieses Problem, indem m_posBuff auf die richtige Position zeigt.

Wenn m_posBuff auf einen Index kleiner als Null zeigen würde, würde der Indikator abbrechen. Bitte beachten Sie, dass wir in Zeile 107 mit der Aktualisierung der Indikatordaten beginnen. Dieser Punkt ist der einfachste. Jetzt kommt der schwierigste Teil, der in den Zeilen 109, 112 und 114 dargestellt wird. In diesen Zeilen puffern wir andere Werte, immer ausgehend von der ursprünglich berechneten Position.

Können wir die gleichen Werte auch an anderen Orten veröffentlichen? NEIN. Können wir sie in einer anderen Reihenfolge anordnen? JA. Wenn Sie jedoch die Reihenfolge ändern, müssen Sie künftige Codes auflösen bzw. korrigieren, um die richtigen Informationen zu erhalten. Wenn sich die Reihenfolge ändert, werden die Werte anders berechnet, und es müssen hier oder in jedem anderen Code, der auf der Grundlage dieses Indikators erstellt wird, Anpassungen vorgenommen werden. Dies betrifft auch jeden anderen Code, den wir erstellen wollen.

Daher wäre es gut, mit der Entwicklung einer Art von Methodik zu beginnen. Andernfalls wird alles sehr verwirrend, und wir werden nicht in der Lage sein, die Vorteile von MetaTrader 5 voll auszuschöpfen. Die hier angewandte Methodik ist also folgende:

  • Zunächst speichern wir in der NULL-Position den Kurs, an dem sich die Maus befindet.
  • In der ERSTEN Position speichern wir den Zeitwert, an dem sich der Mauszeiger befindet.
  • In der ZWEITEN Position werden Werte für die Position des Bildschirms gespeichert, d. h. in X- und Y-Koordinaten. Zuerst der X-Wert und dann der Y-Wert.
  • An der DRITTEN Stelle werden wir zusätzliche Elemente speichern. Der aktuelle Zustand der Maustasten. Sie muss im niederwertigsten Byte (LSB) platziert werden. Das heißt, im Null-Byte.

Die hier verwendete Methodik wird für diesen Indikator bis zum Ende seiner Lebensdauer angewendet, sofern er nicht in irgendeiner Weise geändert oder aktualisiert wird.


Schlussfolgerung

Es gibt noch ein paar andere Probleme, die wir lösen müssen. Daher möchte ich, dass Sie die Artikel verfolgen, damit Sie verstehen, was im Code steht, denn oft kann der Code im Anhang von dem abweichen, was im Artikel erklärt wird. Dafür gibt es einen einfachen Grund: Der beigefügte Code ist stabil und funktioniert einwandfrei, während der Code, der in diesem Artikel zur Verfügung gestellt wird, einige Änderungen erfahren kann. Sie müssen sich über den Inhalt des Artikels auf dem Laufenden halten, um den Code zu verstehen, sobald er stabil wird.

Allerdings kann es für jemanden schwierig sein, komplexen Code zu kompilieren, da er mehrere Anwendungen umfasst. Aber keine Sorge. Von Zeit zu Zeit werde ich den kompilierten Code anhängen, damit jeder die Entwicklung des Systems verfolgen kann. Darüber hinaus hilft Ihnen der bereits kompilierte Code, der in einigen Artikeln vorgestellt wird, zu verstehen, wie die Anwendungen aussehen werden, wenn alles perfekt kompiliert ist. Wenn Sie jedoch schon etwas mehr Erfahrung haben und Ihre Programmierkenntnisse verbessern wollen, können Sie die in den Artikeln gezeigten Inhalte ergänzen. Auf diese Weise lernen Sie, wie Sie das System ändern können, sodass Sie in Zukunft alles verwenden können, was Sie wollen. Aber wenn Sie sich dafür entscheiden, rate ich Ihnen, mit Vorsicht vorzugehen und daran zu denken, immer zu testen, was Sie zu Ihrem Code hinzufügen.

Wie auch immer, es ist noch nicht vorbei. Im nächsten Artikel werden wir dieses Thema fortsetzen und einige ungelöste Fragen bearbeiten.

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

Beigefügte Dateien |
Anexo.zip (420.65 KB)
Entwicklung eines Replay Systems (Teil 41): Beginn der zweiten Phase (II) Entwicklung eines Replay Systems (Teil 41): Beginn der zweiten Phase (II)
Wenn Ihnen bis zu diesem Punkt alles richtig erschien, bedeutet dies, dass Sie bei der Entwicklung von Anwendungen nicht wirklich an die langfristige Perspektive denken. Im Laufe der Zeit müssen Sie keine neuen Anwendungen mehr programmieren, sondern nur noch dafür sorgen, dass sie zusammenarbeiten. Schauen wir uns also an, wie man den Mauszeiger fertigstellt.
Entwicklung eines Replay Systems (Teil 39): Den Weg ebnen (III) Entwicklung eines Replay Systems (Teil 39): Den Weg ebnen (III)
Bevor wir zur zweiten Stufe der Entwicklung übergehen, müssen wir einige Ideen überarbeiten. Wissen Sie, wie Sie MQL5 dazu bringen können, das zu tun, was Sie brauchen? Haben Sie jemals versucht, über das hinauszugehen, was in der Dokumentation enthalten ist? Wenn nicht, dann machen Sie sich bereit. Denn wir werden etwas tun, was die meisten Menschen normalerweise nicht tun.
Entwicklung eines MQL5 RL-Agenten mit Integration von RestAPI (Teil 4): Organisieren von Funktionen in Klassen in MQL5 Entwicklung eines MQL5 RL-Agenten mit Integration von RestAPI (Teil 4): Organisieren von Funktionen in Klassen in MQL5
In diesem Artikel wird der Übergang von der prozeduralen Codierung zur objektorientierten Programmierung (OOP) in MQL5 mit Schwerpunkt auf der Integration mit der REST-API erörtert. Heute werden wir besprechen, wie HTTP-Anfragefunktionen (GET und POST) in Klassen organisiert werden können. Wir werden einen genaueren Blick auf das Refactoring von Code werfen und zeigen, wie isolierte Funktionen durch Klassenmethoden ersetzt werden können. Der Artikel enthält praktische Beispiele und Tests.
DoEasy. Steuerung (Teil 33): Vertikale Bildlaufleiste DoEasy. Steuerung (Teil 33): Vertikale Bildlaufleiste
In diesem Artikel werden wir die Entwicklung der grafischen Elemente der DoEasy-Bibliothek fortsetzen und das vertikale Scrollen von Formularobjekt-Steuerelementen sowie einige nützliche Funktionen und Methoden hinzufügen, die in Zukunft benötigt werden.