English Русский Español Português
preview
Entwicklung eines Replay Systems (Teil 58): Wiederaufnahme der Arbeit am Dienst

Entwicklung eines Replay Systems (Teil 58): Wiederaufnahme der Arbeit am Dienst

MetaTrader 5Beispiele | 13 Februar 2025, 08:43
45 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel Entwicklung eines Replay Systems (Teil 57): Verstehen eines Testdienstes habe ich ausführlich den Quellcode erläutert, der benötigt wird, um eine mögliche Art der Interaktion zwischen den Modulen zu demonstrieren, die wir in unserem Replay/Simulator-System verwenden werden.

Dieser Code gibt uns zwar eine Vorstellung davon, was wir tatsächlich implementieren müssen, aber es fehlt noch ein wichtiges Detail, das für unser System wirklich nützlich sein könnte - die Möglichkeit, Vorlagen zu verwenden. Sie denken vielleicht, dass dies nicht so wichtig ist, wenn Sie die Vorteile von Vorlagen nicht nutzen oder nicht vollständig verstehen, die sie uns bieten, sowohl in Bezug auf die Codierung als auch auf die MetaTrader 5-Einstellungen.

Wenn wir jedoch Muster kennen, verstehen und anwenden, verringert sich unsere Arbeitsbelastung erheblich. Es gibt Dinge, die mit Vorlagen sehr einfach zu bewerkstelligen sind, die aber extrem komplex und schwierig zu implementieren sind, wenn man versucht, sie direkt zu programmieren. Vielleicht zeige ich Ihnen in Zukunft, wie Sie einige Dinge nur mit Vorlagen erledigen können, aber im Moment haben wir andere, dringendere Aufgaben.

Um ehrlich zu sein, dachte ich, ich hätte den Punkt erreicht, an dem die Steuerungs- und Mausmodule nicht mehr verbessert werden müssten. Aufgrund einiger Details, die wir in den folgenden Artikeln sehen werden, müssen beide Module jedoch noch geringfügige Änderungen erfahren. Das werden wir später sehen, aber jetzt, in diesem Artikel, werden wir herausfinden, wie wir das Wissen, das wir im vorherigen Artikel gewonnen haben, in etwas Durchführbares und Funktionelles umsetzen können. Wenden wir uns also einem neuen Thema zu.


Ändern des alten Replay/Simulator-Dienstes

Obwohl es einige Zeit her ist, seit wir das letzte Mal Änderungen oder Verbesserungen am Code des Replays/Simulators vorgenommen haben, wurden bestimmte Header-Dateien, die an der Erstellung der ausführbaren Datei des Replays/Simulators beteiligt sind, geändert. Die vielleicht bemerkenswerteste Änderung ist die Entfernung der Header-Datei InterProcess.mqh, die durch Defines.mqh ersetzt wurde, eine Datei mit einem viel breiteren Zweck.

Da wir bereits Anpassungen an den Steuerungs- und Mausmodulen vorgenommen haben, um diese neue Header-Datei zu berücksichtigen, müssen wir nun die gleichen Änderungen am Wiedergabe-/Simulationsdienst vornehmen. Daher führt der Versuch, den Replay-/Simulationsdienst mit der aktualisierten Header-Dateistruktur zu kompilieren, zu Kompilierungsfehlern, wie in Abbildung 01 dargestellt.

Abbildung 01

Abbildung 01. Versuch, den Replay-/Modellierungsdienst zu kompilieren

Von den verschiedenen Fehlern, die auftreten können, sollten Sie zunächst die beiden hervorgehobenen Fehler beheben. Um sie zu beheben, öffnen Sie die Header-Datei C_Simulation.mqh und ändern Sie den Code wie im folgenden Ausschnitt gezeigt. Die erforderliche Änderung ist minimal - streichen Sie einfach Zeile 04 und ersetzen Sie sie durch die in Zeile 05 angegebene Anpassung. Diese Änderung stellt sicher, dass C_Simulation.mqh mit dem neuen Rahmenwerk, das wir implementieren, konform ist.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\..\Auxiliar\Interprocess.mqh"
05. #include "..\..\Defines.mqh"
06. //+------------------------------------------------------------------+
07. #define def_MaxSizeArray    16777216 // 16 Mbytes of positions
08. //+------------------------------------------------------------------+
09. class C_Simulation
10. {
11.    private   :
12. //+------------------------------------------------------------------+
13.       int       m_NDigits;
14.       bool       m_IsPriceBID;

Ein Fragment des Quellcodes der Datei C_Simulation.mqh

Genau wie in der Header-Datei C_Simulation.mqh müssen wir etwas Ähnliches in der Datei C_FilesBars.mqh tun. Öffnen Sie dazu die Header-Datei C_FilesBars.mqh und ändern Sie den Code wie unten gezeigt.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\..\Auxiliar\Interprocess.mqh"
05. #include "..\..\Defines.mqh"
06. //+------------------------------------------------------------------+
07. #define def_BarsDiary   1440
08. //+------------------------------------------------------------------+
09. class C_FileBars
10. {
11.    private   :
12.       int      m_file;

Ein Fragment des Quellcodes der Datei C_FilesBars.mqh

In beiden Codefragmenten haben wir die Header-Datei InterProcess.mqh entfernt und durch Defines.mqh ersetzt. Mit diesen beiden Änderungen wird der größte Teil des Codes an die erwartete Struktur des Wiedergabe-/Simulationsdienstes angepasst. Es gibt jedoch ein Problem. Wenn Sie den Inhalt von InterProcess.mqh und Defines.mqh vergleichen, werden Sie feststellen, dass Defines.mqh keine globalen Terminalvariablen referenziert. Trotzdem bezieht sich das Replay/Simulator-System auf diese Variablen.

Genauer gesagt, werden diese Variablen in der Datei C_Replay.mqh verwendet. Dies ist jedoch nicht unsere einzige Sorge. In Zukunft werde ich den Code möglicherweise weiter umstrukturieren, um seine Organisation, Stabilität und Flexibilität zu verbessern. Vorerst werde ich mich jedoch darauf konzentrieren, die bestehende Struktur anzupassen, anstatt drastische Änderungen am gesamten System vorzunehmen, nur um eine geringfügige Verbesserung der Flexibilität und Stabilität zu erreichen - obwohl beides immer verbesserungswürdig ist.

Der Übersichtlichkeit halber gliedern wir diese Erklärung in Abschnitte. Das erste Problem, das wir ansprechen werden, ist ein Fehler, der zwar nicht kritisch ist, aber gegen eines der Grundprinzipien der objektorientierten Programmierung verstößt: die Kapselung.


Überprüfung der Codekapselung

Eines der schwerwiegendsten Probleme in jeder Codebasis ist die Nichteinhaltung grundlegender objektorientierter Programmierprinzipien, die Sicherheit und Wartbarkeit gewährleisten. Lange Zeit habe ich einen bestimmten Teil des Codes übersehen und missbraucht, um den direkten Zugriff auf bestimmte Daten zu ermöglichen, die für die Wiedergabe-/Simulatorfunktionen erforderlich sind.

Von nun an wird diese Praxis jedoch nicht mehr angewandt werden. Ich beziehe mich insbesondere auf den Bruch der Kapselung in der Klasse C_ConfigService.

Wenn Sie sich die Header-Datei für diese Klasse (C_ConfigService.mqh) ansehen, werden Sie eine geschützte Klausel mit mehreren Variablen feststellen. Das Vorhandensein dieser Variablen in diesem Abschnitt bricht die Kapselung auf, obwohl sie nur innerhalb von C_ConfigService und seiner abgeleiteten Klasse C_Replay verwendet werden. Es ist nicht sinnvoll, dass diese Variablen in ihrer derzeitigen Form außerhalb von C_ConfigService zugänglich sind. Wenn Sie sich die Klasse C_Replay ansehen, werden Sie feststellen, dass sie diese Variablen verändert, und genau das macht diesen Ansatz problematisch. In C++ gibt es Möglichkeiten, Klassenvariablen privat zu machen und trotzdem kontrollierten Zugriff und Manipulation außerhalb der Basisklasse zu ermöglichen. Diese Techniken führen jedoch häufig zu einem übermäßig komplexen und schwer zu wartenden Code. Außerdem machen sie künftige Verbesserungen deutlich schwieriger.

Da MQL5 von C++ abgeleitet ist, vermeidet es die Einbeziehung bestimmter riskanter Praktiken, die C++ erlaubt. Daher ist es sinnvoller, sich strikt an die drei Grundprinzipien der objektorientierten Programmierung zu halten, einschließlich einer angemessenen Kapselung.

Durch Änderung der Header-Datei C_ConfigService.mqh wird die ordnungsgemäße Kapselung in unserem System wiederhergestellt. Diese Änderung wird jedoch Anpassungen auf höheren Ebenen der Codebasis erfordern. Insbesondere die Klasse C_Replay, die sich in der Datei C_Replay.mqh befindet, wird erheblich geändert. Gleichzeitig werden wir diese Gelegenheit nutzen, um die Codestruktur zu verbessern und den Replay/Simulator-Dienst weniger verschachtelt zu gestalten. Durch die Implementierung kleinerer, schrittweiser Änderungen können wir die Wartung vereinfachen und die Kontrolle über die einzelnen Schritte verbessern. Dies wird vor allem für zukünftige Updates von Vorteil sein, da wir bald noch komplexere Funktionen implementieren müssen, die mehrere miteinander verbundene Komponenten umfassen.

Wir werden sehen, was getan werden muss, um die Dinge besser zu gestalten. Um die Kapselung zu verbessern, öffnen wir die Header-Datei C_ConfigService.mqh und ändern den Code wie im folgenden Fragment gezeigt. Der Rest des Codes bleibt unverändert, aber die Änderungen in diesem Fragment gewährleisten, dass die Kapselung ordnungsgemäß durchgesetzt wird.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Support\C_FileBars.mqh"
05. #include "Support\C_FileTicks.mqh"
06. #include "Support\C_Array.mqh"
07. //+------------------------------------------------------------------+
08. class C_ConfigService : protected C_FileTicks
09. {
10.    protected:
11.         datetime m_dtPrevLoading;
12.         int      m_ReplayCount,
13.                  m_ModelLoading;
14. //+------------------------------------------------------------------+
15. inline void FirstBarNULL(void)
16.          {
17.             MqlRates rate[1];
18.             int c0 = 0;
19.             
20.             for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
21.             rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
22.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
23.             rate[0].tick_volume = 0;
24.             rate[0].real_volume = 0;
25.             rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
26.             CustomRatesUpdate(def_SymbolReplay, rate);
27.             m_ReplayCount = 0;
28.          }
29. //+------------------------------------------------------------------+
30.    private   :
31.       enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
32.       enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
33.       struct st001
34.       {
35.          C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
36.          int      Line;
37.       }m_GlPrivate;
38.       string    m_szPath;
39.       bool      m_AccountHedging;
40.       datetime  m_dtPrevLoading;
41.       int       m_ReplayCount,
42.                 m_ModelLoading;
43. //+------------------------------------------------------------------+
44. inline void FirstBarNULL(void)
45.          {
46.             MqlRates rate[1];
47.             int c0 = 0;
48.             
49.             for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
50.             rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
51.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
52.             rate[0].tick_volume = 0;
53.             rate[0].real_volume = 0;
54.             rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
55.             CustomRatesUpdate(def_SymbolReplay, rate);
56.             m_ReplayCount = 0;
57.          }
58. //+------------------------------------------------------------------+
59. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)

Ein Fragment des Quellcodes der Datei C_ConfigService.mqh

Beachten Sie, dass der Inhalt der Zeilen 11-13 in die Zeilen 40 und 42 verschoben wurde. Dies bedeutet, dass es nun unmöglich ist, auf diese Variablen außerhalb des Körpers der Klasse C_ConfigService zuzugreifen. Außerdem wurde eine weitere Änderung vorgenommen. Diese Änderung hätte ignoriert werden können, aber da einige Dinge außerhalb der Klasse nicht verwendet werden sollen, habe ich beschlossen, die FirstBarNULL-Prozedur privat zu machen. Der Inhalt, der sich zwischen den Zeilen 15 und 28 befand, wurde also in die Zeilen 44 bis 57 verschoben.

Es ist klar, dass sich die Zeilennummern ändern werden, wenn Sie diese Änderungen in der eigentlichen Datei vornehmen, da der entfernte Code nicht mehr Teil des Klassencodes ist. Aus Gründen der Klarheit habe ich jedoch beschlossen, alles in dem Fragment so zu lassen, wie es ist. Ich denke, auf diese Weise wird es klarer und leichter zu verstehen sein, was geändert wurde.

Großartig. Nachdem wir diese Änderungen vorgenommen haben, müssen wir nun den Code in der Datei C_Replay.mqh grundlegend ändern. Aber lassen Sie uns die Dinge weiter voneinander trennen und sich im nächsten Thema damit befassen.


Neustart der Implementierung der Klasse C_Replay

Auch wenn der Titel dieses Abschnitts entmutigend klingen mag, weil er suggeriert, dass wir etwas neu erfinden, das bereits gebaut wurde, ist dies nicht der Fall. Ich möchte betonen, dass wir zwar einen großen Teil der Klasse C_Replay überarbeiten müssen, dass aber das in dieser Artikelserie erworbene Wissen wertvoll bleibt. Wir passen uns an eine neue Struktur und Methodik an, da bestimmte Dinge nicht mehr so umgesetzt werden können wie bisher.

Der vollständige überarbeitete Code für die Klasse C_Replay ist unten aufgeführt.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. //+------------------------------------------------------------------+
006. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
007. #resource "\\" + def_IndicatorControl
008. //+------------------------------------------------------------------+
009. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""))
010. //+------------------------------------------------------------------+
011. #define def_ShortNameIndControl "Market Replay Control"
012. //+------------------------------------------------------------------+
013. class C_Replay : public C_ConfigService
014. {
015.    private   :
016.       long      m_IdReplay;
017.       struct st00
018.       {
019.          ushort Position;
020.          short  Mode;
021.       }m_IndControl;
022. //+------------------------------------------------------------------+
023. inline bool MsgError(string sz0) { Print(sz0); return false; }
024. //+------------------------------------------------------------------+
025. inline void UpdateIndicatorControl(void)
026.          {
027.             uCast_Double info;
028.             int handle;
029.             double Buff[];
030.             
031.             if ((handle = ChartIndicatorGet(m_IdReplay, 0, def_ShortNameIndControl)) == INVALID_HANDLE) return;
032.             info.dValue = 0;
033.             if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
034.                info.dValue = Buff[0];
035.             IndicatorRelease(handle);
036.             if ((short)(info._16b[0]) != SHORT_MIN)
037.                m_IndControl.Mode = (short)info._16b[1];
038.             if (info._16b[0] != m_IndControl.Position)
039.             {
040.                if (((short)(info._16b[0]) != SHORT_MIN) && ((short)(info._16b[1]) == SHORT_MAX))
041.                   m_IndControl.Position = info._16b[0];
042.                info._16b[0] = m_IndControl.Position;
043.                info._16b[1] = (ushort)m_IndControl.Mode;
044.                EventChartCustom(m_IdReplay, evCtrlReplayInit, 0, info.dValue, "");
045.             }
046.          }
047. //+------------------------------------------------------------------+
048.       void SweepAndCloseChart(void)
049.          {
050.             long id;
051.             
052.             if ((id = ChartFirst()) > 0) do
053.             {
054.                if (ChartSymbol(id) == def_SymbolReplay)
055.                   ChartClose(id);
056.             }while ((id = ChartNext(id)) > 0);
057.          }
058. //+------------------------------------------------------------------+
059.    public   :
060. //+------------------------------------------------------------------+
061.       C_Replay()
062.          :C_ConfigService()
063.          {
064.             Print("************** Market Replay Service **************");
065.             srand(GetTickCount());
066.             SymbolSelect(def_SymbolReplay, false);
067.             CustomSymbolDelete(def_SymbolReplay);
068.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
069.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
070.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
071.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
072.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
073.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
074.             SymbolSelect(def_SymbolReplay, true);
075.          }
076. //+------------------------------------------------------------------+
077.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
078.          {
079.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
080.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
081.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
082.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
083.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
084.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
085.             SweepAndCloseChart();
086.             m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
087.             if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl"))
088.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
089.             else
090.                Print("Apply template: ", szNameTemplate, ".tpl");
091. 
092.             return true;
093.          }
094. //+------------------------------------------------------------------+
095.       bool InitBaseControl(const ushort wait = 1000)
096.          {
097.             int handle;
098.             
099.             Print("Waiting for Mouse Indicator...");
100.             Sleep(wait);
101.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
102.             if (def_CheckLoopService)
103.             {
104.                Print("Waiting for Control Indicator...");
105.                if ((handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay)) == INVALID_HANDLE) return false;
106.                ChartIndicatorAdd(m_IdReplay, 0, handle);
107.                IndicatorRelease(handle);
108.                m_IndControl.Position = 0;
109.                m_IndControl.Mode = SHORT_MIN;
110.                UpdateIndicatorControl();
111.             }
112.             
113.             return def_CheckLoopService;
114.          }
115. //+------------------------------------------------------------------+
116.       bool LoopEventOnTime(void)
117.          {         
118.             
119.             while (def_CheckLoopService)
120.             {
121.                UpdateIndicatorControl();
122.                Sleep(250);
123.             }
124.             
125.             return false;
126.          }
127. //+------------------------------------------------------------------+
128.       ~C_Replay()
129.          {
130.             SweepAndCloseChart();
131.             SymbolSelect(def_SymbolReplay, false);
132.             CustomSymbolDelete(def_SymbolReplay);
133.             Print("Finished replay service...");
134.          }
135. //+------------------------------------------------------------------+

Quellcode der Datei C_Replay.mqh

Obwohl dieser Code noch nicht die Replay/Simulation wie zuvor durchführt, da bestimmte Komponenten noch fehlen, besteht sein Zweck darin, den Wiedergabe/Simulationsdienst in die Lage zu versetzen, Elemente zu nutzen, die im vorherigen Artikel nicht behandelt wurden. Zu diesen Elementen gehört die Möglichkeit, frühere Balken zu laden, genau wie früher, sowie die Balken, die für die Wiedergabe und die Simulation erforderlich sind. In diesem Artikel werden wir jedoch noch nicht in der Lage sein, diese Wiederholungs- oder Simulationsleisten vollständig zu nutzen. Stattdessen werden sie geladen und zur Verfügung gestellt, sobald das System in der Lage ist, sie im nutzerdefinierten Anlagenchart ordnungsgemäß darzustellen.

Mehrere Aspekte des obigen Codes bedürfen einer näheren Erläuterung. Viele seiner Komponenten sind selbst für diejenigen, die über solide Erfahrung mit MQL5 verfügen, vielleicht nicht sofort klar. Die hier gegebenen Erklärungen richten sich jedoch an diejenigen, die wirklich verstehen wollen, warum dieser Code auf diese besondere Weise strukturiert ist.

Zu Beginn des Codes, in den Zeilen 5 bis 11, definieren wir bestimmte Parameter und binden die kompilierte Indikator-Datei in die ausführbare Datei des Dienstes ein. Die Gründe dafür wurden bereits in früheren Artikeln dieser Serie über Replay/Simulation ausführlich erörtert. Ich möchte Sie daher nur darauf hinweisen, dass es nicht notwendig ist, die Steuerkennzeichen-Datei manuell zu übertragen.

In Zeile 13 wird dann eine öffentliche Vererbung von der Klasse C_ConfigService eingerichtet. Damit soll sichergestellt werden, dass die Arbeitslast nicht nur in der Klasse C_Replay konzentriert ist, sondern auf C_Replay und C_ConfigService verteilt wird. Dies unterstreicht die Bedeutung der Änderungen, die im vorangegangenen Abschnitt vorgenommen wurden, in dem wir die notwendigen Änderungen für die ordnungsgemäße Kapselung von Daten und Variablen erörtert haben.

Der private Teil der Klasse C_Replay beginnt in Zeile 15 und reicht bis Zeile 58, wo der öffentliche Teil beginnt. Schauen wir uns zunächst an, wie der private Bereich funktioniert. Er enthält eine kleine Anzahl globaler Variablen, die zwischen 16 und 21 deklariert sind. Achten Sie besonders auf Zeile 21, in der eine Variable als Struktur deklariert wird, was bedeutet, dass sie zusätzliche verschachtelte Daten enthält.

In Zeile 23 definieren wir eine kleine Funktion, deren einziger Zweck es ist, eine Fehlermeldung auf dem Terminal auszugeben und false zurückzugeben. Aber warum ist die Antwort hier falsch? Ohne diesen Rückgabewert bräuchten wir jedes Mal eine zusätzliche Codezeile, wenn wir eine Fehlermeldung auf dem Terminal ausgeben. Zur Verdeutlichung sehen Sie sich Zeile 79 an, wo wir eine bestimmte Bedingung überprüfen. Im Falle eines Fehlers werden normalerweise zwei getrennte Zeilen benötigt: eine zum Ausdrucken der Fehlermeldung und eine weitere zur Rückgabe eines Fehlerhinweises. Dies würde zu unnötiger Redundanz führen. Mit der in Zeile 23 deklarierten Funktion können wir jedoch in einem einzigen Schritt die Meldung ausgeben und eine Fehleranzeige zurückgeben. Dies ist in Zeile 80 zu sehen und vereinfacht die Implementierung. Wir kombinieren die Dinge so, dass sich unser Codierungsaufwand verringert.

Der vielleicht wichtigste Abschnitt des Codes befindet sich zwischen den Zeilen 25 und 46. Dieser Code erledigt einige sehr wichtige Aufgaben für uns. Sie verwaltet und korrigiert die Daten des Kontrollanzeigers. Bevor Sie versuchen, diesen Abschnitt zu verstehen, sollten Sie sich vergewissern, dass Sie das Zusammenspiel aller zugehörigen Komponenten vollständig verstanden haben. Im Zweifelsfall lesen Sie bitte die vorangegangenen Artikel, in denen erklärt wird, wie das Kontrollgerät mit externen Komponenten kommuniziert.

In Zeile 31 wird versucht, ein Handle für den Zugriff auf den Kontrollindikator zu erfassen. Wenn dies fehlschlägt, handelt es sich nicht um einen kritischen Fehler. Die Funktion kehrt einfach zurück und überspringt den Rest der Prozedur. Wenn ein Handle erfolgreich ‚ergiffen‘ wurde, setzen wir den Testwert zurück, wie in Zeile 32 gezeigt. Dies ist von entscheidender Bedeutung und muss korrekt durchgeführt werden. In Zeile 33 wird geprüft, ob der Indikatorpuffer lesbar ist. Ist dies der Fall, wird der Wert in Zeile 34 einer Test- und Anpassungsvariablen zugewiesen. Dieser Abschnitt wird in künftigen Artikeln möglicherweise geringfügig überarbeitet, aber die Kernlogik bleibt dieselbe.

Sobald das Handle nicht mehr benötigt wird, wird es in Zeile 35 freigegeben, und wir treten in die Phase der Prüfung und Anpassung der abgerufenen Informationen ein. In Zeile 36 wird geprüft, ob der Kontrollindikator gültige Daten enthält. Wenn ja, speichern wir Informationen darüber, ob sich das System im Pausenmodus oder im aktiven Spielmodus (Wiedergabe/Simulation) befindet. Diese Speicherung wird in Zeile 37 vorgenommen. Dies muss geschehen, bevor Änderungen vorgenommen werden; andernfalls könnten die abgerufenen Daten vorzeitig geändert werden, was die Integrität der Informationen gefährden würde. Damit soll sichergestellt werden, dass der Dienst die neueste Form von Kontrollindikatoren bereitstellt - etwas, das früher durch eine globale Terminalvariable erreicht wurde.

Achten Sie nun auf Zeile 38. Er vergleicht den Inhalt des Anzeigepuffers mit dem globalen Positionierungssystem. Wird eine Unstimmigkeit festgestellt, wird in Zeile 40 eine zweite Prüfung durchgeführt, um festzustellen, ob der Kontrollanzeiger initialisiert wurde und ob sich das System im Spielmodus befindet. Wenn beide Bedingungen erfüllt sind, wird der Pufferwert in Zeile 41 gespeichert. Dies ist wichtig, weil wir die Daten im Pausenmodus nicht automatisch aktualisieren wollen. Wir möchten dem Nutzer die Möglichkeit geben, den Kontrollindikator bei Bedarf manuell einzustellen.

In den Zeilen 42 und 43 werden schließlich die Informationen zusammengestellt, die an den Kontrollindikator weitergegeben werden sollen. Dies wird über ein nutzerdefiniertes Ereignis übermittelt, das in Zeile 44 ausgelöst wird. Sobald der Dienst ausgelöst wird, übernimmt MetaTrader 5 die Ausführung seiner Aufgaben, während der Dienst parallel weiterläuft.

Der in diesem Verfahren enthaltene Code sollte sehr sorgfältig analysiert werden, bis wirklich klar ist, was vor sich geht. Verglichen mit dem Ansatz aus dem vorherigen Artikel ist diese Version komplexer, obwohl sie im Wesentlichen die gleiche Funktion erfüllt. Sobald der Kontrollindikator von MetaTrader 5 auf dem Chart platziert wurde, wird er mit diesem Code initialisiert. Von da an überwacht sie ihren Zustand. Wenn der Nutzer den Zeitrahmen ändert, behält der Dienst den letzten bekannten Zustand des Indikators bei und stellt sicher, dass er bei der Rückkehr zum Chart mit seinen vorherigen Einstellungen neu initialisiert wird.

Schauen wir uns nun einen Code an, der unter dem Gesichtspunkt der Wiederverwendbarkeit erstellt wurde. Sie befindet sich in Zeile 48. Die Prozedur schließt einfach alle MetaTrader 5 Chart-Fenster, die zu replizierende Symbole enthalten. Wie Sie sehen können, ist es nicht kompliziert. Da wir dies jedoch mindestens zweimal tun müssen, habe ich beschlossen, dieses Verfahren zu erstellen, um doppelten Code zu vermeiden.

Von diesem Punkt aus gehen wir also zu den öffentlichen Prozeduren der Klasse C_Replay über. Wie Sie sehen, unterscheidet sich der Code nicht wesentlich von dem, was vorher war, zumindest was den Klassenkonstruktor und den Destruktor betrifft. Daher werde ich keine weiteren Kommentare zu ihnen abgeben, da sie bereits in früheren Artikeln, in denen ich die Funktionsweise der Klasse C_Replay erklärt habe, ausführlich behandelt worden sind. Allerdings gibt es hier drei Funktionen, die eine Erklärung verdienen. Schauen wir sie uns in der Reihenfolge an, in der sie im Code erscheinen.

Die erste Funktion heißt OpenChartReplay, sie beginnt in Zeile 77 und endet in Zeile 93. Sie prüft die Integrität der vom Boot-System gesammelten Informationen. Dies ist notwendig, damit die Wiedergabe oder der Simulator tatsächlich durchgeführt werden kann. In dieser Funktion finden wir jedoch etwas recht Komplexes, das uns zusammen mit der Funktion InitBaseControl, über die wir später noch sprechen werden, die Verwendung der Vorlage ermöglicht.

Die Frage der Verwendung einer Vorlage ist für uns von großer Bedeutung. Es ist wichtig, dass es richtig verwendet und in geeigneter Weise gestartet wird. Aber das ist nicht so einfach, wie viele, mich eingeschlossen, anfangs gedacht haben mögen. In Zeile 87 wird versucht, die Vorlage zum Chart hinzuzufügen, nachdem sie zuvor in Zeile 86 geöffnet wurde. Die zu verwendende Vorlage wird als eines der Funktionsargumente angegeben. In jedem Fall wird die Vorlage auf dem Chart platziert, unabhängig davon, ob es sich um eine nutzerdefinierte Vorlage oder eine Standard-MetaTrader 5-Vorlage handelt. Allerdings gibt es hier ein Detail, das selten erwähnt wird: Die Vorlage wird nicht sofort platziert. Mit der Funktion ChartApplyTemplate wird die Vorlage nicht sofort angewendet. Diese Funktion ist asynchron, d. h. sie kann innerhalb weniger Millisekunden nach ihrem Aufruf ausgeführt werden. Und das ist ein Problem für uns.

Um das Ausmaß des Problems zu verstehen, machen wir eine kurze Pause von der Klasse C_Replay und sehen uns den nachstehenden Code des Dienstes an.

01. //+------------------------------------------------------------------+
02. #property service
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property copyright "Daniel Jose"
05. #property version   "1.58"
06. #property description "Replay-Simulator service for MetaTrade 5 platform."
07. #property description "This is dependent on the Market Replay indicator."
08. #property description "For more details on this version see the article."
09. #property link "https://www.mql5.com/pt/articles/"
10. //+------------------------------------------------------------------+
11. #include <Market Replay\Service Graphics\C_Replay.mqh>
12. //+------------------------------------------------------------------+
13. input string            user00 = "Mini Dolar.txt";   //Replay Configuration File.
14. input ENUM_TIMEFRAMES   user01 = PERIOD_M5;          //Initial Graphic Time.
15. input string            user02 = "Default";          //Template File Name
16. //+------------------------------------------------------------------+
17. C_Replay *pReplay;
18. //+------------------------------------------------------------------+
19. void OnStart()
20. {
21.    pReplay = new C_Replay();
22. 
23.    UsingReplay();   
24.    
25.    delete pReplay;
26. }
27. //+------------------------------------------------------------------+
28. void UsingReplay(void)
29. {
30.    if (!(*pReplay).SetSymbolReplay(user00)) return;
31.    if (!(*pReplay).OpenChartReplay(user01, user02)) return;
32.    if (!(*pReplay).InitBaseControl()) return;
33.    Print("Permission granted. Replay service can now be used...");
34.    while ((*pReplay).LoopEventOnTime());
35. }
36. //+------------------------------------------------------------------+

Quellcode des Replay-/Simulationsdienstes

Beachten Sie, dass wir die Aufgaben in einer bestimmten Reihenfolge ausführen, wie zwischen den Zeilen 30 und 34 zu sehen ist. Nach der Initialisierung über den Konstruktor in Zeile 21 fahren wir mit Zeile 30 fort, um zu überprüfen, ob alles mit dem Ladevorgang in Ordnung ist. In Zeile 31 versuchen wir dann, das Chart zu öffnen, und erst danach, in Zeile 32, laden wir die notwendigen Elemente zur Steuerung des Dienstes. Wenn alles glatt läuft, geben wir in Zeile 33 eine Meldung auf dem Terminal aus und beginnen in Zeile 34 mit der Ausführungsschleife.

Auf den ersten Blick sieht es so aus, als ob zwischen dem Öffnen des Charts in Zeile 31 und dem Hinzufügen der Steuerelemente in Zeile 32 nichts Ungewöhnliches passiert. Aufgrund der Verwendung der in der Klasse C_Replay geladenen Vorlage können jedoch einige unvorhergesehene Probleme auftreten. Um das potenzielle Problem besser zu verstehen, sollten wir uns die Klasse noch einmal ansehen, um die tatsächliche Komplikation der Verwendung einer Vorlage zu untersuchen.

Nachdem MetaTrader 5 angewiesen wurde, eine Vorlage anzuwenden, wie in Zeile 87 der Klasse C_Replay zu sehen, kann der Code viel schneller ausgeführt werden, als er eigentlich sollte. In Zeile 99 teilen wir dem Nutzer daher mit, dass der Dienst auf den Mauszeiger wartet. Wenn der Mauszeiger in der Vorlage vorhanden ist, wird er automatisch geladen; andernfalls muss der Nutzer ihn manuell hinzufügen.

Dies stellt ein Problem dar, da die für die Anwendung der Vorlage zuständige Funktion asynchron läuft. Um die möglichen Probleme zu entschärfen, verwenden wir Zeile 100, in der wir den Dienst für eine kurze Zeit unterbrechen, damit sich das Chart stabilisieren und die Funktion der Vorlagenanwendung ordnungsgemäß ausgeführt werden kann. Erst nach dieser Wartezeit überprüfen wir in Zeile 101, ob der Mauszeiger vorhanden ist. Diese Schleife wird so lange fortgesetzt, bis der Mausindikator im Chart erscheint oder das Chart durch den Nutzer geschlossen wird.

Sobald der Mauszeiger erkannt oder das Chart geschlossen ist, wird der Code fortgesetzt. Wenn alles wie erwartet funktioniert, versuchen wir, den Kontrollindikator in Zeile 105 in das Chart einzufügen. Das funktioniert zwar wunderbar, aber es gibt ein wichtiges Detail: Der Kontrollindikator wird nicht akzeptiert, wenn er bereits Teil der Vorlage ist. Dies ist eine der Änderungen, die ich später zeigen werde und die verhindert, dass der Kontrollindikator in der Vorlage angezeigt wird. Eine geringfügige Änderung wird auch für den Mauszeiger erforderlich sein, aber das wird später kommen. Ohne die Zeile 100 würde das Chart kurz nach dem Öffnen geschlossen werden, was wir ja gerade verhindern wollen.


Schlussfolgerung

Auch wenn man das Gefühl hat, dass dies nicht das Ende ist, ist es notwendig, im Detail zu erklären, warum sich das Chart unmittelbar nach der Anwendung der Vorlage schließt. Es ist ziemlich kompliziert, und es müssen noch andere Dinge gezeigt werden, damit Sie wirklich verstehen, wie dies möglich ist und warum die Zeile 100 dies verhindert. Daher werde ich eine ausführlichere Diskussion über die Vorlage und die notwendigen Änderungen in den Indikatormodulen für den nächsten Artikel reservieren. So können Sie besser nachvollziehen, wie diese Änderungen sicherstellen, dass der Wiedergabe-/Simulationsdienst wie erwartet funktioniert.

Wie Sie sehen, unterscheidet sich dieses System von dem im vorigen Artikel beschriebenen Prüfdienst. Bevor ich Sie verlasse, möchte ich Ihnen ein Video zeigen, das das Ergebnis der Anwendung dieses Systems zeigt. Da es sich noch nicht in dem im Video gezeigten Zustand befindet, wird es in diesem Artikel keinen Anhang geben.

Demo-Video

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

Beigefügte Dateien |
Anexo.zip (420.65 KB)
Von der Grundstufe bis zur Mittelstufe: Variablen (I) Von der Grundstufe bis zur Mittelstufe: Variablen (I)
Vielen Programmieranfängern fällt es schwer zu verstehen, warum ihr Code nicht so funktioniert, wie sie es erwarten. Es gibt viele Dinge, die einen Code wirklich funktional machen. Es ist nicht nur ein Haufen verschiedener Funktionen und Operationen, die den Code zum Laufen bringen. Heute lade ich Sie dazu ein, zu lernen, wie man richtigen Code erstellt, anstatt Fragmente zu kopieren und einzufügen. Die hier vorgestellten Materialien sind ausschließlich für didaktische Zwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Nutzerdefinierter Indikator: Darstellen von partiellen Eintritts-, Austritts- und Stornogeschäften für Netting-Konten Nutzerdefinierter Indikator: Darstellen von partiellen Eintritts-, Austritts- und Stornogeschäften für Netting-Konten
In diesem Artikel werden wir uns eine nicht standardisierte Methode zur Erstellung eines Indikators in MQL5 ansehen. Anstatt sich auf einen Trend oder ein Chartmuster zu konzentrieren, wird unser Ziel sein, unsere eigenen Positionen zu verwalten, einschließlich partieller Ein- und Ausstiege. Wir werden ausgiebig Gebrauch von dynamischen Matrizen und einigen Handelsfunktionen machen, die sich auf die Handelshistorie und offene Positionen beziehen, um auf dem Chart anzuzeigen, wo diese Geschäfte getätigt wurden.
Chaostheorie im Handel (Teil 2): Tiefer tauchen Chaostheorie im Handel (Teil 2): Tiefer tauchen
Wir setzen unsere Untersuchung der Chaostheorie auf den Finanzmärkten fort. Dieses Mal werde ich seine Anwendbarkeit auf die Analyse von Währungen und anderen Vermögenswerten untersuchen.
Entwicklung eines Replay System (Teil 57): Verstehen eines Testdienstes Entwicklung eines Replay System (Teil 57): Verstehen eines Testdienstes
Ein Hinweis: Obwohl der Code für einen Dienst in diesem Artikel nicht enthalten ist und erst im nächsten Artikel zur Verfügung gestellt wird, werde ich ihn erläutern, da wir denselben Code als Sprungbrett für unsere eigentliche Entwicklung verwenden werden. Seien Sie also aufmerksam und geduldig. Warten Sie auf den nächsten Artikel, denn jeden Tag wird es interessanter.