
Entwicklung eines Replay Systems (Teil 56): Anpassen der Module
Einführung
Im vorherigen Artikel „Entwicklung eines Replay Systems (Teil 55): Steuermodul“ haben wir einige Änderungen vorgenommen, die es uns ermöglichen, einen Kontrollindikator zu erstellen, ohne globale Terminalvariablen zu verwenden. Zumindest was die Speicherung von Informationen und nutzerspezifischen Einstellungen betrifft.
Während alles gut funktionierte und relativ stabil war, stürzte das System immer wieder ab, wenn es auf einem Chart mit einer bestimmten Anzahl von Balken platziert wurde, da die Bereichsgrenzen bei der Verwendung von Daten für ein nutzerdefiniertes Symbol überschritten wurden.
Der Grund dafür ist nicht, dass das nutzerdefinierte Symbol tatsächlich defekt ist oder Probleme verursacht, sondern dass die Datenbank für das ordnungsgemäße Funktionieren des gesamten Systems oft unzureichend ist. Der Hauptproblemfaktor ist der Puffer.
Vielleicht fragen Sie sich jetzt: „Wie ist es möglich, dass der Puffer das Problem ist? Das System funktioniert, wenn wir ein echtes Symbol verwenden, aber wenn wir ein nutzerdefiniertes Symbol verwenden, stürzt es ab, und der Grund für diesen Absturz ist der Indikatorpuffer?!“
Ja, der Grund ist der Puffer. Aber nicht so, wie Sie es sich vielleicht vorstellen. Denn wenn wir ein nutzerdefiniertes Symbol erstellen, kann es sein, dass der Puffer nicht die erforderliche Größe hat, um die Daten zu speichern, die wir darin unterbringen wollen.
Man könnte meinen, dass man nur mehr Speicher zuweisen muss und das Problem ist gelöst. Bei MQL5 sind die Dinge jedoch nicht so einfach. Die Zuweisung von Speicher für Daten im Indikatorpuffer funktioniert nicht so, wie Sie sich das wahrscheinlich vorstellen. Die Speicherzuweisung hängt von der Anzahl der Balken im Chart ab. Es macht also keinen Sinn, irgendeine Funktion zu verwenden, um Speicher zuzuweisen, da er nicht in der erwarteten Weise verwendet wird.
Das Problem verstehen
Das eigentliche Problem ist nicht der Kontrollindikator, sondern der Mausindikator. Bei der Behebung dieser Anomalie werden wir eine Lösung schaffen, die sich auch auf den Kontrollindikator auswirken wird. Die Änderungen werden später in diesem Artikel sichtbar. Doch zunächst müssen wir verstehen, was für ein Versagen vorliegt und wie es zustande kommt.
Wenn Sie den in den vorangegangenen Artikeln vorgestellten Mausindikator verwenden und ihn auf einem nutzerdefinierten Symbolchart platzieren, der z. B. 60 Ein-Minuten-Balken hat, haben Sie kein Problem mit Zeitrahmen, die gleich oder kleiner als 10 Minuten sind. Wenn Sie jedoch versuchen, einen Zeitrahmen von mehr als 10 Minuten zu verwenden, erhalten Sie eine Meldung von MetaTrader 5: „Mouse indicator: Range error“ (Mausindikator: Bereichsfehler).
Warum ist das so? Der Grund dafür ist, dass der im vorherigen Artikel vorgestellte Mausindikator 6 Positionen benötigt, um Daten im Indikatorpuffer zu speichern. Daher wird die Mathematik die Frage beantworten, was tatsächlich geschehen ist. Bei 60 Balken pro Minute können Sie den Zeitrahmen auf 10 Minuten ändern, was zu 6 Balken im Chart führt. Diese sechs Balken bieten die sechs erforderlichen Pufferpositionen für die Daten. Wenn Sie jedoch einen höheren Zeitrahmen wählen, wird die Anzahl der Balken im Chart weniger als sechs betragen.
An diesem Punkt gibt der Mausindikator einen Bereichsfehler zurück, da er versucht, Daten in eine Speicherposition zu schreiben, die MetaTrader 5 nicht zugewiesen hat.
Genau hier liegt der Fehler, und es gibt zwei Möglichkeiten, ihn zu beheben. Die erste besteht darin, eine ausreichende Anzahl von Balken auf dem nutzerdefinierten Symbol zu platzieren, sodass es in jedem Zeitrahmen mindestens sechs davon gibt. Dies ist nicht die beste Lösung, denn um auf den monatlichen Zeitrahmen zugreifen zu können, müssten wir mindestens sechs Monate mit 1-Minuten-Balken in das nutzerdefinierte Symboldiagramm laden. Dies ist nur notwendig, um zu verhindern, dass ein Bereichsfehler auftritt.
Ich persönlich denke, und ich bin mir sicher, dass viele zustimmen würden, dass dies bei weitem nicht die beste Lösung ist, insbesondere wenn es um das Wiedergabe-/Simulatorsystem geht. Wenn das System nur auf die Wiedergabe (Replay) ausgerichtet wäre, könnte diese Lösung vielleicht funktionieren, vorausgesetzt, die Analyse wird nur für einen oder wenige Zeitrahmen durchgeführt. Da wir das System jedoch zur Modellierung von Marktbewegungen nutzen können, ist diese Lösung völlig inakzeptabel und es wird etwas Eleganteres benötigt.
Genau das werden wir jetzt tun. Wir werden den Mauszeiger so verändern, dass die Informationen kompakt an einer Stelle im Puffer Platz finden. Wir benötigen also nur einen Balken im Chart, damit der Indikator seine Funktion erfüllen kann.
Beginn der Implementierung der Lösung
Ich habe mich für die kompakte Platzierung von Informationen an einer Stelle entschieden, weil es für das Wiedergabe-/Simulatorsystem viel einfacher ist, mindestens einen Balken in das Chart einzufügen und zu pflegen, als etwas anderes zu tun.
Der Hauptgrund ist jedoch, dass wir eine Marktsimulation erstellen wollen, ohne eine große Anzahl von Balken zu verwenden. Wir werden nur die Menge nutzen können, die wir wollen, und der Replay/Simulator-Dienst selbst wird dem System die notwendige Stabilität verleihen. So können wir die gleichen Mittel sowohl auf einem echten Konto als auch auf einem Demokonto verwenden.
Zusammenfassend: Wir werden den Puffer des Mauszeigers viel kleiner machen. Es wird nur eine Position verwendet, aber wir erhalten die gleiche Menge an Informationen, die zurückgegeben wird, wenn wir den Indikator auffordern, den Puffer zu lesen. Ein Punkt ist erwähnenswert: Wenn Sie direkt aus dem Indikatorpuffer der Maus lesen, erhalten Sie nur Daten an einer Position in diesem Puffer. Solche Daten müssen übersetzt werden, damit sie wirklich nützlich sind. Mit der Klasse C_Mouse können wir eine bestimmte Funktion verwenden, um sicherzustellen, dass diese Übersetzung korrekt durchgeführt wird.
Damit sind wir in der Umsetzungsphase angelangt. Das erste, was wir ändern müssen, ist die Header-Datei. Ihr Code ist unten angegeben.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. //+------------------------------------------------------------------+ 16. union uCast_Double 17. { 18. double dValue; 19. long _long; // 1 Information 20. datetime _datetime; // 1 Information 21. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 22. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 23. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 24. }; 25. //+------------------------------------------------------------------+ 26. enum EnumEvents { 27. evHideMouse, //Hide mouse price line 28. evShowMouse, //Show mouse price line 29. evHideBarTime, //Hide bar time 30. evShowBarTime, //Show bar time 31. evHideDailyVar, //Hide daily variation 32. evShowDailyVar, //Show daily variation 33. evHidePriceVar, //Hide instantaneous variation 34. evShowPriceVar, //Show instantaneous variation 35. evSetServerTime, //Replay/simulation system timer 36. evCtrlReplayInit //Initialize replay control 37. }; 38. //+------------------------------------------------------------------+
Quellcode der Datei Defines.mqh
Hier gibt es praktisch keine signifikanten Unterschiede, zumindest nicht in allgemeiner Hinsicht. Wenn Sie jedoch genau hinsehen, werden Sie Änderungen in den Zeilen 21 und 23 feststellen. Diese Änderungen wurden vorgenommen, um eine effizientere Nutzung der Bits zu ermöglichen. Um die Art der Informationen, mit denen wir arbeiten, leichter identifizieren zu können, habe ich eine einfache Notation verwendet: _32b steht für 32 Bits, _16b für 16 Bits und _8b für 8 Bits. Auf diese Weise wissen wir bei jeder Art von Zugriff genau, wie viele Bits verwendet werden. Es sei darauf hingewiesen, dass ein Doppelwert 64 Bit darstellt, sodass jedes Datenpaket eine Längenbegrenzung innerhalb dieser 64 Bit hat.
So kann _32b 2 Werte enthalten, _16b kann 4 Werte enthalten und _8b kann 8 Werte enthalten. Achten Sie jedoch darauf, dass wir eine Vereinigung verwenden, was bedeutet, dass wir diese Mengen kombinieren können. Um dies richtig zu tun, müssen Sie verstehen, dass jedes Array in MQL5 auf dem in C/C++ verwendeten Nummerierungssystem basiert. Mit anderen Worten: Arrays beginnen immer bei Null, und jede weitere Position wird um eine Einheit erhöht.
An diesem Punkt könnten sich viele verwirrt fühlen, wenn ihnen ein grundlegendes Verständnis dafür fehlt, wie Dinge in C/C++ funktionieren. Dies liegt daran, dass der Begriff „unit“ hier möglicherweise nicht angemessen wiedergibt, was tatsächlich erforderlich ist, wenn der Indexwert erhöht wird, um Daten korrekt im Paket zu platzieren. Wenn Sie dieses Konzept missverstehen, können Sie völlig verloren gehen oder zumindest nicht verstehen, wie die Informationen tatsächlich verdichtet werden.
Bevor wir uns mit den Änderungen am Mauszeiger befassen, werfen wir einen kurzen Blick auf den Kontrollzeiger. Der Grund dafür ist einfach: Im Kontrollindikator mussten wir lediglich die Funktionen, Prozeduren und Variablen an die neuen Typen anpassen, die in der Header-Datei Defines.mqh eingeführt wurden. Beginnen wir mit der Untersuchung des Klassencodes, der unten zu sehen ist.
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. short Minimal; 044. }m_Slider; 045. struct st_01 046. { 047. C_DrawImage *Btn; 048. bool state; 049. short x, y, w, h; 050. }m_Section[eObjectControl::eNull]; 051. C_Mouse *m_MousePtr; 052. //+------------------------------------------------------------------+ 053. inline void CreteBarSlider(short x, short 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(short 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(short &x, short &y) 115. { 116. C_Mouse::st_Mouse InfoMouse; 117. 118. InfoMouse = (*m_MousePtr).GetInfoMouse(); 119. x = (short) InfoMouse.Position.X_Graphics; 120. y = (short) 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 = SHORT_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 SetBuffer(const int rates_total, double &Buff[]) 161. { 162. uCast_Double info; 163. 164. info._16b[0] = (ushort) m_Slider.Minimal; 165. info._16b[1] = (ushort) (m_Section[ePlay].state ? SHORT_MAX : SHORT_MIN); 166. if (rates_total > 0) 167. Buff[rates_total - 1] = info.dValue; 168. } 169. //+------------------------------------------------------------------+ 170. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 171. { 172. short x, y; 173. static short iPinPosX = -1, six = -1, sps; 174. uCast_Double info; 175. 176. switch (id) 177. { 178. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 179. info.dValue = dparam; 180. iPinPosX = m_Slider.Minimal = (short) info._16b[0]; 181. if (info._16b[1] == 0) SetUserError(C_Terminal::ERR_Unknown); else 182. { 183. SetPlay((short)(info._16b[1]) == SHORT_MAX); 184. if ((short)(info._16b[1]) == SHORT_MIN) CreateCtrlSlider(); 185. } 186. break; 187. case CHARTEVENT_OBJECT_DELETE: 188. if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName) 189. { 190. if (sparam == (def_PrefixCtrlName + EnumToString(ePlay))) 191. { 192. delete m_Section[ePlay].Btn; 193. m_Section[ePlay].Btn = NULL; 194. SetPlay(m_Section[ePlay].state); 195. }else 196. { 197. RemoveCtrlSlider(); 198. CreateCtrlSlider(); 199. } 200. } 201. break; 202. case CHARTEVENT_MOUSE_MOVE: 203. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 204. { 205. case ePlay: 206. SetPlay(!m_Section[ePlay].state); 207. if (m_Section[ePlay].state) 208. { 209. RemoveCtrlSlider(); 210. m_Slider.Minimal = iPinPosX; 211. }else CreateCtrlSlider(); 212. break; 213. case eLeft: 214. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 215. break; 216. case eRight: 217. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 218. break; 219. case ePin: 220. if (six == -1) 221. { 222. six = x; 223. sps = iPinPosX; 224. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 225. } 226. iPinPosX = sps + x - six; 227. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 228. break; 229. }else if (six > 0) 230. { 231. six = -1; 232. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 233. } 234. break; 235. } 236. ChartRedraw(GetInfoTerminal().ID); 237. } 238. //+------------------------------------------------------------------+ 239. }; 240. //+------------------------------------------------------------------+ 241. #undef def_PosXObjects 242. #undef def_ButtonPlay 243. #undef def_ButtonPause 244. #undef def_ButtonLeft 245. #undef def_ButtonRight 246. #undef def_ButtonPin 247. #undef def_PrefixCtrlName 248. #undef def_PathBMP 249. //+------------------------------------------------------------------+
Quellcode von C_Control.mqh
Wenn Sie den Code untersuchen, werden Sie feststellen, dass es keine sichtbaren Unterschiede gibt, zumindest nicht auf den ersten Blick, zwischen dem oben gezeigten Code und dem gleichen Code, der im letzten Artikel vorgestellt wurde, in dem diese Header-Datei besprochen wurde. Aufgrund von Änderungen an der Definitionsdatei wurden jedoch bestimmte Teile des Codes geringfügig geändert. Eine dieser Änderungen betrifft die explizite Verwendung von Typecasting, wie in den Zeilen 164 und 165 zu sehen ist, wo wir den Compiler anweisen, explizit einen bestimmten Datentyp zu verwenden.
Beachten Sie die Änderung in Zeile 165. Früher haben wir Integer-Konstanten verwendet, jetzt verwenden wir Konstanten vom Typ „short“. Obwohl es sich um vorzeichenbehaftete Konstanten handelt, d. h. sie können negative Werte darstellen, sind die im Array platzierten Werte vorzeichenlos. An dieser Stelle könnte man meinen, dass dies zu einer falschen Interpretation der Werte führen könnte. Wenn ja, dann haben Sie vielleicht nicht richtig verstanden, wie Werte in binärer Form dargestellt werden. Ich empfehle, sich mit der Darstellung von Binärwerten zu befassen, um zu verstehen, wie negative Werte in einem System dargestellt werden können, das sie nicht explizit behandelt und dennoch Informationen ohne Qualitätsverlust überträgt.
Im gleichen Code sehen Sie in Zeile 180 eine weitere explizite Typkonvertierung. Hier wird ein in einer vorzeichenlosen Variablen gespeicherter Wert einer vorzeichenbehafteten Variablen zugewiesen. Diese Typkonvertierung stellt sicher, dass negative Werte angemessen dargestellt werden. Im gesamten Code für die nutzerdefinierte Ereignisbehandlung und während der Initialisierung des Steuerungsindikators wird diese Art der Konvertierung intensiv genutzt. Nehmen Sie sich die Zeit, den Abschnitt zwischen den Zeilen 178 und 186 sorgfältig zu studieren, da er eine intensive Nutzung dieser Umrechnungsmethode zeigt.
Es ist erwähnenswert, dass die Komprimierung in diesem Kontrollindikator nicht so tief war, wie sie hätte sein können, um die effiziente Nutzung von Bits innerhalb eines Doubles zu maximieren. Das liegt daran, dass in unserem Fall keine so fortgeschrittene Optimierung erforderlich ist. Was schließlich den Code des Kontrollkennzeichens betrifft, so wurden lediglich die Versionsnummer und ein Link geändert. Daher werde ich hier nicht den gesamten Code wiederholen. Sie müssen lediglich die Header-Datei aus dem vorherigen Artikel durch die in diesem Artikel bereitgestellte Datei ersetzen. Der restliche Code des Indikators bleibt unverändert und kann ohne Probleme verwendet werden.
Wenn es um den Mauszeiger geht, werden die Dinge ein wenig komplexer. Folglich benötigen wir alle drei Dateien, die zur Erstellung des Indikators erforderlich sind. Wir werden diese Dateien im nächsten Abschnitt besprechen.
Implementierung der Lösung für den Mauszeiger
Wie Sie vielleicht im vorherigen Abschnitt gesehen haben, wurden geringfügige Anpassungen am Code des Kontrollindikators vorgenommen, und diese Änderungen beschränkten sich auf die Header-Datei. Beim Mauszeiger ist die Situation jedoch ganz anders und wesentlich komplexer.
Schauen wir uns die Änderungen an. Betrachten wir zunächst die neue Klasse C_Mouse, die im Folgenden vorgestellt wird.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MousePrefixName "MouseBase_" 007. #define def_NameObjectLineH def_MousePrefixName + "H" 008. #define def_NameObjectLineV def_MousePrefixName + "TV" 009. #define def_NameObjectLineT def_MousePrefixName + "TT" 010. #define def_NameObjectStudy def_MousePrefixName + "TB" 011. //+------------------------------------------------------------------+ 012. class C_Mouse : public C_Terminal 013. { 014. public : 015. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 016. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 017. struct st_Mouse 018. { 019. struct st00 020. { 021. short X_Adjusted, 022. Y_Adjusted, 023. X_Graphics, 024. Y_Graphics; 025. double Price; 026. datetime dt; 027. }Position; 028. uchar ButtonStatus; 029. bool ExecStudy; 030. }; 031. //+------------------------------------------------------------------+ 032. protected: 033. //+------------------------------------------------------------------+ 034. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 035. { 036. if (m_Mem.szShortName != NULL) return; 037. CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE); 038. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 039. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 042. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 046. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 047. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 048. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 049. } 050. //+------------------------------------------------------------------+ 051. private : 052. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 053. struct st01 054. { 055. st_Mouse Data; 056. color corLineH, 057. corTrendP, 058. corTrendN; 059. eStudy Study; 060. }m_Info; 061. struct st_Mem 062. { 063. bool CrossHair, 064. IsFull; 065. datetime dt; 066. string szShortName; 067. }m_Mem; 068. bool m_OK; 069. //+------------------------------------------------------------------+ 070. void GetDimensionText(const string szArg, int &w, int &h) 071. { 072. TextSetFont("Lucida Console", -100, FW_NORMAL); 073. TextGetSize(szArg, w, h); 074. h += 5; 075. w += 5; 076. } 077. //+------------------------------------------------------------------+ 078. void CreateStudy(void) 079. { 080. if (m_Mem.IsFull) 081. { 082. CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); 083. CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); 084. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); 085. CreateObjToStudy(0, 0, def_NameObjectStudy); 086. } 087. m_Info.Study = eStudyCreate; 088. } 089. //+------------------------------------------------------------------+ 090. void ExecuteStudy(const double memPrice) 091. { 092. double v1 = GetInfoMouse().Position.Price - memPrice; 093. int w, h; 094. 095. if (!CheckClick(eClickLeft)) 096. { 097. m_Info.Study = eStudyNull; 098. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 099. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 100. }else if (m_Mem.IsFull) 101. { 102. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 103. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)); 104. GetDimensionText(sz1, w, h); 105. ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1); 106. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 107. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w); 108. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h); 109. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 110. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 111. ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 112. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 113. } 114. m_Info.Data.ButtonStatus = eKeyNull; 115. } 116. //+------------------------------------------------------------------+ 117. inline void DecodeAlls(int xi, int yi) 118. { 119. int w = 0; 120. 121. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short) xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 122. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 123. m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price); 124. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi); 125. m_Info.Data.Position.X_Adjusted = (short) xi; 126. m_Info.Data.Position.Y_Adjusted = (short) yi; 127. } 128. //+------------------------------------------------------------------+ 129. public : 130. //+------------------------------------------------------------------+ 131. C_Mouse(const long id, const string szShortName) 132. :C_Terminal(id), 133. m_OK(false) 134. { 135. m_Mem.szShortName = szShortName; 136. } 137. //+------------------------------------------------------------------+ 138. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 139. :C_Terminal(id) 140. { 141. if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown); 142. if (_LastError != ERR_SUCCESS) return; 143. m_Mem.szShortName = NULL; 144. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 145. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 146. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 147. ZeroMemory(m_Info); 148. m_Info.corLineH = corH; 149. m_Info.corTrendP = corP; 150. m_Info.corTrendN = corN; 151. m_Info.Study = eStudyNull; 152. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 153. CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 154. } 155. //+------------------------------------------------------------------+ 156. ~C_Mouse() 157. { 158. if (!m_OK) return; 159. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 160. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); 161. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 162. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 163. } 164. //+------------------------------------------------------------------+ 165. inline bool CheckClick(const eBtnMouse value) 166. { 167. return (GetInfoMouse().ButtonStatus & value) == value; 168. } 169. //+------------------------------------------------------------------+ 170. inline const st_Mouse GetInfoMouse(void) 171. { 172. if (m_Mem.szShortName != NULL) 173. { 174. double Buff[]; 175. uCast_Double loc; 176. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 177. 178. ZeroMemory(m_Info.Data); 179. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 180. { 181. loc.dValue = Buff[0]; 182. m_Info.Data.ButtonStatus = loc._8b[0]; 183. DecodeAlls((int)loc._16b[1], (int)loc._16b[2]); 184. } 185. IndicatorRelease(handle); 186. } 187. 188. return m_Info.Data; 189. } 190. //+------------------------------------------------------------------+ 191. inline void SetBuffer(const int rates_total, double &Buff[]) 192. { 193. uCast_Double info; 194. 195. info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0); 196. info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics; 197. info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics; 198. Buff[rates_total - 1] = info.dValue; 199. } 200. //+------------------------------------------------------------------+ 201. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 202. { 203. int w = 0; 204. static double memPrice = 0; 205. 206. if (m_Mem.szShortName == NULL) 207. { 208. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 209. switch (id) 210. { 211. case (CHARTEVENT_CUSTOM + evHideMouse): 212. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); 213. break; 214. case (CHARTEVENT_CUSTOM + evShowMouse): 215. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); 216. break; 217. case CHARTEVENT_MOUSE_MOVE: 218. DecodeAlls((int)lparam, (int)dparam); 219. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); 220. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0); 221. m_Info.Data.ButtonStatus = (uchar) sparam; //Mudança no tipo ... 222. if (CheckClick(eClickMiddle)) 223. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 224. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 225. { 226. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 227. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 228. m_Info.Study = eStudyExecute; 229. } 230. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 231. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 232. break; 233. case CHARTEVENT_OBJECT_DELETE: 234. if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 235. break; 236. } 237. } 238. } 239. //+------------------------------------------------------------------+ 240. }; 241. //+------------------------------------------------------------------+ 242. #undef def_NameObjectLineV 243. #undef def_NameObjectLineH 244. #undef def_NameObjectLineT 245. #undef def_NameObjectStudy 246. //+------------------------------------------------------------------+
Quellcode der Datei C_Mouse.mqh
Die Änderungen werden Ihnen vielleicht nicht sofort auffallen, vor allem weil die meisten davon Variablentypen betreffen. Nichtsdestotrotz sind sie erwähnenswert, und ein richtiges Verständnis wird Ihnen helfen, die Grenzen dieses Mausindikators zu erkennen. Ja, es gibt Einschränkungen, aber wenn Sie diese erst einmal verstanden haben, können Sie es angemessen nutzen und sicherstellen, dass diese Einschränkungen Ihre Nutzung nicht beeinträchtigen.
Zunächst werden Sie in den Zeilen 21 bis 24 feststellen, dass wir jetzt den Typ SHORT verwenden, während es vorher INT war. In ähnlicher Weise wurde der Variablentyp in Zeile 28 geändert.
Bevor Sie in Panik geraten und denken, dass diese Änderungen ein Chaos verursachen, möchte ich Sie an einige Merkmale und Besonderheiten der Typen SHORT und INT erinnern.
In MQL5 ist der INT-Typ ein 32-Bit-Datentyp, d. h. seine Werte reichen von -2.147.483.648 bis 2.147.483.647, wenn sie vorzeichenbehaftet sind. Ist er vorzeichenlos, d. h. nur positiv, kann der Wert zwischen 0 und 4.294.967.295 liegen. Im Gegensatz dazu ist der Typ SHORT ein 16-Bit-Datentyp mit einem Bereich von -32.768 bis 32.767 mit Vorzeichen und von 0 bis 65.535 ohne Vorzeichen.
Überlegen Sie einmal: Wie groß sind die Pixel Ihres Monitors? Und warum frage ich das? Denn die Verwendung eines INT zur Darstellung kartesischer Dimensionen (X und Y) auf einem Monitor ist einfach ineffizient. Verstehen Sie mich nicht falsch, aber Sie verschwenden damit eine Menge Platz. Ein 8K-Monitor, ein besonders hochauflösender Bildschirm, hat beispielsweise 7.680 horizontale Pixel und 4.320 vertikale Pixel. Diese Auflösung beträgt sowohl horizontal als auch vertikal weniger als 2 hoch 13 Stellen, was bequem zu den 2 hoch 6 Stellen des Typs SHORT passt. Es bleiben also noch 3 ungenutzte Bits für andere Zwecke übrig.
Diese einfache Optimierung ist mehr als ausreichend. Bei der Verwendung von INT wären nur zwei Werte möglich, aber durch die Verwendung von SHORT können wir vier Werte innerhalb desselben 64-Bit-Doppels speichern. Da wir nur zwei SHORT-Werte benötigen, um die Mausposition darzustellen (X und Y), haben wir zwei zusätzliche SHORT-Plätze für andere Zwecke. Außerdem haben wir selbst bei einem 8K-Display immer noch 3 ungenutzte Bits in jedem SHORT, um die Mauskoordinaten zu speichern.
Wie Sie sehen können, bezieht sich die Einschränkung nicht auf die Positionsdaten. Die eigentliche Einschränkung liegt woanders. Wenn Sie diese Artikelserie verfolgt haben, werden Sie bemerkt haben, dass wir darauf abzielen, dass der Mauszeiger Informationen für eine einfachere und schnellere Analyse liefert. Hierin liegt die Einschränkung: Preis- und Zeitwerte können nicht direkt im Puffer des Mausindikators gespeichert werden. Diese Werte benötigen jeweils 64 Bit, und wir können es uns nicht leisten, ihnen zwei Positionen zuzuweisen. Um dies zu beheben, benötigen wir einen anderen Ansatz, der zu Änderungen in der Klasse C_Mouse führt.
Die erste auffällige Änderung findet sich in Zeile 117. Hinweis: Um umfangreiche Änderungen an allen Typen des Mausindikators zu vermeiden, habe ich beschlossen, die Positionen als INT innerhalb der Klasse zu belassen. Außerhalb der Klasse werden diese Werte jedoch nach Bedarf angepasst.
Die Prozedur in Zeile 117 übersetzt die vom Betriebssystem gelieferten Bildschirmkoordinaten in von MQL5 angepasste Koordinaten und gleicht sie mit der Darstellung auf der Karte ab. Achten Sie hierauf besonders. Diese Prozedur ist für die Klasse privat, was bedeutet, dass kein Code außerhalb der Klasse darauf zugreifen kann. Sie gewährleistet jedoch die Datenübersetzung unter Beibehaltung der Funktionalität. Wenn Sie diesen Indikator zur Übersetzung von Daten über die Klasse verwendet haben, werden Sie keine Probleme haben.
Um dies zu verstehen, sehen Sie sich Zeile 170 an. Mit dieser Funktion werden die Daten des Indikators übersetzt. Beachten Sie, dass der interne Code zwar geändert wurde, die Schnittstelle jedoch unverändert bleibt. Zuvor wurden sechs Werte zurückgegeben, jetzt ist es nur noch einer. Achten Sie genau darauf, wie wir das handhaben. In Zeile 179 wird ein Wert aus dem Indikatorpuffer abgerufen. Wenn ein Wert zurückgegeben wird, wird er in Zeile 181 dem Übersetzungssystem zugewiesen. In Zeile 182 wird der Wert erfasst und übersetzt, um den Status der Schaltfläche zu bestimmen. Beachten Sie den verwendeten Index und die Bitlänge. In Zeile 183 werden die Daten dann an eine Prozedur gesendet, die die Bildschirmkoordinaten X und Y in Typen umwandelt, die für die Verwendung im MetaTrader 5 oder anderen Anwendungen, die den Mausindikator als Hilfsmittel verwenden, erwartet werden. Trotz der Änderungen bleiben sie auf die Funktion beschränkt, sodass alles, was außerhalb der Klasse liegt, davon unberührt bleibt.
Nun zu einem komplexeren, aber wesentlichen Aspekt: In Zeile 191 wird eine Prozedur eingeführt, die Daten in den Indikatorpuffer schreibt. Zuvor war dieses Verfahren Teil des Indikatorcodes. Aus praktischen Gründen habe ich sie jedoch in die Klasse verschoben, vor allem um den Zugriff auf die Klassendaten zu beschränken.
Lassen Sie uns dies Schritt für Schritt untersuchen. In Zeile 193 deklarieren wir die Kompressionsvariable. In Zeile 195 beginnt der Prozess der Datenkompression. Achten Sie genau darauf, wie Indizes beim Zugriff auf Arrays verwendet werden.
Das Feld _8b enthält 8 Positionen, während das Feld _16b 4 Positionen enthält. Beide beginnen bei Index Null. Es ist jedoch zu beachten, dass der Index Null des Feldes _16b den Indizes Null und Eins des Feldes _8b entspricht. Wenn wir zum Beispiel in Zeile 195 den Index Null in _8b verwenden, belegen wir den Index Null von _16b. Da für die gewünschten Informationen nur _8b-Index Null benötigt wird, bleibt _8b-Index Eins unbesetzt. Der _16b-Index Null kann jedoch nicht wiederverwendet werden, da er zwei _8b-Indizes für seinen Aufbau benötigt.
Somit bleibt _8b-Index eins frei, während die Werte für die Mausposition bei _8b-Index zwei beginnen. Da der _8b-Index zwei dem _16b-Index eins entspricht, verweist Zeile 196 auf diesen Index und trennt die Daten logisch und funktional voneinander. Zeile 197 folgt demselben Indexierungsprinzip. Trotz des Anscheins sind nicht alle Bits besetzt. Bits in _16b Index drei, in _8b Index eins und sechs Bits in den beiden SHORT-Werten für die X- und Y-Positionen bleiben ungenutzt. Diese ungenutzten Bits können bei Bedarf für andere Daten verwendet werden.
Zur Verdeutlichung sehen Sie sich bitte das nachstehende Bild an.
Das Bild zeigt den Pufferinhalt Byte für Byte, wobei jedes Byte 8 Bits darstellt. Blaue Bereiche zeigen belegte Bits an, während weiße Bereiche für zukünftige Daten frei sind. X steht für die grafische X-Koordinate und Y für die Y-Koordinate. Wenn Sie sich dieses Bild ansehen, werden Sie hoffentlich besser verstehen, was ich eigentlich tue.
Der restliche Code des Mauszeigers muss nicht näher erläutert werden, da er relativ einfach ist. Da der Code jedoch geändert wurde, wird im Folgenden die aktualisierte Fassung wiedergegeben. So wird sichergestellt, dass alles wie vorgesehen funktioniert.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. #define def_ExpansionBtn1 def_ExpansionPrefix + "B1" 008. #define def_ExpansionBtn2 def_ExpansionPrefix + "B2" 009. #define def_ExpansionBtn3 def_ExpansionPrefix + "B3" 010. //+------------------------------------------------------------------+ 011. class C_Study : public C_Mouse 012. { 013. private : 014. //+------------------------------------------------------------------+ 015. struct st00 016. { 017. eStatusMarket Status; 018. MqlRates Rate; 019. string szInfo; 020. color corP, 021. corN; 022. int HeightText; 023. bool bvT, bvD, bvP; 024. datetime TimeDevice; 025. }m_Info; 026. //+------------------------------------------------------------------+ 027. const datetime GetBarTime(void) 028. { 029. datetime dt; 030. int i0 = PeriodSeconds(); 031. 032. if (m_Info.Status == eInReplay) 033. { 034. if ((dt = m_Info.TimeDevice) == ULONG_MAX) return ULONG_MAX; 035. }else dt = TimeCurrent(); 036. if (m_Info.Rate.time <= dt) 037. m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0; 038. 039. return m_Info.Rate.time - dt; 040. } 041. //+------------------------------------------------------------------+ 042. void Draw(void) 043. { 044. double v1; 045. 046. if (m_Info.bvT) 047. { 048. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 049. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo); 050. } 051. if (m_Info.bvD) 052. { 053. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 054. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 055. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 056. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 057. } 058. if (m_Info.bvP) 059. { 060. v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 061. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 062. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 063. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 064. } 065. } 066. //+------------------------------------------------------------------+ 067. inline void CreateObjInfo(EnumEvents arg) 068. { 069. switch (arg) 070. { 071. case evShowBarTime: 072. C_Mouse::CreateObjToStudy(2, 110, def_ExpansionBtn1, clrPaleTurquoise); 073. m_Info.bvT = true; 074. break; 075. case evShowDailyVar: 076. C_Mouse::CreateObjToStudy(2, 53, def_ExpansionBtn2); 077. m_Info.bvD = true; 078. break; 079. case evShowPriceVar: 080. C_Mouse::CreateObjToStudy(58, 53, def_ExpansionBtn3); 081. m_Info.bvP = true; 082. break; 083. } 084. } 085. //+------------------------------------------------------------------+ 086. inline void RemoveObjInfo(EnumEvents arg) 087. { 088. string sz; 089. 090. switch (arg) 091. { 092. case evHideBarTime: 093. sz = def_ExpansionBtn1; 094. m_Info.bvT = false; 095. break; 096. case evHideDailyVar: 097. sz = def_ExpansionBtn2; 098. m_Info.bvD = false; 099. break; 100. case evHidePriceVar: 101. sz = def_ExpansionBtn3; 102. m_Info.bvP = false; 103. break; 104. } 105. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 106. ObjectDelete(GetInfoTerminal().ID, sz); 107. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 108. } 109. //+------------------------------------------------------------------+ 110. public : 111. //+------------------------------------------------------------------+ 112. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 113. :C_Mouse(IdParam, szShortName, corH, corP, corN) 114. { 115. if (_LastError != ERR_SUCCESS) return; 116. ZeroMemory(m_Info); 117. m_Info.Status = eCloseMarket; 118. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 119. m_Info.corP = corP; 120. m_Info.corN = corN; 121. CreateObjInfo(evShowBarTime); 122. CreateObjInfo(evShowDailyVar); 123. CreateObjInfo(evShowPriceVar); 124. } 125. //+------------------------------------------------------------------+ 126. void Update(const eStatusMarket arg) 127. { 128. datetime dt; 129. 130. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 131. { 132. case eCloseMarket : 133. m_Info.szInfo = "Closed Market"; 134. break; 135. case eInReplay : 136. case eInTrading : 137. if ((dt = GetBarTime()) < ULONG_MAX) 138. { 139. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 140. break; 141. } 142. case eAuction : 143. m_Info.szInfo = "Auction"; 144. break; 145. default : 146. m_Info.szInfo = "ERROR"; 147. } 148. Draw(); 149. } 150. //+------------------------------------------------------------------+ 151. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 152. { 153. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 154. switch (id) 155. { 156. case CHARTEVENT_CUSTOM + evHideBarTime: 157. RemoveObjInfo(evHideBarTime); 158. break; 159. case CHARTEVENT_CUSTOM + evShowBarTime: 160. CreateObjInfo(evShowBarTime); 161. break; 162. case CHARTEVENT_CUSTOM + evHideDailyVar: 163. RemoveObjInfo(evHideDailyVar); 164. break; 165. case CHARTEVENT_CUSTOM + evShowDailyVar: 166. CreateObjInfo(evShowDailyVar); 167. break; 168. case CHARTEVENT_CUSTOM + evHidePriceVar: 169. RemoveObjInfo(evHidePriceVar); 170. break; 171. case CHARTEVENT_CUSTOM + evShowPriceVar: 172. CreateObjInfo(evShowPriceVar); 173. break; 174. case (CHARTEVENT_CUSTOM + evSetServerTime): 175. m_Info.TimeDevice = (datetime)dparam; 176. break; 177. case CHARTEVENT_MOUSE_MOVE: 178. Draw(); 179. break; 180. } 181. ChartRedraw(GetInfoTerminal().ID); 182. } 183. //+------------------------------------------------------------------+ 184. }; 185. //+------------------------------------------------------------------+ 186. #undef def_ExpansionBtn3 187. #undef def_ExpansionBtn2 188. #undef def_ExpansionBtn1 189. #undef def_ExpansionPrefix 190. #undef def_MousePrefixName 191. //+------------------------------------------------------------------+
Quellcode der Datei C_Study.mqh
Die Klasse C_Study bleibt praktisch unverändert. Daher bedarf es keiner weiteren Erklärung. Schauen wir uns nun den unten stehenden Indikatorcode an.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This is an indicator for graphical studies using the mouse." 04. #property description "This is an integral part of the Replay / Simulator system." 05. #property description "However it can be used in the real market." 06. #property version "1.56" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/12000" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 14. //+------------------------------------------------------------------+ 15. C_Study *Study = NULL; 16. //+------------------------------------------------------------------+ 17. input long user00 = 0; //ID 18. input C_Study::eStatusMarket user01 = C_Study::eAuction; //Market Status 19. input color user02 = clrBlack; //Price Line 20. input color user03 = clrPaleGreen; //Positive Study 21. input color user04 = clrLightCoral; //Negative Study 22. //+------------------------------------------------------------------+ 23. C_Study::eStatusMarket m_Status; 24. int m_posBuff = 0; 25. double m_Buff[]; 26. //+------------------------------------------------------------------+ 27. int OnInit() 28. { 29. ResetLastError(); 30. Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04); 31. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 32. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 33. { 34. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 35. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 36. m_Status = C_Study::eCloseMarket; 37. }else 38. m_Status = user01; 39. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 40. ArrayInitialize(m_Buff, EMPTY_VALUE); 41. 42. return INIT_SUCCEEDED; 43. } 44. //+------------------------------------------------------------------+ 45. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 46. { 47. m_posBuff = rates_total; 48. (*Study).Update(m_Status); 49. 50. return rates_total; 51. } 52. //+------------------------------------------------------------------+ 53. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 54. { 55. (*Study).DispatchMessage(id, lparam, dparam, sparam); 56. (*Study).SetBuffer(m_posBuff, m_Buff); 57. 58. ChartRedraw((*Study).GetInfoTerminal().ID); 59. } 60. //+------------------------------------------------------------------+ 61. void OnBookEvent(const string &symbol) 62. { 63. MqlBookInfo book[]; 64. C_Study::eStatusMarket loc = m_Status; 65. 66. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 67. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 68. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 69. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 70. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 71. if (loc != m_Status) (*Study).Update(m_Status); 72. } 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. if (reason != REASON_INITFAILED) 77. { 78. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 79. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 80. } 81. delete Study; 82. } 83. //+------------------------------------------------------------------+
Quellcode des Maus-Indikators
Bitte beachten Sie, dass es hier einige Unterschiede zu dem bisherigen Code gibt. Aber sie sind nicht so wichtig, dass man den Code nicht verstehen kann. Es gibt jedoch zwei Punkte, die meines Erachtens einen kurzen Kommentar verdienen. Sie befinden sich in den Zeilen 47 und 56.
In Zeile 47 sehen Sie, dass wir im Gegensatz zu früher nur den Wert speichern, den MetaTrader 5 liefert, und keine Änderungen daran vornehmen, da dies an anderer Stelle geschieht.
Dann, in Zeile 56, übergeben wir Informationen an die Klasse C_Mouse, wo wir die Situation korrigieren und sie in den Indikatorpuffer schreiben. Beachten Sie, dass die Dinge hier viel einfacher geworden sind, gerade weil wir die gesamte Komplexität in die Klasse verlagert haben.
Schlussfolgerung
Nach all den Änderungen und Anpassungen des Codes sind wir nun in der Lage, den Mauszeiger in dem von uns entwickelten Wiedergabe-/Simulatorsystem praktisch zu verwenden. Wir können nun das Nachrichtenaustauschsystem zwischen den beteiligten Anwendungen nutzen und gleichzeitig das Lesen von Puffern verwenden, um Informationen zwischen ihnen zu übertragen. Die einzige Einschränkung, die wir haben, ist, dass wir immer eine einzige Position im Puffer verwenden müssen. Dies ist notwendig, um das Ereignissystem nicht zu überlasten. Aber das wird ein weiteres Thema sein, das wir in Zukunft diskutieren werden.
In dem folgenden Video sehen Sie eine Demonstration des Systems in Betrieb. Im Anhang finden Sie den kompilierten Code, damit Sie Tests durchführen und verstehen können, was tatsächlich passiert. Bevor ich es vergesse zu erwähnen, können Sie den Bereichsfehler testen, indem Sie den Mauszeiger durch den aus dem vorherigen Artikel ersetzen und den Dienst aus diesem Artikel ausführen. Sie werden den genannten Fehler sehen.
Ein letzter Punkt: Obwohl in diesem Artikel der verwendete Servicecode nicht erwähnt wird, werde ich seine Erklärung für den nächsten Artikel aufheben, da wir denselben Code als Sprungbrett für unsere eigentliche Entwicklung verwenden werden. Haben Sie also Geduld und warten Sie auf den nächsten Artikel, denn jeden Tag wird es interessanter.
Demo-Video
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12000





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