English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay Systems (Teil 39): Den Weg ebnen (III)

Entwicklung eines Replay Systems (Teil 39): Den Weg ebnen (III)

MetaTrader 5Beispiele | 10 Juli 2024, 12:49
118 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel Entwicklung eines Replay Systems (Teil 38): Den Weg ebnen (II), habe ich erklärt und demonstriert, wie man Daten sendet, in unserem Fall vom Expert Advisor zum Indikator. Auf diese Weise konnten wir den Indikator konfigurieren und dafür sorgen, dass er einige plausible Informationen liefert.

Es ist also an der Zeit, in die entgegengesetzte Richtung zu gehen, d.h. den Indikator dazu zu bringen, dem Aufrufer, in unserem Fall dem Expert Advisor, etwas mitzuteilen, das eine Art von Bedeutung hat. Wir müssen wissen, wie wir vorgehen sollen.

Hier gibt es mehrere Dinge. Einige davon sind recht einfach zu erklären, andere nicht so sehr. Dementsprechend werden einige von ihnen leicht zu demonstrieren sein, während andere mehr Zeit in Anspruch nehmen werden. Aber so oder so, in diesem Artikel werden wir die notwendige Grundlage für den Aufbau unseres Chart Traders schaffen. Der Chat Trader, der hier im Replay/Simulationssystem entwickelt wird, wird dem im letzten Artikel vorgestellten sehr ähnlich sein.

Aber sie wird sich von dem unterscheiden, was wir in dem Artikel gesehen haben: Entwicklung eines Expert Advisors für den Handel von Grund auf (Teil 30): CHART TRADE als Indikator?!. Was wir hier tun werden, mag dank der gewählten Methode zur Erstellung des Chart Traders wie ein Kinderspiel erscheinen. Aber wir wollen die Überraschung nicht verderben. Sonst wird es uninteressant. Um zu erklären, was wir tun werden, müssen wir zunächst ein paar Dinge verstehen, die wir in diesem Artikel behandeln werden.


Erste Konstruktion des Modells

Obwohl alles, was in den letzten beiden Artikeln beschrieben wurde, bis zu einem gewissen Grad OK ist und recht gut funktioniert, was ich ja eigentlich zeigen wollte, haben wir jetzt ein anderes Problem. Wir senden nur Daten vom EA an den Indikator. Das bedeutet, dass es eine Kommunikation gibt. Allerdings lesen wir die Indikatordaten immer noch nicht. In diesem Fall müssen wir die Fähigkeit implementieren, Daten aus dem Indikator zu lesen. 

Um uns das Leben zu erleichtern, müssen wir unseren Code vorbereiten. 

Als erstes erstellen wir eine Header-Datei (siehe unten).

Header-Datei:

1. #property copyright "Daniel Jose"
2. #property link      ""
3. //+------------------------------------------------------------------+
4. #define def_ShortName        "SWAP MSG"
5. //+------------------------------------------------------------------+

Diese Header-Datei heißt Defines.mqh. Sie sollte im Verzeichnis Includes im Unterordner Mode Swap gespeichert werden. Die einzige Zeile, die für uns interessant ist, ist Zeile 4. Hier legen wir den Namen fest, der sowohl im EA als auch im Indikator verwendet wird, um die Nutzung zu erleichtern. Denn Sie können einen Namen im Indikator angeben und vergessen, denselben Namen im EA anzugeben. Ich spreche nicht von dem Dateinamen. Ich meine den Namen, unter dem der Indikator in MetaTrader 5 erkannt wird.

Um dies zu verdeutlichen, sehen wir uns den nachstehenden Indikatorcode an.

Quellcode des Indikators:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 0
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
33. {
34.     return rates_total;
35. }
36. //+------------------------------------------------------------------+

Achten Sie auf den Code in Zeile 08. In dieser Zeile bitten wir den Compiler, den Code aus der obigen Header-Datei einzubinden. Der Code bleibt derselbe, mit der Ausnahme, dass wir diese Definition jetzt nicht mehr im Indikatorcode haben.

Somit bleiben alle bisherigen Erklärungen gültig. Schauen wir uns nun den folgenden EA-Code an.

EA-Quellcode:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Mode Swap\\Swap MSG.ex5"
08. #resource "\\Indicators\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     m_id = ChartID();
18. 
19.     EraseIndicator();
20.     m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
21.     ChartIndicatorAdd(m_id, 0, m_handle);
22.     
23.     Print("Indicator loading result:", m_handle != INVALID_HANDLE ? "Success" : "Failed");
24.     
25.     return INIT_SUCCEEDED;
26. }
27. //+------------------------------------------------------------------+
28. void OnDeinit(const int reason)
29. {
30.     EraseIndicator();
31. }
32. //+------------------------------------------------------------------+
33. void OnTick()
34. {
35. }
36. //+------------------------------------------------------------------+
37. void EraseIndicator(void)
38. {
39.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE) return;
40.     ChartIndicatorDelete(m_id, 0, def_ShortName);
41.     IndicatorRelease(m_handle);
42. }
43. //+------------------------------------------------------------------+

Hier haben wir es mit etwas zu tun, das sehr unterschiedlich zu sein scheint, aber bei näherer Betrachtung ist der Unterschied nicht so groß. Der einzige wirkliche Unterschied ist das Vorhandensein einer Funktion namens EraseIndicator. Sie befindet sich in Zeile 37. Diese Funktion wird an zwei Stellen aufgerufen: die erste ist in Zeile 19, die zweite in Zeile 30. Ich glaube nicht, dass irgendjemand eine Frage dazu hat, warum er in Zeile 30 aufgerufen wird. Aber was ist mit Zeile 19? Kennen Sie den Grund für diesen Anruf?

Der Grund für den Aufruf in Zeile 19 besteht darin, die Synchronisierung zwischen dem im EA gemeldeten Wert und dem Wert, der an den Indikator gesendet wird, sicherzustellen. Es gibt aber auch etwas, das eine aufmerksame Betrachtung erfordert. Sehen Sie sich Zeile 39 des EA-Codes an. Bitte beachten Sie, dass wir beim Aufruf der Funktion sowohl in Zeile 19 als auch in Zeile 30 prüfen, ob der Indikator auf dem Chart läuft. Wenn der Indikator nicht vorhanden ist, wird der Code abgebrochen. In diesem Fall werden sowohl Zeile 40, die den Indikator aus dem Chart entfernt, als auch Zeile 41, die das Handle freigibt, nicht ausgeführt.

Aber warum müssen wir den Indikator in Zeile 40 aus dem Chart entfernen? Der Grund dafür liegt nicht im EA-Code, sondern im Code des Indikators.

Wenn Sie sich den Code des Indikators ansehen, werden Sie feststellen, dass der Indikator in dem Moment, in dem der EA versucht, seinen Wert zu ändern, in Zeile 20 blockiert wird. Dadurch wird eine Aktualisierung verhindert, da das System erkennt, dass Sie versuchen, einen neuen Indikator auf dem Chart zu platzieren. Obwohl der neue Indikator nicht wirklich hinzugefügt wird, wird es in Zeile 20 als solches wahrgenommen. Aus diesem Grund müssen wir den Indikator aus dem Chart entfernen.

Aber kommen wir zurück zum EA-Code. Haben Sie eine Ahnung, was die Zeilen 07 und 08 bedeuten? Und warum unterscheidet sich die Zeile 20 von der im vorigen Artikel besprochenen Zeile?

Dies ist der Punkt, an dem der Übergang vom Hauptbereich zu einem anderen Bereich beginnt. In Zeile 07 wird eine Definition erstellt, die jedoch angibt, wo sich der Indikatorcode befindet. Wenn der Compiler also die ausführbare Datei EA erstellt und Zeile 08 findet, sucht er nach der in Zeile 07 angegebenen ausführbaren Datei. Wenn die ausführbare Datei nicht vorhanden ist, beginnt der Compiler mit der Kompilierung des Codes, der für die Erstellung einer solchen ausführbaren Datei verantwortlich ist. Das heißt, in Zeile 08 machen wir den Indikator zu einer internen Ressource des EA.

Das ist sehr interessant. Aber es gibt Vor- und Nachteile. Einer der erwähnenswerten Vorteile ist, dass Sie nach der Kompilierung des EA die ausführbare Datei des Indikators löschen können. Sie müssen dies jedoch noch nicht tun. Es gibt ein Detail, das berücksichtigt werden muss, um dies zu ermöglichen. Ein ebenfalls erwähnenswerter Nachteil ist, dass Sie nach der Kompilierung des EA bei Problemen mit dem Indikator den gesamten EA-Code neu kompilieren müssen. Selbst wenn das Problem nur im Indikator liegt. Bitte bleiben Sie ruhig. Es gibt ein Detail, das berücksichtigt werden muss.

Was für ein Detail ist das? Dies ist ein eher subtiler Punkt, der fast unbemerkt bleibt. Aus diesem Grund ist jeder gute Programmierer auch ein sehr guter Beobachter. Hier sind also die Details. Sehen Sie sich Zeile 20 des EA-Codes an. Beachten Sie, dass in der Deklaration des dritten Parameters eine kleine Kette erscheint (::). Diese kleine Kette, die vor der Definition steht, ist der Operator zur Auflösung des Bereichs.

Allein die Tatsache, dass der Operator hier ist, zeigt, dass wir etwas anders nutzen werden, als wir es uns vielleicht vorstellen. Jedes Mal, wenn dieser Operator auftaucht, teilen wir dem Compiler ausdrücklich mit, was zu tun ist. Normalerweise gibt es eine Entscheidung, die der Compiler treffen sollte, aber wir müssen explizit wissen, was das ist.

In dem im Code gezeigten speziellen Fall weisen wir den Compiler an, den Indikator zu verwenden, der als Ressource in der ausführbaren Datei des EA gefunden wurde. Das bedeutet, dass wir die ausführbare Datei des Indikators nach erfolgreicher Kompilierung des EA einfach löschen können. Und der Berater kann immer noch den richtigen Indikator verwenden.

Wenn dieser Operator zur Bereichsauflösung nicht hinzugefügt wird, wie im folgenden Beispiel gezeigt, dann wird der EA, obwohl der Compiler den Indikator als EA-Ressource einschließt, tatsächlich die ausführbare Datei verwenden, die sich in dem in der Definition angegebenen Verzeichnis befindet. In diesem Fall müssten wir den Indikator zusammen mit dem EA in eine separate Datei portieren.

20.     m_handle = iCustom(NULL, PERIOD_CURRENT, def_SWAP, user00);

Dieses einfache Detail ist entscheidend. Im Fall von Zeile 20 im vollständigen Code können Sie nach dem Kompilieren des EA die ausführbare Datei des Indikators löschen. Es gibt noch ein weiteres Detail: Wenn ein Indikator hinzugefügt und als EA-Ressource verwendet wird, ist es ratsam, vor dem Kompilieren des letzteren die ausführbare Datei des Indikators zu löschen. Dadurch wird gewährleistet, dass der neueste Code im Indikator tatsächlich kompiliert wird. In Systemen, in denen mehrere Kompilierungen verwendet werden, haben wir in der Regel einen Helfer - eine Datei namens MAKE.

Wenn diese Option verwendet wird, entfernt MAKE bei jedem erneuten Kompilieren des Codes die ausführbaren Dateien in angemessener Weise. Völlig automatisch. Er vergleicht die letzte Kompilierungszeit mit der letzten Änderungszeit einiger Dateien, die im Quellcode der ausführbaren Datei verwendet werden. Wenn Änderungen festgestellt werden, entfernt MAKE die ausführbare Datei und zwingt den Compiler, eine neue zu erstellen. Im Moment gibt es in MQL5 keine solche Möglichkeit, aber wer weiß, vielleicht entscheiden sich die Entwickler in Zukunft dafür, sie hinzuzufügen.

All diese Erklärungen zeigen, was wir tun können, wie wir handeln sollten und wie wir in Zukunft handeln werden. Aber wir haben immer noch nicht die Möglichkeit, Daten aus dem Indikator zu lesen.

Lassen Sie uns eine sehr einfache Version der Kommunikation implementieren. Um dies ausführlich und konsequent zu erläutern, sollten wir uns einem neuen Thema zuwenden.


Lesen von Daten aus dem Indikator

In der Tat gibt es nur eine Möglichkeit, die im Indikator dargestellten Informationen zu lesen, nämlich die des Puffers. Allerdings gibt es hier eine kleine Nuance: Die Funktion CopyBuffer erlaubt es uns nicht, mit jeder Art von Daten zu arbeiten, die im Puffer vorhanden sind. Nicht in seiner natürlichen Form. Wenn Sie sich die Deklaration dieser Funktion ansehen, werden Sie sehen, was ich meine.

Deklaration der Funktion CopyBuffer:

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // number of indicator buffer
   int       start_pos,            // starting position 
   int       count,                // amount to copy 
   double    buffer[]              // destination array to copy 
   );


Zur Verdeutlichung sehen Sie oben, wie eine der Varianten der CopyBuffer-Funktion deklariert ist. Ich sage eine der Varianten, weil es drei gibt, aber was uns wirklich interessiert, ist die letzte Variable in der Deklaration. Achten Sie auf den Typ - double, das heißt, wir können nur Werte vom Typ double zurückgeben. Dies ist nur theoretisch, denn in der Praxis können wir alles zurückgeben. Und im Artikel Entwicklung eines Trading Expert Advisors von Grund auf (Teil 17): Zugriff auf Daten im Internet (III) Ich habe gezeigt, wie man diese Einschränkung umgehen kann.

In dieser Sequenz werden wir auch etwas Ähnliches verwenden, allerdings auf einer anderen Ebene. Vielleicht wird es für Anfänger etwas größer und komplexer sein. Aber die Idee ist die gleiche wie in dem genannten Artikel. Wenn Sie weitere Einzelheiten benötigen, lesen Sie bitte den genannten Artikel, um zu verstehen, was wir genau tun werden. Hier werden wir dies einfach tun, und ich werde nicht auf die Einzelheiten eingehen.

Es handelt sich also um etwas, das uns bereits bekannt ist. Ändern wir den Quellcode des Indikators wie unten gezeigt. Anmerkung: Ich werde schrittweise vorgehen, damit Sie verfolgen und verstehen können, was getan wird.

Quellcode des Indikators:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 1
07. //+------------------------------------------------------------------+
08. #include <Mode Swap\Defines.mqh>
09. //+------------------------------------------------------------------+
10. #define def_ShortNameTmp    def_ShortName + "_Tmp"
11. //+------------------------------------------------------------------+
12. input double user00 = 0.0;
13. //+------------------------------------------------------------------+
14. long m_id;
15. double m_Buff[];
16. //+------------------------------------------------------------------+
17. int OnInit()
18. {
19.     m_id = ChartID();
20.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
21.     if (ChartWindowFind(m_id, def_ShortName) != -1)
22.     {
23.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
24.             Print("Only one instance is allowed...");
25.             return INIT_FAILED;
26.     }
27.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
28.     Print("Indicator configured with the following value:", user00);
29.     
30.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
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.     m_Buff[rates_total - 1] = user00 * 2.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

Wenn Sie den oben gezeigten Quellcode mit dem zu Beginn des Artikels vorgestellten Quellcode vergleichen, werden Sie nur wenige kleine Unterschiede feststellen. Wie ich schon sagte, wir werden langsam vorgehen. Wir haben eine Änderung in Zeile 06, die besagt, dass der Indikator einen Puffer hat. Dieser Puffer wird außerhalb des Indikators sichtbar sein. Wir werden später darüber sprechen. Lassen Sie uns zunächst herausfinden, was in dem Indikator passiert.

Als Nächstes wird in Zeile 15 eine globale Variable deklariert. Diese Variable ist jedoch nur für den Indikatorcode oder die Teile davon sichtbar, die sich auf ihn beziehen. Diese Variable, die ein Puffer ist, muss initialisiert werden. Dies geschieht in den Zeilen 30 und 31. Jetzt ist unser Puffer initialisiert und mit einer Art von Information versehen.

Wenn Sie sie jedoch lesen, erhalten Sie nur nutzlose Daten. Wir brauchen eine effiziente Methode, um Daten in den Puffer zu laden. Dies kann auf verschiedene Weise geschehen, aber technisch gesehen ist die beste und sicherste Methode die, die bereits angewendet wird. Aus diesem Grund haben wir Zeile 38.

Sie werden feststellen, dass ich in dieser Zeile nicht auf einen Bereich im Puffer zeige. Warum?

Der Grund dafür ist, dass wir den Prozess irgendwie standardisieren müssen. Wenn wir Informationen einfügen, in diesem Fall das Ergebnis der Multiplikation des vom EA gemeldeten Wertes mit 2,0, kann es schwierig sein zu wissen, wo wir nach wirklich nützlichen Informationen suchen müssen. Wir versuchen, die Dinge in Ruhe zu verstehen. Aber in Zukunft wird alles komplizierter werden.

Deshalb verwenden wir unser eigenes System zur Berechnung von Indikatoren, damit die Informationen immer an der gleichen Stelle stehen.

Um dies zu verdeutlichen, sehen wir uns den EA-Quellcode unten an.

EA-Quellcode:

01. #property copyright "Daniel Jose"
02. #property link      ""
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #include <Mode Swap\Defines.mqh>
06. //+------------------------------------------------------------------+
07. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
08. #resource "\\" + def_SWAP
09. //+------------------------------------------------------------------+
10. input double user00 = 2.2;
11. //+------------------------------------------------------------------+
12. int m_handle;
13. long m_id;
14. //+------------------------------------------------------------------+
15. int OnInit()
16. {   
17.     double Buff[];
18.     
19.     m_id = ChartID();
20. 
21.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
22.     {
23.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
24.             ChartIndicatorAdd(m_id, 0, m_handle);
25.     }
26.             
27.     Print ("Buffer reading:", (m_handle == INVALID_HANDLE ? "Error..." : CopyBuffer(m_handle, 0, 0, 1, Buff) > 0 ?  (string)Buff[0] : " Failed."));
28.     
29.     return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.     ChartIndicatorDelete(m_id, 0, def_ShortName);
35.     IndicatorRelease(m_handle);
36. }
37. //+------------------------------------------------------------------+
38. void OnTick()
39. {
40. }
41. //+------------------------------------------------------------------+

Bitte beachten Sie, dass sich der Code gegenüber der früheren Darstellung nicht wesentlich geändert hat. Wir erinnern uns, dass der Indikator hier als Ressource verwendet wird, sodass wir nach der Kompilierung des EA seine ausführbare Datei löschen können. Schauen wir uns an, was dem Code hinzugefügt wurde.

Zunächst haben wir jetzt eine Variablendeklaration in Zeile 17. Dies ist unser Rückgabepuffer oder unser Indikator-Lesepuffer. Zusätzlich zu dieser Zeile 17 haben wir auch eine neue Zeile 27.

Viele mögen diese Zeile 27 für ein echtes Chaos halten, aber es handelt sich nur um zwei verschachtelte ternäre Operatoren. Zunächst wird geprüft, ob das Indikator-Handle gültig ist; falls nicht, erscheint eine entsprechende Warnung im Meldungsfenster des Terminals. Wenn das Handle gültig ist, lesen wir eine Position aus dem Indikatorpuffer. Welche Position wird gelesen? Die erste. Nicht so klar? Beruhigen und entspannen Sie sich, ich werde alles später erklären. Wenn der Puffer erfolgreich gelesen wurde, wird der darin enthaltene Wert gedruckt. Wenn nicht, wird eine weitere Fehlermeldung angezeigt. Sie wird sich von der ersten unterscheiden, was auf einen anderen Grund für das Scheitern hinweist.

Nun wollen wir sehen, warum wir beim Lesen des Puffers, wie in Zeile 27 gezeigt, die erste Position lesen und nicht die letzte.


Das Schreiben und Lesen von Puffern verstehen

Dies ist ein Bild aus der MQL5-Dokumentation. Sie veranschaulicht deutlich, was ich jetzt erklären werde. Siehe unten:

Abbildung 01

Abbildung 01 - Schreiben und Lesen des Indikatorpuffers

Dieses Bild macht deutlich, was passiert. Sehen wir uns nun im Detail an, wie die Daten in den Indikatorpuffer geschrieben und aus ihm gelesen werden.

Beim Schreiben in den Indikatorpuffer ist es wichtig, immer an eine bestimmte Stelle zu schreiben. Wenn neue Kerzen erscheinen, werden automatisch neue Positionen in den Puffer aufgenommen. Das Problem ist, dass Sie für jeden Zeitrahmen eine andere Puffergröße haben werden.

Sie fragen sich vielleicht, wo das Problem liegt. Es gibt nicht nur ein Problem, es gibt mehrere. Nehmen wir an, dass wir im Indikator beschließen, mit folgendem Code in die Nullposition zu schreiben.

Codeschnipsel - Modell 01:

35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.     m_Buff[0] = user00 * 3.0;
39.     
40.     return rates_total;
41. }
42. //+------------------------------------------------------------------+

Wenn Sie die Zeilen im Code des Indikators, der im vorherigen Thema besprochen wurde, in diesen Code ändern, schreiben Sie buchstäblich in die Nullposition des Puffers, wie in Zeile 38 zu sehen. Allerdings gibt es bei diesem Ansatz ein Problem.

Das Problem tritt nicht beim Schreiben auf, sondern wenn man versucht, den Puffer außerhalb des Indikatorcodes zu lesen, z. B. in einem EA.

Beim Lesen eines Puffers gibt der MetaTrader 5 normalerweise maximal 2000 Positionen zurück. Das Ergebnis ist in Abbildung 01 dargestellt. Wenn die Datenmenge des Indikators zufällig diese 2000 Positionen überschreitet, treten Probleme auf. Denken Sie daran, dass wir an die Nullposition geschrieben haben, aber die Nullposition ist nicht dieselbe Nullposition, auf die CopyBuffer verweist. Bei CopyBuffer kann diese Nullposition, die im Indikatorcode steht, in Wirklichkeit die Position 2001 sein, und wenn dies der Fall ist, ist es nicht möglich, die Nullposition aus dem Indikatorpuffer zu lesen. Dieser Wert wird im Indikator festgelegt.

Aus diesem Grund schreiben wir nicht an die Nullposition, sondern beginnen mit dem Schreiben an der Position, die sich am Ende des Puffers befindet. Immer noch nicht klar?

Die Nullposition im Indikatorpuffer sollte immer als die Position „rates_total - 1“ betrachtet werden. Dies ist die Position, die wir in den Indikatorcode schreiben, der im vorherigen Thema besprochen wurde. Genau aus diesem Grund verwenden wir, wenn wir den Indikatorpuffer über CopyBuffer lesen, bei der Ausgabe des Wertes tatsächlich den Null-Index.

Vielleicht ist das noch nicht ganz klar. Zur Verdeutlichung sehen wir uns ein weiteres Codebeispiel an, bei dem wir Daten an den Indikator übergeben, aber nicht nur einen Wert zurückgeben, wie zuvor, sondern mehrere. Eine davon wird eine einfache Zeichenkette sein.

Es ist erwähnenswert, dass wir in den Puffer entweder in umgekehrter Reihenfolge oder in der üblichen Weise schreiben können, oder besser gesagt, in der Reihenfolge: erste Information - erster Wert, zweite Information - zweiter Wert. Im umgekehrten Fall sieht es folgendermaßen aus: erste Information - letzter Wert, zweite Information - letzter Wert minus eine Position usw. Um die Interpretation zu erleichtern, nicht beim Schreiben, sondern beim Lesen, gehen wir den üblichen Weg.

Ändern wir zunächst die Header-Datei, die unten zu sehen ist.

Definiert den Quellcode der Datei.mqh:

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_ShortName       "SWAP MSG"
04. //+------------------------------------------------------------------+
05. union uCharDouble
06. {
07.     double  dValue;
08.     char    cInfo[sizeof(double)];
09. };
10. //+------------------------------------------------------------------+

Wie Sie sehen können, gibt es zwischen den Zeilen 05 und 09 eine Verbindung. Diese Vereinigung ermöglicht es uns, Daten vom Typ Text mit einem Wert vom Typ Double zu übergeben. Wenn Sie dies zum ersten Mal sehen, mag es Ihnen seltsam vorkommen. Aber wir haben das schon einmal gemacht. Ein Beispiel dafür ist in dem Artikel zu sehen: Entwicklung eines Expert Advisors für den Handel von Grund auf (Teil 17): Zugriff auf Daten im Internet (III)“. Aber kommen wir zurück zu unserer Frage. Jetzt haben wir eine Möglichkeit, eine kleine Zeichenkette vom Indikator an den EA zu senden. Der Grund für die Verwendung des Double-Wertes ist, dass wir keinen Wert eines anderen Typs über CopyBuffer senden können. Wir müssen den Typ double verwenden.

Nachdem wir diese Änderung in der Datei Defines.mqh vorgenommen haben, können wir mit dem Quellcode des Indikators fortfahren.

Der Quellcode der Indikatoren wurde aktualisiert, sodass mehr als eine Position beschrieben werden kann:

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. #property indicator_buffers 1
06. //+------------------------------------------------------------------+
07. #include <Mode Swap\Defines.mqh>
08. //+------------------------------------------------------------------+
09. #define def_ShortNameTmp    def_ShortName + "_Tmp"
10. //+------------------------------------------------------------------+
11. input double user00 = 0.0;
12. //+------------------------------------------------------------------+
13. long m_id;
14. double m_Buff[];
15. //+------------------------------------------------------------------+
16. int OnInit()
17. {
18.     m_id = ChartID();
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNameTmp);
20.     if (ChartWindowFind(m_id, def_ShortName) != -1)
21.     {
22.             ChartIndicatorDelete(m_id, 0, def_ShortNameTmp);
23.             Print("Only one instance is allowed...");
24.             return INIT_FAILED;
25.     }
26.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
27.     Print("Indicator configured with the following value:", user00);
28.     
29.     SetIndexBuffer(0, m_Buff, INDICATOR_CALCULATIONS);
30.     ArrayInitialize(m_Buff, EMPTY_VALUE);
31. 
32.     return INIT_SUCCEEDED;
33. }
34. //+------------------------------------------------------------------+
35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
36. {
37.     uCharDouble info;
38.     int pos = rates_total - 3;
39.     
40.     StringToCharArray("Config", info.cInfo);
41.     m_Buff[pos + 0] = info.dValue;
42.     m_Buff[pos + 1] = user00 * 2.0;
43.     m_Buff[pos + 2] = user00 * 2.5;
44.             
45.     return rates_total;
46. }
47. //+------------------------------------------------------------------+

Schauen wir uns den obigen Code genauer an. Erst ab Zeile 37 gibt es einige Änderungen gegenüber dem zu Beginn des Artikels vorgestellten Code. Warum ist das so? Denn wir werden nun weitere Informationen zusammenstellen, die über den CopyBuffer zurückgegeben werden. Kein anderer Teil des Codes muss geändert werden, nur der Code ab Zeile 37.

Jetzt wollen wir herausfinden, was los ist. In Zeile 37 wird eine Variable deklariert, die zur Umwandlung der Zeichenkette in einen Double-Wert verwendet wird. Beachten Sie, dass die Länge der Zeichenfolge auf 8 Zeichen begrenzt ist. Enthält die Information mehr Zeichen, muss dafür ein Array bereitgestellt werden, das die Information immer in 8er-Blöcken berücksichtigt.

In Zeile 38 deklarieren wir eine Variable, die dazu verwendet wird, Informationen auf normale Weise zu schreiben, d. h. als ob wir Text von links nach rechts schreiben würden. Im Falle des Arabischen wird von rechts nach links geschrieben. Ich denke, die Idee ist klar. In der gleichen Zeile 38 geben wir die Anzahl der doppelten Werte an, die wir verbuchen werden. In unserem Fall haben wir 3 Werte.

In Zeile 40 wird string in ein Array von Zeichen umgewandelt. So erhalten wir den Wert, der in unserer ersten Position verwendet wird. In Zeile 41 wird dieser Wert dann in den Puffer geschrieben. 

In den Zeilen 42 und 43 führen wir eine einfache Berechnung durch, um einige Daten zum Lesen zu haben. So können Sie zeigen, wie das Lesen später abläuft, wenn Sie auf weitere Positionen im Puffer zugreifen müssen.

Im Grunde genommen geht es nur um die Frage des Indikators. Zum jetzigen Zeitpunkt gibt es nichts mehr über Kommunikation zu diskutieren. Schauen wir uns an, wie man vorgeht, um den im Indikator angelegten Puffer zu erkennen und zu lesen. Wenden wir uns dazu dem unten dargestellten, bereits aktualisierten EA-Code zu.

EA-Quellcode aktualisiert, um mehr als eine Position zu lesen:

01. #property copyright "Daniel Jose"
02. #property version   "1.00"
03. //+------------------------------------------------------------------+
04. #include <Mode Swap\Defines.mqh>
05. //+------------------------------------------------------------------+
06. #define def_SWAP "Indicators\\Mode Swap\\Swap MSG.ex5"
07. #resource "\\" + def_SWAP
08. //+------------------------------------------------------------------+
09. input double user00 = 2.2;
10. //+------------------------------------------------------------------+
11. int m_handle;
12. long m_id;
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {   
16.     double Buff[];
17.     uCharDouble Info;
18.     int iRet;
19.     string szInfo;
20.     
21.     m_id = ChartID();
22.     if ((m_handle = ChartIndicatorGet(m_id, 0, def_ShortName)) == INVALID_HANDLE)
23.     {
24.             m_handle = iCustom(NULL, PERIOD_CURRENT, "::" + def_SWAP, user00);
25.             ChartIndicatorAdd(m_id, 0, m_handle);
26.     }
27.     ArraySetAsSeries(Buff, false);
28.     if (m_handle == INVALID_HANDLE) szInfo = "Invalid handler to read the buffer.";
29.     else
30.     {
31.             if ((iRet = CopyBuffer(m_handle, 0, 0, 3, Buff)) < 3) szInfo = "Buffer reading failed.";
32.             else
33.             {
34.                     Info.dValue = Buff[0];
35.                     szInfo = CharArrayToString(Info.cInfo) + " [ " + (string)Buff[1] + " ] [ " + (string)Buff[2] + " ]";
36.             }
37.     }
38.     Print("Return => ", szInfo);
39.             
40.     return INIT_SUCCEEDED;
41. }
42. //+------------------------------------------------------------------+
43. void OnDeinit(const int reason)
44. {
45.     ChartIndicatorDelete(m_id, 0, def_ShortName);
46.     IndicatorRelease(m_handle);
47. }
48. //+------------------------------------------------------------------+
49. void OnTick()
50. {
51. }
52. //+------------------------------------------------------------------+

Dieser aktualisierte EA-Code oben unterscheidet sich nicht wesentlich von dem, was wir am Anfang gesehen haben. Dennoch gibt es hier einige neue Elemente, die einer Erklärung bedürfen.

Zwischen den Zeilen 17 und 19 werden neue Variablen deklariert, die zur Entschlüsselung der im Puffer enthaltenen Informationen verwendet werden. Das mag seltsam erscheinen, aber tatsächlich ist die Information im Puffer kodiert, da wir eine Zeichenkette an einer der Positionen übergeben.

Wirklich interessant ist der Abschnitt zwischen den Zeilen 27 und 38. Hier wird der Puffer gelesen. Fangen wir also mit diesem Teil an.

Zeile 27 enthält Code, der manchmal beim Lesen zu sehen ist, auch wenn wir mehrere Pufferpositionen lesen. Dies liegt daran, dass das Lesen standardmäßig direkt durchgeführt wird. In Abbildung 01 ist zu sehen, dass die Anordnung in direkter Folge erfolgt. Aber es gibt Momente, in denen das Lesen in umgekehrter Reihenfolge erfolgt. Anstelle der umgekehrten Indizierung für den Zugriff auf das Array verwenden wir dann die in Zeile 27 definierte Funktion, um festzulegen, dass das Lesen in umgekehrter Reihenfolge erfolgen soll. In diesem Fall ist der Wert, der an die Funktion übergeben wird, im Gegensatz zu dem, was wir sehen, falsch. Auf diese Weise können wir die Zugriffsindizierung wie einen direkten Zugriff verwenden.

Obwohl Zeile 27 in unserem aktuellen Code nicht viel Sinn macht, da wir direkt schreiben, wurde sie hinzugefügt, um zu erklären, was zuvor gesagt wurde.

Auf die meisten anderen Zeilen muss nicht näher eingegangen werden, da viele von ihnen selbsterklärend sind. Allerdings gibt es in Zeile 31 etwas, das zum Nachdenken anregt.

Wenn wir Daten in einen Puffer innerhalb des Indikators schreiben, wissen wir, wie viele Positionen von Informationen wir senden. In Zeile 31 wird die erwartete Anzahl von Positionen abgelesen. Es wäre sinnvoller, eine bessere Kombination mit einer Header-Datei zu erstellen. Wenn also die Anzahl der Positionen, die Informationen enthalten, zunimmt, sind sich sowohl der EA als auch der Indikator dessen stets bewusst. Da dies aber nur Beispiele sind, um ein Konzept zu erklären, das später verwendet wird, kann dies vorerst ignoriert werden.

Wenn die Anzahl der gelesenen Positionen geringer ist als erwartet, handelt es sich um einen Fehler, und die entsprechenden Informationen werden im Terminal angezeigt. Wenn die Zahl nun wie erwartet ist, gehen wir zu Zeile 34 und wandeln die als Double gelieferte Information in eine Zeichenkette um. Wir erhalten also die Informationen zurück, die der Indikator in den Puffer eingegeben hat.

Beachten Sie, dass der Index in diesem Fall Null ist, wie im Indikator angegeben. Wenn die umgekehrte Schreibweise im Indikator erfolgt, können wir die gleiche Indexierung im EA verwenden. Ändern Sie einfach den Wert in Zeile 27, und der EA wird die Informationen mit der gleichen Indizierungsmethode verstehen.

Versuchen Sie dies zu tun, um zu verstehen, wie die Dinge wirklich ablaufen. Die Kenntnis dieser Details ist wichtig für das Verständnis der nächsten Artikel dieser Reihe.

Sobald die Konvertierung abgeschlossen ist, wird in Zeile 35 eine Meldung erzeugt, die im Terminal angezeigt wird. So einfach ist das.


Schlussfolgerung

In diesem kurzen Exkurs, den wir in unserem Projekt für ein Wiedergabe-/Simulationssystem hatten, habe ich die Grundlagen dessen gezeigt, was in zukünftigen Artikeln behandelt werden wird.

Das ist nur der einfachste Teil von allem, was uns erwartet. Es gibt einige Dinge, die wir noch nicht hinzugefügt oder implementiert haben, die aber Teil des Replay/Simulator-Systems sein werden. Alle hier vorgestellten Inhalte sind für künftige Artikel von großer Bedeutung. Wenn Sie die Grundlagen der Datenübertragung zwischen Indikatoren und anderen Prozessen nicht verstehen, können Sie sich verirren. Ich habe noch nicht gezeigt, wie man den Indikator aus dem Indikatorfenster ausblenden kann, um zu verhindern, dass der Nutzer ihn löscht. Außerdem haben wir einige Elemente nicht hinzugefügt, die das Verständnis in verschiedenen möglichen Kombinationen erheblich erschweren.

Ich hoffe wirklich, dass Sie die vorgestellten Konzepte und die tatsächliche Bedeutung der letzten drei Artikel verstehen. Dann wird alles noch viel komplizierter werden.

Da ich möchte, dass Sie eine Art mechanisches Gedächtnis entwickeln, werde ich diesem Artikel keine Dateien beifügen. Wenn Sie experimentieren und die hier gezeigten Grundlagen erlernen wollen, müssen Sie den Code selbst schreiben. Da die Codes aber sehr kurz sind, sollte es damit keine Probleme geben.

Wir sehen uns im nächsten Artikel, in dem wir den Chart Trader für die Verwendung in einem Replay/Simulator-System integrieren werden.


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

Beigefügte Dateien |
EA.mq5 (1.81 KB)
swap.mq5 (1.72 KB)
Defines.mqh (0.24 KB)
Entwicklung eines Replay Systems (Teil 40): Beginn der zweiten Phase (I) Entwicklung eines Replay Systems (Teil 40): Beginn der zweiten Phase (I)
Heute werden wir über die neue Phase des Replay/Simulator-Systems sprechen. In dieser Phase wird das Gespräch wirklich interessant und sehr inhaltsreich. Ich empfehle Ihnen dringend, den Artikel sorgfältig zu lesen und die darin enthaltenen Links zu nutzen. Dies wird Ihnen helfen, den Inhalt besser zu verstehen.
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.
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.
MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 22): Conditional GANs MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 22): Conditional GANs
Generative Adversarial Networks (GAN) sind eine Kombination von neuronalen Netzen, die sich gegenseitig trainieren, um genauere Ergebnisse zu erzielen. Wir nehmen den bedingten Typ dieser Netze an, da wir eine mögliche Anwendung bei der Vorhersage von Finanzzeitreihen innerhalb einer Klasse von Expertensignalen anstreben.