
Entwicklung eines Replay Systems (Teil 50): Die Dinge werden kompliziert (II)
Einführung
Im vorherigen Artikel „Entwicklung eines Replay Systems (Teil 49): Die Dinge werden kompliziert (I)“, haben wir begonnen, die Dinge in unserem Replay/Simulator-System noch komplizierter zu machen. Diese Komplikation ist zwar unbeabsichtigt, soll aber das System stabiler und sicherer machen: Es geht um die Möglichkeit, es in einem vollständig modularen Modell zu verwenden.
Diese Änderungen mögen auf den ersten Blick völlig unnötig erscheinen, aber sie verhindern, dass Nutzer einige der Dinge, die wir entwickeln, missbrauchen, indem sie bestimmte Teile schützen, die nur für das Replay/Simulator-System benötigt werden. Auf diese Weise verhindern wir, dass ein unerfahrener Nutzer versehentlich Teile des Charts löscht oder falsch konfiguriert, die vom Replay/Simulator-Dienst benötigt werden. Dies geschieht, während der Dienst ausgeführt wird.
Am Ende des letzten Artikels habe ich auf ein Problem hingewiesen, das zu einer Instabilität des Kontrollanzeigers führt. Aus diesem Grund können einige Dinge passieren, die eigentlich nicht passieren sollten. Diese Probleme sind nicht schwerwiegend und führen nicht zum Absturz der Plattform, aber sie können manchmal zu unerwarteten Serviceausfällen führen. Aus diesem Grund habe ich dem vorigen Artikel keine Dateien beigefügt, da Sie sonst Zugang zum System im aktuellen Entwicklungsstadium hätten.
In diesem Artikel werden wir bestehende Probleme beheben oder zu beheben versuchen. Zumindest einer von ihnen.
Lösung des ersten Problems
Das erste Problem besteht darin, dass der Replay/Simulator-Dienst so implementiert wurde, dass er eine Vorlage verwendet. Die Vorlage sollte alle Daten enthalten, die zum Öffnen des Charts und zum Starten des Dienstes erforderlich sind.
Dieser Ansatz ist zwar funktional und kann als attraktiv angesehen werden, schränkt aber den Nutzer ein. Einfacher ausgedrückt: Die Tatsache, dass ein Dienst eine bestimmte Vorlage verwendet, bedeutet, dass der Nutzer keine eigenen Methoden oder eine gewünschte Chartkonfiguration mehr verwenden kann. Es ist viel bequemer, ein Chart mit einer vom Nutzer erstellten Vorlage zu konfigurieren, die alles enthält, was er braucht, als es jedes Mal manuell einzurichten, wenn er mit einem bestimmten Vermögenswert handeln wollen.
Wenn Sie neu auf dem Markt sind, macht dies vielleicht nicht viel Sinn, aber erfahrenere Händler haben voreingestellte Vorlagen, um eine Standardanalyse für einen bestimmten Vermögenswert zu einem bestimmten Zeitpunkt durchzuführen. Sie entwickeln diese Vorlagen über mehrere Monate oder sogar Jahre, um alles im Voraus vorzubereiten. Auf diese Weise kann der Händler die Vorlage einfach speichern und bei Bedarf auf das Chart anwenden.
Etwas Ähnliches wurde in dem Artikel „Entwicklung eines Replay Systems (Teil 48) gezeigt: Verstehen einiger Konzepte“, womit die ganze Arbeit an der Modifizierung des Replay/Simulator-Systems begann. In diesem Artikel habe ich gezeigt, wie Sie ein Chart nicht mit einer Vorlage, sondern mit einem Dienst oder einem Standardchart einrichten können, sodass unabhängig von der verwendeten Vorlage bestimmte Elemente im Chart vorhanden sind. Ein solches Setup wird immer von dem auf MetaTrader 5 laufenden Dienst unterstützt, um die Arbeit zu standardisieren.
Um jedoch die erforderlichen grafischen Objekte auf dem Chart zu platzieren, damit der Kontrollindikator den Dienst verwalten kann, werden wichtige Informationen benötigt, einschließlich der Chart-ID, die diese Objekte erhalten soll.
Man könnte meinen, dass es einfach wäre, dem Indikator dies mit Hilfe der Funktion ChartID() mitzuteilen. Manche sagen, Unwissenheit sei ein Segen. Verstehen Sie mich nicht falsch, ich hatte selbst jedes Mal Kopfschmerzen, wenn ich versuchte herauszufinden, warum etwas zu einer bestimmten Zeit nicht richtig funktionierte. Ich glaube also nicht, dass ich falsch liege.
Mit der Funktion ChartID() erhalten Sie auf jeden Fall die ID des Charts zurück, sodass wir Objekte darauf platzieren können. Vergessen Sie nicht: Wir benötigen die ID, um MetaTrader 5 mitzuteilen, an welchen Chart das Objekt gestartet werden soll.
Die ChartID-Funktion funktioniert jedoch nicht, wenn das Chart über einen Dienst geöffnet wird. Das heißt, wenn der Dienst die Klasse C_Replay.mqh verwendet und den Code in Zeile 183 ausführt, wird eine andere ID erstellt. Wir haben diesen Code bereits im vorherigen Artikel gesehen. In derselben Zeile 183 rufen wir ChartOpen auf, um ein Chart des Symbols zu erstellen, auf dem Reaplay bzw. die Simulation laufen wird.
Wenn Sie die vom ChartOpen-Dienst zurückgegebenen Werte mit dem ChartID-Wert im Kontrollindikator vergleichen, können Sie feststellen, dass sie unterschiedlich sind. Dies bedeutet, dass die MetaTrader 5-Plattform nicht weiß, welche ID sie verwenden soll. Wenn Sie die von ChartID zurückgegebene ID verwenden, platzieren Sie die Objekte im falschen oder gar nicht existierenden Fenster. Wenn Sie jedoch die ID verwenden, die innerhalb des Dienstes generiert wird, können wir, sobald ChartOpen die ID erstellt, die Objekte verwenden.
Nun stellt sich das Problem: Wie lässt sich das Problem der Chart-ID am besten lösen? Sie denken jetzt vielleicht: Warum verwenden Sie nicht den ID-Wert, den Sie durch den Aufruf von ChartOpen erhalten? Aber genau hier liegt das Problem. Im vorherigen Artikel haben wir die globale Terminalvariable entfernt, die für die Übergabe der in ChartOpen generierten Chart-ID an den Kontrollindikator verantwortlich war.
Danach wurde die Aufgabe, die Chart-ID zu ermitteln, an den Klassencode C_Terminal übertragen. Dies geschieht mit der Funktion ChartID. Wenn Sie dieser Serie gefolgt sind und die Codes entsprechend aktualisiert haben, sollte Ihr C_Terminal-Klassencode wie folgt aussehen:
59. //+------------------------------------------------------------------+ 60. C_Terminal(const long id = 0) 61. { 62. m_Infos.ID = (id == 0 ? ChartID() : id); 63. m_Mem.AccountLock = false; 64. CurrentSymbol(); 65. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 66. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 67. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 68. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true); 69. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 70. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 71. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 72. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 73. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 74. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 75. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 76. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 77. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 78. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 79. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 80. ResetLastError(); 81. } 82. //+------------------------------------------------------------------+ 83.
Quellcode-Fragment aus C_Terminal.mqh
In diesem Code können Sie sehen, dass Zeile 60 den Konstruktor der Klasse C_Terminal enthält. Er erhält den Standardwert NULL, sodass sich der Konstruktor wie ein normaler Konstruktor verhält. Das eigentliche Problem tritt in Zeile 62 auf, wo bei der Überprüfung des an den Konstruktor übergebenen Wertes ermittelt wird, welche Chart-ID zu verwenden ist. Wenn dieser Wert standardmäßig verwendet wird, fordert die Klasse C_Terminal MetaTrader 5 auf, die Chart-ID anhand des von ChartID zurückgegebenen Wertes anzugeben. Dieser Wert ist falsch, wenn der Aufruf erfolgt, weil der Dienst das Chart erstellt und den Indikator gestartet hat, der wiederum C_Terminal aufruft, um den ID-Wert zu ermitteln.
Wir können also dem Konstruktor der Klasse C_Terminal den ID-Wert übergeben, und wenn wir das tun, wird der ChartID-Aufruf ignoriert, und die dem Konstruktor übergebene ID wird die vom Indikator verwendete sein.
Denken Sie daran, dass wir nicht mehr die globale Terminalvariable verwenden, um diesen Wert an den Indikator zu übergeben. Wir können es als etwas Vorläufiges tun, aber die Lösung wird eine andere sein. Wir übergeben den ID-Wert über den Aufrufparameter des Indikators.
Umsetzung der Lösung
Sie werden vielleicht überrascht sein, was wir tun werden, aber das liegt daran, dass ich in dem vorherigen Artikel nicht erklärt habe, wie die Lösung implementiert wird und wie sie funktioniert, bevor sie angewendet wird. Bitte sehen Sie sich Video 01 an, das zeigt, wie sich das System verhält. Dies war, bevor die ID als Parameter an das Kontrollkennzeichen übergeben wurde.
Video 01
Möglicherweise wird eine Fehlermeldung angezeigt. Diese Meldung erscheint, weil der Indikator die Chart-ID nicht kennen kann. Daher gibt der ChartID-Aufruf einen Fehler zurück, und der Indikatorcode überprüft dies (siehe Zeile 25 im Indikatorcode oben). Aber wenn Sie sich diesen Code ansehen, werden Sie feststellen, dass es einen Unterschied zwischen dem Code und dem Video gibt: einige Dinge sind tatsächlich anders. Aber keine Sorge, Sie werden bald Zugang zu dem im Video gezeigten Code haben, sodass alles zuverlässiger sein wird. Der Unterschied zwischen dem Video und dem obigen Code besteht darin, dass ich damals nicht verstand, warum der Indikator zwar im Chart aufgeführt war, aber nicht angezeigt wurde.
Ich musste den Code ändern, um herauszufinden, warum es nicht richtig funktionierte. Deshalb habe ich Sie auch gebeten, nicht beleidigt zu sein, wenn ich sage, dass Unwissenheit ein Segen ist. Ich habe auch nicht verstanden, warum sich der Code so verhalten hat.
Ich werde jedoch nicht behaupten, dass alles vollständig in Ordnung ist, denn das wäre unehrlich von meiner Seite. Die Übergabe einer ID aus einem Chart an einen Indikator bewirkt, dass der Indikator angezeigt wird. Aber... Bevor wir uns mit diesem „aber“ beschäftigen, wollen wir uns ansehen, wie wir den Code geändert haben, damit alles wieder funktioniert. Zumindest wird jetzt der Kontrollindikator in dem Chart angezeigt.
Dazu musste der Quellcode des Dienstes nicht geändert werden, wohl aber der Code der Header-Datei C_Replay.mqh. Der vollständige Text der geänderten Datei ist nachstehend aufgeführt:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_ConfigService.mqh" 005. //+------------------------------------------------------------------+ 006. class C_Replay : private C_ConfigService 007. { 008. private : 009. long m_IdReplay; 010. struct st01 011. { 012. MqlRates Rate[1]; 013. datetime memDT; 014. }m_MountBar; 015. struct st02 016. { 017. bool bInit; 018. double PointsPerTick; 019. MqlTick tick[1]; 020. }m_Infos; 021. //+------------------------------------------------------------------+ 022. void AdjustPositionToReplay(const bool bViewBuider) 023. { 024. u_Interprocess Info; 025. MqlRates Rate[def_BarsDiary]; 026. int iPos, nCount; 027. 028. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 029. if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return; 030. iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1))); 031. Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time); 032. CreateBarInReplay(true); 033. if (bViewBuider) 034. { 035. Info.s_Infos.isWait = true; 036. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 037. }else 038. { 039. for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++); 040. for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++); 041. nCount = CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount); 042. } 043. for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false); 044. CustomTicksAdd(def_SymbolReplay, m_Ticks.Info, m_ReplayCount); 045. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 046. Info.s_Infos.isWait = false; 047. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 048. } 049. //+------------------------------------------------------------------+ 050. inline void CreateBarInReplay(const bool bViewTicks) 051. { 052. #define def_Rate m_MountBar.Rate[0] 053. 054. bool bNew; 055. double dSpread; 056. int iRand = rand(); 057. 058. if (BuildBar1Min(m_ReplayCount, def_Rate, bNew)) 059. { 060. m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount]; 061. if ((!m_Ticks.bTickReal) && (m_Ticks.ModePlot == PRICE_EXCHANGE)) 062. { 063. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 064. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 065. { 066. m_Infos.tick[0].ask = m_Infos.tick[0].last; 067. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 068. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 069. { 070. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 071. m_Infos.tick[0].bid = m_Infos.tick[0].last; 072. } 073. } 074. if (bViewTicks) CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 075. CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate); 076. } 077. m_ReplayCount++; 078. #undef def_Rate 079. } 080. //+------------------------------------------------------------------+ 081. void ViewInfos(void) 082. { 083. MqlRates Rate[1]; 084. 085. ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX); 086. ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX); 087. ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE); 088. m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 089. m_MountBar.Rate[0].time = 0; 090. m_Infos.bInit = true; 091. CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, Rate); 092. if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE)) 093. for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++); 094. if (Rate[0].close > 0) 095. { 096. if (m_Ticks.ModePlot == PRICE_EXCHANGE) m_Infos.tick[0].last = Rate[0].close; else 097. { 098. m_Infos.tick[0].bid = Rate[0].close; 099. m_Infos.tick[0].ask = Rate[0].close + (Rate[0].spread * m_Infos.PointsPerTick); 100. } 101. m_Infos.tick[0].time = Rate[0].time; 102. m_Infos.tick[0].time_msc = Rate[0].time * 1000; 103. }else 104. m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount]; 105. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 106. ChartRedraw(m_IdReplay); 107. } 108. //+------------------------------------------------------------------+ 109. void CreateGlobalVariable(const string szName, const double value) 110. { 111. GlobalVariableDel(szName); 112. GlobalVariableTemp(szName); 113. GlobalVariableSet(szName, value); 114. } 115. //+------------------------------------------------------------------+ 116. void AddIndicatorControl(void) 117. { 118. int handle; 119. 120. handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay); 121. ChartIndicatorAdd(m_IdReplay, 0, handle); 122. IndicatorRelease(handle); 123. } 124. //+------------------------------------------------------------------+ 125. public : 126. //+------------------------------------------------------------------+ 127. C_Replay(const string szFileConfig) 128. { 129. m_ReplayCount = 0; 130. m_dtPrevLoading = 0; 131. m_Ticks.nTicks = 0; 132. m_Infos.bInit = false; 133. Print("************** Market Replay Service **************"); 134. srand(GetTickCount()); 135. GlobalVariableDel(def_GlobalVariableReplay); 136. SymbolSelect(def_SymbolReplay, false); 137. CustomSymbolDelete(def_SymbolReplay); 138. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol); 139. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 140. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 141. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 142. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 143. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 144. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 145. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 146. m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1); 147. SymbolSelect(def_SymbolReplay, true); 148. } 149. //+------------------------------------------------------------------+ 150. ~C_Replay() 151. { 152. ArrayFree(m_Ticks.Info); 153. ArrayFree(m_Ticks.Rate); 154. m_IdReplay = ChartFirst(); 155. do 156. { 157. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 158. ChartClose(m_IdReplay); 159. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 160. for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++); 161. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 162. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 163. CustomSymbolDelete(def_SymbolReplay); 164. GlobalVariableDel(def_GlobalVariableReplay); 165. GlobalVariableDel(def_GlobalVariableServerTime); 166. Print("Finished replay service..."); 167. } 168. //+------------------------------------------------------------------+ 169. bool ViewReplay(ENUM_TIMEFRAMES arg1) 170. { 171. #define macroError(A) { Print(A); return false; } 172. u_Interprocess info; 173. 174. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 175. macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); 176. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 177. macroError("Asset configuration is not complete, need to declare the ticket value."); 178. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 179. macroError("Asset configuration not complete, need to declare the minimum volume."); 180. if (m_IdReplay == -1) return false; 181. if ((m_IdReplay = ChartFirst()) > 0) do 182. { 183. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 184. { 185. ChartClose(m_IdReplay); 186. ChartRedraw(); 187. } 188. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 189. Print("Waiting for [Market Replay] indicator permission to start replay ..."); 190. info.ServerTime = ULONG_MAX; 191. CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value); 192. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 193. AddIndicatorControl(); 194. while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); 195. info.s_Infos.isHedging = TypeAccountIsHedging(); 196. info.s_Infos.isSync = true; 197. GlobalVariableSet(def_GlobalVariableReplay, info.df_Value); 198. 199. return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); 200. #undef macroError 201. } 202. //+------------------------------------------------------------------+ 203. bool LoopEventOnTime(const bool bViewBuider) 204. { 205. u_Interprocess Info; 206. int iPos, iTest, iCount; 207. 208. if (!m_Infos.bInit) ViewInfos(); 209. iTest = 0; 210. while ((iTest == 0) && (!_StopFlag)) 211. { 212. iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1); 213. iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value) ? iTest : -1); 214. iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest); 215. if (iTest == 0) Sleep(100); 216. } 217. if ((iTest < 0) || (_StopFlag)) return false; 218. AdjustPositionToReplay(bViewBuider); 219. Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; 220. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 221. iPos = iCount = 0; 222. while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) 223. { 224. iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); 225. CreateBarInReplay(true); 226. while ((iPos > 200) && (!_StopFlag)) 227. { 228. if (ChartSymbol(m_IdReplay) == "") return false; 229. GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value); 230. if (!Info.s_Infos.isPlay) return true; 231. Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks); 232. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 233. Sleep(195); 234. iPos -= 200; 235. iCount++; 236. if (iCount > 4) 237. { 238. iCount = 0; 239. GlobalVariableGet(def_GlobalVariableServerTime, Info.df_Value); 240. if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else 241. { 242. Info.ServerTime += 1; 243. Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); 244. }; 245. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 246. } 247. } 248. } 249. return (m_ReplayCount == m_Ticks.nTicks); 250. } 251. //+------------------------------------------------------------------+ 252. }; 253. //+------------------------------------------------------------------+ 254. #undef macroRemoveSec 255. #undef def_SymbolReplay 256. //+------------------------------------------------------------------+
Quellcode der Klasse C_Replay.mqh
Wir haben diesen Code zwar so geändert, dass er das unterstützt, was wir brauchen, aber in naher Zukunft werden wir noch viel mehr damit machen können. Sehen wir uns zunächst an, was neu hinzugekommen ist. Wie Sie sehen können, bleibt der größte Teil des Codes derselbe wie im vorherigen Artikel. Da ich möchte, dass Sie ein gutes Verständnis dafür haben, wie dies implementiert wird, habe ich den vollständigen Code beigefügt, damit Sie sicher wissen, wo Sie die verwendeten Funktionen platzieren müssen.
In Zeile 116 habe ich eine neue Prozedur hinzugefügt, um den Kontrollindikator auf dem Chart zu starten. Diese Prozedur wird in Zeile 193 aufgerufen, d.h. unmittelbar nachdem der Chart vom Dienst geöffnet und vom MetaTrader 5 angezeigt wurde. Aber gehen wir zurück zu Zeile 116. Als erstes erstellen wir in Zeile 120 ein Handle, das auf den Indikator im Servicecode verweist. Denken Sie daran, dass der Indikator als Ressource in die ausführbare Datei des Dienstes eingebettet ist. Nachdem wir MetaTrader 5 mitgeteilt haben, wo sich der Indikator befindet, müssen wir ihm einige Informationen zur Verfügung stellen. Er repräsentiert m_IdReplay, die durch den Aufruf von ChartOpen erstellte Chart-ID.
So weiß der Indikator, welche der Chart-IDs korrekt ist. Bitte beachten Sie dies. Auch wenn Sie einen anderen Chart öffnen, der mit dem Reaplay-Symbol verknüpft ist, wird der Indikator nicht angezeigt, sondern nur in dem Chart, der vom Dienst erstellt wurde. Dies wird in Zeile 121 vorgeschrieben und ausgeführt. Dann, in Zeile 122, geben wir das erstellte Handle frei, da wir ihn nicht mehr benötigen.
Was wir gerade gesehen haben, ist jedoch nur ein Teil der Lösung. Der andere Teil befindet sich im Quellcode des Steuerkennzeichens. Sie können es unten sehen:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property description "Control indicator for the Replay-Simulator service." 05. #property description "This one doesn't work without the service loaded." 06. #property version "1.50" 07. #property link "https://www.mql5.com/en/articles/11871" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Controls.mqh> 12. //+------------------------------------------------------------------+ 13. C_Terminal *terminal = NULL; 14. C_Controls *control = NULL; 15. //+------------------------------------------------------------------+ 16. input long user00 = 0; //ID 17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. u_Interprocess Info; 21. 22. ResetLastError(); 23. if (CheckPointer(control = new C_Controls(terminal = new C_Terminal(user00))) == POINTER_INVALID) 24. SetUserError(C_Terminal::ERR_PointerInvalid); 25. if ((!(*terminal).IndicatorCheckPass("Market Replay Control")) || (_LastError != ERR_SUCCESS)) 26. { 27. Print("Control indicator failed on initialization."); 28. return INIT_FAILED; 29. } 30. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 31. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 32. (*control).Init(Info.s_Infos.isPlay); 33. 34. return INIT_SUCCEEDED; 35. } 36. //+------------------------------------------------------------------+ 37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 38. { 39. static bool bWait = false; 40. u_Interprocess Info; 41. 42. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 43. if (!bWait) 44. { 45. if (Info.s_Infos.isWait) 46. { 47. EventChartCustom(user00, C_Controls::ev_WaitOn, 1, 0, ""); 48. bWait = true; 49. } 50. }else if (!Info.s_Infos.isWait) 51. { 52. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 53. bWait = false; 54. } 55. 56. return rates_total; 57. } 58. //+------------------------------------------------------------------+ 59. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 60. { 61. (*control).DispatchMessage(id, lparam, dparam, sparam); 62. } 63. //+------------------------------------------------------------------+ 64. void OnDeinit(const int reason) 65. { 66. switch (reason) 67. { 68. case REASON_REMOVE: 69. case REASON_CHARTCLOSE: 70. if (ChartSymbol(user00) != def_SymbolReplay) break; 71. GlobalVariableDel(def_GlobalVariableReplay); 72. ChartClose(user00); 73. break; 74. } 75. delete control; 76. delete terminal; 77. } 78. //+------------------------------------------------------------------+
Quellcode des Kontrollindikators
Beachten Sie, dass wir in Zeile 16 nun einen Eingang haben, was bedeutet, dass der Indikator einen Parameter erhält. Dies ist einer der Gründe, warum der Nutzer keinen direkten Zugriff auf diesen Indikator hat und ihn nicht manuell auf dem Chart platzieren kann. Dieser Parameter, den der Indikator in Zeile 16 erhält, gibt die ID des Charts an, auf dem die Objekte platziert werden sollen. Dieser Wert muss korrekt ausgefüllt werden. Standardmäßig ist es NULL, d.h. wenn der Dienst versucht, einen Indikator zu platzieren, aber kein Chart bereitstellt, wird ein Fehler erzeugt. Die Fehlermeldung erscheint in Zeile 27. Dies erklärt den Unterschied zwischen dem, was erwartet wurde, und dem, was in Video 01 gezeigt wurde.
Beachten Sie nun, wo der in Zeile 16 eingegebene Parameterwert verwendet wird. Sie wird an mehreren Stellen verwendet, vor allem aber in Zeile 23, wo wir der Klasse C_Terminal mitteilen, dass sie beim Nachschlagen der Chart-ID nicht den generierten Wert verwenden soll. Die Klasse C_Terminal sollte den Wert verwenden, der von dem Dienst gemeldet wurde, der das Chart erstellt hat. Wie Sie sehen können, verwenden auch andere Teile den in Zeile 16 angegebenen Wert. Nachdem wir jedoch die Servicedatei neu kompiliert und in MetaTrader 5 ausgeführt haben, erhalten wir das in Video 02 gezeigte Ergebnis.
Video 02
Sehen Sie sich Video 02 genau an. Wie Sie sehen können, verhält sich das System ziemlich seltsam, wenn wir versuchen, mit ihm zu interagieren. Die Frage ist, warum dies geschieht. Wir haben keine Änderungen vorgenommen, die dieses Verhalten verursachen könnten.
Dies ist das zweite Problem, das wir lösen müssen. Die Lösung für dieses Problem ist jedoch viel komplizierter. Der Grund dafür liegt nicht im Service, nicht im Kontrollindikator und auch nicht in der MetaTrader 5 Plattform. Dies ist die Interaktion oder fehlende Interaktion des Mauszeigers mit Objekten auf dem Chart.
An diesem Punkt denken Sie wahrscheinlich: „Wie sollen wir einen Fehler beheben, der nicht existierte, bevor wir anfingen, scheinbar perfekt funktionierenden Code zu ändern?“ Das war, bevor die verhängnisvolle Entscheidung getroffen wurde, dem Nutzer die Möglichkeit zu geben, eine nutzerdefinierte Vorlage zu verwenden, anstatt derjenigen, die das Reaplay-/Simulatorsystem seit langem verwendete. Gut. Das ist Programmierung: Sie löst Probleme, die bei der Einführung neuer Elemente auftreten, und sie löst zukünftige Probleme.
Doch bevor wir uns diesem Problem zuwenden und das Problem der Interaktion des Mausindikators mit den vom Kontrollindikator erstellten Chartobjekten lösen, sollten wir eine Funktion implementieren, die es dem Nutzer ermöglicht, eine nutzerdefinierte Vorlage zu verwenden. Dies ermöglicht dem Nutzer, eine voreingestellte Vorlage anzuwenden, die für das spezifische Symbol auf einem Demo- oder Realkonto verwendet werden soll. Aber jetzt werden wir dem Nutzer erlauben, dieselbe Vorlage im Replay-/Simulatorsystem zu verwenden.
Es ist nicht schwer, dies zu tun. Da die Nutzer jedoch zu unlogischen Handlungen neigen können, müssen wir dafür sorgen, dass der Kontrollindikator im Chart bleibt. Wir sollten sie auch dann schützen, wenn der Nutzer darauf besteht, das Replay-/Simulatorsystem auf völlig unbeabsichtigte Weise zu nutzen.
In diesem Fall muss der Nutzer dem Dienst zunächst die Möglichkeit geben, das Chart nach einer bestimmten Vorlage zu öffnen. Dies lässt sich mit einigen kleinen Änderungen leicht bewerkstelligen. Um nicht den gesamten Code zu wiederholen, werde ich nur den Teil der Funktion veröffentlichen, der geändert werden muss. Schauen wir uns zunächst den Code des Dienstes an. Er befindet gleich hier:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property copyright "Daniel Jose" 05. #property version "1.50" 06. #property description "Replay-Simulator service for MT5 platform." 07. #property description "This is dependent on the Market Replay indicator." 08. #property description "For more details on this version see the article." 09. #property link "https://www.mql5.com/en/articles/11871" 10. //+------------------------------------------------------------------+ 11. #define def_IndicatorControl "Indicators\\Replay\\Market Replay.ex5" 12. #resource "\\" + def_IndicatorControl 13. //+------------------------------------------------------------------+ 14. #include <Market Replay\Service Graphics\C_Replay.mqh> 15. //+------------------------------------------------------------------+ 16. input string user00 = "Forex - EURUSD.txt"; //Replay Configuration File. 17. input ENUM_TIMEFRAMES user01 = PERIOD_M5; //Initial Graphic Time. 18. input string user02 = "Default"; //Template File Name 19. //+------------------------------------------------------------------+ 20. void OnStart() 21. { 22. C_Replay *pReplay; 23. 24. pReplay = new C_Replay(user00); 25. if ((*pReplay).ViewReplay(user01, user02)) 26. { 27. Print("Permission granted. Replay service can now be used..."); 28. while ((*pReplay).LoopEventOnTime(false)); 29. } 30. delete pReplay; 31. } 32. //+------------------------------------------------------------------+
Quellcode des Dienstes
Wir haben eine neue Zeile 18 hinzugefügt, die dem Nutzer die Möglichkeit gibt, anzugeben, welche Vorlage verwendet werden soll, wenn der Dienst ein SymbolChart zur Verwendung als Reaplay oder Simulator öffnet. Dieser Wert wird in Zeile 25 an die Klasse übergeben, wo wir die notwendigen Konfigurationen vornehmen. Um zu verstehen, was hier geschieht, sehen Sie sich einen Ausschnitt aus diesem Verfahren an. Ich gebe nur dieses Fragment wieder, da es sinnlos wäre, den gesamten Code zu wiederholen.
175. //+------------------------------------------------------------------+ 176. bool ViewReplay(ENUM_TIMEFRAMES arg1, const string szNameTemplate) 177. { 178. #define macroError(A) { Print(A); return false; } 179. u_Interprocess info; 180. 181. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 182. macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); 183. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 184. macroError("Asset configuration is not complete, need to declare the ticket value."); 185. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 186. macroError("Asset configuration not complete, need to declare the minimum volume."); 187. if (m_IdReplay == -1) return false; 188. if ((m_IdReplay = ChartFirst()) > 0) do 189. { 190. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 191. { 192. ChartClose(m_IdReplay); 193. ChartRedraw(); 194. } 195. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 196. Print("Waiting for [Market Replay] indicator permission to start replay ..."); 197. info.ServerTime = ULONG_MAX; 198. CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value); 199. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 200. if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl")) 201. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 202. AddIndicatorControl(); 203. while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); 204. info.s_Infos.isHedging = TypeAccountIsHedging(); 205. info.s_Infos.isSync = true; 206. GlobalVariableSet(def_GlobalVariableReplay, info.df_Value); 207. 208. return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); 209. #undef macroError 210. } 211. //+------------------------------------------------------------------+
Quellcodefragment (Aktualisierung) C_Replay.mqh
Beachten Sie, dass wir in Zeile 176 einen zusätzlichen Parameter hinzufügen, der der Klasse mitteilt, welche Vorlage geladen werden soll. In Zeile 200 versuchen wir dann, die vom Nutzer bereitgestellte Vorlage anzuwenden. Wenn der Versuch fehlschlägt, informieren wir den Nutzer in Zeile 201 darüber und wechseln zur Standardvorlage. Lassen Sie uns nun eine Pause einlegen, um etwas zu erklären. Standardmäßig verwendet MetaTrader 5 immer eine vordefinierte Vorlage, sodass kein neuer Aufruf erforderlich ist, um sie zu laden und auf das Chart anzuwenden.
An dieser Stelle gibt es etwas Interessantes zu klären: Wenn Sie mit mehreren Symbolen arbeiten und für jedes Symbol gezielte Studien unter Verwendung einer bestimmten Vorlage durchführen möchten, kann es sinnvoll sein, den Namen der gewünschten Vorlage in die Konfigurationsdatei für die Modellierung aufzunehmen. Er wird vorkonfiguriert sein, und die Einführung des Dienstes muss nicht gemeldet werden. Aber das ist nur eine Idee für diejenigen, die es tun wollen. Das werde ich in diesem System nicht tun. Außerdem hat diese Geschichte mit der Schablone noch einen weiteren Aspekt. Das ist genau der Aspekt, der den Programmierern Kopfzerbrechen bereitet. Der Aspekt ist der Nutzer.
Warum ist der Nutzer ein Problem für uns? Das macht vielleicht nicht viel Sinn. Aber das ist ein großes Problem, denn wir können MetaTrader 5 anweisen, den Dienst zu starten, und im richtigen Moment kann die Chartvorlage für den Replay/Simulator geändert werden. Dies geschieht, ohne dass der Dienst neu gestartet wird, damit er die Änderungen übernehmen kann. Vielleicht haben Sie den Kern des Problems nicht verstanden. In diesem Fall haben die Vorlageneinstellungen Vorrang vor den Charteinstellungen, und das Problem ist, dass der Kontrollindikator aus dem Chart entfernt wird.
Der Nutzer kann den Kontrollindikator nicht manuell auf dem Chart platzieren. Es gibt jedoch Möglichkeiten, dies zu tun. Aber wenn der Nutzer nicht weiß, wie man in MetaTrader 5 und MQL5 arbeitet, dann wird er nicht in der Lage sein, den Kontrollindikator auf dem Chart zu ersetzen. Diese Art von Situation ist genau die Art von Fehler, für die der Nutzer verantwortlich ist. Aber derjenige, der für die Korrektur verantwortlich ist, ist der Programmierer.
Wenn eine Vorlage geändert wird, benachrichtigt MetaTrader 5 die Programme auf dem Chart, dass eine Vorlagenänderung stattgefunden hat, sodass diese Maßnahmen ergreifen können. So funktioniert der MetaTrader 5:
void OnDeinit(const int reason) { switch (reason) { case REASON_TEMPLATE: Print("Template change ..."); break;
Wenn eine Vorlage geändert wird, tritt in MetaTrader 5 ein DeInit-Ereignis ein, das die oben gezeigte Prozedur aufruft. Wir können die Bedingung überprüfen, die MetaTrader 5 dazu veranlasst hat, das DeInit-Ereignis aufzurufen, und wenn es sich um eine Template-Änderung handelt, wird die entsprechende Meldung im Terminal angezeigt.
Mit anderen Worten: Wir können wissen, ob sich die Vorlage geändert hat, aber dieses Wissen führt nicht sofort zu einer Rücksetzung des Indikators. Hier müssen wir eine Entscheidung treffen: den Dienst zur Beendigung zwingen oder den Dienst zwingen, den Kontrollindikator auf dem Chart neu zu starten. Meiner subjektiven Meinung nach sollten wir den Dienst zwangsweise einstellen. Der Grund dafür ist einfach. Wenn der Nutzer die Vorlage, die für das Replay-/Simulator-Chart verwendet wird, anpassen darf, warum sollten wir dann dem Nutzer erlauben, die Vorlage manuell zu ändern? Es ist sinnlos. Meiner Meinung nach ist es daher am besten, den Dienst zu beenden und den Nutzer aufzufordern, beim Start des Dienstes eine Vorlage bereitzustellen. Warum wäre es sonst notwendig, dass der Nutzer dem Dienst eine Vorlage zur Verfügung stellt?
Wir müssen also eine weitere einfache Aktualisierung im Code des Indikators vornehmen, die Sie unten sehen können:
64. //+------------------------------------------------------------------+ 65. void OnDeinit(const int reason) 66. { 67. switch (reason) 68. { 69. case REASON_TEMPLATE: 70. Print("Modified template. Replay/simulation system shutting down."); 71. case REASON_PARAMETERS: 72. case REASON_REMOVE: 73. case REASON_CHARTCLOSE: 74. if (ChartSymbol(user00) != def_SymbolReplay) break; 75. GlobalVariableDel(def_GlobalVariableReplay); 76. ChartClose(user00); 77. break; 78. } 79. delete control; 80. delete terminal; 81. } 82. //+------------------------------------------------------------------+
Fragment des Quellcodes der Kontrollindikator (Update)
Beachten Sie, dass wir in Zeile 69 einen Test hinzufügen, um zu prüfen, warum der Indikator aus dem Chart entfernt wird. Wenn der Grund eine Vorlagenänderung ist, wird in Zeile 70 eine Meldung auf dem Terminal ausgegeben, und wir erhalten dasselbe Ergebnis, als ob der Nutzer das Chart geschlossen hätte oder der Indikator vom Nutzer gelöscht worden wäre. Das heißt, der Dienst wird eingestellt. Diese Entscheidung mag zu radikal erscheinen, aber wie ich bereits erklärt habe, gibt es keinen Grund, etwas anderes zu tun.
Wir haben einen Fehler behoben, und jetzt haben wir ein weiteres Problem: Der Nutzer kann einfach jeden Parameter ändern, der vom Kontrollindikator verwendet und vom Dienst an ihn übergeben wird. Wir sind noch dabei, das System zu konfigurieren, daher können neue Parameter hinzukommen. Es ist viel schwieriger, mit einer solchen Situation fertig zu werden, aber wir werden einen radikalen Ansatz wählen, um dieses Problem zu lösen. Diese Lösung wurde bereits in dem obigen Fragment umgesetzt.
Achten Sie auf die Zeile 71. Eine solche Linie gab es vorher nicht. Sie wurde hinzugefügt, um zu verhindern, dass der Nutzer einen der Parameter ändert, die der Dienst an den Kontrollindikator übergeben hat. Wenn dies geschieht, erzeugt MetaTrader 5 ein DeInit-Ereignis mit dem Argument der Parameteränderung. Wir werden zu diesem Zeitpunkt keinen Fehler melden, da der Fehler auftritt, wenn MetaTrader 5 den Indikator neu startet. Da aber ein klügerer Nutzer die ID des tatsächlichen Charts angeben kann, schließen wir das Replay-/Simulationschart in Zeile 76. Wenn der Dienst also prüft, ob das Chart geöffnet ist, wird er einen Fehler erhalten, was bedeutet, dass der Dienst beendet werden sollte. Daher korrigieren wir auch diesen Punkt.
Schlussfolgerung
In diesem Artikel haben wir mehrere primäre Fehler behoben, die dadurch verursacht wurden, dass der Kontrollindikator für den Nutzer nicht mehr verfügbar war. Er ist zwar immer noch im Indikatorenfenster über die Tastenkombination STRG+I sichtbar, aber er ist nicht mehr in der Liste der Indikatoren enthalten, die in jedem Chart verwendet werden können. Diese Art von Änderung beinhaltet eine Menge Modifikationen am Code, um ihn stabiler und konsistenter zu machen und um zu verhindern, dass der Nutzer etwas tut, was wir nicht wollen oder erwarten.
Allerdings gibt es immer noch ein Problem, das es dem Nutzer erschwert, mit dem Indikator zu interagieren und den Kontrollindikator mithilfe des Mauszeigers anzupassen und zu manipulieren. Aber dieses Problem hängt mit etwas zusammen, das wir bald beheben werden, wodurch das System noch freier und vollständig modular wird.
In Video 03, das Sie gleich unten finden, können Sie sehen, wie sich das System jetzt mit den in diesem Artikel beschriebenen Aktualisierungen verhält. Da der Code jedoch noch instabil ist, werde ich keine Dateien an diesen Artikel anhängen.
Video 03
Im nächsten Artikel werden wir uns weiter mit Fragen und Problemen im Zusammenhang mit der Interaktion zwischen dem Nutzer und dem Reaplay-/Simulationsdienst befassen.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/11871






- 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.