
Entwicklung eines Replay-Systems (Teil 71): Das richtige Bestimmen der Zeit (IV)
Einführung
Im vorigen Artikel „Entwicklung eines Replay-Systems (Teil 70): Das richtige Bestimmen der Zeit (III)“ habe ich die erforderlichen Änderungen am Mauszeiger erläutert. Diese Änderungen zielten darauf ab, dass der Mauszeiger Ereignisse des Order Books empfangen kann. Dies bezieht sich speziell auf den Fall, dass es zusammen mit der Wiedergabe-/Simulationsanwendung verwendet wird. Sie, liebe Leserin und lieber Leser, haben sich durch all diese Änderungen vielleicht ziemlich frustriert und verwirrt gefühlt. Ich verstehe, dass viele von ihnen anfangs vielleicht keinen Sinn ergeben haben. Sie waren wahrscheinlich viel komplexer, als ich es mir vorgestellt habe. Dennoch ist es wichtig, dass Sie diesen Inhalt vollständig verstehen, egal wie verwirrend er auf den ersten Blick erscheinen mag. Ich weiß, dass viele von Ihnen wahrscheinlich Mühe hatten zu verstehen, was ich in diesem Artikel zu vermitteln versuchte. Ohne den vorherigen Inhalt zu verstehen (wo ich einen viel einfacheren Dienst verwendet habe, um zu zeigen, wie der ganze Mechanismus funktioniert), wäre es jedoch wesentlich schwieriger zu verstehen, was hier erklärt wird.
Bevor Sie also erfahren, was wir in diesem Artikel tun werden, sollten Sie sich vergewissern, dass Sie verstanden haben, was im vorherigen Artikel behandelt wurde. Insbesondere der Teil, in dem es darum geht, wie wir durch das Hinzufügen von Ereignissen des Order Books zum nutzerdefinierten Symbol die OnCalculate-Funktion auf eine Weise nutzen können, die vorher nicht möglich war. Dazu mussten wir einen iSpread-Aufruf verwenden, um die Daten abzurufen, die MetaTrader 5 uns zur Verfügung stellt.
In diesem Artikel werden wir einen Teil des im Testdienst verwendeten Codes übertragen (oder genauer gesagt umschreiben) und in den Wiedergabe-/Simulationsdienst einbringen. Die Hauptfrage ist hier nicht, wie wir dies tun können, sondern wie wir es tun sollten.
Ich möchte Sie, lieber Leser, daran erinnern, dass bis zum letzten Artikel, in dem wir uns mit dem Wiedergabe-/Simulationsdienst befasst haben, der Mauszeiger über eine Vorlage geladen wurde. Ich werde dies jedoch nicht mehr tun. Sie können versuchen, die Vorlage zum Laden des Mauszeigers weiter zu verwenden, wenn Sie möchten. Aus praktischen Erwägungen werden wir den Mausindikator manuell in den Chart des Wiedergabe-/Simulationssymbols einfügen. Wundern Sie sich also nicht, wenn ich in einigen Videos Dinge auf diese Weise demonstriere. Ich habe meine Gründe, auf die ich hier nicht näher eingehen werde. Beginnen wir also damit, den Code aus dem Testdienst in den Wiedergabe-/Simulationsdienst zu übertragen.
Starten der Transkription
Als erstes ändern wir einen Teil des Codes in der Header-Datei C_Replay.mqh. Sehen Sie sich den folgenden Ausschnitt an:
197. //+------------------------------------------------------------------+ 198. bool InitBaseControl(const ushort wait = 1000) 199. { 200. Print("Waiting for Mouse Indicator..."); 201. Sleep(wait); 202. while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 203. if (def_CheckLoopService) 204. { 205. AdjustViewDetails(); 206. Print("Waiting for Control Indicator..."); 207. if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false; 208. ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle); 209. UpdateIndicatorControl(); 210. } 211. 212. return def_CheckLoopService; 213. } 214. //+------------------------------------------------------------------+
Code aus der Datei C_Terminal.mqh
Dieses Fragment ist der ursprüngliche Code. Ich möchte, dass Sie auf den folgenden Punkt achten. Dieser Code sollte ursprünglich funktionieren, wenn der Mauszeiger über eine Vorlage geladen wurde. Da der Mausindikator nun jedoch manuell auf dem Chart platziert wird, ist dieser Code nicht mehr sinnvoll. Technisch gesehen, würde es trotzdem funktionieren. Wir können sie jedoch verbessern, um eine geeignetere Konfiguration sowohl im Hinblick auf den Ausführungsablauf als auch auf die dargestellten Nachrichten zu erhalten. Hier ist also der neue Code, der verwendet werden soll:
197. //+------------------------------------------------------------------+ 198. bool InitBaseControl(const ushort wait = 1000) 199. { 200. Sleep(wait); 201. AdjustViewDetails(); 202. Print("Loading Control Indicator..."); 203. if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false; 204. ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle); 205. Print("Waiting for Mouse Indicator..."); 206. while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 207. UpdateIndicatorControl(); 208. 209. return def_CheckLoopService; 210. } 211. //+------------------------------------------------------------------+
Code aus der Datei C_Replay.mqh
Der Code selbst hat sich im Grunde nur sehr wenig geändert. Aber die Nachrichten geben jetzt ein klareres Bild von dem, was passiert. Außerdem wurde die Reihenfolge der Vollstreckung umgedreht. Wir werden nun zunächst versuchen, den Kontrollindikator zu laden, der Teil der Wiedergabe-/Simulationsanwendung ist. Das liegt daran, dass der Kontrollindikator in die ausführbare Datei des Replays/Simulators eingebettet ist. Erst danach laden wir den Mauszeiger. Beachten Sie die folgenden Punkte: Wenn der Kontrollindikator, der in der Anwendung enthalten ist, nicht geladen wird, ist dies ein kritisches Problem. Wenn er erfolgreich geladen wird, können wir den Nutzer darüber informieren, dass der Mauszeiger ebenfalls geladen werden muss. Meiner Meinung nach ist dies ein angemessener Arbeitsablauf. Sie können jedoch die Reihenfolge des Ladens anpassen, wenn Sie dies wünschen. In jedem Fall funktioniert der Kontrollindikator nur dann, wenn der Mausindikator im Chart vorhanden ist.
Diese Veränderung ist eigentlich ästhetischer Natur. Kommen wir nun zu den Änderungen, die die Zeile Meldungen des Order Books tatsächlich unterstützen werden. Wenn Sie den vorherigen Artikel nicht verstanden haben, gehen Sie zurück und wiederholen Sie den Stoff anhand des vorherigen Codes. Versuchen Sie nicht, anhand des Codes, der ab jetzt gezeigt wird, zu verstehen, wie alles funktioniert. Wenn Sie dies versuchen, werden Sie sich in völliger Verwirrung wiederfinden.
Wir müssen eine neue Zeile in den Klassenkonstruktor einfügen. Diese neue Zeile ist im unten stehenden Code zu sehen:
149. //+------------------------------------------------------------------+ 150. C_Replay() 151. :C_ConfigService() 152. { 153. Print("************** Market Replay Service **************"); 154. srand(GetTickCount()); 155. SymbolSelect(def_SymbolReplay, false); 156. CustomSymbolDelete(def_SymbolReplay); 157. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 158. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 159. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 160. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 161. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 162. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 163. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1); 164. SymbolSelect(def_SymbolReplay, true); 165. m_Infos.CountReplay = 0; 166. m_IndControl.Handle = INVALID_HANDLE; 167. m_IndControl.Mode = C_Controls::ePause; 168. m_IndControl.Position = 0; 169. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState; 170. } 171. //+------------------------------------------------------------------+
Code aus der Datei C_Replay.mqh
Die neue Zeile ist genau die Zeile 163. Sobald dies geschehen ist, können wir die Meldungen des Order Books verwenden. Achten Sie jetzt auf eine Sache. Der wichtige Punkt liegt nicht in der Header-Datei C_Replay.mqh, sondern im Mauszeiger. Lassen Sie uns daher einen Auszug aus dem Code des Indikators nehmen, um besser zu verstehen, wie er funktioniert. Weil es wichtig ist.
27. //+------------------------------------------------------------------+ 28. int OnInit() 29. { 30. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 31. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 32. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 33. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 34. m_Status = C_Study::eCloseMarket; 35. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 36. ArrayInitialize(m_Buff, EMPTY_VALUE); 37. 38. return INIT_SUCCEEDED; 39. } 40. //+------------------------------------------------------------------+
Dateifragment des Mauszeigers
Beachten Sie, dass wir in Zeile 34 einen Anfangsstatus für den Mauszeiger festlegen. Dieser Status zeigt an, dass der Markt geschlossen ist. Aber wir haben es hier nicht mit dem realen Markt zu tun. Wir arbeiten mit einer Anwendung, deren Hauptziel es ist, die Wiedergabe oder Simulation potenzieller Marktbewegungen zu ermöglichen. Die Meldung, die derzeit vom Mauszeiger angezeigt wird - wenn wir uns auf dem nutzerdefinierten Symbol befinden, das für die Wiedergabe/Simulation verwendet wird - ist also falsch. Glücklicherweise ist dies sehr, sehr einfach zu beheben. Aber bevor wir das Problem beheben, müssen Sie verstehen, dass in dem Moment, in dem der Kontrollindikator auf dem Chart platziert wird, die Wiedergabe/Simulation effektiv angehalten wird. Dies gilt, wenn die Anwendung von Anfang an initialisiert wird. Und nun stehen wir vor einer Entscheidung, die das weitere Vorgehen maßgeblich beeinflussen wird.
Lassen Sie uns das durchdenken: Sollte der Mauszeiger im Pausemodus eine Auktionsnachricht anzeigen? Oder soll sie die verbleibende Zeit im aktuellen Balken anzeigen? Wenn wir uns entscheiden, die verbleibende Zeit anzuzeigen, sollte diese Information erscheinen, bevor das erste Wiedergabe ausgelöst wird, oder erst danach? Das mag etwas verwirrend klingen, also lassen Sie uns das klarstellen. Bevor der Markt offiziell eröffnet wird, findet eine Auktionsphase statt. Dies ermöglicht es den Teilnehmern, ihre Aufträge zu den bestmöglichen Preisen zu platzieren, d. h. zu den Preisen, zu denen sie wirklich kaufen oder verkaufen wollen. Sobald also die Wiedergabe-/Simulationsanwendung MetaTrader 5 dazu veranlasst, das Chart zu laden und der Mausindikator sichtbar wird, sollte eine Auktionsnachricht angezeigt werden. Das ist eine Tatsache. Wenn Sie nun die Simulation oder Wiedergabe aktivieren und dann wieder anhalten, was sollte dann in der Meldung stehen? Sollte es immer noch „Auktion“ anzeigen? Oder soll sie jetzt die verbleibende Zeit im aktuellen Takt anzeigen? Das ist die Frage, über die wir nachdenken müssen. Unabhängig davon müssen wir als erstes sicherstellen, dass beim Start der Anwendung deutlich angezeigt wird, dass wir uns in einer Auktionsphase befinden.
Das ist eigentlich ganz einfach. Siehe das unten stehende Codefragment.
212. //+------------------------------------------------------------------+ 213. bool LoopEventOnTime(void) 214. { 215. int iPos, iCycles; 216. MqlBookInfo book[1]; 217. 218. book[0].price = 1.0; 219. book[0].volume = 1; 220. book[0].type = BOOK_TYPE_BUY_MARKET; 221. CustomBookAdd(def_SymbolReplay, book, 1); 222. while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay)) 223. { 224. UpdateIndicatorControl(); 225. Sleep(200); 226. } 227. m_MemoryData = GetInfoTicks(); 228. AdjustPositionToReplay(); 229. iPos = iCycles = 0; 230. while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService)) 231. { 232. if (m_IndControl.Mode == C_Controls::ePause) return true; 233. 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); 234. CreateBarInReplay(true); 235. while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause)) 236. { 237. Sleep(195); 238. iPos -= 200; 239. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks); 240. UpdateIndicatorControl(); 241. iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1); 242. } 243. } 244. 245. return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService)); 246. } 247. }; 248. //+------------------------------------------------------------------+
Code aus der Datei C_Replay.mqh
Hier ist der einfache Teil dessen, was wir tatsächlich umsetzen müssen. Ich werde die Umsetzung schrittweise einführen, sodass Sie wirklich mitverfolgen können, was getan wird. Schauen Sie sich Zeile 216 an, wo wir eine neue Variable haben. Es handelt sich um ein Array mit einem einzigen Element. Und jetzt kommt der interessante Teil.
Sie gehen wahrscheinlich davon aus, dass die Werte, die in ein Ereignis des Order Books einfließen, eine gewisse Bedeutung haben müssen. Aber in Wirklichkeit müssen sie überhaupt nichts bedeuten. Wir müssen nur sicherstellen, dass die Werte, die zum Auslösen des Ereignisses des Order Books verwendet werden, einer internen Logik folgen. Sie müssen aber nicht aussagekräftig sein, es sei denn, Ihr Ziel ist es, ein tatsächliches Order Book zu simulieren. Das ist jedoch nicht meine Absicht. Zumindest nicht im Moment. Vielleicht in der Zukunft.
In jedem Fall werden die Zeilen 218 und 219 verwendet, um MetaTrader 5 etwas zu geben, um das Order Book zu füllen. Diese Werte haben keine besondere Bedeutung. Sie sind einfach dazu da, das zu unterstützen, was mir wirklich am Herzen liegt, nämlich die Zeile 220. In Zeile 220 teilen wir dem Order Book mit, dass wir eine Position haben, die einen Auktionsstatus anzeigt. Wenn das keinen Sinn ergibt, empfehle ich, den vorherigen Artikel noch einmal zu lesen, um dieses Verhalten besser zu verstehen. In Zeile 221 teilen wir MetaTrader 5 mit, dass er ein nutzerdefiniertes Ereignis des Order Books auslösen soll. Dieses Ereignis wird von der Funktion OnEventBook erfasst, die sich in diesem Fall im Mauszeiger befindet. Ergebnis: Jedes Mal, wenn die Funktion LoopEventOnTime aufgerufen wird, zeigt der Mauszeiger tatsächlich an, dass wir uns in einer Auktion befinden. Es gibt zwei Szenarien, in denen LoopEventOnTime von Anfang an läuft: Der erste Fall ist, wenn die Anwendung zum ersten Mal initialisiert wird. Der zweite Fall tritt ein, wenn der Nutzer mit dem Kontrollanzeiger interagiert, indem er die Pausentaste drückt. In diesem Fall wird Zeile 232 ausgeführt, und unmittelbar danach wird die Funktion LoopEventOnTime erneut ausgeführt. Haben Sie bemerkt, wie einfach das ist? Nun, da die Auktionsnachricht bereits angezeigt wird, wie können wir die verbleibende Zeit in den Balken anzeigen? Das ist eigentlich ganz einfach. Zu diesem Zweck muss der Code wie folgt geändert werden:
212. //+------------------------------------------------------------------+ 213. bool LoopEventOnTime(void) 214. { 215. int iPos, iCycles; 216. MqlBookInfo book[1]; 217. 218. book[0].price = 1.0; 219. book[0].volume = 1; 220. book[0].type = BOOK_TYPE_BUY_MARKET; 221. CustomBookAdd(def_SymbolReplay, book, 1); 222. while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay)) 223. { 224. UpdateIndicatorControl(); 225. Sleep(200); 226. } 227. m_MemoryData = GetInfoTicks(); 228. AdjustPositionToReplay(); 229. iPos = iCycles = 0; 230. book[0].type = BOOK_TYPE_BUY; 231. CustomBookAdd(def_SymbolReplay, book, 1); 232. while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService)) 233. { 234. if (m_IndControl.Mode == C_Controls::ePause) return true; 235. 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); 236. CreateBarInReplay(true); 237. while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause)) 238. { 239. Sleep(195); 240. iPos -= 200; 241. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks); 242. UpdateIndicatorControl(); 243. iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1); 244. } 245. } 246. 247. return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService)); 248. } 249. }; 250. //+------------------------------------------------------------------+
Code aus der Datei C_Replay.mqh
Haben Sie den Unterschied bemerkt? Wenn Sie es nicht bemerkt haben, liegt es wahrscheinlich daran, dass Sie zu sehr abgelenkt waren - denn der Unterschied liegt genau in der Einbeziehung der Zeilen 230 und 231. Diese beiden Zeilen sorgen dafür, dass der Mauszeiger ein nutzerdefiniertes Ereignis des Order Books empfängt, sobald der Nutzer auf dem Wiedergabe-/Simulationsdienst auf Play drückt. Dieses Ereignis signalisiert, dass wir den Zustand der Auktion verlassen haben und in den aktiven Handel eingetreten sind. Daraufhin wird die verbleibende Zeit des aktuellen Taktes auf dem Mauszeiger angezeigt. Wie Sie sehen können, ist alles ziemlich einfach. Allerdings haben wir es jetzt mit einer etwas komplexeren Situation zu tun.
Auf dem realen Markt, d.h. wenn wir mit dem Handelsserver verbunden sind, kann es vorkommen, dass ein Wertpapier ausgesetzt wird oder in eine Auktion geht. Dies geschieht in der Regel aufgrund bestimmter regulatorischer Bedingungen. Ich habe dies in einem früheren Artikel erörtert. Aber für den Moment werden wir eine vereinfachte Regel einführen. Wenn der Vermögenswert zwischen einem Tick und dem nächsten eine Zeitlücke von 60 Sekunden oder mehr aufweist, zeigt der Mauszeiger an, dass der Vermögenswert eine Auktion begonnen hat. Das ist eigentlich ganz einfach. Der knifflige Teil ist der folgende: Wie können wir erreichen, dass der Mauszeiger danach wieder die verbleibende Zeit des Taktes anzeigt?
Man könnte sagen: „Wenn das Finanzinstrument in den Auktionsmodus eintritt, senden wir die Konstante BOOK_TYPE_BUY_MARKET an das Order Book, und wenn sie die Auktion verlässt, senden wir BOOK_TYPE_BUY.“ Genau das ist es, was wir tun müssen. Aber wie machen wir das richtig? Lassen Sie uns das durchdenken: Wir wollen nicht, dass die Funktion LoopEventOnTime wieder von vorne beginnt. Wir wollen, dass das System innerhalb der Schleife, die in Zeile 232 beginnt und in Zeile 245 endet, weiterläuft. Wenn Sie nun innerhalb dieser Schleife sowohl BOOK_TYPE_BUY_MARKET als auch BOOK_TYPE_BUY senden, treten Probleme auf. Das liegt daran, dass jeder Aufruf von CustomBookAdd() mit diesen unterschiedlichen Konstanten definitiv ein unangenehmes visuelles Ergebnis für den Nutzer erzeugt, der den Mauszeiger beobachtet. Die Anzeige flackert und wechselt schnell zwischen der verbleibenden Zeit und dem Wort „AUCTION“.
Aus diesem Grund müssen wir kreativ werden. Wir müssen eine Lösung finden, die diesen Flackereffekt vermeidet und dennoch das Problem wirksam löst. Die von mir vorgeschlagene Lösung ist unten dargestellt:
212. //+------------------------------------------------------------------+ 213. bool LoopEventOnTime(void) 214. { 215. int iPos, iCycles; 216. MqlBookInfo book[1]; 217. ENUM_BOOK_TYPE typeMsg; 218. 219. book[0].price = 1.0; 220. book[0].volume = 1; 221. book[0].type = BOOK_TYPE_BUY_MARKET; 222. CustomBookAdd(def_SymbolReplay, book, 1); 223. while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay)) 224. { 225. UpdateIndicatorControl(); 226. Sleep(200); 227. } 228. m_MemoryData = GetInfoTicks(); 229. AdjustPositionToReplay(); 230. iPos = iCycles = 0; 231. book[0].type = BOOK_TYPE_BUY; 232. CustomBookAdd(def_SymbolReplay, book, 1); 233. while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService)) 234. { 235. if (m_IndControl.Mode == C_Controls::ePause) return true; 236. 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); 237. if ((typeMsg = (iPos >= 60000 ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY)) != book[0].type) 238. { 239. book[0].type = typeMsg; 240. CustomBookAdd(def_SymbolReplay, book, 1); 241. } 242. CreateBarInReplay(true); 243. while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause)) 244. { 245. Sleep(195); 246. iPos -= 200; 247. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks); 248. UpdateIndicatorControl(); 249. iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1); 250. } 251. } 252. 253. return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService)); 254. } 255. }; 256. //+------------------------------------------------------------------+
Code aus der Datei C_Replay.mqh
Ich weiß, dass es nicht gerade elegant ist, aber zumindest funktioniert es. Und wenn Sie wollen, können Sie sie unter verschiedenen Bedingungen testen, indem Sie die Zeitschwellen erhöhen oder verringern. Das ist ganz Ihnen überlassen. Doch bevor wir uns auf den Weg machen und die Dinge ändern, sollten wir uns einen Moment Zeit nehmen, um zu verstehen, was in diesem Codeschnipsel passiert.
Zunächst ist folgendes zu beachten: In Zeile 217 wird eine neue Variable deklariert. Diese Variable wird verwendet, um eine der möglichen Konstanten zu speichern, die vom Order Book akzeptiert werden. In Zeile 237 verwende ich dann den ternären Operator, um die Logik zu straffen, da es darum geht, eine Bedingung auszuwerten und auf dieser Grundlage der Variablen typeMsg einen Wert zuzuweisen. Und jetzt aufgepasst: Ich hätte diesen Code noch mehr verdichten können, aber das hätte die Erklärung unnötig kompliziert gemacht. Und so funktioniert es. Nachdem eine Konstante dem TypMsg zugewiesen wurde, wird geprüft, ob sich ihr Wert von dem letzten Wert unterscheidet, der als nutzerdefiniertes Ereignis des Order Books gesendet wurde. Wenn er sich geändert hat, weisen wir in Zeile 239 die neue Konstante zu, die verwendet werden soll, und rufen in Zeile 240 CustomBookAdd auf. Der Teil, auf den Sie sich wirklich konzentrieren müssen, ist der Wert, der mit der Variablen iPos verglichen wird, und zwar in Zeile 237. Beachten Sie, dass wir ihn mit dem Wert 60000 (sechzigtausend) vergleichen. Aber warum dieser Wert? Hatten wir nicht einen Schwellenwert von einer Minute verwendet? Ja, aber vielleicht vergessen Sie diese einfache Tatsache: Eine Minute entspricht 60 Sekunden. Sehen Sie sich nun Zeile 236 an - der iPos zugewiesene Wert ist in Millisekunden angegeben. Und eine einzige Sekunde hat 1.000 Millisekunden. Deshalb vergleichen wir iPos mit 60.000. Sie spiegelt ein 60-Sekunden-Intervall, ausgedrückt in Millisekunden, wider. Wenn Sie also beschließen, diesen Schwellenwert in einen anderen Wert zu ändern, den Sie für angemessener halten, müssen Sie ihn unbedingt in Millisekunden umrechnen. Andernfalls könnte der Mausindikator ein unerwartetes Verhalten zeigen, insbesondere dann, wenn der Vermögenswert eine Zeit lang keine Ticks in das Chart einträgt.
Damit können wir endlich die endgültige Version der C_Replay.mqh-Codedatei anzeigen. In diesem Stadium sind alle zeitlichen Aspekte vollständig implementiert, zumindest so weit, dass sich das Endergebnis wie im unten gezeigten Video verhält.
Der vollständige Quellcode der Header-Datei C_Replay.mqh ist unten aufgeführt:
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 int RateUpdate(bool bCheck) 070. { 071. static int st_Spread = 0; 072. 073. st_Spread = (bCheck ? (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time) : st_Spread + 1); 074. m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread); 075. CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate); 076. 077. return 0; 078. } 079. //+------------------------------------------------------------------+ 080. inline void CreateBarInReplay(bool bViewTick) 081. { 082. bool bNew; 083. double dSpread; 084. int iRand = rand(); 085. 086. if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew)) 087. { 088. m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay]; 089. if (m_MemoryData.ModePlot == PRICE_EXCHANGE) 090. { 091. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 092. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 093. { 094. m_Infos.tick[0].ask = m_Infos.tick[0].last; 095. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 096. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 097. { 098. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 099. m_Infos.tick[0].bid = m_Infos.tick[0].last; 100. } 101. } 102. if (bViewTick) 103. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 104. RateUpdate(true); 105. } 106. m_Infos.CountReplay++; 107. } 108. //+------------------------------------------------------------------+ 109. void AdjustViewDetails(void) 110. { 111. MqlRates rate[1]; 112. 113. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 114. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 115. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE); 116. m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 117. CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate); 118. if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE)) 119. for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++); 120. if (rate[0].close > 0) 121. { 122. if (GetInfoTicks().ModePlot == PRICE_EXCHANGE) 123. m_Infos.tick[0].last = rate[0].close; 124. else 125. { 126. m_Infos.tick[0].bid = rate[0].close; 127. m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick); 128. } 129. m_Infos.tick[0].time = rate[0].time; 130. m_Infos.tick[0].time_msc = rate[0].time * 1000; 131. }else 132. m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay]; 133. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 134. } 135. //+------------------------------------------------------------------+ 136. void AdjustPositionToReplay(void) 137. { 138. int nPos, nCount; 139. 140. if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return; 141. nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider); 142. for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread); 143. if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1); 144. while ((nPos > m_Infos.CountReplay) && def_CheckLoopService) 145. CreateBarInReplay(false); 146. } 147. //+------------------------------------------------------------------+ 148. public : 149. //+------------------------------------------------------------------+ 150. C_Replay() 151. :C_ConfigService() 152. { 153. Print("************** Market Replay Service **************"); 154. srand(GetTickCount()); 155. SymbolSelect(def_SymbolReplay, false); 156. CustomSymbolDelete(def_SymbolReplay); 157. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 158. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 159. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 160. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 161. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 162. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 163. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1); 164. SymbolSelect(def_SymbolReplay, true); 165. m_Infos.CountReplay = 0; 166. m_IndControl.Handle = INVALID_HANDLE; 167. m_IndControl.Mode = C_Controls::ePause; 168. m_IndControl.Position = 0; 169. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState; 170. } 171. //+------------------------------------------------------------------+ 172. ~C_Replay() 173. { 174. SweepAndCloseChart(); 175. IndicatorRelease(m_IndControl.Handle); 176. SymbolSelect(def_SymbolReplay, false); 177. CustomSymbolDelete(def_SymbolReplay); 178. Print("Finished replay service..."); 179. } 180. //+------------------------------------------------------------------+ 181. bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate) 182. { 183. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 184. return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket."); 185. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 186. return MsgError("Asset configuration is not complete, need to declare the ticket value."); 187. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 188. return MsgError("Asset configuration not complete, need to declare the minimum volume."); 189. SweepAndCloseChart(); 190. m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1); 191. if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl")) 192. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 193. else 194. Print("Apply template: ", szNameTemplate, ".tpl"); 195. 196. return true; 197. } 198. //+------------------------------------------------------------------+ 199. bool InitBaseControl(const ushort wait = 1000) 200. { 201. Sleep(wait); 202. AdjustViewDetails(); 203. Print("Loading Control Indicator..."); 204. if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false; 205. ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle); 206. Print("Waiting for Mouse Indicator..."); 207. while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 208. UpdateIndicatorControl(); 209. 210. return def_CheckLoopService; 211. } 212. //+------------------------------------------------------------------+ 213. bool LoopEventOnTime(void) 214. { 215. int iPos, iCycles; 216. MqlBookInfo book[1]; 217. ENUM_BOOK_TYPE typeMsg; 218. 219. book[0].price = 1.0; 220. book[0].volume = 1; 221. book[0].type = BOOK_TYPE_BUY_MARKET; 222. CustomBookAdd(def_SymbolReplay, book, 1); 223. while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay)) 224. { 225. UpdateIndicatorControl(); 226. Sleep(200); 227. } 228. m_MemoryData = GetInfoTicks(); 229. AdjustPositionToReplay(); 230. iPos = iCycles = 0; 231. book[0].type = BOOK_TYPE_BUY; 232. CustomBookAdd(def_SymbolReplay, book, 1); 233. while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService)) 234. { 235. if (m_IndControl.Mode == C_Controls::ePause) return true; 236. 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); 237. if ((typeMsg = (iPos >= 60000 ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY)) != book[0].type) 238. { 239. book[0].type = typeMsg; 240. CustomBookAdd(def_SymbolReplay, book, 1); 241. } 242. CreateBarInReplay(true); 243. while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause)) 244. { 245. Sleep(195); 246. iPos -= 200; 247. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks); 248. UpdateIndicatorControl(); 249. iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1); 250. } 251. } 252. 253. return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService)); 254. } 255. }; 256. //+------------------------------------------------------------------+ 257. #undef def_SymbolReplay 258. #undef def_CheckLoopService 259. #undef def_MaxSlider 260. //+------------------------------------------------------------------+
Der Quellcode der Datei C_Replay.mqh
Dies ist ein recht interessanter, aber harmloser Fehler.
Na gut. Nun gut, obwohl das System, das wir gerade aufgebaut und demonstriert haben, wunderbar funktioniert, gibt es immer noch ein Problem. Auch wenn ich das in dem Video nicht gezeigt habe, glaube ich, dass Sie, wenn Sie die Dinge sorgfältig durchdenken, einen Fehler in der derzeitigen Umsetzung feststellen werden. Aber bevor ich Ihnen erkläre, was das Problem ist, lassen Sie uns kurz überprüfen, wie gut Sie verstehen, wie MetaTrader 5 funktioniert. Wenn Sie nicht genau wissen, wie der MetaTrader 5 unter verschiedenen Bedingungen funktioniert, haben Sie dieses Problem wahrscheinlich noch nicht bemerkt. Das liegt daran, dass der Fehler in einem ganz bestimmten Punkt liegt: WIR KÖNNEN DEN ZEITRAHMEN DES CHARTS NICHT ÄNDERN, während wir das Wiederholungs-/Simulationssystem verwenden.
Das fragen Sie sich jetzt wahrscheinlich: „Was meint er damit, dass der Zeitrahmen des Charts nicht geändert werdeb kann? Was passiert, wenn ich es versuche?" Nun, genau das passiert - und ja, es wird zu 100 % der Zeit passieren - die Schwachstelle in unserem System wird ausgelöst. Diese Schwachstelle tritt jedoch nur unter ganz bestimmten Umständen auf. Sie wird nur ausgelöst, wenn das Wiederholungs-/Simulationssystem im Spielmodus läuft. Wenn es sich im Pausemodus befindet oder wenn das System anhand der Anzahl der Abschlüsse erkennt, dass sich das Symbol in einer Auktion befindet, wird der Fehler nicht ausgelöst. Auch dies geschieht nur, wenn wir uns im Spielmodus befinden und dem Chart Ticks hinzugefügt werden.
Lassen Sie mich noch einmal fragen, vor allem für diejenigen, die diese Artikelserie über den Aufbau eines Wiederholungs-/Simulatorsystems in MQL5 verfolgt haben: Haben Sie eine Idee, was der Fehler sein könnte? Wenn Ihre Antwort ja lautet - großartig! Das ist ein Zeichen dafür, dass Sie sich intensiv mit der Funktionsweise von MQL5 und MetaTrader 5 beschäftigt haben. Wenn Ihre Antwort nein lautet - keine Sorge! Das bedeutet nur, dass Sie sich noch in einem frühen Stadium Ihrer Lernreise befinden. Dies sollte ein Ansporn sein, weiter zu lernen.
Schauen wir uns einmal an, was dieser Fehler eigentlich ist. Sie ist unbedeutend, harmlos und wird nur im Spielmodus angezeigt, wo Ticks aktiv verarbeitet werden. Sobald Sie den Zeitrahmen des Charts ändern, geschieht etwas Interessantes. Wenn alle oben genannten Bedingungen erfüllt sind und Sie den Zeitrahmen ändern, zeigt der Mausindikator plötzlich an, dass der Markt geschlossen ist. Sie werden auch nicht mehr die verbleibende Zeit in der aktuellen Leiste sehen. Um das Problem zu beheben, müssen Sie die Simulation unterbrechen und dann wieder aufnehmen.
Jetzt werden Sie vielleicht denken: „Warum zeigt der Indikator bei einer Änderung des Zeitrahmens an, dass der Markt geschlossen ist? Das macht doch keinen Sinn." Und ich würde Ihnen zustimmen. Aber wenn Sie lange genug mit MetaTrader 5 gearbeitet haben, kennen Sie wahrscheinlich schon den Grund. MetaTrader 5 entlädt alle Indikatoren und andere Elemente aus dem Chart. Dann werden die Chartdaten auf der Grundlage des neuen Zeitrahmens aktualisiert und die Indikatoren (und andere Elemente) von Grund auf neu geladen. Das ist die Funktionsweise von MetaTrader 5. Wenn der Mauszeiger also neu geladen wird, beginnt er mit seinem Standardzustand. Um genau zu sehen, was ich meine, sehen Sie sich das folgende Codefragment an, der direkt aus dem Mauszeiger stammt:
27. //+------------------------------------------------------------------+ 28. int OnInit() 29. { 30. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 31. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 32. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 33. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 34. m_Status = C_Study::eCloseMarket; 35. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 36. ArrayInitialize(m_Buff, EMPTY_VALUE); 37. 38. return INIT_SUCCEEDED; 39. } 40. //+------------------------------------------------------------------+
Dateifragment des Mauszeigers
Beachten Sie nun Folgendes: In Zeile 34 dieses Codefragments initialisieren wir den Wert der Statusvariablen. Dieser Wert zeigt an, dass der Markt geschlossen ist. Es kommen jedoch weiterhin Ticks vom Wiedergabe-/Simulationsdienst an. Und genau hier liegt der Fehler. Wie Sie sehen können, ist es harmlos und verursacht keine ernsteren Probleme. Es handelt sich lediglich um eine Unannehmlichkeit, die durch einfaches Anhalten und anschließendes Fortsetzen des Dienstes behoben werden kann. Sobald Sie das getan haben, ist alles wieder normal.
Es gibt tausend Möglichkeiten, dieses Problem zu lösen. Aber die Lösung, die ich Ihnen zeige, ist viel ungewöhnlicher, als Sie vielleicht erwarten. Bevor wir dazu kommen, lassen Sie mich eine Frage stellen: Wissen Sie, warum das Problem verschwindet, wenn Sie die Simulation anhalten und dann wieder fortsetzen? Falls nicht, erklären wir Ihnen, warum dies das Problem löst. Um dies zu verstehen, müssen wir uns den Code in der Header-Datei C_Replay.mqh ansehen, insbesondere die Funktion LoopEventOnTime. Wenn der Fehler auftritt, zeigt der Mauszeiger an, dass der Markt geschlossen ist. Sobald Sie den Pausemodus auslösen, unterbricht Zeile 235 die Schleife, die in Zeile 233 beginnt. Dadurch wird die Funktion gezwungen, zum Hauptteil des Codes zurückzukehren. Da der Rückgabewert wahr ist, ruft die Hauptroutine des Dienstes sofort wieder LoopEventOnTime auf. An diesem Punkt löst Zeile 222 ein nutzerdefiniertes Ereignis des Order Books aus, wobei der in Zeile 221 definierte Wert verwendet wird. Infolgedessen aktualisiert der Mauszeiger seinen Status von „Markt geschlossen“ auf „Auktionsmodus“. Wenn der Nutzer dann die Simulation durch erneutes Drücken von Play fortsetzt, löst Zeile 232 ein weiteres nutzerdefiniertes Ereignis des Order Books aus. Aber jetzt wird die Konstante aus Zeile 231 verwendet. Mit dieser Konstante kann der Mauszeiger wieder die verbleibende Zeit im aktuellen Takt anzeigen.
Wenn der Fehler durch eine Änderung des Zeitrahmens des Charts ausgelöst wird, reicht es daher aus, das Programm anzuhalten und dann wieder fortzusetzen, um die normale Funktionalität wiederherzustellen.
Während dieses Problem jedoch den Mausindikator nicht wesentlich beeinträchtigt, führt es bei einer anderen Komponente zu Problemen: dem Kontrollindikator. In diesem Fall gibt es keine einfache Abhilfe. Wir müssen warten, bis die in Zeile 42 der Header-Datei C_Replay.mqh überprüfte Bedingung fehlschlägt. Warum ist das so? Warum muss diese Bedingung fehlschlagen, damit der Kontrollindikator wieder im Chart angezeigt wird? Denn nur wenn diese Prüfung fehlschlägt, wird Zeile 54 ausgeführt. Und wir müssen die Zeile 54 ausführen, damit der Kontrollindikator aktualisiert wird. Nach der Aktualisierung wird der Kontrollindikator wieder eingezeichnet, sofern er zuvor aus irgendeinem Grund ausgeblendet war.
Na gut. Jetzt wird Ihnen wahrscheinlich klar, dass die Situation immer komplizierter wird. Während der Mauszeiger-Fehler durch einfaches Umschalten von Wiedergabe auf Pause und zurück behoben werden kann, ist nicht einmal das möglich, wenn der Kontrollzeiger nicht sichtbar ist. Wie können wir das beheben? Ohne den Kontrollindikator können wir die Anwendung nicht unterbrechen, und der Dienst wird weiterhin Ticks in das Chart übertragen. Und solange der Dienst nicht aufhört, Ticks hinzuzufügen, werden wir keinen Zustand erreichen, bei dem der Test in Zeile 42 fehlschlägt. Dies geschieht erst, wenn eine bestimmte Anzahl von Ticks in das Chart eingefügt worden ist. Dies ist ein ernstes Problem.
Sie denken vielleicht: „Ich muss nur den Chart-Zeitrahmen im Dienst überwachen. Wenn sie sich ändert, werde ich eine Neuinitialisierung der Maus und der Kontrollanzeigen auslösen." Na gut. Aber wissen Sie, wie Sie eine Änderung des Zeitrahmens im Chart innerhalb des Dienstes erkennen können? Vielleicht ist es möglich. Aber ich versichere Ihnen, es wäre viel komplizierter als die Lösung, die ich Ihnen mitteilen werde. Nicht in diesem Artikel, aber im nächsten. Denn ich möchte Ihnen, liebe Leserin, lieber Leser, die Möglichkeit geben, die Lösung zunächst selbst zu versuchen.
Schlussfolgerung
In diesem Artikel habe ich Ihnen gezeigt, wie Sie den Code aus dem vorangegangenen Artikel, der zum Testen der Funktionalität von nutzerdefinierten Meldungen des Order Books verwendet wurde, wiederverwenden und in einen Dienst integrieren können, den wir seit einiger Zeit entwickeln. Ich habe auch gezeigt, wie kleine, scheinbar harmlose Fehler in Ihrer Implementierung zu großen Kopfschmerzen führen können.
Ich hoffe jedoch, dass Sie neugierig sind, wie Sie das Problem mit dem Zeitrahmen im Chart beheben können. Konkret geht es darum, wie der Dienst erkennen kann, wenn sich der Zeitrahmen des Charts geändert hat. Ich werde die Lösung im nächsten Artikel zeigen.
Programmierer zu sein bedeutet, Lösungen zu finden, wenn andere keinen Weg mehr sehen. Probleme wird es immer geben. Ohne sie würde das Programmieren nicht so viel Spaß machen und sich nicht lohnen. Also, je mehr Probleme, desto besser. Und je komplexer sie sind, desto mehr Spaß macht es, sie zu lösen. Sie bringen uns dazu, über den Tellerrand hinauszuschauen. Sie befreien uns von der Monotonie, Dinge immer wieder auf die gleiche Weise zu tun.
Wir sehen uns im nächsten Artikel wieder. Und bevor Sie den nächsten Artikel lesen, sollten Sie versuchen, eine Lösung für das Problem des Zeitrahmenwechsels im Chart zu finden.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12335





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.