
Entwicklung eines Replay Systems (Teil 55): Steuermodul
Einführung
Im vorherigen Artikel „Entwicklung eines Replay Systems (Teil 54): Die Geburt des ersten Moduls“, wir haben das erste echte Modul unseres neuen Replay/Simulator-Systems zusammengebaut. Neben der Möglichkeit, sie in dem von uns entwickelten System zu verwenden, werden wir auch in der Lage sein, die Module individuell und personalisiert einzusetzen, um einen hohen Programmieraufwand für die Erstellung eines solchen Systems zu vermeiden. So oder so, sobald das Modul erstellt ist, können wir es leicht anpassen, ohne es neu kompilieren zu müssen.
Dazu müssen Sie nur eine Nachricht an das Modul senden, um sein Aussehen oder seine Funktionsweise zu ändern, was mit einem einfachen Skript leicht zu bewerkstelligen ist.
Basierend auf dem, was wir in den letzten Artikeln betrachtet haben, haben wir also die Möglichkeit, ein System zu erstellen, das sowohl auf einem echten Konto als auch auf einem Demokonto verwendet werden kann. Aber das ist nicht das Einzige, was wir tun können. Unter anderem können wir ein Replay/Simulator-System erstellen, das sich sehr ähnlich verhält wie ein echtes oder Demo-Konto.
Der Hauptvorteil dieses neuen Modells, mit dem wir beginnen werden, ist jedoch, dass Sie dieselben Instrumente und Anwendungen sowohl im Replay/Simulator-System als auch bei Ihrer täglichen Arbeit im MetaTrader 5 für das Training auf einem Demokonto oder für den Handel auf einem echten Konto verwenden können.
Nun, da unser Mauszeiger fertig ist, können wir damit beginnen, unseren Kontrollzeiger zu erstellen oder besser gesagt anzupassen, sodass er auf modulare Weise funktioniert. Zu dem soeben Gesagten bedarf es einer kurzen Erklärung.
Bis vor kurzem verwendete das Wiedergabe-/Simulatorsystem globale Terminalvariablen, um ein gewisses Maß an Kommunikation zwischen den Programmen zu ermöglichen, die für die Interaktion, die Steuerung und den Zugriff auf den Wiedergabe-/Simulatorservice erforderlich sind.
Da wir nun ein Modulsystem verwenden, bei dem die Nachrichtenübermittlung über Nutzerereignisse erfolgt, brauchen wir keine globalen Terminalvariablen mehr zu verwenden. In diesem Sinne können wir nun alle globalen Terminalvariablen, die zuvor verwendet wurden, entfernen. In diesem Fall müssen wir das System jedoch so anpassen, dass der Informationsfluss zwischen den Programmen weiterhin gewährleistet ist.
Die Modellierung eines Informationsübertragungssystems ist eine Aufgabe, die große Aufmerksamkeit und Vorsicht erfordert, da es einfach keine Möglichkeit gibt, die Informationen später zu lesen. Wenn sich ein Programm oder eine Anwendung nicht auf dem Chart befindet, wenn Informationen über ein nutzerdefiniertes Ereignis empfangen werden, gehen diese Informationen verloren. Daher sind zusätzliche Mechanismen erforderlich, um die gleichen Informationen erneut zu senden, bis wir die Bestätigung haben, dass sie von der vorgesehenen Anwendung oder dem Programm empfangen wurden.
Auf der Grundlage dieses Kriteriums wurde beschlossen, dass das Wiedergabe-/Simulatorsystem drei Kernprogramme enthalten sollte, die für einen minimalen Betrieb erforderlich sind. Von diesen Programmen sind nur zwei für den Nutzer sichtbar: das Programm, das für den Dienst selbst zuständig ist, und der Mauszeiger. Das Steuerkennzeichen wird wie eine Ressource des Serviceprogramms behandelt, d.h. es kann nicht verwendet werden, ohne Dienstleistungen zu erbringen.
Nach dieser kurzen Erläuterung wollen wir uns nun ansehen, wie der Kontrollindikator geändert wurde, damit er seine Aufgabe erfüllen kann, nämlich die Verwaltung des Wiedergabe-/Simulationsdienstes.
Ändern des Kontrollanzeigers
Es gab nicht viele Änderungen, die am Kontrollindikator vorgenommen werden mussten, da viele davon bereits im vorherigen Schritt vorgenommen wurden, als wir mit dem Entfernen der globalen Variablen des Terminals begannen. Ohne eine klare Vorstellung davon, wie genau der Nachrichtenaustausch abläuft, ist es jedoch auch unmöglich zu verstehen, wie das System seine Aufgaben erfüllen wird.
Lassen Sie uns also versuchen, alles von Anfang an klar zu machen, damit Sie sich beim Lesen künftiger Artikel nicht verirren.
Wenn der Indikator auf dem Chart platziert ist, kann der Nutzer mehrere Parameter konfigurieren. Diese Parameter beziehen sich auf die Indikator-Eingangsvariablen. In bestimmten Momenten sind diese Variablen jedoch eher ein Hindernis als eine Hilfe. Verstehen Sie mich nicht falsch, ich versuche nicht, radikale Änderungen zu fordern. Aber wenn wir dem Nutzer erlauben, auf eine Variable zuzugreifen, um den Indikator (in diesem Fall) im Voraus zu konfigurieren, öffnen wir die Tür zu möglichen Problemen.
Wenn man einmal davon absieht, dass der Nutzer versehentlich etwas ändern könnte, was er nicht sollte, erweisen sich genau diese Variablen als sehr nützlich. So sehr, dass wir sie verwenden, um die Chart-ID an den Indikator zu übergeben. Nicht, dass der Indikator diese Information wirklich benötigt, aber man sollte bedenken, dass zum Zeitpunkt der Platzierung des Indikators im Chart seine ID von der erwarteten ID für die Objekte abweichen kann. Dies wurde bereits in einem der früheren Artikel dieser Reihe erörtert.
Wir können zwar das Nachrichtensystem verwenden, um die ID an den Indikator zu übermitteln, aber da das Chart vom Dienst geöffnet wird und dieser seine ID kennt, würde dies den Code sowohl des Dienstes als auch des Indikators nur unnötig verkomplizieren. Aus diesem Grund werde ich alles so belassen, wie es bisher gemacht worden ist. Allerdings müssen wir einige kleine Änderungen am Code des Kontrollindikators vornehmen, da wir keine globalen Terminalvariablen mehr für die Datenübergabe verwenden werden.
Nachfolgend finden Sie den vollständigen Code für die Datei C_Control.mqh. Da der größte Teil des Codes bereits in früheren Artikeln erklärt wurde, werden wir uns nur auf die neuen Teile konzentrieren, die einer Erklärung bedürfen.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_ButtonLeft def_PathBMP + "Left.bmp" 011. #define def_ButtonLeftBlock def_PathBMP + "Left_Block.bmp" 012. #define def_ButtonRight def_PathBMP + "Right.bmp" 013. #define def_ButtonRightBlock def_PathBMP + "Right_Block.bmp" 014. #define def_ButtonPin def_PathBMP + "Pin.bmp" 015. #resource "\\" + def_ButtonPlay 016. #resource "\\" + def_ButtonPause 017. #resource "\\" + def_ButtonLeft 018. #resource "\\" + def_ButtonLeftBlock 019. #resource "\\" + def_ButtonRight 020. #resource "\\" + def_ButtonRightBlock 021. #resource "\\" + def_ButtonPin 022. //+------------------------------------------------------------------+ 023. #define def_PrefixCtrlName "MarketReplayCTRL_" 024. #define def_PosXObjects 120 025. //+------------------------------------------------------------------+ 026. #define def_SizeButtons 32 027. #define def_ColorFilter 0xFF00FF 028. //+------------------------------------------------------------------+ 029. #include "..\Auxiliar\C_Terminal.mqh" 030. #include "..\Auxiliar\C_Mouse.mqh" 031. //+------------------------------------------------------------------+ 032. class C_Controls : private C_Terminal 033. { 034. protected: 035. private : 036. //+------------------------------------------------------------------+ 037. enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull}; 038. //+------------------------------------------------------------------+ 039. struct st_00 040. { 041. string szBarSlider, 042. szBarSliderBlock; 043. int Minimal; 044. }m_Slider; 045. struct st_01 046. { 047. C_DrawImage *Btn; 048. bool state; 049. int x, y, w, h; 050. }m_Section[eObjectControl::eNull]; 051. C_Mouse *m_MousePtr; 052. //+------------------------------------------------------------------+ 053. inline void CreteBarSlider(int x, int size) 054. { 055. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 064. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 070. } 071. //+------------------------------------------------------------------+ 072. void SetPlay(bool state) 073. { 074. if (m_Section[ePlay].Btn == NULL) 075. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause); 076. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1)); 077. } 078. //+------------------------------------------------------------------+ 079. void CreateCtrlSlider(void) 080. { 081. CreteBarSlider(77, 436); 082. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 083. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 084. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin); 085. PositionPinSlider(m_Slider.Minimal); 086. } 087. //+------------------------------------------------------------------+ 088. inline void RemoveCtrlSlider(void) 089. { 090. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 091. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 092. { 093. delete m_Section[c0].Btn; 094. m_Section[c0].Btn = NULL; 095. } 096. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B"); 097. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 098. } 099. //+------------------------------------------------------------------+ 100. inline void PositionPinSlider(int p) 101. { 102. int iL, iR; 103. 104. m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 105. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 106. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 107. m_Section[ePin].x += def_PosXObjects; 108. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 109. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 110. m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0))); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 112. } 113. //+------------------------------------------------------------------+ 114. inline eObjectControl CheckPositionMouseClick(int &x, int &y) 115. { 116. C_Mouse::st_Mouse InfoMouse; 117. 118. InfoMouse = (*m_MousePtr).GetInfoMouse(); 119. x = InfoMouse.Position.X_Graphics; 120. y = InfoMouse.Position.Y_Graphics; 121. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 122. { 123. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 124. return c0; 125. } 126. 127. return eNull; 128. } 129. //+------------------------------------------------------------------+ 130. public : 131. //+------------------------------------------------------------------+ 132. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 133. :C_Terminal(Arg0), 134. m_MousePtr(MousePtr) 135. { 136. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 137. if (_LastError != ERR_SUCCESS) return; 138. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 139. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 141. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 142. { 143. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 144. m_Section[c0].y = 25; 145. m_Section[c0].Btn = NULL; 146. } 147. m_Section[ePlay].x = def_PosXObjects; 148. m_Section[eLeft].x = m_Section[ePlay].x + 47; 149. m_Section[eRight].x = m_Section[ePlay].x + 511; 150. m_Slider.Minimal = INT_MIN; 151. } 152. //+------------------------------------------------------------------+ 153. ~C_Controls() 154. { 155. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 156. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 157. delete m_MousePtr; 158. } 159. //+------------------------------------------------------------------+ 160. void SetBuff(const int rates_total, double &Buff[]) 161. { 162. uCast_Double info; 163. 164. info._int[0] = m_Slider.Minimal; 165. info._int[1] = (m_Section[ePlay].state ? INT_MAX : INT_MIN); 166. Buff[rates_total - 1] = info.dValue; 167. } 168. //+------------------------------------------------------------------+ 169. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 170. { 171. int x, y; 172. static int iPinPosX = -1, six = -1, sps; 173. uCast_Double info; 174. 175. switch (id) 176. { 177. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 178. info.dValue = dparam; 179. iPinPosX = m_Slider.Minimal = info._int[0]; 180. if (info._int[1] == 0) SetUserError(C_Terminal::ERR_Unknown); else 181. { 182. SetPlay(info._int[1] == INT_MAX); 183. if (info._int[1] == INT_MIN) CreateCtrlSlider(); 184. } 185. break; 186. case CHARTEVENT_OBJECT_DELETE: 187. if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName) 188. { 189. if (sparam == (def_PrefixCtrlName + EnumToString(ePlay))) 190. { 191. delete m_Section[ePlay].Btn; 192. m_Section[ePlay].Btn = NULL; 193. SetPlay(m_Section[ePlay].state); 194. }else 195. { 196. RemoveCtrlSlider(); 197. CreateCtrlSlider(); 198. } 199. } 200. break; 201. case CHARTEVENT_MOUSE_MOVE: 202. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 203. { 204. case ePlay: 205. SetPlay(!m_Section[ePlay].state); 206. if (m_Section[ePlay].state) 207. { 208. RemoveCtrlSlider(); 209. m_Slider.Minimal = iPinPosX; 210. }else CreateCtrlSlider(); 211. break; 212. case eLeft: 213. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 214. break; 215. case eRight: 216. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 217. break; 218. case ePin: 219. if (six == -1) 220. { 221. six = x; 222. sps = iPinPosX; 223. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 224. } 225. iPinPosX = sps + x - six; 226. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 227. break; 228. }else if (six > 0) 229. { 230. six = -1; 231. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 232. } 233. break; 234. } 235. ChartRedraw(GetInfoTerminal().ID); 236. } 237. //+------------------------------------------------------------------+ 238. }; 239. //+------------------------------------------------------------------+ 240. #undef def_PosXObjects 241. #undef def_ButtonPlay 242. #undef def_ButtonPause 243. #undef def_ButtonLeft 244. #undef def_ButtonRight 245. #undef def_ButtonPin 246. #undef def_PrefixCtrlName 247. #undef def_PathBMP 248. //+------------------------------------------------------------------+
Quellcode von C_Control.mqh
Der Code enthält einige seltsam anmutende Dinge. Die erste befindet sich in Zeile 150, wo wir den minimalen Wert des Schieberegler-Offsets mit einer in MQL5 definierten Konstante, INT_MIN, festlegen. Dies ist ein negativer Wert, der das Minimum für eine Integer-Variable darstellt. Warum habe ich das getan? Es ist etwas schwierig, den Grund dafür zu verstehen, also haben Sie bitte etwas Geduld, denn es gibt noch andere Dinge, die geklärt werden müssen, um die Zeile 150 vollständig zu verstehen.
Der nächste Punkt, auf den Sie achten sollten, befindet sich in Zeile 160, wo wir eine Routine zum Schreiben von Daten in den Puffer für Kontrollanzeigen haben. In diesem Stadium werden nur zwei Werte erfasst. Ich weiß nicht, ob wir in Zukunft mehr Werte schreiben müssen, aber im Moment werden diese beiden Werte zu einem einzigen Doppelwert komprimiert, sodass er nur eine Position im Puffer einnimmt.
Für diese Komprimierung verwenden wir eine in Zeile 162 deklarierte Union. In Zeile 164 wird dann der Wert angegeben, der vom Nutzer bei der Betätigung des Schiebereglers eingestellt werden soll. Achtung: Wir werden die vom Nutzer geänderte Position des Schiebereglers speichern. In Zeile 165 geben wir den Status des Kontrollindikators an, d. h. ob er sich im Wiedergabe- oder Pausenmodus befindet.
Hier gibt es etwas Wichtiges. Wenn wir angeben, dass sich der Nutzer im Wiedergabemodus befindet, d. h. dass der Nutzer die Wiedergabetaste gedrückt hat, wird ein bestimmter Wert gespeichert. Ein anderer Wert wird für den Pausenmodus verwendet. Die Werte, die wir verwenden werden, sind die äußersten Enden des möglichen Bereichs für den Datentyp Ganzzahl. Auf diese Weise vermeiden wir die Mehrdeutigkeit der Verwendung von Null und garantieren gleichzeitig die Integrität der Informationen, was ihre spätere Prüfung erleichtert.
Dieser Punkt ist äußerst wichtig: Wenn Sie dies in der Phase der Erstellung des Kontrollindikators nicht verstehen, können Sie bei späteren Erläuterungen Schwierigkeiten bekommen. Denken Sie daran: Die Informationen kommen nicht direkt vom Kontrollindikator zum Dienst, sondern müssen den von uns verwendeten Kanal, den Puffer, durchlaufen. Ich werde in Kürze auf diesen Punkt zurückkommen, da einige Details geklärt werden müssen.
In Zeile 166 schließlich speichern wir den komprimierten Wert an einer bestimmten Stelle im Indikatorpuffer. In einem anderen Artikel dieser Reihe habe ich bereits den Grund für die Speicherung von Daten in dieser besonderen Position erläutert. Wenn Sie also noch Zweifel haben, empfehle ich Ihnen, frühere Artikel zur Klärung zu lesen.
Der nächste erklärungsbedürftige Teil des Codes ist der Nachrichtenbehandlung, der in Zeile 169 beginnt. Es gibt zwei besondere Punkte, die Aufmerksamkeit verdienen. Beginnen wir mit derjenigen, die weniger Details umfasst, aber dennoch mit dem oben Erklärten zusammenhängt. Springen wir also zu Zeile 209.
Diese Zeile ist interessant: Wir speichern den Wert, den der Nutzer beim Verschieben des Schiebereglers einstellt, in der Variablen m_Slider.Minimal. Der Grund dafür ist die Vereinfachung des Prozesses, indem alles auf Schlüsselpunkte des Codes konzentriert werden kann. Gäbe es die Zeile 209 nicht, müssten wir irgendwo im Code eine Prüfung durchführen, um die vom Nutzer eingestellte Position an den Puffer zu übergeben. Oder, noch schlimmer, wir müssten einen Weg finden, den vom Nutzer angegebenen Wert direkt an den Dienst zu übergeben, ohne globale Terminalvariablen zu verwenden. Bisher haben wir dafür eine globale Variable verwendet, aber jetzt werden wir mit einem Puffer arbeiten. Um wiederholte Überprüfungen und Anpassungen zu vermeiden, platzieren wir den Wert daher an einem leicht zugänglichen Ort. Bitte beachten Sie, dass dieser Wert erst dann gespeichert wird, wenn der Nutzer auf die Schaltfläche „Abspielen“ klickt.
Gehen wir nun zurück zum vorherigen Codeinhalt und sehen wir uns Zeile 177 an, wo wir ein nutzerdefiniertes Ereignis haben, dessen Zweck es ist, den Kontrollindikator zu initialisieren, damit der Schieberegler richtig positioniert werden kann.
Dieses nutzerdefinierte Ereignis wird von Zeit zu Zeit ausgelöst, aber beachten Sie, dass der Wert, der die Daten enthält, in einem doppelten Wert und in einer gepackten Form vorliegen wird. Daher müssen wir diese Informationen übersetzen und gleichzeitig ihre Sicherheit gewährleisten. Die empfangenen Informationen folgen dem gleichen Prinzip wie die Informationen, die wir im Puffer speichern. Allerdings gibt es hier eine Nuance: Wir überprüfen ihre Integrität.
Beachten Sie, dass es in Zeile 180 einen kleinen Test gibt. Er prüft, ob der Wert, der angibt, ob sich das System im Abspiel- oder Pausenmodus befindet, Null ist. Ist er Null, stimmt etwas nicht, der Kontrollanzeiger empfängt falsche Daten und es ist ein Fehler aufgetreten. Aus diesem Grund wird SetUserError aufgerufen. Normalerweise wird diese Funktion nicht wahrgenommen, aber wenn doch, müssen entsprechende Maßnahmen ergriffen werden. Wir werden diese Maßnahmen später im Indikatorcode erörtern.
Wenn alles in Ordnung ist, werden wir zwei weitere Aktionen durchführen. Die erste befindet sich in Zeile 182, wo wir eine Funktion aufrufen, die für die Anzeige der Wiedergabe- oder Pausentaste verantwortlich ist. Die zweite ist die in Zeile 183 gezeigte Prüfung. Wenn der Wert minimal ist, bedeutet dies, dass wir uns im Pausemodus befinden, sodass wir den Schieberegler neu erstellen müssen, damit der Nutzer ihn anpassen kann.
Das ist im Grunde alles. Sobald der Indikator dem Chart hinzugefügt wurde, funktioniert er nicht mehr, bis er durch ein nutzerdefiniertes Ereignis initialisiert wird. Aber wie das geschehen soll, werden wir später betrachten. Die Interaktion zwischen dem Mauszeiger und dem Steuerungsanzeiger erzeugt den gesamten Nachrichtenzyklus, der für die effektive Steuerung des Wiedergabe-/Simulationsdienstes erforderlich ist.
Schauen wir uns nun den nachstehenden Code für den Kontrollindikator in vollem Umfang an. Achten Sie genau auf die Erklärung dieses Codes, da wir an dieser Stelle die Grundlage für alle zukünftigen Module dieses Systems legen.
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.55" 07. #property link "https://www.mql5.com/pt/articles/11988" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. #property indicator_buffers 1 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Service Graphics\C_Controls.mqh> 13. //+------------------------------------------------------------------+ 14. C_Controls *control = NULL; 15. //+------------------------------------------------------------------+ 16. input long user00 = 0; //ID 17. //+------------------------------------------------------------------+ 18. double m_Buff[]; 19. int m_RatesTotal; 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. ResetLastError(); 24. if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID) 25. SetUserError(C_Terminal::ERR_PointerInvalid); 26. if (_LastError != ERR_SUCCESS) 27. { 28. Print("Control indicator failed on initialization."); 29. return INIT_FAILED; 30. } 31. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 32. ArrayInitialize(m_Buff, EMPTY_VALUE); 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. return m_RatesTotal = rates_total; 40. } 41. //+------------------------------------------------------------------+ 42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 43. { 44. (*control).DispatchMessage(id, lparam, dparam, sparam); 45. if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown) 46. { 47. Print("Internal failure in the messaging system..."); 48. ChartClose(user00); 49. } 50. (*control).SetBuff(m_RatesTotal, m_Buff); 51. } 52. //+------------------------------------------------------------------+ 53. void OnDeinit(const int reason) 54. { 55. switch (reason) 56. { 57. case REASON_TEMPLATE: 58. Print("Modified template. Replay/simulation system shutting down."); 59. case REASON_INITFAILED: 60. case REASON_PARAMETERS: 61. case REASON_REMOVE: 62. case REASON_CHARTCLOSE: 63. ChartClose(user00); 64. break; 65. } 66. delete control; 67. } 68. //+------------------------------------------------------------------+
Quellcode des Kontrollindikators
Achten Sie auf Zeile 10, in der wir MQL5 mitteilen, dass wir einen Puffer benötigen, und auf Zeile 9, in der wir angeben, dass wir keine Informationen im Chart anzeigen wollen. Das bedeutet, dass der Puffer intern im Indikator liegt und für den Nutzer unsichtbar ist, aber für jeden Code zugänglich ist, der weiß, wie er zu verwenden ist.
Da wir einen Puffer verwenden werden, müssen wir ihn deklarieren. Wir tun dies zuerst in Zeile 18 und deklarieren dann in Zeile 31 den Puffer, sodass er außerhalb des Indikators zugänglich ist. In Zeile 32 stellen wir sicher, dass der Puffer vollständig leer ist. Bitte achten Sie nun genau auf das Folgende, denn es ist wichtig.
Wenn ein Nutzer mit der MetaTrader 5-Plattform interagiert, z. B. indem er den Zeitrahmen ändert, löscht MetaTrader 5 alles aus dem Chart und lädt es dann neu. In diesem Fall wird der gesamte Code zurückgesetzt. Für jeden Indikator bedeutet dies einen neuen Aufruf des OnInit-Ereignisses, das den gesamten Prozess seiner Neuinitialisierung durchführt. Unser Kontrollindikator macht eigentlich nichts, oder besser gesagt, er führt keine spezifischen Berechnungen durch. Seine einzige Funktion besteht darin, dem Nutzer die Möglichkeit zu geben, mit dem Dienst zu interagieren und ihn zu steuern, der zur Anzeige von Balken in einem Chart mit einem nutzerdefinierten Symbol verwendet wird.
Wenn der Nutzer also den Zeitrahmen ändert, gehen alle Werte im Indikator verloren. Wir müssen sicherstellen, dass alles in angemessener Weise geschieht. Der Dienst, der durch den Indikator gesteuert wird, hat keine Ahnung, was auf dem Chart passiert, genauso wie der Indikator nicht weiß, was der Dienst tut. Um dem Dienst und dem Indikator mitzuteilen, was sie gerade tun, tauschen sie Nachrichten untereinander aus.
Bisher geschah dies über eine globale Terminalvariable. Nun aber schaffen wir einen anderen Weg und müssen sicherstellen, dass sowohl der Indikator als auch der Dienst unabhängig von den Aktionen des Nutzers auf dem Chart konsistent bleiben und wissen, was passiert. Um den Dienst über den Zustand des Indikators zu informieren, verwenden wir daher den Indikatorpuffer, in dem wir alle Informationen zu den darin stattfindenden Prozessen ablegen.
Der Indikator weiß durch die Eingabeparameter, nämlich die in Zeile 16 deklarierten, und durch die Nutzerereignisse, die im OnChartEvent-Aufruf des Indikators behandelt werden, was der Dienst tut.
Es ist nicht praktikabel, Daten über Eingabeparameter an den Indikator zu übergeben, wenn dieser bereits im Chart angezeigt wird. Ich sage nicht, dass es nicht möglich ist, aber es ist unpraktisch und wir werden es nicht tun. Sobald der Dienst den Indikator im Chart platziert hat, verlieren Sie die Möglichkeit, über Eingabeparameter mit ihm zu interagieren. Deshalb verwenden Sie hier nutzerdefinierte Ereignisse.
Nun zu den Einzelheiten. Wenn der Nutzer den Zeitrahmen ändert, verliert der Indikator die Informationen über den vorherigen Status und die Verschiebeposition. Der Dienst verfügt jedoch über diese Informationen. Aber wie können wir sicherstellen, dass der Dienst diese Daten an den Indikator weitergeben kann, sodass seine Integrität gewahrt bleibt? Der Dienst weiß nicht, wann MetaTrader 5 den Indikator auf dem Chart wiederherstellt, aber der Dienst kann den Indikatorpuffer sehen. Das ist der wichtigste Trick.
Wenn der Indikator in einem Chart platziert wird, wird sein Puffer zunächst auf Null gesetzt. Wenn der Konstruktor der Klasse C_Controls ausgeführt wird, wird in Zeile 150 ein bestimmter Wert initialisiert (um dies zu verstehen, sehen Sie sich den Code der Klasse an). Dieser Wert wird jedoch erst in den Puffer geschrieben, wenn OnChartEvent aufgerufen wird, und dann wird der Puffer in Zeile 50 des Indikatorcodes aktualisiert.
Wenn der Dienst also den Puffer liest, nachdem MetaTrader 5 den Kontrollindikator auf dem Chart wiederhergestellt hat, wird er null oder anormale Werte sehen. Zu diesem Zeitpunkt wird ein spezielles Ereignis für MetaTrader 5 ausgelöst, und der Dienst informiert den Indikator über die aktualisierten Werte, damit diese korrekt auf dem Bildschirm angezeigt werden. Dadurch wird sichergestellt, dass die Schaltflächen und Schieberegler korrekt angezeigt werden.
Wenn wir dies auf andere Weise versuchen würden, müssten wir uns etwas einfallen lassen, um die verlorenen Daten wiederherzustellen. Am Ende würden wir verschiedene Lösungen mit demselben Ergebnis erstellen: die Initialisierung der Indikatoren. Einige dieser Lösungen könnten jedoch anfällig für Manipulationen durch den Nutzer sein, was die Kontrolle der Kommunikation zwischen dem Dienst und dem Indikator erschwert. Unsere Lösung schafft eine zusätzliche Sicherheitsebene und stellt gleichzeitig sicher, dass die Informationen nur für die Bereiche zugänglich sind, die sie tatsächlich lesen und nutzen müssen.
Was ich gerade erklärt habe, mag den meisten Leuten sehr verwirrend und exotisch vorkommen, besonders denen, die gerade erst mit dem Programmieren anfangen. Die Idee des Nachrichtenaustauschs und der kontrollierten Initialisierung ist etwas, wovon viele Menschen wahrscheinlich noch nie gehört haben. Wie können wir also nachweisen, dass dies in der Praxis tatsächlich funktioniert? Hierfür verwenden wir den Kontrollindikator und den Mausindikator. Aber bevor wir sehen können, wie das System wirklich funktioniert, müssen wir etwas schaffen, um die Idee selbst zu demonstrieren und zu verstehen.
Hierfür werden wir einen viel einfacheren Code verwenden, der aber effektiv genug ist, um das Hauptkonzept zu verdeutlichen. Dieser Code wird im Folgenden vorgestellt.
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property description "Data synchronization demo service." 05. #property version "1.00" 06. //+------------------------------------------------------------------+ 07. #include <Market Replay\Defines.mqh> 08. //+------------------------------------------------------------------+ 09. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 10. #resource "\\" + def_IndicatorControl 11. //+------------------------------------------------------------------+ 12. input string user00 = "BOVA11"; //Symbol 13. //+------------------------------------------------------------------+ 14. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != "")) 15. //+------------------------------------------------------------------+ 16. void OnStart() 17. { 18. uCast_Double info; 19. long id; 20. int handle, iPos, iMode; 21. double Buff[]; 22. 23. SymbolSelect(user00, true); 24. id = ChartOpen(user00, PERIOD_H1); 25. if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE) 26. ChartIndicatorAdd(id, 0, handle); 27. IndicatorRelease(handle); 28. if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE) 29. ChartIndicatorAdd(id, 0, handle); 30. IndicatorRelease(handle); 31. Print("Service maintaining sync state..."); 32. iPos = 0; 33. iMode = INT_MIN; 34. while (def_Loop) 35. { 36. while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50); 37. info.dValue = 0; 38. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0]; 39. IndicatorRelease(handle); 40. if (info._int[0] == INT_MIN) 41. { 42. info._int[0] = iPos; 43. info._int[1] = iMode; 44. EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, ""); 45. }else if (info._int[1] != 0) 46. { 47. iPos = info._int[0]; 48. iMode = info._int[1]; 49. } 50. Sleep(250); 51. } 52. ChartClose(id); 53. Print("Finished service..."); 54. } 55. //+------------------------------------------------------------------+
Quellcode des Demonstrationsdienstes
Beachten Sie Zeile 10, in der wir den Kontrollindikator in eine Serviceressource umwandeln. Daher ist es nicht notwendig, ihn in die Liste der Indikatoren aufzunehmen, da er für nichts anderes als unser System von Nutzen ist. In Zeile 12 geben wir das Symbol für den Test des Systems an. Achten Sie darauf, ein gültiges Zeichen zu verwenden, da seine Gültigkeit später nicht überprüft wird. In Zeile 14 haben wir eine Definition, die dazu dient, einige Bedingungen zu überprüfen, um den Dienst ordnungsgemäß abschalten zu können.
In Zeile 23 platzieren wir das Symbol im Marktbeobachtungsfenster, falls es dort nicht vorhanden ist. In Zeile 24 öffnen wir ein Grafikfenster mit dem vom Nutzer (in diesem Fall von Ihnen) angegebenen Symbol. Sobald dies geschehen ist, haben wir eine Chart-ID, mit der wir Indikatoren auf dem Chart platzieren können.
In Zeile 25 platzieren wir also den Kontrollindikator auf dem neu geöffneten Chart. In Zeile 29 fügen wir dann den Mauszeiger hinzu. Bitte beachten Sie, dass der Mausindikator in der Liste der Indikatoren aufgeführt wird, der Kontrollindikator jedoch nicht. Für eine vollständige Prüfung benötigen wir jedoch beides.
In Zeile 31 teilen wir dem Terminal mit, dass der Dienst aktiv ist und überwacht, was auf der Karte passiert.
Zu diesem Zeitpunkt sollte der Mausindikator bereits auf dem Chart zu sehen sein. Der Kontrollindikator ist jedoch nicht sichtbar, obwohl er unter den Indikatoren auf der Karte aufgeführt ist. Ich habe bereits erklärt, wie der Kontrollindikator initialisiert wird. Zu diesem Zeitpunkt wird der Puffer zufällige Werte enthalten, die für uns keine Bedeutung haben. Aus diesem Grund können wir nicht mit ihm interagieren. Aber wenn der Kontrollindikator korrekt initialisiert wurde und der Puffer bereits geschrieben wurde, erhalten wir einen bestimmten Wert. In Zeile 34 beginnen wir also eine Schleife, die so lange läuft, wie die in Zeile 14 definierten Bedingungen erfüllt sind.
Bitte beachten Sie, dass in Zeile 36 überprüft wird, ob der Kontrollindikator tatsächlich der Karte hinzugefügt wurde. Aber warum führen wir diese Prüfung durch und warten, bis sie abgeschlossen ist? Der Grund dafür ist, dass der Code viel schneller ausgeführt werden kann, als es tatsächlich der Fall ist. Wir müssen also irgendwie dafür sorgen, dass MetaTrader 5 die Situation stabilisiert, und dazu verwenden wir diese Schleife in Zeile 36.
Wenn alles in Ordnung ist, versuchen wir, den Puffer der Kontrollanzeige zu lesen. Ich möchte Sie noch einmal darauf hinweisen, dass der Indikator im Chart nicht sichtbar sein wird.
Wenn der Puffer erfolgreich gelesen wurde, wird ein Wert ungleich Null in die Variable info.dValue eingetragen. An dieser Stelle können wir den Status des Kontrollindikators überprüfen. In Zeile 40 wird geprüft, ob er bereits initialisiert wurde. Da es sich um das erste Mal handelt, ist der Indikator noch nicht initialisiert worden. In den Zeilen 42 und 43 generieren wir einen Wert, den wir an den Indikator übergeben, und senden eine Anfrage an MetaTrader 5, um ein nutzerdefiniertes Ereignis im Chart zu erzeugen. Dieses Ereignis wird in Zeile 44 angezeigt, wo wir eine Nachricht an den Kontrollindikator übergeben, um ihn zu initialisieren.
Zu jedem anderen Zeitpunkt wird geprüft, ob sich der Indikator im Pausen- oder Abspielstatus befindet. Befindet sich der Indikator in einem dieser Zustände, können in Zeile 45 die vom Nutzer angegebenen Werte im Indikator gespeichert werden. Wenn der Nutzer also den Zeitrahmen ändert, gibt die Prüfung in Zeile 40 einen wahren Wert zurück, und die im Dienst gespeicherten Werte werden an den Indikator zurückgegeben, sodass dieser auch bei einer Änderung des Zeitrahmens korrekt initialisiert werden kann.
Schließlich schließen wir das Chart in Zeile 52 und geben in Zeile 53 eine Meldung über die Beendigung des Dienstes aus.
Im Video unten können Sie das System in Betrieb sehen, wenn Sie es noch nicht ausprobieren möchten. Ich habe auch die ausführbaren Dateien beigefügt, damit Sie sehen können, wie das Ganze in der Praxis funktioniert.
Demo-Video
Schlussfolgerung
In diesem Artikel haben wir damit begonnen, die Grundlagen für das zu schaffen, was in zukünftigen Artikeln behandelt werden wird. Mir ist klar, dass der Stoff recht intensiv ist, also studieren Sie ihn in Ruhe, denn im weiteren Verlauf wird alles noch komplizierter.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/11988





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