English Русский Español Português
preview
Entwicklung eines Replay-Systems (Teil 73): Eine ungewöhnliche Kommunikation (II)

Entwicklung eines Replay-Systems (Teil 73): Eine ungewöhnliche Kommunikation (II)

MetaTrader 5Beispiele |
38 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Wiedergabesystems (Teil 72): Eine ungewöhnliche Kommunikation (I)“ begann ich zu zeigen, wie man einen Indikator verwenden kann, um bestimmte Informationen zu übermitteln, die sonst unmöglich zu erhalten wären. Obwohl ich in diesem Artikel eine Reihe von Erklärungen gegeben habe, ist die tatsächliche Implementierung des Codes in unserer Wiedergabe-/Simulator-Anwendung nicht ganz so einfach. Sie werden vielleicht denken, dass ich nur dramatisch bin. Die Implementierung einer solchen Funktionalität ist recht einfach und klar. Dass ich die Spannung nur für den Effekt aufbaue.

Nun, ich wünschte, ich würde einfach Spannung zu diesem Thema aufbauen. Die Wahrheit ist jedoch, dass dies weitaus komplexer ist, als Sie sich vielleicht zunächst vorstellen. Ich habe mich bewusst bemüht, diese Artikel für all diejenigen verständlich zu machen, die MQL5 wirklich lernen wollen. Und das aktuelle Thema, zumindest bis zum Zeitpunkt des Schreibens dieses Artikels, ist etwas, das ich noch nicht gesehen habe. Nicht, weil es undenkbar wäre, sondern weil es zumindest ziemlich exotisch und sehr ungewöhnlich ist. Deshalb versuche ich, so viele Einzelheiten wie möglich zu erläutern, wie Sie vorgehen sollten, wenn Sie etwas in Angriff nehmen, für das es keinen Präzedenzfall gibt.


Fortsetzung der Umsetzung

Bevor wir uns mit dem Dienst selbst befassen, der zweifellos der komplexeste Teil der gesamten Implementierung ist, werfen wir einen Blick auf den Code der Kontrollanzeige. Das liegt daran, dass ich im vorherigen Artikel nur die Header-Datei C_Controls.mqh vorgestellt habe. Und da dort bereits viele Informationen enthalten waren, habe ich beschlossen, die Erläuterungen zum Kontrollindikator in diesem Artikel zu vervollständigen. Beginnen wir also mit der Überprüfung des Quellcodes.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.73"
07. #property link "https://www.mql5.com/pt/articles/12363"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. #property indicator_buffers 1
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Service Graphics\C_Controls.mqh>
13. //+------------------------------------------------------------------+
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;      //ID
17. //+------------------------------------------------------------------+
18. double m_Buff[];
19. int    m_RatesTotal = 0;
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
24.       SetUserError(C_Terminal::ERR_PointerInvalid);
25.    if ((_LastError >= ERR_USER_ERROR_FIRST) || (user00 == 0))
26.    {
27.       Print("Control indicator failed on initialization.");
28.       return INIT_FAILED;
29.    }
30.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
31.    ArrayInitialize(m_Buff, EMPTY_VALUE);
32.       
33.    return INIT_SUCCEEDED;
34. }
35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.    (*control).SetBuffer(m_RatesTotal = rates_total, m_Buff);
39.    
40.    return rates_total;
41. }
42. //+------------------------------------------------------------------+
43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
44. {
45.    (*control).DispatchMessage(id, lparam, dparam, sparam);
46.    (*control).SetBuffer(m_RatesTotal, m_Buff);
47. }
48. //+------------------------------------------------------------------+
49. void OnDeinit(const int reason)
50. {
51.    switch (reason)
52.    {
53.       case REASON_TEMPLATE:
54.          Print("Modified template. Replay // simulation system shutting down.");
55.       case REASON_INITFAILED:
56.       case REASON_PARAMETERS:
57.       case REASON_REMOVE:
58.       case REASON_CHARTCLOSE:
59.          ChartClose(user00);
60.          break;
61.    }
62.    delete control;
63. }
64. //+------------------------------------------------------------------+

Quellcode des Kontrollindikators

Wenn Sie diesen Code mit den vorherigen vergleichen, werden Sie in Zeile 25 sofort einen Unterschied feststellen. Zuvor wurde die Variable _LastError anhand der Konstante ERR_SUCCESS überprüft. Dabei traten jedoch einige Probleme auf. Das liegt daran, dass der Indikator manchmal im Chart platziert wurde, während die Variable _LastError einen anderen Wert als ERR_SUCCESS enthielt.

Ich brauchte einige Zeit, um zu verstehen, warum die Initialisierung gelegentlich fehlschlug. Es ist merkwürdig, denn selbst nach dem expliziten Aufruf der Bibliotheksfunktion ResetLastError und dem Debuggen des Codes, um alle Fehler zu beseitigen, kehrte der Konstruktor der Klasse C_Controls manchmal mit _LastError zurück, das noch einen Wert enthielt.

Noch seltsamer ist, dass der Fehler oft mit einem anderen Symbol oder einem offenen Chart zusammenhing. Offensichtlich gibt es also - das möchte ich ganz klar sagen, denn ich stelle keine Behauptungen auf - ein Informationsleck zwischen verschiedenen Charts. Aber selbst wenn nur das nutzerdefinierte Symbolchart vorhanden war, wurde immer noch ein Fehler zurückgegeben, der nichts mit der Anwendung zu tun hatte. Deshalb habe ich beschlossen, die Fehler zu isolieren. Nur Fehler, die mit der Funktion SetUserError gesetzt wurden, führen dazu, dass der Indikator aus dem Chart entfernt wird.

Worauf wir aber wirklich achten müssen, ist die Funktion OnCalculate. Warum ist diese Funktion so wichtig für uns? Nun, die Funktion OnChartEvent wird in mehreren Momenten ausgelöst, insbesondere durch Mausbewegungen. Und denken Sie daran, dass wir in unserer Anwendung die Maus verwenden. OnCalculate wird jedoch immer dann aufgerufen, wenn ein neuer Tick oder eine neue Notierung eintrifft. Okay, also selbst wenn Sie nur die Tastatur oder eine Schnittstelle verwenden, die bestimmte Ereignisse über Tastendrucke auslöst, würden Sie Aufrufe von OnChartEvent erhalten. Aber mit OnCalculate können wir sicherstellen, dass die Dinge schneller vorankommen, oder besser gesagt, dass die Informationen im Puffer so aktuell wie möglich sind. Aus diesem Grund verwenden wir OnCalculate für diesen Zweck.

Schauen Sie sich Zeile 38 an. Sie ist einfach und erfüllt die gleiche Aufgabe wie Zeile 46. Der Unterschied besteht darin, dass Zeile 38 den Puffer jedes Mal aktualisiert, wenn der Indikator einen neuen Tick erhält. Auch wenn die Maus eingefroren ist oder kein Mausereignis ausgelöst wurde. Während OnChartEvent also nur nach einer Änderung ausgeführt wird, wird OnCalculate fast ständig ausgelöst.

Jetzt verstehen Sie die Rolle des Kontrollindikators. Vergessen Sie aber nicht, dass beim Wechsel des Zeitrahmens die Funktion OnDeinit ausgeführt wird, die den Indikator aus dem Chart entfernt, und dass unmittelbar danach OnInit aufgerufen wird. Bevor also etwas auf dem Chart erscheint, wird OnCalculate ausgeführt und erst danach OnChartEvent. Und die Pufferdaten müssen stets aktuell sein.

Bedeutet dies, dass wir mit der Änderung der Header-Datei C_Replay.mqh beginnen können? Nun, technisch gesehen könnten wir jetzt damit anfangen. Ich bin jedoch nicht davon überzeugt, dass alle angehenden Entwickler wirklich verstanden haben, wie diese Form der Kommunikation funktioniert. Deshalb bitte ich Sie, liebe Leserin und lieber Leser, noch um ein wenig Geduld. Wenn Sie bereits wissen, wie das funktioniert, sollten wir geduldig sein, damit auch diejenigen, die es noch nicht verstehen, lernen können, was hier geschieht. Deshalb gehen wir jetzt zum nächsten Thema über.


Zum Verständnis des schnellen Informationsaustauschs

Die meisten Anfänger und Entwickler mit begrenzter Erfahrung in MQL5 werden wahrscheinlich davon ausgehen, dass der Austausch von Informationen, genauer gesagt das Auslesen des Indikatorpuffers von einem Dienst, einfach ist. Alles, was wir brauchen, ist ein Handle. Und ja, das funktioniert in der Tat recht gut. Aber es gibt einen Vorbehalt. Oder genauer gesagt, ein Fehler bei der Verwendung eines Handles in diesem speziellen Szenario.

An dieser Stelle werden Sie vielleicht denken: „Wie kann es einen Fehler geben, wenn man ein Handle nutzt? Ich habe das immer so gemacht, und es hat immer funktioniert.“ Ich bin nicht hier, um mit Ihrer Logik oder Erfahrung zu argumentieren. Sinnlose Argumente bringen uns nicht weiter. Anstatt zu debattieren oder zu versuchen, etwas mit Worten zu beweisen, sollten wir einen echten Test durchführen. Auf diese Weise kann niemand widersprechen, denn wenn etwas einmal getestet und bewiesen ist, kann es nicht mehr bestritten werden. Sie wird zu einer unbestreitbaren Tatsache.

Um also zu beweisen, dass die Verwendung eines Handles für den Zugriff auf Daten aus einem Indikatorpuffer keine zuverlässige Methode ist, wenn der Zugriff über einen Dienst erfolgt - und um es klar zu sagen: Das Problem wird UNSTABIL, wenn Sie den Zeitrahmen des Charts ändern - führen wir ein praktisches Experiment durch. Es ist wichtig, dies zu verstehen. Das Problem liegt in der Umstellung des Zeitrahmens. Wir werden also Folgendes tun: Wir werden zwei einfache Codebeispiele erstellen, um dies zu testen. Keine Sorge, sie sind leicht zu befolgen. Beginnen wir mit dem ersten: dem Indikatorcode.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 1
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Defines.mqh>
09. //+------------------------------------------------------------------+
10. double m_Buff[];
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
15.    ArrayInitialize(m_Buff, EMPTY_VALUE);
16.    IndicatorSetString(INDICATOR_SHORTNAME, "TEST");
17. 
18.    return INIT_SUCCEEDED;
19. }
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
22. {
23.    m_Buff[rates_total - 1] = 1.0 * def_IndicatorTimeFrame;
24.    
25.    return rates_total;
26. }
27. //+------------------------------------------------------------------+

Quellcode eines Testindikators

Beachten Sie, dass wir in Zeile 5 den Compiler anweisen, dass wir keine Daten anzeigen werden. Dadurch wird verhindert, dass es bei jedem neuen Kompilierungsversuch Warnmeldungen ausgibt. In Zeile 6 geben wir an, dass wir einen Puffer verwenden werden. In Zeile 8 fügen wir dann eine Header-Datei ein, die in der Wiedergabe-/Simulationsanwendung verwendet wird. Das ist wichtig, denn wir wollen verstehen, wie das Wiedergabe-/Simulationssystem tatsächlich auf die Daten zugreifen wird. In Zeile 10 deklarieren wir unseren Puffer. In der Funktion OnInit wird der Puffer initialisiert und der Name des Indikators festgelegt. Achten Sie darauf, da wir diese Informationen später noch brauchen werden.

Schauen Sie sich nun den Hauptteil der Funktion OnCalculate genau an. In Zeile 23 wird der Wert des Chart-Zeitrahmens in den Puffer geschrieben. Dies ist derselbe Wert, der im vorigen Artikel erörtert wurde, weshalb das Verständnis dieses früheren Inhalts von wesentlicher Bedeutung ist. Nun gut, ich glaube, jeder hier weiß, was ein Indikator tut und wie er funktioniert, zumindest auf einer grundlegenden Ebene. Schauen wir uns nun das zweite Codebeispiel an.

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. input string user01 = "IBOV";         //Accompanying Symbol
07. //+------------------------------------------------------------------+
08. void OnStart()
09. {
10.    int ret, handle;
11.    long id;
12.    double Buff[1];
13.             
14.    if ((id = ChartFirst()) > 0) do
15.    {
16.       if (ChartSymbol(id) == user01) break;
17.    }while ((id = ChartNext(id)) > 0);
18.    handle = ChartIndicatorGet(id, 0, "TEST");
19.    do
20.    {
21.       ret = CopyBuffer(handle, 0, 0, 1, Buff);
22.       PrintFormat("CopyBuffer: [ %d ]  Value: [ %f ]", ret, Buff[0]);
23.       Sleep(250);
24.    } while ((!_StopFlag) && (ret > 0));
25.    IndicatorRelease(handle);
26. }
27. //+------------------------------------------------------------------+

Quellcode eines Testdienstes

Beachten Sie, dass wir dem Compiler in Zeile 2 mitteilen, dass dieses Skript ein Dienst ist. In Zeile 6 fügen wir dann einen Eingabeparameter hinzu, mit dem der Nutzer festlegen kann, welches Symbol der Dienst beobachten soll. Bislang nichts Ungewöhnliches. Zwischen den Zeilen 10 und 12 deklarieren wir einige Variablen. Jetzt kommt der wichtige Teil: Damit der Dienst funktioniert, muss mindestens eine Chart geöffnet sein. Andernfalls werden wir Probleme bekommen. Wenn jedoch mindestens ein Chart geöffnet ist, wird dessen ID in Zeile 14 erfolgreich erfasst. Anschließend werden die offenen Charts durchlaufen und derjenige gesucht, der dem in Zeile 6 definierten Symbol entspricht. Sobald wir sie gefunden haben, rufen wir ihre korrekte Chart-ID ab.

Mit dieser Chart-ID in der Hand fordern wir MetaTrader 5 auf, uns das Handle für den Indikator zu geben. Beachten Sie den hier verwendeten Indikatornamen. Sie muss mit der in Zeile 16 des Quellcodes des Indikators definierten übereinstimmen. Von dort aus gelangen wir in eine Schleife. Da wir nun ein gültiges Handle haben, können wir es verwenden, um den Indikatorpuffer zu lesen. Das ist genau das, was wir in Zeile 21 tun. In Zeile 22 geben wir die Pufferdaten und das Ergebnis des Lesevorgangs auf dem Terminal aus. Diese Schleife läuft so lange, bis die Bedingungen in Zeile 24 nicht mehr erfüllt sind, d. h. bis der Dienst angehalten wird oder das Lesen des Puffers fehlschlägt. Sobald dies der Fall ist, gibt Zeile 25 das Handle frei.

Wir sind uns also alle einig, dass dieser Code tatsächlich den Indikatorpuffer liest. Und der Wert, den er ausgibt, entspricht dem erwarteten Wert, dank der Tests, die wir im vorherigen Artikel besprochen haben. Führen wir dies also in MetaTrader 5 aus. Die Ergebnisse können Sie sich im folgenden Video ansehen:


Moment, was ist hier gerade passiert? Sie denken jetzt vielleicht, dass ich Sie austricksen will. Das ist in Ordnung. Es steht Ihnen frei, zu glauben, was Sie wollen. Sie brauchen mir das nicht zu glauben. Führen Sie Ihre eigenen Tests durch. Achten Sie aber darauf, dass der Kerngedanke erhalten bleibt: Verwendung eines Handles zum Lesen eines Indikatorpuffers aus einem Dienst. Was Sie im Video gesehen haben, wird passieren. Aber warum?

Der Schlüssel liegt im Handle. Sobald MetaTrader 5 den Chart-Zeitrahmen ändert, ändert sich die Handle-ID. Ihr Code verweist jedoch immer noch auf das alte Handle. Dieses Handle ist jedoch nicht mehr gültig, da MetaTrader 5 den Indikator intern neu erstellt hat und der neue Puffer nun mit einem anderen Handle verknüpft ist. Wenn Sie mit einem Expert Advisor oder einem anderen Indikator arbeiten, werden diese nach einem Wechsel des Zeitrahmens automatisch wieder an den Chart angehängt. Dies führt dazu, dass alle Funktionsaufrufe erneut ausgeführt werden und das Handle aktualisiert wird. Das liegt daran, dass der Expert Advisor oder Indikator neu in das Chart geladen wird. An diesem Punkt wird das Handle aktualisiert, sodass der Puffer korrekt gelesen werden kann.

Ein Dienst läuft jedoch außerhalb des Chart-Kontexts. Infolgedessen wird das Handle nicht aktualisiert. Aus diesem Grund bleibt der aus dem Puffer gelesene Wert derselbe. Sie denken vielleicht immer noch, dass dies nicht auf Sie zutrifft. Dass Ihr Dienst irgendwie anders funktioniert. Gut. Testen wir diese Hypothese. Hier ist die neue Version des Service-Skripts, die Sie gleich unten finden: Dieses Mal ändern wir nur den Dienstcode. Der Code ist unten abgebildet:

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. input string user01 = "IBOV";         //Accompanying Symbol
07. //+------------------------------------------------------------------+
08. void OnStart()
09. {
10.    int ret;
11.    long id;
12.    double Buff[1];
13.             
14.    if ((id = ChartFirst()) > 0) do
15.    {
16.       if (ChartSymbol(id) == user01) break;
17.    }while ((id = ChartNext(id)) > 0);
18.    do
19.    {
20.       ret = CopyBuffer(ChartIndicatorGet(id, 0, "TEST"), 0, 0, 1, Buff);
21.       PrintFormat("CopyBuffer: [ %d ]  Value: [ %f ]", ret, Buff[0]);
22.       Sleep(250);
23.    } while ((!_StopFlag) && (ret > 0));
24. }
25. //+------------------------------------------------------------------+

Quellcode eines Testdienstes

Beachten Sie, dass der Code geringfügig geändert wurde - Änderungen, die auf den ersten Blick kaum wahrnehmbar sind. Aber hier ist der springende Punkt: In Zeile 20 findet die entscheidende Änderung statt. Jetzt ist das Handle nicht mehr statisch, sondern dynamisch geworden. Das bedeutet, dass der Dienst auch dann, wenn er den aktuellen Zeitrahmen des Charts nicht kennt oder nicht weiß, welche Änderungen daran vorgenommen werden, dennoch die richtige Instanz des Indikators finden und mit ihr interagieren und seinen Puffer korrekt lesen kann. Zur Veranschaulichung können Sie sich dieses Verhalten in dem folgenden Video ansehen:


Jetzt denken Sie wahrscheinlich, dass ich Sie auf den Arm nehme. Wie ist das möglich? Immer mit der Ruhe, lieber Leser. Wie ich bereits erwähnt habe, wollte ich Ihnen die Änderungen in der Datei C_Replay.mqh nicht zeigen, bevor ich Ihnen das Konzept vorgestellt habe.

Sehen Sie, wie etwas, das einfach erscheint, aber völlig gültig und vernünftig ist, das Verhalten Ihres Codes drastisch verändern kann? Der Schlüssel ist folgender: Sie müssen die Dinge immer in der einfachsten Umgebung testen, die möglich ist. Ich erlebe oft, dass Leute versuchen, übermäßig aufwendigen Code zu schreiben, ohne eine solide, fundierte Grundlage zu haben. Am Ende geben sie auf, weil sie nicht herausfinden können, wo sich die subtilen Probleme verstecken.

In Wahrheit müssen Sie jedoch immer die Rechenkosten gegen die Komplexität der Implementierung abwägen. Es nützt nichts, einen funktionierenden Code zu haben, wenn er langsam ist. Es ist auch nicht gut, schnellen Code zu haben, der einfach nicht funktioniert.

Indem wir den Handle dynamisch machen, wie wir es in dieser zweiten Version des Dienstes getan haben, führen wir einen leichten Leistungsverlust ein. Denn jetzt wird bei jeder Ausführung das Handle überprüft. In der ersten Version war das Handle bereits gespeichert, sodass es viel schneller war, einfach auf eine vorgeladene Variable zu verweisen.

Dies ist der Kompromiss, den Sie immer abwägen müssen. Deshalb bin ich der Meinung, dass man Dinge mit minimalem, einfachem Code entwickeln und testen sollte. Aber wie wird diese Logik tatsächlich in die Header-Datei C_Replay.mqh integriert und implementiert, die den Wiedergabe-/Simulator-Dienst steuert? Um diese Frage zu beantworten, gehen wir zu einem neuen Abschnitt über.


Ändern der Datei C_Replay.mqh

Hier stand ich vor einem kleinen Dilemma: Soll ich den bereits geänderten Code zeigen oder Sie Schritt für Schritt durch den Änderungsprozess führen? Diese Art von Entscheidung verlangsamt den Artikel oft mehr, als mir lieb ist. Das Schreiben und Testen von Code geht schnell. Aber zu erklären, was sich geändert hat und warum, braucht Zeit.

Mein Ziel ist es jedoch, dass Sie, liebe Leserin, lieber Leser, dies tatsächlich verstehen und lernen. Ich habe das schon erlebt. Jahrelanges Programmieren hinterlässt oft Spuren. Dennoch glaube ich, dass die schrittweise Umwandlung der beste Weg nach vorne ist. Auch wenn das bedeutet, dass Sie auf Ihrer Seite ein wenig aufräumen müssen, um veraltete Abschnitte zu entfernen. Sehen wir uns nun die gesamte Header-Datei C_Replay.mqh an.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl    "Market Replay Control"
013. #define def_MaxSlider             (def_MaxPosSlider + 1)
014. //+------------------------------------------------------------------+
015. class C_Replay : public C_ConfigService
016. {
017.    private   :
018.       struct st00
019.       {
020.          C_Controls::eObjectControl Mode;
021.          uCast_Double               Memory;
022.          ushort                     Position;
023.          int                        Handle;
024.       }m_IndControl;
025.       struct st01
026.       {
027.          long     IdReplay;
028.          int      CountReplay;
029.          double   PointsPerTick;
030.          MqlTick  tick[1];
031.          MqlRates Rate[1];
032.       }m_Infos;
033.       stInfoTicks m_MemoryData;
034. //+------------------------------------------------------------------+
035. inline bool MsgError(string sz0) { Print(sz0); return false; }
036. //+------------------------------------------------------------------+
037. inline void SendEventCustom(const ENUM_BOOK_TYPE Arg1 = BOOK_TYPE_BUY_MARKET)
038.          {
039.             MqlBookInfo book[1];
040.             
041.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
042.             m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
043.             m_IndControl.Memory._8b[7] = 'D';
044.             m_IndControl.Memory._8b[6] = 'M';
045.             EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
046.             book[0].price = 1.0;
047.             book[0].volume = 1;
048.             book[0].type = Arg1;
049.             CustomBookAdd(def_SymbolReplay, book, 1);
050.          }
051. //+------------------------------------------------------------------+
052. inline void CheckIndicatorControl(void)
053.          {
054.             static uchar memTimeFrame = 0;
055.             static C_Controls::eObjectControl memMode = m_IndControl.Mode;
056.             double Buff[];
057.             
058.             if (CopyBuffer(ChartIndicatorGet(m_Infos.IdReplay, 0, "Market Replay Control"), 0, 0, 1, Buff) < 0) ChartClose(m_Infos.IdReplay);
059.             m_IndControl.Memory.dValue = Buff[0];
060.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] >= m_IndControl.Position)
061.             {
062.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
063.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
064.                if (m_IndControl.Memory._8b[def_IndexTimeFrame] == memTimeFrame)
065.                {
066.                   memMode = m_IndControl.Mode;
067.                   return;
068.                }
069.                memTimeFrame = m_IndControl.Memory._8b[def_IndexTimeFrame];
070.                m_IndControl.Mode = memMode;
071.             }
072.             SendEventCustom(m_IndControl.Mode != C_Controls::ePlay ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY);
073.          }
074. //+------------------------------------------------------------------+
075. inline void UpdateIndicatorControl(void)
076.          {
077.             double Buff[];
078.                                  
079.             if (m_IndControl.Handle == INVALID_HANDLE) return;
080.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
081.             {
082.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
083.                   m_IndControl.Memory.dValue = Buff[0];
084.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
085.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
086.             {
087.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
088.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
089.                m_IndControl.Memory._8b[7] = 'D';
090.                m_IndControl.Memory._8b[6] = 'M';
091.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
092.             }
093.          }
094. //+------------------------------------------------------------------+
095.       void SweepAndCloseChart(void)
096.          {
097.             long id;
098.             
099.             if ((id = ChartFirst()) > 0) do
100.             {
101.                if (ChartSymbol(id) == def_SymbolReplay)
102.                   ChartClose(id);
103.             }while ((id = ChartNext(id)) > 0);
104.          }
105. //+------------------------------------------------------------------+
106. inline int RateUpdate(bool bCheck)
107.          {
108.             static int st_Spread = 0;
109. 
110.             st_Spread = (bCheck ? (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time) : st_Spread + 1);
111.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
112.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
113.             
114.             return 0;
115.          }
116. //+------------------------------------------------------------------+
117. inline void CreateBarInReplay(bool bViewTick)
118.          {
119.             bool   bNew;
120.             double dSpread;
121.             int    iRand = rand();
122. 
123.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
124.             {
125.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
126.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
127.                {                  
128.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
129.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
130.                   {
131.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
132.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
133.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
134.                   {
135.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
136.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
137.                   }
138.                }
139.                if (bViewTick)
140.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
141.                RateUpdate(true);
142.             }
143.             m_Infos.CountReplay++;
144.          }
145. //+------------------------------------------------------------------+
146.       void AdjustViewDetails(void)
147.          {
148.             MqlRates rate[1];
149. 
150.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
151.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
152.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
153.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
154.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
155.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
156.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
157.             if (rate[0].close > 0)
158.             {
159.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
160.                   m_Infos.tick[0].last = rate[0].close;
161.                else
162.                {
163.                   m_Infos.tick[0].bid = rate[0].close;
164.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
165.                }               
166.                m_Infos.tick[0].time = rate[0].time;
167.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
168.             }else
169.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
170.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
171.          }
172. //+------------------------------------------------------------------+
173.       void AdjustPositionToReplay(void)
174.          {
175.             int nPos, nCount;
176.             
177.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
178.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
179.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
180.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
181.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
182.                CreateBarInReplay(false);
183.          }
184. //+------------------------------------------------------------------+
185.       void WaitIndicatorLoad(const string szArg, const bool ViewCtrl = true)
186.          {
187.             Print("Waiting for ", szArg);
188.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, szArg) == INVALID_HANDLE))
189.             {
190.                if (ViewCtrl) CheckIndicatorControl();
191.                Sleep(100);
192.             }
193.          }
194. //+------------------------------------------------------------------+
195.    public   :
196. //+------------------------------------------------------------------+
197.       C_Replay()
198.          :C_ConfigService()
199.          {
200.             Print("************** Market Replay Service **************");
201.             srand(GetTickCount());
202.             SymbolSelect(def_SymbolReplay, false);
203.             CustomSymbolDelete(def_SymbolReplay);
204.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
205.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
206.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
207.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
208.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
209.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
210.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1);
211.             SymbolSelect(def_SymbolReplay, true);
212.             m_Infos.CountReplay = 0;
213.             m_IndControl.Handle = INVALID_HANDLE;
214.             m_IndControl.Mode = C_Controls::ePause;
215.             m_IndControl.Position = 0;
216.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
217.          }
218. //+------------------------------------------------------------------+
219.       ~C_Replay()
220.          {
221.             SweepAndCloseChart();
222.             IndicatorRelease(m_IndControl.Handle);
223.             SymbolSelect(def_SymbolReplay, false);
224.             CustomSymbolDelete(def_SymbolReplay);
225.             Print("Finished replay service...");
226.          }
227. //+------------------------------------------------------------------+
228.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
229.          {
230.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
231.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
232.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
233.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
234.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
235.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
236.             SweepAndCloseChart();
237.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
238.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
239.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
240.             else
241.                Print("Apply template: ", szNameTemplate, ".tpl");
242. 
243.             return true;
244.          }
245. //+------------------------------------------------------------------+
246.       bool InitBaseControl(const ushort wait = 1000)
247.          {
248.             int handle;
249.             
250.             Sleep(wait);
251.             AdjustViewDetails();
252.             Print("Loading Control Indicator...");
253.             if ((handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
254.             ChartIndicatorAdd(m_Infos.IdReplay, 0, handle);
255.             IndicatorRelease(handle);
256.             WaitIndicatorLoad("Market Replay Control", false);
257.             SendEventCustom();
258.             WaitIndicatorLoad("Indicator Mouse Study");
259.             UpdateIndicatorControl();
260.             SendEventCustom();
261. 
262.             return def_CheckLoopService;
263.          }
264. //+------------------------------------------------------------------+
265.       bool LoopEventOnTime(void)
266.          {         
267.             int iPos, iCycles;
268.             MqlBookInfo book[1];
269.             ENUM_BOOK_TYPE typeMsg, memBook;
270.             
271.             book[0].price = 1.0;
272.             book[0].volume = 1;
273.             book[0].type = BOOK_TYPE_BUY_MARKET;
274.             CustomBookAdd(def_SymbolReplay, book, 1);
275.             SendEventCustom();
276.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
277.             {
278.                UpdateIndicatorControl();
279.                CheckIndicatorControl();
280.                Sleep(200);
281.             }
282.             m_MemoryData = GetInfoTicks();
283.             AdjustPositionToReplay();
284.             iPos = iCycles = 0;
285.             SendEventCustom(memBook = BOOK_TYPE_BUY);
286.             book[0].type = BOOK_TYPE_BUY;
287.             CustomBookAdd(def_SymbolReplay, book, 1);
288.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
289.             {
290.                if (m_IndControl.Mode == C_Controls::ePause) return true;
291.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
292.                if ((typeMsg = (iPos >= 60000 ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY)) != book[0].type)
293.                if ((typeMsg = (iPos >= 60000 ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY)) != memBook)
294.                   SendEventCustom(memBook = typeMsg);
295.                {
296.                   book[0].type = typeMsg;
297.                   CustomBookAdd(def_SymbolReplay, book, 1);
298.                }
299.                CreateBarInReplay(true);
300.                while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause))
301.                {
302.                   Sleep(195);
303.                   iPos -= 200;
304.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
305.                   UpdateIndicatorControl();
306.                   CheckIndicatorControl();
307.                   iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1);
308.                }
309.             }
310. 
311.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
312.          }
313. };
314. //+------------------------------------------------------------------+
315. #undef def_SymbolReplay
316. #undef def_CheckLoopService
317. #undef def_MaxSlider
318. //+------------------------------------------------------------------+

Der Quellcode der Datei C_Replay.mqh

Beachten Sie, dass jede durchgestrichene Zeile aus dem Code entfernt werden muss. Aber sehen wir uns einmal genauer an, warum so viele Zeilen gestrichen wurden.

Der erste wichtige Punkt ist in 23. Wie im vorigen Abschnitt gezeigt, sollten wir kein statisches Handle verwenden. Aus diesem Grund ist die Variable „handle“ überflüssig geworden. Folglich wurden alle Teile des Codes, die darauf verwiesen, entfernt. Außerdem wurde ein ganzes Verfahren gestrichen. Es handelt sich um UpdateIndicatorControl, das sich zuvor zwischen den Zeilen 75 und 93 befand. Dementsprechend wurde auch jeder Hinweis darauf entfernt. Das bedeutet, dass wir nun einen Weg finden müssen, um die Funktionalität zu ersetzen, die zuvor von UpdateIndicatorControl abgedeckt wurde.

Aber dazu kommen wir gleich noch. Bevor wir uns damit befassen, werfen wir unter einen kurzen Blick auf zwei spezielle Funktionen. Das erste ist InitBaseControl, das in Zeile 243 beginnt. Sie werden einige kleine, aber bedeutsame Änderungen an dieser Funktion feststellen. Diese Anpassungen zielen darauf ab, sowohl die Nutzerfreundlichkeit zu verbessern als auch die Initialisierung der Indikatoren zu standardisieren. Schauen wir mal, was da passiert.

Zwischen den Zeilen 253 und 255 wird versucht, den Kontrollindikator zu laden. Hier ein entscheidendes Detail: Das Laden der Indikatoren erfolgt nicht sofort. Es gibt eine leichte Verzögerung bei der Ausführung. Bevor wir die Zeile 257 ausführen, müssen wir daher sicherstellen, dass der Kontrollindikator ordnungsgemäß in das Chart geladen wurde. Diese Prüfung wird in Zeile 253 durchgeführt. Achten Sie auf die Namen der Indikatoren in den Zeilen 256 und 258 - dies sind die spezifischen Indikatoren, die geladen werden sollen. Diese Aufrufe werden in Zeile 185 ausgelöst. Von diesem Punkt an wird es interessant. Passen Sie also gut auf.

Die Schleife in Zeile 188 wartet darauf, dass der angegebene Indikator in den Chart geladen wird. Wenn Zeile 256 verlangt, dass wir warten, bis der Kontrollindikator geladen ist, verhindert die Prüfung in Zeile 187, dass wir die Prüfung vorzeitig durchführen. Wenn Zeile 255 jedoch auffordert, auf das Laden des Mauszeigers zu warten, prüft Zeile 190 den Puffer des Kontrollzeigers. Warum ist das so? Denn der Nutzer kann den Zeitrahmen des Charts ändern. Um dies besser zu verstehen, springen wir zu Zeile 52.

In Zeile 52 finden wir die Prozedur, die für das Auslesen des Puffers des Kontrollindikators zuständig ist. Und jetzt kommt der interessanteste Teil. In den Zeilen 54 und 55 sind zwei statische Variablen deklariert. In diesem Moment interessiert uns jedoch der in Zeile 54. Beachten Sie, dass er mit einem Wert von 0 initialisiert wird. Wenn nun der Versuch, aus dem Puffer in Zeile 58 zu lesen, einen Wert kleiner als Null ergibt, bedeutet dies, dass der Kontrollindikator aus dem Chart entfernt wurde. Da der Nutzer sie nicht manuell wieder anbringen kann und wir ihr Vorhandensein voraussetzen, wird das Chart geschlossen. Dadurch wird die Anwendung beendet. Aus diesem Grund darf der Kontrollindikator nicht überprüft werden, solange er sich noch in der Ladephase befindet.

In Zeile 60 wird geprüft, ob der Wert des Kontrollindikators größer ist als die Position, die im Dienst analysiert wird. Wenn dies der Fall ist, bedeutet dies, dass wir die Ausführung vorspulen müssen, sobald der Nutzer auf Play drückt. Der wirklich kritische Zustand ist jedoch der in Zeile 64. Diese Prüfung ist die Achillesferse der Sperre des Zeitrahmens - des im vorigen Abschnitt beschriebenen Mechanismus. Wenn der Nutzer den Zeitrahmen des Charts nicht geändert hat, gibt diese Bedingung den Wert true zurück, und es werden keine weiteren Maßnahmen ergriffen.

Ergibt die Prüfung „false“, so wird in Zeile 69 der neue Zeitrahmen gespeichert, und in Zeile 70 wird der letzte bekannte Status des Indikators abgerufen. Dies ist notwendig, weil in Zeile 72 eine andere Prozedur aufgerufen wird. Springen wir zu Zeile 37. Hier werden die Dinge wirklich interessant. Genau hier weisen wir MetaTrader 5 an, Ereignisse auf dem Chart auszulösen. Der Grund für diese Trennung von der Puffervalidierung ist, dass wir manchmal einfach nur Chartereignisse auslösen wollen, während wir in anderen Fällen bestätigen müssen, ob irgendwelche bedeutsamen Änderungen im Wert des Kontrollindikators aufgetreten sind.

Beachten Sie, dass die gesamte Logik von SendEventCustom bereits in der vorherigen Codeversion vorhanden war. Es ist also nicht nötig, dies noch einmal zu erklären. Der Code ist übersichtlich und selbsterklärend. Ich glaube, ich habe die wichtigsten strukturellen Änderungen erläutert. Wir müssen jedoch noch auf die Änderungen eingehen, die an der Funktion LoopEventOnTime vorgenommen wurden. Obwohl diese Änderungen nicht dramatisch oder strukturell sind, verdeutlichen sie den Grund, warum UpdateIndicatorControl in zwei neue Prozeduren aufgeteilt wurde. Lassen Sie uns das schnell durchgehen.

In Zeile 275 bitten wir MetaTrader 5, nutzerdefinierte Ereignisse an uns zu senden. Dadurch wird sichergestellt, dass sowohl die Maus als auch die Kontrollanzeigen korrekt konfiguriert sind, bevor die Simulation oder Wiedergabe beginnt. Obwohl Zeile 275 bei der ersten Ausführung übersprungen werden könnte, muss sie nach dem ersten Befehl „Play“ ausgeführt werden, um sicherzustellen, dass die Daten erneut validiert werden. Diese Zeile wird erneut ausgeführt, sobald das System in den Pausemodus wechselt.

In Zeile 279 brauchen wir keine Ereignisse auszulösen. Wir beobachten einfach den Kontrollindikator. Sobald der Nutzer auf Play drückt, endet die Schleife in Zeile 276 und die Simulation beginnt.

In Zeile 285 fordern wir erneut nutzerdefinierte Ereignisse an. Dieses Mal müssen wir dem Mauszeiger erlauben, die verbleibende Zeit auf dem Balken anzuzeigen.

Eine weitere subtile Veränderung findet sich in den Zeilen 293 und 294. Es handelt sich um einen einfachen Logikblock, der den Status des Mauszeigers aktualisiert. Anhand dieses Status können wir erkennen, ob die Anlage in den Auktionsmodus eingetreten ist oder diesen verlassen hat.

Die letzte Änderung wird in Zeile 306 angezeigt, wo wir prüfen, ob sich der Zeitrahmen geändert hat. Wenn eine Änderung festgestellt wird, werden die Indikatoren neu geladen, wie am Anfang dieses Abschnitts beschrieben.

Damit sind alle notwendigen Code-Anpassungen vorgenommen worden. Das folgende Video zeigt, wie sich das System jetzt verhält, wenn der Zeitrahmen des Charts geändert wird.




Schlussfolgerung

In den letzten beiden Artikeln habe ich gezeigt, wie wichtig es ist, zu experimentieren und die eigene Programmiersprache bis an ihre Grenzen auszureizen. Auch wenn einige Leser den Eindruck haben könnten, dass nur wenig direktes Wissen gewonnen wurde, ist das nicht der Fall. Ich bin sicher, dass viele glaubten, das, was ich hier erklärt habe, sei entweder nicht möglich oder zumindest nicht praktikabel in der Umsetzung oder Wartung. Bevor ich jedoch den Hauptcode änderte, erklärte ich, wie wichtig es ist, zunächst eine Hypothese aufzustellen und dann einen einfachen Prototyp zu bauen, um sie zu testen.

Aber die wichtigste Lektion ist diese: Geben Sie niemals beim ersten Versuch auf. Wenn etwas nicht funktioniert, passen Sie Ihren Ansatz an, aber bleiben Sie immer darauf konzentriert, Ihre Hypothese zu testen. Das ist genau das, was ich getan habe. Mein Ziel war es, Daten in den Indikatorpuffer zu schieben, um Änderungen im Zeitrahmen eindeutig und zuverlässig zu erkennen. Es war einfach, dies direkt auf dem Chart zu tun. Die Frage war also: Könnte der Dienst auch diese Änderung feststellen? Der erste Umsetzungsversuch scheiterte. Dennoch konnten wir den Wert des Zeitrahmens ablesen. Das Problem war, dass dieser Wert den Zustand zu dem Zeitpunkt wiedergab, als der Indikator zum ersten Mal dem Chart hinzugefügt wurde.

Hier ist das Konzept wirklich entstanden. Wenn ich einfach aufgegeben hätte, anstatt die Architektur zu überdenken, um das Handle bei jedem Aufruf dynamisch abzurufen, hätte ich die Gelegenheit verpasst, auf in Echtzeit aktualisierte Daten des Indikators zuzugreifen. Indem wir das Design überdachten, eröffneten wir neue Möglichkeiten und bewiesen, dass wir weit über das hinausgehen können, was viele für möglich halten.

So werden echte Systeme aufgebaut: Man beginnt mit einer Hypothese, testet sie, und selbst wenn der erste Ansatz nur teilweise funktioniert, wiederholt man ihn, immer geleitet von derselben Kernidee.

Im Anhang finden Sie die notwendigen ausführbaren Dateien für die Verwendung des Wiedergabe/Simulators. Im nächsten Artikel werden wir uns mit den zusätzlichen Funktionen befassen, die wir in die Wiedergabe-/Simulator-Anwendung integrieren müssen. Wir sehen uns dort.

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

Beigefügte Dateien |
Versxo_Demo.zip (247.04 KB)
Von der Grundstufe bis zur Mittelstufe: Union (I) Von der Grundstufe bis zur Mittelstufe: Union (I)
In diesem Artikel werden wir uns ansehen, was eine Union ist. Hier werden wir anhand von Experimenten die ersten Konstruktionen analysieren, in denen Union verwendet werden kann. Was hier gezeigt wird, ist jedoch nur ein Kernstück einer Reihe von Konzepten und Informationen, die in späteren Artikeln behandelt werden. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Neuronale Netze im Handel: Der Contrastive Muster-Transformer Neuronale Netze im Handel: Der Contrastive Muster-Transformer
Der Contrastive Transformer wurde entwickelt, um Märkte sowohl auf der Ebene einzelner Kerzen als auch auf der Basis ganzer Muster zu analysieren. Dies trägt dazu bei, die Qualität der Modellierung von Markttrends zu verbessern. Darüber hinaus fördert der Einsatz des kontrastiven Lernens zum Abgleich der Darstellungen von Kerzen und Mustern die Selbstregulierung und verbessert die Genauigkeit der Prognosen.
Entwicklung eines Expert Advisors für mehrere Währungen (Teil 19): In Python implementierte Stufen erstellen Entwicklung eines Expert Advisors für mehrere Währungen (Teil 19): In Python implementierte Stufen erstellen
Bisher haben wir die Automatisierung des Starts von sequentiellen Verfahren zur Optimierung von EAs ausschließlich im Standard-Strategietester betrachtet. Was aber, wenn wir zwischen diesen Starts die gewonnenen Daten mit anderen Mitteln bearbeiten wollen? Wir werden versuchen, die Möglichkeit hinzuzufügen, neue Optimierungsstufen zu erstellen, die von in Python geschriebenen Programmen ausgeführt werden.
Von der Grundstufe bis zur Mittelstufe: Array (IV) Von der Grundstufe bis zur Mittelstufe: Array (IV)
In diesem Artikel sehen wir uns an, wie wir etwas sehr Ähnliches wie in Sprachen wie C, C++ und Java implementieren können. Ich spreche von der Übergabe einer praktisch unendlichen Anzahl von Parametern innerhalb einer Funktion oder Prozedur. Auch wenn dies ein ziemlich fortgeschrittenes Thema zu sein scheint, kann das, was hier gezeigt wird, meiner Meinung nach von jedem, der die vorherigen Konzepte verstanden hat, leicht umgesetzt werden. Vorausgesetzt, sie wurden wirklich richtig verstanden.