English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay-Systems (Teil 65): Abspielen des Dienstes (VI)

Entwicklung eines Replay-Systems (Teil 65): Abspielen des Dienstes (VI)

MetaTrader 5Beispiele | 13 Mai 2025, 07:37
64 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay-Systems (Teil 64): Abspielen des Dienstes (V)“, wir haben zwei Fehler in der Wiedergabe-/Simulationsanwendung behoben. Es wurde jedoch nicht alles gelöst. Zumindest nicht in dem Maße, dass wir mit neuen Entwicklungen in diesem Artikel vorankommen können. Es gibt noch einige, kleinere Probleme, die das System weiterhin beeinträchtigen. Diese Probleme gab es nicht, als wir noch globale Terminalvariablen verwendet haben. Da wir aber von diesem Ansatz abgekommen sind und neue Techniken und Methoden eingeführt haben, um die Wiedergabe-/Simulationsanwendung funktionsfähig zu machen, müssen wir nun eine neue Implementierung erstellen und anpassen. Wie Sie, lieber Leser, vielleicht bemerkt haben, fangen wir nicht bei Null an. Tatsächlich passen wir den bestehenden Code an und verfeinern ihn, um sicherzustellen, dass die Arbeit, die zuvor mit globalen Terminalvariablen geleistet wurde, nicht völlig verloren geht.

Damit haben wir jetzt fast den gleichen Funktionsumfang wie vorher. Um diesen Punkt zu erreichen, müssen wir jedoch noch einige Details klären. Ich werde versuchen, dies in diesem Artikel zu tun, da die übrigen Fragen relativ einfach sind. Dies ist etwas anderes als der Fehler beim Speicherauszug, den wir bereits angesprochen haben. Dieses spezielle Problem war recht komplex und erforderte eine detaillierte Erklärung, warum der Fehler auftrat, selbst wenn der Code völlig korrekt zu sein schien. Es hätte nicht ausgereicht, nur die Zeile zu zeigen, die hinzugefügt werden muss, und viele Leser wären verwirrt gewesen. Alternativ hätte ich den Code auch einfach ohne jede Erklärung korrigieren können, was ebenso enttäuschend gewesen wäre. Sie wären nie mit solchen Problemen konfrontiert worden und hätten vielleicht ein falsches Gefühl der Sicherheit entwickelt. Und dann waren Sie frustriert, wenn ähnliche Probleme auftauchten, ohne dass Sie eine klare Orientierung hatten. Schlimmer noch, Sie könnten anfangen, an Ihren eigenen Fähigkeiten als Fachmann zu zweifeln. Das möchte ich vermeiden. Auch erfahrene Profis machen Fehler. Auch wenn man sie nicht immer sieht, passieren sie doch. Was sie auszeichnet, ist ihre Fähigkeit, diese Probleme schnell zu erkennen und zu lösen. Deshalb wünsche ich jedem angehenden Entwickler, dass er sich zu einem echten Profi entwickelt. Und zwar nicht irgendeinen Fachmann, sondern einen herausragenden auf seinem Gebiet. Lassen Sie uns nun die erste der verbleibenden Fragen in Angriff nehmen.


Hinzufügen der schnellen Vorlauffunktion (Basismodell)

Diese Funktion existierte bereits in der Vergangenheit und wurde in der Zeit eingeführt, als wir uns auf globale Terminalvariablen verlassen haben. Da wir diese Variablen nicht mehr verwenden, müssen wir den Code anpassen, um die schnelle Vorlauffunktion wieder einzuführen. Ich werde die gleiche Logik des schnellen Vorlaufs beibehalten, die wir zuvor verwendet haben. So können Sie leichter nachvollziehen, wie die alte Implementierung an das neue System angepasst wird.

Zunächst müssen wir eine kleine Änderung an dem im letzten Artikel vorgestellten Code vornehmen. Durch diese Änderung wird sichergestellt, dass der Kontrollanzeiger korrekt funktioniert. Sie können die Änderungen unten sehen:

35. //+------------------------------------------------------------------+
36. inline void UpdateIndicatorControl(void)
37.          {
38.             double Buff[];
39.                                  
40.             if (m_IndControl.Handle == INVALID_HANDLE) return;
41.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
42.             {
43.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
44.                   m_IndControl.Memory.dValue = Buff[0];
45.                if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
46.                   if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
47.                      m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
48.             }else if (m_IndControl.Mode == C_Controls::ePause)
49.             {
50.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
51.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
52.                m_IndControl.Memory._8b[7] = 'D';
53.                m_IndControl.Memory._8b[6] = 'M';
54.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
55.             }
56.          }
57. //+------------------------------------------------------------------+

Codeabschnitt aus der Datei C_Replay.mqh

Die Änderung, oder genauer gesagt der Zusatz, wurde speziell in Zeile 48 vorgenommen. Aber warum war diese Änderung notwendig? Der Grund dafür liegt im Code innerhalb der Funktion LoopEventOnTime. Moment, das klingt jetzt verwirrend. Warum eine Änderung in der UpdateIndicatorControl-Prozedur vornehmen, wenn das Problem von den Vorgängen in LoopEventOnTime herrührt? Das macht keinen Sinn. Es wäre in der Tat sinnlos, wenn die Funktion LoopEventOnTime nicht aus dem Kontrollindikator lesen und in ihn schreiben würde, indem sie Nachrichten sendet und empfängt. Ohne die bedingte Prüfung in Zeile 48 tritt etwas Ungewöhnliches auf, wenn Sie versuchen, schnell vorzuspulen und dann sofort auf Play drücken. Und das, bevor wir die eigentliche Logik für den schnellen Vorlauf implementieren.

Wenn Sie vorspulen und dann die Wiedergabetaste drücken, können Sie keinen Pause-Befehl an den Dienst senden. Wie? Das klingt absurd. Ein Druck auf die Pausentaste würde doch sicher die entsprechende Aktualisierung an den Dienst senden, oder? Es sendet die Aktualisierung mit der Anweisung, das System anzuhalten. Die Wirkung tritt jedoch nicht sofort ein. Was denken Sie, warum? Der Grund liegt in Zeile 41. Das Problem besteht darin, dass der Puffer des Kontrollanzeigers und der referenzierte Speicherbereich nicht synchron sind. Wenn Sie den Dienst starten, auf Play drücken und dann durch die Zeit vorspulen, scheint nichts schief zu gehen. Wenn Sie jedoch den Dienst anhalten und versuchen, schnell vorzuspulen, während die Prüfung in Zeile 48 fehlt, bewegt sich der Sperrbalken der Kontrollanzeige mit dem schnellen Vorlauf. Dies würde den Nutzer daran hindern, die Position manuell einzustellen.

Wenn Sie nun den Wiedergabe-/Simulationsanwendungsdienst starten, sich um eine einzige Position vorwärts bewegen und dann auf „Play“ klicken, können Sie den Dienst nicht mit der Pausentaste anhalten. Er hält erst an, wenn die Bedingung in Zeile 41 erfüllt ist und der Puffer anzeigt, dass der Pausenmodus aktiv ist. Dies könnte einige Zeit in Anspruch nehmen. Vielleicht erscheint die Erklärung etwas verworren, aber das liegt daran, dass wir drei verschiedene Szenarien in Betracht ziehen müssen. Jede davon hat ihre eigenen Herausforderungen, da LoopEventOnTime während der normalen Ausführung des Wiedergabe-/Simulationsanwendungsprozesses ständig Nachrichten aus dem Kontrollindikator liest und an diesen sendet.

Durch einfaches Hinzufügen eines einzigen bedingten Tests in Zeile 48, wie im Ausschnitt gezeigt, werden alle diese Probleme gelöst. Wir beseitigen die Probleme, die mit der Funktion LoopEventOnTime verbunden sind. So können wir uns voll und ganz darauf konzentrieren, dass die schnelle Vorlauffunktion wie vorgesehen funktioniert.

Die Implementierung des Schnellvorlaufs ist eigentlich keine komplexe Aufgabe. Wir können dies sogar erreichen, indem wir einfach die folgenden Codeabschnitt in die Datei C_Replay.mqh einfügen.

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. //+------------------------------------------------------------------+
014. class C_Replay : public C_ConfigService
015. {
016.    private   :

              ...

035. //+------------------------------------------------------------------+
036. inline void UpdateIndicatorControl(void)
037.          {
038.             double Buff[];
039.                                  
040.             if (m_IndControl.Handle == INVALID_HANDLE) return;
041.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
042.             {
043.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
044.                   m_IndControl.Memory.dValue = Buff[0];
045.                if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
046.                   if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
047.                      m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
048.             }else if (m_IndControl.Mode == C_Controls::ePause)
049.             {
050.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
051.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
052.                m_IndControl.Memory._8b[7] = 'D';
053.                m_IndControl.Memory._8b[6] = 'M';
054.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
055.             }
056.          }
057. //+------------------------------------------------------------------+

              ...

068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074. 
075.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
076.             {
077.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
078.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
079.                {                  
080.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
081.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
082.                   {
083.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
084.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
085.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
086.                   {
087.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
088.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
089.                   }
090.                }
091.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
092.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
093.             }
094.             m_Infos.CountReplay++;
095.          }
096. //+------------------------------------------------------------------+

              ...

123. //+------------------------------------------------------------------+
124.       void AdjustPositionToReplay(void)
125.          {
126.             int nPos;
127.             
128.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxPosSlider * 1.0) / m_MemoryData.nTicks)) return;
129.             nPos = (int)(m_MemoryData.nTicks * ((m_IndControl.Position * 1.0) / (def_MaxPosSlider + 1)));
130.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
131.                CreateBarInReplay(false);
132.          }
133. //+------------------------------------------------------------------+
134.    public   :
135. //+------------------------------------------------------------------+

              ...

200. //+------------------------------------------------------------------+
201.       bool LoopEventOnTime(void)
202.          {         
203.             int iPos;
204. 
205.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
206.             {
207.                UpdateIndicatorControl();
208.                Sleep(200);
209.             }
210.             m_MemoryData = GetInfoTicks();
211.             AdjustPositionToReplay();
212.             iPos = 0;
213.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
214.             {
215.                if (m_IndControl.Mode == C_Controls::ePause) return true;
216.                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);
217.                CreateBarInReplay(true);
218.                while ((iPos > 200) && (def_CheckLoopService))
219.                {
220.                   Sleep(195);
221.                   iPos -= 200;
222.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxPosSlider) / m_MemoryData.nTicks);
223.                   UpdateIndicatorControl();
224.                }
225.             }
226. 
227.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
228.          }
229. //+------------------------------------------------------------------+
230. };
231. //+------------------------------------------------------------------+
232. #undef macroRemoveSec
233. #undef def_SymbolReplay
234. #undef def_CheckLoopService
235. //+------------------------------------------------------------------+

Codeabschnitt aus der Datei C_Replay.mqh

Wie in den obigen Codeabschnittn aus der Datei C_Replay.mqh zu sehen ist, können wir deutlich erkennen, was für die Implementierung der grundlegenden, schnellen Vorlauffunktion erforderlich ist. Ich sage „grundlegend“, weil es noch ein paar kleinere Probleme gibt, die ich erklären muss. Es ist jedoch wichtig, dass Sie diesen einfacheren Ansatz verstehen, da er die Grundlage für die umfassendere Implementierung bildet, die wir in Kürze aufbauen werden. Beachten Sie den Hinweis weiter oben auf Zeile 48. Sehen Sie sich nun die Zeile 207 an. Dies ist die Zeile, die Probleme mit dem Kontrollindikator verursacht, wenn die Bedingung in Zeile 48 nicht gegeben ist. Sie können die Probleme beobachten, indem Sie sowohl die Prüfung in Zeile 48 als auch in Zeile 211 deaktivieren. Da aber der Kontrollindikator in unserer aktuellen Version bereits korrekt funktioniert, lassen wir diese unverändert. Konzentrieren wir uns darauf, zu verstehen, wie der schnelle Vorlauf in diesem Grundmodell funktioniert.

Wenn der Dienstcode die Funktion LoopEventOnTime aufruft, befinden wir uns im Pausenmodus. Zu diesem Zeitpunkt läuft die Schleife, die in Zeile 205 beginnt und in Zeile 209 endet, kontinuierlich ab, sodass der Nutzer die Position der Kontrollanzeige einstellen kann. Wir machen Folgendes. Sobald der Nutzer die Wiedergabetaste in der Wiedergabe-/Simulationsschnittstelle drückt, erfassen wir die Asset-Daten in Zeile 210, um einen schnelleren Zugriff zu ermöglichen. In Zeile 211 rufen wir dann die in Zeile 124 definierte Prozedur auf. Dies ist die Prozedur, die für den Schnellvorlauf von der aktuellen Position zu der vom Nutzer angegebenen Position verantwortlich ist.

In Zeile 128 wird geprüft, ob die angeforderte Position mit der aktuellen Position übereinstimmt. Ist dies der Fall, endet die Prozedur, und die Schleife zwischen den Zeilen 213 und 225 beginnt mit der Ausführung. Liegt die gewünschte Position vor uns, berechnen wir den Zielversatz in Zeile 129. Bis hierhin ist nichts besonders magisch, aber der eigentliche Trick passiert in Zeile 130, wo wir eine Schleife eingeben, die Zeile 131 wiederholt auslöst. Diese Zeile ruft die Prozedur auf, die für die Erzeugung der Balken zuständig ist. Dieser Vorgang läuft so schnell wie möglich ab, bis der Positionszähler mit dem berechneten Ziel übereinstimmt. Sie werden jedoch feststellen, dass die in Zeile 131 aufgerufene Prozedur den Code zwischen den Zeilen 69 und 95 ausführen wird. Beachten Sie, dass wir diesem Verfahren zur Erzeugung von Balken einen falschen Parameter übergeben. Aus diesem Grund verhindert die Prüfung in Zeile 91, dass die Funktion CustomTicksAdd aufgerufen wird. Dies bedeutet, dass dem Symbol keine Ticks hinzugefügt werden. In Zeile 92 werden die Balken jedoch immer noch einzeln im Chart dargestellt, während sie aufgebaut werden.

Insgesamt funktioniert das Verfahren sehr gut, mit Ausnahme von zwei spezifischen Aspekten, die zu spürbaren Verzögerungen bei der Ausführung des Schnellvorlaufs führen. Wenn Sie als Nutzer einen ausreichend großen Sprung nach vorne anfordern, sehen Sie tatsächlich, wie die Balken im Chart gezeichnet werden. Die Hauptursachen für diese Verzögerung sind die Zeilen 75 und 92, zusätzlich zum Prozeduraufruf selbst. Der Aufwand für letztere ist jedoch relativ gering und kann vernachlässigt werden. Vor allem die Zeile 75 trägt erheblich zu den Verzögerungen bei.

Dies ist also die grundlegende Art und Weise, wie der Schnellvorlauf realisiert wird. Aber es gibt eine wesentlich schnellere Alternative. Wenn Sie sehen wollen, wie die Balken erzeugt werden, ist dieser einfachere Ansatz völlig in Ordnung. Wenn Sie die Datei C_Replay.mqh ändern und den oben beschriebenen Code einfügen, funktioniert der grundlegende Schnellvorlauf bereits. Es bleibt Ihnen überlassen, ob Sie diese Methode anwenden oder sich für die fortschrittlichere und wesentlich schnellere Variante entscheiden, die wir im Folgenden vorstellen. Um die Informationen zu trennen, gehen wir zu einem neuen Abschnitt über.


Hinzufügen der schnellen Vorlauffunktion (dynamisches Modell)

Wenn Sie sich den Code genauer ansehen, werden Sie feststellen, dass die Klasse C_FileTicks bereits beim Laden von Ticks Ein-Minuten-Balken erzeugt. Warum also Zeit damit verschwenden, etwas nachzubauen, das bereits geschaffen wurde? Stattdessen werden wir diese vorgefertigten Balken nutzen, um uns so nah wie möglich an den Zielpunkt heranzutasten. Bei Bedarf können wir dann zu der exakt berechneten Position vorspulen. Dadurch fühlt sich der Schnellvorlauf fast wie ein Augenblick an.

Natürlich ist nicht alles sofort nach dem Auspacken perfekt. Um das gewünschte Maß an Effizienz zu erreichen, müssen wir eine Art Index oder Verweis für schnelle Suchvorgänge einführen. Glücklicherweise können wir einen Teil der Datenstruktur wiederverwenden, der im Kontext des Wiedergabe-/Simulationsanwendung nicht besonders nützlich ist: das Feld Spread innerhalb der Struktur MqlRates. Um die Änderungen zu verstehen, werfen Sie einen Blick auf den folgenden Codeabschnitt:

126. //+------------------------------------------------------------------+
127.       bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume)
128.          {
129.             C_FileBars     *pFileBars;
130.             C_Simulation   *pSimulator = NULL;
131.             int            iMem = m_Ticks.nTicks,
132.                            iRet = -1;
133.             MqlRates       rate[1];
134.             MqlTick        local[];
135.             bool           bInit = false;
136.             
137.             pFileBars = new C_FileBars(szFileNameCSV);
138.             ArrayResize(local, def_MaxSizeArray);
139.             Print("Converting bars to ticks. Please wait...");
140.             while ((*pFileBars).ReadBar(rate) && (!_StopFlag))
141.             {
142.                if (!bInit)
143.                {
144.                   m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX);
145.                   pSimulator = new C_Simulation(SetSymbolInfos());
146.                   bInit = true;
147.                }
148.                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary);
149.                m_Ticks.Rate[++m_Ticks.nRate] = rate[0];
150.                if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local, MaxTickVolume);
151.                if (iRet < 0) break;
152.                rate[0].spread = m_Ticks.nTicks;
153.                for (int c0 = 0; c0 <= iRet; c0++)
154.                {
155.                   ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
156.                   m_Ticks.Info[m_Ticks.nTicks++] = local[c0];
157.                }
158.                m_Ticks.Rate[++m_Ticks.nRate] = rate[0];
159.             }
160.             ArrayFree(local);
161.             delete pFileBars;
162.             delete pSimulator;
163.             m_Ticks.bTickReal = false;
164.             
165.             return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0));
166.          }
167. //+------------------------------------------------------------------+
168.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
169.          {
170.             int      MemNRates,
171.                      MemNTicks,
172.                      nDigits,
173.                      nShift;
174.             datetime dtRet = TimeCurrent();
175.             MqlRates RatesLocal[],
176.                      rate;
177.             MqlTick  TicksLocal[];
178.             bool     bNew;
179.             
180.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
181.             nShift = MemNTicks = m_Ticks.nTicks;
182.             if (!Open(szFileNameCSV)) return 0;
183.             if (!ReadAllsTicks()) return 0;         
184.             rate.time = 0;
185.             nDigits = SetSymbolInfos(); 
186.             m_Ticks.bTickReal = true;
187.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
188.             {
189.                if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
190.                if (!BuildBar1Min(c0, rate, bNew)) continue;
191.                if (bNew)
192.                {
193.                   if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
194.                   {
195.                      nShift = MemShift;
196.                      ArrayResize(TicksLocal, def_MaxSizeArray);
197.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
198.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
199.                         nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
200.                      delete pSimulator;
201.                      ArrayFree(TicksLocal);
202.                      if (c1 < 0) return 0;
203.                   }
204.                   rate.spread = MemShift;
205.                   MemShift = nShift;
206.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
207.                };
208.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
209.             }
210.             if (!ToReplay)
211.             {
212.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
213.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
214.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
215.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
216.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
217.                m_Ticks.nTicks = MemNTicks;
218.                ArrayFree(RatesLocal);
219.             }else   m_Ticks.nTicks = nShift;
220.                            
221.             return dtRet;
222.          };
223. //+------------------------------------------------------------------+

Codeabschnitt aus der Datei C_FileTicks.mqh

Dieser Ausschnitt zeigt genau, welche Zeilen in der Datei C_FileTicks.mqh geändert werden müssen. Beachten Sie, dass die Zeile 149 gestrichen oder besser gesagt, an eine neue Stelle verschoben werden sollte: Zeile 158. Aber warum diese Änderung? Geduld, lieber Leser. Alles wird zu gegebener Zeit erklärt werden. Beachten Sie nun, dass eine neue Zeile hinzugefügt wurde: Zeile 152. Achten Sie genau darauf: Die Funktion hier wandelt die Balken durch Simulation in Ticks um. In Zeile 152 wird der Indexwert erfasst, mit dem ein neuer Balken beginnen soll. Dieser Wert wird dann im Spread-Feld des Balkens gespeichert.

Gehen wir nun zur nächsten Funktion über, bei der wir etwas ganz Ähnliches tun werden. Sie werden feststellen, dass nur eine neue Zeile - Zeile 204 - innerhalb der Funktion, die für das Lesen von Ticks zuständig ist, hinzugefügt wurde. Aber denken Sie an diesen wichtigen Punkt: Auch wenn wir Ticks aus einer Datei lesen, gibt es Fälle, in denen diese Ticks verworfen und durch simulierte Ticks ersetzt werden müssen. Dies wurde in früheren Artikeln erörtert, in denen ich die Gründe für einen solchen Ansatz erläuterte. Aus diesem Grund ist der Wert, der uns wirklich interessiert, der Speicherindex MemShift, der uns sagt, wo der neue Balken beginnt. Genau wie in der vorherigen Funktion speichern wir diesen Wert nun im Feld spread der Struktur MqlRates.

Warum also tun wir das? Was ist der praktische Zweck? Lassen Sie uns das jetzt klären. Bei der Simulation der Ticks in die Balken, die in der vorherigen Funktion erfolgt, wissen wir genau, wann und wo jeder Balken beginnt. Das liegt daran, dass der Index direkt auf die Startposition des Balkens zeigt, bevor er überhaupt simuliert wird. Das Gleiche gilt für die Funktion, die die Ticks aus einer Datei lädt. In Zeile 190 zum Beispiel werden die Ticks in einen Balken umgewandelt, genau wie in der Klasse C_Replay. So wissen wir jedes Mal, wenn hier ein neuer Balken entsteht, genau, wo er beginnt. Das bedeutet, dass wir die Klasse C_Replay nicht mehr benötigen, um diesen Startpunkt manuell zu bestimmen. Und da das Spread-Feld in unserem Kontext der Wiedergabe-/Simulationsanwendung keinen praktischen Nutzen hat, verwenden wir es, um etwas Wertvolles zu speichern: den exakten Startindex jedes Balkens.

Wenn Sie sich an den vorherigen Abschnitt erinnern, enthält die Prozedur, die den Schnellvorlauf durchführt, eine Berechnung in Zeile 129. Diese Berechnung bestimmt den genauen Index, zu dem wir springen müssen, um die Simulation effizient vorzuspulen. Erkennen Sie langsam die Bedeutung dieses Wertes, der beim Laden von Ticks erzeugt wird? Das ist von entscheidender Bedeutung, denn so können wir den Prozess des schnellen Vorlaufs erheblich beschleunigen. Es ist also nicht nötig, jeden Balken einzeln zu rekonstruieren. Wir können direkt zum richtigen Punkt springen und dann MetaTrader 5 bitten, die Balken zwischen der vorherigen und der aktuellen Position zu rendern und zu aktualisieren. Mit anderen Worten: Wir haben jetzt eine neue Art, die Dinge zu handhaben.

Um diese Daten effektiv nutzen zu können, müssen wir einige Aspekte der Klasse C_Replay ändern. Glücklicherweise sind diese Änderungen im Vergleich zu dem, was wir im vorherigen Abschnitt behandelt haben, minimal. Als Nächstes müssen Sie die im folgenden Ausschnitt gezeigten Änderungen einfügen, indem Sie die Datei C_Replay.mqh entsprechend aktualisieren.

013. #define def_MaxSlider             (def_MaxPosSlider + 1)

              ...

124. //+------------------------------------------------------------------+
125.       void AdjustPositionToReplay(void)
126.          {
127.             int nPos, nCount;
128.             
129.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
130.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
131.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
132.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
133.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
134.                CreateBarInReplay(false);
135.          }
136. //+------------------------------------------------------------------+

Codeabschnitt aus der Datei C_Replay.mqh

Wie Sie sehen können, mussten wir der Header-Datei C_Replay.mqh eine neue Zeile hinzufügen. Dies ist die Zeile 13, in der wir eine kleine Korrektur der gesamten Offset-Berechnung vornehmen. Und noch einmal: Alles hier hat einen Grund und einen Zweck. Wenn Sie diese kleine Anpassung nicht vornehmen, werden Sie entweder auf ein anderes Problem stoßen oder die Dinge anders angehen müssen. Um nicht einen großen Teil der Datenmodellierung neu machen zu müssen, ziehe ich es vor, die Anpassung einfach hier vorzunehmen. Warum ist nun diese Anpassung in Zeile 13 für uns wichtig? Der Grund dafür ist, dass es sonst zu einem Versatz kommt, bei dem die Endposition des Schiebereglers in der Kontrollanzeige die Simulation oder Wiedergabe vorzeitig beenden würde. Indem wir hier einfach eine Position hinzufügen, stellen wir sicher, dass noch ein paar zusätzliche Ticks auf den Chart angewendet werden können. Dies ist sogar von Vorteil, da es ein reibungsloseres Ende des Wiederholungs- oder Simulationsprozesses garantiert.

Aber der wirklich wichtige Teil findet sich in den Zeilen 131 und 132. Durch das Hinzufügen von nur diesen beiden Zeilen erreichen wir einen wesentlich schnelleren Schnellvorlauf als zuvor. Dennoch kann es vorkommen, dass einige Ticks unbearbeitet bleiben, die dann wie bisher behandelt werden müssen. Für diese Ticks wird die in Zeile 133 beginnende Schleife verwendet. Da diese verbleibenden Ticks jedoch in der Regel minimal sind, geht der Prozess recht schnell vonstatten.

Was tun wir hier also eigentlich? In Zeile 131 suchen wir nach dem Index des Balkens, dessen Wert unmittelbar unter unserer Zielposition liegt. Dies geschieht vollständig innerhalb der for-Schleife. Obwohl diese Konstruktion den meisten von Ihnen ungewöhnlich erscheinen mag, funktioniert sie perfekt. Sein ungewöhnliches Aussehen ist darauf zurückzuführen, dass ich die Zuweisung des Werts von CountReplay direkt in der Schleifendeklaration platziere. Aber Sie können diese Aufgabe natürlich auch außerhalb der Schleife erledigen, wenn Sie das möchten.

In Zeile 132 müssen wir dann den Wert von nCount überprüfen. Das liegt daran, dass ich keinen Fehler im Aufruf von CustomRatesUpdate der MQL5-Bibliothek riskieren möchte, der die Anzahl der zu verarbeitenden Datenpunkte oder Balken möglicherweise nicht korrekt interpretiert. Der Rest der Funktion wurde bereits im vorherigen Abschnitt erläutert. Interessant an diesen letzten Änderungen ist, dass der endgültige Code in der Datei C_Replay.mqh erneut aktualisiert werden musste. Da diese Änderungen unkompliziert sind und keiner weiteren Erklärung bedürfen, zeige ich Ihnen einfach die endgültige Version des Codes (zumindest in der jetzigen Fassung). Den vollständigen Code finden Sie unten.

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 UpdateIndicatorControl(void)
038.          {
039.             double Buff[];
040.                                  
041.             if (m_IndControl.Handle == INVALID_HANDLE) return;
042.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
043.             {
044.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
045.                   m_IndControl.Memory.dValue = Buff[0];
046.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
047.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
048.             }else
049.             {
050.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
051.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
052.                m_IndControl.Memory._8b[7] = 'D';
053.                m_IndControl.Memory._8b[6] = 'M';
054.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
055.             }
056.          }
057. //+------------------------------------------------------------------+
058.       void SweepAndCloseChart(void)
059.          {
060.             long id;
061.             
062.             if ((id = ChartFirst()) > 0) do
063.             {
064.                if (ChartSymbol(id) == def_SymbolReplay)
065.                   ChartClose(id);
066.             }while ((id = ChartNext(id)) > 0);
067.          }
068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074. 
075.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
076.             {
077.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
078.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
079.                {                  
080.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
081.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
082.                   {
083.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
084.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
085.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
086.                   {
087.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
088.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
089.                   }
090.                }
091.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
092.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
093.             }
094.             m_Infos.CountReplay++;
095.          }
096. //+------------------------------------------------------------------+
097.       void AdjustViewDetails(void)
098.          {
099.             MqlRates rate[1];
100. 
101.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
102.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
103.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
104.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
105.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
106.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
107.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
108.             if (rate[0].close > 0)
109.             {
110.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
111.                   m_Infos.tick[0].last = rate[0].close;
112.                else
113.                {
114.                   m_Infos.tick[0].bid = rate[0].close;
115.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
116.                }               
117.                m_Infos.tick[0].time = rate[0].time;
118.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
119.             }else
120.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
121.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
122.          }
123. //+------------------------------------------------------------------+
124.       void AdjustPositionToReplay(void)
125.          {
126.             int nPos, nCount;
127.             
128.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
129.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
130.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
131.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
132.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
133.                CreateBarInReplay(false);
134.          }
135. //+------------------------------------------------------------------+
136.    public   :
137. //+------------------------------------------------------------------+
138.       C_Replay()
139.          :C_ConfigService()
140.          {
141.             Print("************** Market Replay Service **************");
142.             srand(GetTickCount());
143.             SymbolSelect(def_SymbolReplay, false);
144.             CustomSymbolDelete(def_SymbolReplay);
145.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
146.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
147.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
148.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
149.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
150.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
151.             SymbolSelect(def_SymbolReplay, true);
152.             m_Infos.CountReplay = 0;
153.             m_IndControl.Handle = INVALID_HANDLE;
154.             m_IndControl.Mode = C_Controls::ePause;
155.             m_IndControl.Position = 0;
156.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
157.          }
158. //+------------------------------------------------------------------+
159.       ~C_Replay()
160.          {
161.             SweepAndCloseChart();
162.             IndicatorRelease(m_IndControl.Handle);
163.             SymbolSelect(def_SymbolReplay, false);
164.             CustomSymbolDelete(def_SymbolReplay);
165.             Print("Finished replay service...");
166.          }
167. //+------------------------------------------------------------------+
168.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
169.          {
170.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
171.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
172.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
173.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
174.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
175.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
176.             SweepAndCloseChart();
177.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
178.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
179.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
180.             else
181.                Print("Apply template: ", szNameTemplate, ".tpl");
182. 
183.             return true;
184.          }
185. //+------------------------------------------------------------------+
186.       bool InitBaseControl(const ushort wait = 1000)
187.          {
188.             Print("Waiting for Mouse Indicator...");
189.             Sleep(wait);
190.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
191.             if (def_CheckLoopService)
192.             {
193.                AdjustViewDetails();
194.                Print("Waiting for Control Indicator...");
195.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
196.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
197.                UpdateIndicatorControl();
198.             }
199.             
200.             return def_CheckLoopService;
201.          }
202. //+------------------------------------------------------------------+
203.       bool LoopEventOnTime(void)
204.          {         
205.             int iPos;
206. 
207.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
208.             {
209.                UpdateIndicatorControl();
210.                Sleep(200);
211.             }
212.             m_MemoryData = GetInfoTicks();
213.             AdjustPositionToReplay();
214.             iPos = 0;
215.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
216.             {
217.                if (m_IndControl.Mode == C_Controls::ePause) return true;
218.                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);
219.                CreateBarInReplay(true);
220.                while ((iPos > 200) && (def_CheckLoopService))
221.                {
222.                   Sleep(195);
223.                   iPos -= 200;
224.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
225.                   UpdateIndicatorControl();
226.                }
227.             }
228. 
229.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
230.          }
231. };
232. //+------------------------------------------------------------------+
233. #undef macroRemoveSec
234. #undef def_SymbolReplay
235. #undef def_CheckLoopService
236. #undef def_MaxSlider
237. //+------------------------------------------------------------------+

Endgültiger Quellcode der Datei C_Replay.mqh


Aktualisierung der Balkenzeit und des Prozentsatzes der Kurse

Dieses Problem ist relativ einfach zu lösen. Alles, was wir tun müssen, ist, Nachrichten an den Mauszeiger zu senden, damit er die relevanten Informationen richtig interpretieren und anzeigen kann. Die Aufgabe besteht darin, dass der Indikator anzeigt, wie viel Zeit bis zum Beginn eines neuen Balkens verbleibt und wie groß die prozentuale Veränderung zwischen dem Schlusskurs des Vortages und dem aktuellen Kurs ist.

Der Einfachheit halber fangen wir mit der prozentualen Anpassung an. Wenn Sie es vorziehen, können Sie gerne Ihre eigene Methode auf der Grundlage der hier vorgestellten Methode entwickeln. Sie können es nach Belieben an Ihre Bedürfnisse anpassen. Beginnen wir damit, das Problem mit dem Prozentsatz zu verstehen. Wenn Sie einen Blick auf den Mausindikator werfen, werden Sie feststellen, dass die prozentuale Veränderung zwischen dem vorherigen Schlusskurs und dem aktuellen Kurs nicht korrekt angezeigt wird. Der Wert, der auf der Mausposition basiert, ist jedoch genau. Warum gibt es eine solche Diskrepanz? Auf den ersten Blick könnte man annehmen, dass dies daran liegt, dass der Mausindikator nicht versteht, wo die historischen Daten enden und wo die Simulation oder Wiedergabe beginnt. Aber das ist nicht der Fall. Der Mausindikator ist in der Lage, die Daten korrekt zu lesen und zu interpretieren, was wir überprüfen können, indem wir die Veränderung bei der Bewegung der Maus beobachten. Was wirklich passiert, ist, dass der Mauszeiger Daten falsch interpretiert und gelegentlich einen seltsamen Wert anzeigt. Allerdings wird manchmal auch der richtige Wert angezeigt. Das ist das Problem, das wir lösen müssen.

Die Lösung ist eigentlich sehr einfach. Ich möchte jedoch ein Wort der Vorsicht aussprechen: Sie sollten es vermeiden, den Ansatz, den ich gleich zeigen werde, übermäßig anzuwenden. Wenn Sie nicht aufpassen, können Sie die Kontrolle über die Logik und den Fluss Ihrer Entwicklung verlieren. Sehen wir uns also an, wie das Problem gelöst wurde.

Der erste Schritt besteht darin, den Code des Mausindikators wie unten gezeigt zu ändern:
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. //+------------------------------------------------------------------+
13. double GL_PriceClose;
14. //+------------------------------------------------------------------+
15. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
16. //+------------------------------------------------------------------+
17. C_Study *Study       = NULL;
18. //+------------------------------------------------------------------+
19. input color user02   = clrBlack;                         //Price Line
20. input color user03   = clrPaleGreen;                     //Positive Study
21. input color user04   = clrLightCoral;                    //Negative Study
22. //+------------------------------------------------------------------+
23. C_Study::eStatusMarket m_Status;
24. int m_posBuff = 0;
25. double m_Buff[];
26. //+------------------------------------------------------------------+
27. int OnInit()
28. {
29.    ResetLastError();
30.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
31.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
32.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
33.    {
34.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
35.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
36.       m_Status = C_Study::eCloseMarket;
37.    }else
38.       m_Status = C_Study::eInReplay;
39.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
40.    ArrayInitialize(m_Buff, EMPTY_VALUE);
41.    
42.    return INIT_SUCCEEDED;
43. }
44. //+------------------------------------------------------------------+
45. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
46.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
47.                 const long& volume[], const int& spread[]) 
48. //int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
49. {
50.    GL_PriceClose = close[rates_total - 1];
51.    m_posBuff = rates_total;
52.    (*Study).Update(m_Status);   
53.    
54.    return rates_total;
55. }
56. //+------------------------------------------------------------------+

Codefragment des Mauszeigers

Beachten Sie, dass ich in Zeile 13 eine Variable hinzugefügt habe. Diese Variable ist global. Und ich meine nicht „global“, nur weil es außerhalb einer Funktion oder Prozedur deklariert ist, sondern es ist wirklich global aufgrund seines Umfangs und seiner Platzierung im Code. Diese Variable hat keine besondere magische Wirkung. In Zeile 50 erhält sie jedoch einen vom MetaTrader 5 bereitgestellten Wert. Beachten Sie auch, dass Zeile 48, die zuvor die Deklaration der Ereignisfunktion OnCalculate enthielt, durch eine neue Version ersetzt wurde. Das ist sehr wichtig. Jetzt können wir die in Zeile 13 deklarierte Variable verwenden, um das zuvor besprochene Prozentproblem zu lösen. Die nächste Änderung sollte im Code innerhalb der Header-Datei C_Study.mqh vorgenommen werden. Er ist unten aufgeführt:

41. //+------------------------------------------------------------------+
42.       void Draw(void)
43.          {
44.             double v1;
45.             
46.             if (m_Info.bvT)
47.             {
48.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
49.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
50.             }
51.             if (m_Info.bvD)
52.             {
53.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
54.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
55.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
56.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
57.             }
58.             if (m_Info.bvP)
59.             {
60.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
61.                v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
62.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
63.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
64.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
65.             }
66.          }
67. //+------------------------------------------------------------------+

Codeabschnitt aus der Datei C_Study.mqh

Sie werden sehen, dass die Zeile 61, die zuvor die alte Indikatorlogik enthielt, durch die neue Logik in Zeile 60 ersetzt wurde. Beachten Sie, dass hier auf die in der Indikatordatei deklarierte globale Variable verwiesen wird. Wie ist das möglich? Der Grund dafür ist, dass wir die Variable als global deklariert haben. Genauer gesagt, innerhalb des globalen Geltungsbereichs der Datei. Dies ermöglicht den Zugriff von jeder beliebigen Stelle des Codes, der gerade erstellt wird. Diese Art des globalen Zugriffs kann manchmal Probleme verursachen. Aus diesem Grund müssen Sie bei der Arbeit mit globalen Variablen immer vorsichtig sein.

Wenn ich globale Variablen verwenden muss (in der Regel aus einem ganz bestimmten Grund), tue ich dies bewusst, da ich weiß, dass sie Probleme verursachen können, wenn sie nicht sorgfältig behandelt werden. Deshalb definiere ich sie in der Regel vor allen #include-Direktiven und stelle ihnen das Präfix GL_ voran, um sie eindeutig zu identifizieren. Obwohl wir in Zeile 25 auch eine Variable mit globalem Gültigkeitsbereich haben, macht mir das keine großen Sorgen. Sie dient einem ganz bestimmten Zweck und kann nicht unbeabsichtigt verändert werden. Achten Sie auf die Variable in Zeile 13 - hier ist besondere Vorsicht geboten, da man sie leicht versehentlich ändern kann, ohne es zu merken.

Durch diese einfache Änderung lösen wir das Problem, dass der Prozentsatz gelegentlich falsche Werte anzeigt. Darüber hinaus haben wir auch einige Leistungsverbesserungen erzielt. Dies liegt daran, dass wir nicht mehr iClose verwenden müssen, um den Schlusskurs abzurufen. MetaTrader 5 stellt diese Informationen nun direkt zur Verfügung, sodass wir sie nicht mehr manuell abrufen müssen.


Schlussfolgerung

Obwohl wir uns noch nicht mit der Frage beschäftigt haben, wie man die verbleibende Zeit bis zum Schließen eines Balkens verfolgen kann, insbesondere im Zusammenhang mit der Wiedergabe/Simulation, haben wir in diesem Artikel große Fortschritte gemacht. Die Anwendung entspricht nun weitgehend dem Verhalten, das wir hatten, als wir uns noch auf globale Terminalvariablen verlassen haben. Ich denke, dass viele von Ihnen überrascht sind, wie viel mit einfachem MQL5, ohne externe Abhängigkeiten, erreicht werden kann. Dies ist jedoch erst der Anfang. Es gibt noch viel zu tun, und jeder neue Schritt wird sowohl Herausforderungen als auch neue Lernmöglichkeiten mit sich bringen.

Ich habe zwar noch nicht erklärt, wie man MetaTrader 5 dazu bringt, uns über die verbleibende Balkenzeit zu informieren, wenn wir die Wiedergabe-/Simulationsanwendung verwenden, aber ich werde das gleich zu Beginn des nächsten Artikels behandeln. Verpassen Sie es nicht, denn wir werden auch damit beginnen, einen anderen Aspekt des Systems zu verfeinern, der verbessert werden muss, damit es nahtlos in die derzeitige Struktur passt.

Leider ist dies immer noch nicht der Artikel, mit dem Nicht-Programmierer die Anwendung vollständig nutzen können. Das liegt ganz einfach daran, dass die Frage nach dem Zeitpunkt des Abschlusses noch nicht geklärt ist. Wenn Sie ein Programmierer sind und die von mir beschriebenen Änderungen vorgenommen haben, werden Sie feststellen, dass sich die Wiedergabe-/Simulationsanwendung bereits so verhält, wie im untenstehenden Demo-Video gezeigt. Wir sehen uns im nächsten Artikel. 


Demo-Video

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

Beigefügte Dateien |
Anexo.zip (420.65 KB)
Neuronale Netze im Handel: Szenenspezifische Objekterkennung (HyperDet3D) Neuronale Netze im Handel: Szenenspezifische Objekterkennung (HyperDet3D)
Wir laden Sie ein, einen neuen Ansatz zur Erkennung von Objekten mit Hilfe von Hypernetzwerken kennen zu lernen. Ein Hypernetwork generiert Gewichte für das Hauptmodell, wodurch die Besonderheiten der aktuellen Marktsituation berücksichtigt werden können. Dieser Ansatz ermöglicht es uns, die Vorhersagegenauigkeit zu verbessern, indem wir das Modell an unterschiedliche Handelsbedingungen anpassen.
Anwendung der lokalisierten Merkmalsauswahl in Python und MQL5 Anwendung der lokalisierten Merkmalsauswahl in Python und MQL5
In diesem Artikel wird ein Algorithmus zur Merkmalsauswahl untersucht, der in dem Artikel „Local Feature Selection for Data Classification“ von Narges Armanfard et al. Der Algorithmus ist in Python implementiert, um binäre Klassifizierungsmodelle zu erstellen, die in MetaTrader 5-Anwendungen für Inferenzen integriert werden können.
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 18): Automatisierte Gruppenauswahl unter Berücksichtigung der Vorwärtszeitraum Entwicklung eines Expertenberaters für mehrere Währungen (Teil 18): Automatisierte Gruppenauswahl unter Berücksichtigung der Vorwärtszeitraum
Fahren wir fort, die Schritte zu automatisieren, die wir zuvor manuell durchgeführt haben. Diesmal kehren wir zur Automatisierung der zweiten Phase zurück, d. h. zur Auswahl der optimalen Gruppe von Einzelinstanzen von Handelsstrategien, und ergänzen sie durch die Möglichkeit, die Ergebnisse der Instanzen in dem Vorwärtszeitraum zu berücksichtigen.
Neuronale Netze im Handel: Transformer für die Punktwolke (Pointformer) Neuronale Netze im Handel: Transformer für die Punktwolke (Pointformer)
In diesem Artikel geht es um Algorithmen für die Verwendung von Aufmerksamkeitsmethoden zur Lösung von Problemen bei der Erkennung von Objekten in einer Punktwolke. Die Erkennung von Objekten in Punktwolken ist für viele reale Anwendungen wichtig.