Desarrollo de un sistema de repetición (Parte 61): Presionando play en el servicio (II)
Introducción
En el artículo anterior, "Desarrollo de un sistema de repetición (Parte 60): Presionando play en el servicio (I)", realizamos algunos ajustes para que el sistema de repetición/simulador pudiera comenzar a lanzar nuevos datos en el gráfico. Aunque hicimos lo mínimo necesario para lograrlo, quedó claro que había ocurrido algo extraño. El sistema, que aparentemente no había sufrido cambios significativos, experimentó una caída importante de rendimiento. Esto da la impresión de que el sistema dejó de ser viable, ya que de repente se volvió muy lento. ¿Será realmente así? Y, de ser cierto, ¿cómo podemos resolver este problema? Siempre manteniendo los principios de la programación orientada a objetos.
Aunque efectivamente hubo una caída en el rendimiento, es posible solucionar gran parte de este problema ajustando y comprendiendo algunos aspectos clave del código. Tal vez en este artículo empiece a mostrar herramientas disponibles en el MetaEditor que pueden facilitar mucho el proceso de ajuste y mejora del código en el que estamos trabajando o desarrollando. Esto debería haberse mencionado antes en artículos anteriores, pero no lo consideré tan necesario como ahora, cuando es fundamental entender cómo funciona el código y por qué sufrió tal caída en su rendimiento sin cambios significativos en su lógica de trabajo.
Implementamos las mejoras más evidentes y directas
En muchos casos, el desconocimiento o la falta de una explicación más avanzada sobre el funcionamiento de MetaTrader 5 y MQL5 dificulta algunas implementaciones. Por fortuna, en la comunidad podemos compartir conocimientos que, aunque no siempre se apliquen de inmediato a lo que estamos desarrollando, siempre son bienvenidos.
Uno de estos puntos clave es el que intentaré explicar aquí. Gran parte de lo que expongo resulta más claro cuando realmente trabajas con MQL5 para aprovechar al máximo las capacidades de MetaTrader 5, algo que muchos no suelen conseguir.
Quizás uno de los aspectos menos comprendidos por muchos programadores de MQL5 sean los objetos gráficos. Se cree comúnmente que estos objetos solo pueden accederse, manipularse y ajustarse mediante algo presente en el gráfico, independientemente de que se trate de un indicador, script o EA. Sin embargo, esto está lejos de ser cierto.
Hasta el momento, hemos trabajado para que no se cree ningún tipo de dependencia entre lo que está en la ventana del gráfico del símbolo personalizado y lo que se está ejecutando en MetaTrader 5. Sin embargo, además de los métodos que estamos utilizando para transferir información entre las aplicaciones que se ejecutan en MetaTrader 5, existe la posibilidad de hacer algo un poco más elaborado, aunque arriesgado. No me malinterpreten, pero cuando hacemos las cosas generando alguna dependencia entre lo que se está ejecutando y lo que esperábamos que se ejecutara, pueden comenzar a ocurrir cosas extrañas.
Aunque muchas veces funcione, podríamos estar entrando en un camino tortuoso que podría llevarnos a toparnos con una pared y perder así un tiempo valioso que podría emplearse mejor en otras tareas. El motivo es que, en muchas ocasiones, dichos cambios imposibilitarán la adopción de nuevas medidas para mejorar o implementar algo. Para comprender lo que estoy proponiendo, es necesario tener conocimientos adecuados que permitan entender cómo funcionará el sistema.
El primer punto importante es que el módulo del indicador de control aparecerá únicamente si el servicio de repetición/simulación está en ejecución. No intentes colocar el módulo de control en el gráfico de forma manual, ya que esto invalidará todo lo que hemos hecho hasta ahora.
Segundo punto: los objetos gráficos creados por el módulo de control deberán seguir siempre una nomenclatura fija y muy estricta. De lo contrario, nos enfrentaríamos a serios problemas en el futuro.
Además de estos dos puntos, también implementaremos una medida que facilitará mucho la legibilidad del código. No tiene sentido utilizar símbolos o marcas que no signifiquen nada para nosotros. Sin embargo, estos cambios en la legibilidad estarán orientados más a facilitar la comprensión de ciertos ajustes que a hacer el código más rápido. Esto quedará más claro cuando se presenten los códigos fuente.
Los primeros cambios que realizaremos serán en el archivo de cabecera C_Controls.mqh. Pero antes de entender por qué es necesario realizar estos cambios, veamos cómo ha sido modificado. El nuevo código se muestra a continuación.
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_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A)) 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 eMatrixControl {eCtrlPosition, eCtrlStatus}; 038. enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)}; 039. //+------------------------------------------------------------------+ 040. struct st_00 041. { 042. string szBarSlider, 043. szBarSliderBlock; 044. ushort Minimal; 045. }m_Slider; 046. struct st_01 047. { 048. C_DrawImage *Btn; 049. bool state; 050. short x, y, w, h; 051. }m_Section[eObjectControl::eNull]; 052. C_Mouse *m_MousePtr; 053. //+------------------------------------------------------------------+ 054. inline void CreteBarSlider(short x, short size) 055. { 056. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 064. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 065. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 070. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 071. } 072. //+------------------------------------------------------------------+ 073. void SetPlay(bool state) 074. { 075. if (m_Section[ePlay].Btn == NULL) 076. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay); 077. 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) ? 1 : 0)); 078. if (!state) CreateCtrlSlider(); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateCtrlSlider(void) 082. { 083. CreteBarSlider(77, 436); 084. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 085. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 086. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin); 087. PositionPinSlider(m_Slider.Minimal); 088. } 089. //+------------------------------------------------------------------+ 090. inline void RemoveCtrlSlider(void) 091. { 092. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 093. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 094. { 095. delete m_Section[c0].Btn; 096. m_Section[c0].Btn = NULL; 097. } 098. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B")); 099. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 100. } 101. //+------------------------------------------------------------------+ 102. inline void PositionPinSlider(ushort p) 103. { 104. int iL, iR; 105. 106. m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 107. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 108. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 109. m_Section[ePin].x += def_PosXObjects; 110. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 111. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 112. 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))); 113. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 114. } 115. //+------------------------------------------------------------------+ 116. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 117. { 118. C_Mouse::st_Mouse InfoMouse; 119. 120. InfoMouse = (*m_MousePtr).GetInfoMouse(); 121. x = (short) InfoMouse.Position.X_Graphics; 122. y = (short) InfoMouse.Position.Y_Graphics; 123. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 124. { 125. 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)) 126. return c0; 127. } 128. 129. return eNull; 130. } 131. //+------------------------------------------------------------------+ 132. public : 133. //+------------------------------------------------------------------+ 134. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 135. :C_Terminal(Arg0), 136. m_MousePtr(MousePtr) 137. { 138. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 139. if (_LastError != ERR_SUCCESS) return; 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 141. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 142. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 143. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 144. { 145. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 146. m_Section[c0].y = 25; 147. m_Section[c0].Btn = NULL; 148. } 149. m_Section[ePlay].x = def_PosXObjects; 150. m_Section[eLeft].x = m_Section[ePlay].x + 47; 151. m_Section[eRight].x = m_Section[ePlay].x + 511; 152. m_Slider.Minimal = eTriState; 153. } 154. //+------------------------------------------------------------------+ 155. ~C_Controls() 156. { 157. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 158. ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("")); 159. delete m_MousePtr; 160. } 161. //+------------------------------------------------------------------+ 162. void SetBuffer(const int rates_total, double &Buff[]) 163. { 164. uCast_Double info; 165. 166. info._16b[eCtrlPosition] = m_Slider.Minimal; 167. info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));//SHORT_MAX : SHORT_MIN); 168. if (rates_total > 0) 169. Buff[rates_total - 1] = info.dValue; 170. } 171. //+------------------------------------------------------------------+ 172. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 173. { 174. short x, y; 175. static ushort iPinPosX = 0; 176. static short six = -1, sps; 177. uCast_Double info; 178. 179. switch (id) 180. { 181. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 182. info.dValue = dparam; 183. if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break; 184. x = (short) info._16b[eCtrlPosition]; 185. iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition])); 186. SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay); 187. break; 188. case CHARTEVENT_OBJECT_DELETE: 189. if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName("")) 190. { 191. if (sparam == def_ObjectCtrlName(ePlay)) 192. { 193. delete m_Section[ePlay].Btn; 194. m_Section[ePlay].Btn = NULL; 195. SetPlay(m_Section[ePlay].state); 196. }else 197. { 198. RemoveCtrlSlider(); 199. CreateCtrlSlider(); 200. } 201. } 202. break; 203. case CHARTEVENT_MOUSE_MOVE: 204. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 205. { 206. case ePlay: 207. SetPlay(!m_Section[ePlay].state); 208. if (m_Section[ePlay].state) 209. { 210. RemoveCtrlSlider(); 211. m_Slider.Minimal = iPinPosX; 212. }else CreateCtrlSlider(); 213. break; 214. case eLeft: 215. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 216. break; 217. case eRight: 218. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 219. break; 220. case ePin: 221. if (six == -1) 222. { 223. six = x; 224. sps = (short)iPinPosX; 225. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 226. } 227. iPinPosX = sps + x - six; 228. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 229. break; 230. }else if (six > 0) 231. { 232. six = -1; 233. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 234. } 235. break; 236. } 237. ChartRedraw(GetInfoTerminal().ID); 238. } 239. //+------------------------------------------------------------------+ 240. }; 241. //+------------------------------------------------------------------+ 242. #undef def_PosXObjects 243. #undef def_ButtonPlay 244. #undef def_ButtonPause 245. #undef def_ButtonLeft 246. #undef def_ButtonRight 247. #undef def_ButtonPin 248. #undef def_PathBMP 249. //+------------------------------------------------------------------+
Código fuente del archivo C_Controls.mqh
La manera de hacer que los objetos de control sigan un formato bastante estricto y, al mismo tiempo, sean fáciles de declarar es mediante la definición de la línea 23. Esta línea puede parecer extremadamente complicada, pero no te dejes llevar por su apariencia extravagante. En caso de duda, pruébala de forma aislada para entender su funcionamiento.
Ahora presta atención a algo importante. Observa que en las líneas 37 y 38 hay dos enumeraciones. La de la línea 37 no existía anteriormente, pero se creó para facilitar el acceso a los datos en el búfer. Puedes observar cómo se hace esto en el procedimiento SetBuffer, que se encuentra en la línea 162. Se realizó el mismo tipo de ajuste en el procedimiento de manejo de mensajes, aunque en este caso la implementación es ligeramente diferente. Esto puede observarse entre las líneas 182 y 186. Sin embargo, presta atención a la línea 184, ya que fue eliminada del código original.
Pero, volviendo a la cuestión de las enumeraciones, observa que la enumeración de la línea 38 ha cambiado respecto a su estado anterior. El motivo de este cambio es introducir ajustes que hagan el código más legible. De hecho, la variable de la línea 44, que antes era de un tipo con signo, ahora es de un tipo sin signo. Este tipo de modificaciones permite realizar pequeños cambios, como el observado en la línea 152, o algo similar a lo que puede verse en la línea 186.
Todo esto hace que el código sea un poco más legible, ya que la idea es implementar algo ligeramente diferente a lo anterior.
Muy bien, entonces veamos qué se hará en concreto. De cierta manera, esto podría ahorrar ciclos de máquina en el futuro. Pero primero debemos entender un detalle. En la línea 77, solicitamos cambiar la imagen representada en el objeto gráfico. Se trata de un botón que indica si estamos en modo "play" o "pause". Sin embargo, el servicio siempre observa el búfer del indicador de control, aunque podemos manejar esta funcionalidad de manera diferente en lo que respecta a saber si estamos en modo "play" o "pause". Esto está relacionado precisamente con el objeto manipulado en la línea 77.
Accedemos rápidamente al estado del botón
Como mencioné en el tema anterior, estos simples cambios no benefician al servicio lo suficiente como para lograr una mejora significativa en su rendimiento. Sin embargo, al analizar los puntos donde el servicio necesita realmente mejorar su rendimiento, la perspectiva cambia.
En el artículo anterior, mostré cuándo es necesario. Para refrescar tu memoria, el punto clave es la función LoopEventOnTime. Esta función llama a otra función con intervalos regulares para verificar el estado del botón del indicador de control y determinar si estamos en modo "pausa" o en modo "play".
Esta verificación se realiza inicialmente observando lo que está presente en el búfer del indicador de control. Sin embargo, existe una manera más elegante, aunque tiene otros problemas: observar directamente el objeto de control. Recuerda que el objeto de control es un OBJ_BITMAP_LABEL y este tipo de objeto tiene dos estados que pueden verificarse a través de una variable contenida en el objeto.
Al hacer esto, al inspeccionar el contenido de una variable específica dentro del objeto OBJ_BITMAP_LABEL en el gráfico, permitimos que el servicio ignore la lectura del búfer para determinar si debe iniciar o pausar el envío de datos al gráfico.
Sin embargo, si revisas el código del archivo de cabecera C_DrawImage.mqh, no encontrarás ninguna modificación en la variable necesaria del objeto OBJ_BITMAP_LABEL. Esto ocurre incluso antes de intentar hacer algo en el servicio. No obstante, si revisas el código del archivo C_Controls.mqh, verás que en la línea 77 se hace una solicitud para actualizar el objeto. A partir de este punto, podemos implementar los cambios necesarios para que el servicio se beneficie de ellos. En teoría, ya que el ahorro será de unos pocos ciclos de máquina por cada llamada.
Dado que los cambios no serán muchos, no reproduciré todo el código del archivo de cabecera aquí. Así que abre el archivo de cabecera C_DrawImage.mqh y modifícalo según el fragmento mostrado a continuación.
174. //+------------------------------------------------------------------+ 175. void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what) 176. { 177. 178. if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return; 179. ReSizeImage(w, h, cView, what); 180. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x); 181. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y); 182. if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE)) 183. { 184. ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName); 185. ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName); 186. ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_STATE, what == 1); 187. ChartRedraw(GetInfoTerminal().ID); 188. } 189. } 190. //+------------------------------------------------------------------+
Fragmento del código fuente C_DrawImage.mqh
Observa algo importante: la línea 184 ha sido reemplazada por la línea 185, ya que esta incluye un parámetro que especifica el índice de la imagen. Sin embargo, lo que realmente nos interesa es la línea 186, que actualizará el estado de la variable del objeto OBJ_BITMAP_LABEL. Ahora, la variable OBJPROP_STATE del objeto reflejará directamente su estado. Recordemos que solo hay dos estados posibles. Play o Pause.
Con esto, podemos dirigir nuestra atención al código del archivo de cabecera C_Replay.mqh, donde se permite que el servicio acceda directamente al objeto y determine si estamos en modo de reproducción o pausa.
Para que el servicio pueda entender lo que está sucediendo, primero será necesario agregar algo nuevo. El primer cambio se encuentra justo a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_ConfigService.mqh" 05. #include "C_Controls.mqh" 06. //+------------------------------------------------------------------+ 07. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 08. #resource "\\" + def_IndicatorControl 09. //+------------------------------------------------------------------+ 10. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != "")) 11. //+------------------------------------------------------------------+ 12. #define def_ShortNameIndControl "Market Replay Control" 13. //+------------------------------------------------------------------+ 14. class C_Replay : public C_ConfigService 15. { 16. private : 17. struct st00 18. { 19. C_Controls::eObjectControl Mode; 20. uCast_Double Memory; 21. ushort Position; 22. int Handle; 23. }m_IndControl;
Fragmento del código fuente C_Replay.mqh
Observa que aquí, en la línea 5, hemos añadido una referencia al archivo de cabecera del indicador de control. No implementaremos nada que utilice directamente la clase de control, pero necesitamos acceder a las definiciones contenidas en dicho archivo. La principal es la que nos permite identificar los nombres de los objetos creados por la clase. No te preocupes, llegaremos a eso.
En este mismo fragmento hay otros cambios. Por ejemplo, en la línea 19, la variable es de un tipo diferente, lo que mejora la legibilidad del código. Además, se ha añadido una nueva variable en la línea 20. Esta variable se utilizará para almacenar ciertos valores provenientes del búfer del indicador de control. Sin embargo, no se utilizará exactamente como cabría esperar. Esto se aclarará más adelante. Una vez realizados estos cambios, debemos ajustar inmediatamente el constructor de la clase C_Replay. Dicho ajuste se muestra a continuación.
131. //+------------------------------------------------------------------+ 132. C_Replay() 133. :C_ConfigService() 134. { 135. Print("************** Market Replay Service **************"); 136. srand(GetTickCount()); 137. SymbolSelect(def_SymbolReplay, false); 138. CustomSymbolDelete(def_SymbolReplay); 139. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 140. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 141. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 142. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 143. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 144. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 145. SymbolSelect(def_SymbolReplay, true); 146. m_Infos.CountReplay = 0; 147. m_IndControl.Handle = INVALID_HANDLE; 148. m_IndControl.Mode = C_Controls::ePause; 149. m_IndControl.Position = 0; 150. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState; 151. } 152. //+------------------------------------------------------------------+
Fragmento del código fuente C_Replay.mqh
Observa cómo se inicializan los valores de la estructura m_IndControl. Es fundamental entender cómo se realiza esta inicialización y, sobre todo, por qué se utilizan esos valores y no otros. Aunque el motivo puede no ser evidente en este momento, lo será pronto. La idea es acceder al objeto presente en el gráfico, creado y mantenido por el módulo del indicador de control.
Para aprovechar realmente esta funcionalidad y acceder al objeto OBJ_BITMAP_LABEL directamente desde el gráfico mediante el servicio, será necesario modificar ligeramente el código UpdateIndicatorControl, ya existente en la clase C_Replay. La modificación puede verse en el fragmento siguiente.
34. //+------------------------------------------------------------------+ 35. inline void UpdateIndicatorControl(void) 36. { 37. static bool bTest = false; 38. double Buff[]; 39. 40. if (m_IndControl.Handle == INVALID_HANDLE) return; 41. if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position) 42. { 43. if (bTest) 44. m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1 ? C_Controls::ePause : C_Controls::ePlay); 45. else 46. { 47. if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1) 48. m_IndControl.Memory.dValue = Buff[0]; 49. if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState) 50. if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)) 51. m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition]; 52. } 53. }else 54. { 55. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position; 56. m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode; 57. m_IndControl.Memory._8b[7] = 'D'; 58. m_IndControl.Memory._8b[6] = 'M'; 59. EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, ""); 60. bTest = false; 61. } 62. } 63. //+------------------------------------------------------------------+
Fragmento del código fuente C_Replay.mqh
Verás que este código es muy diferente al anterior. La razón principal es implementar un enfoque más seguro para acceder a cualquier recurso relacionado con el módulo del indicador de control.
Para entender cómo funciona este fragmento, recuerda algunos puntos clave:
1) Los valores se inicializan en el constructor; 2) la primera rutina que llama a esta función es la que inicializa el módulo del indicador de control; 3) periódicamente, el procedimiento del bucle verifica el estado del botón en el indicador de control.
Estos tres puntos funcionan en secuencia, pero el tercero es el que más problemas causa. Este procedimiento es el que, de hecho, genera nuevos ticks en el gráfico y observa constantemente el indicador de control. Sin embargo, al poder leer directamente el estado del objeto presente en el gráfico y acceder al búfer del indicador solo en casos especiales, este procedimiento UpdateIndicatorControl podría evitar una pérdida de rendimiento en las fases críticas del servicio de repetición/simulación.
Veamos cómo funciona este fragmento. Primero, en la línea 40, verificamos si tenemos un manipulador válido. Si es correcto, continuamos. A continuación, en la línea 41, verificamos si el valor en memoria coincide con la posición actual. Si es así, en la línea 43 evaluamos si la variable estática es verdadera. En ese caso, utilizamos una función para acceder al valor contenido en el OBJ_BITMAP_LABEL. Presta mucha atención a cómo se realiza este acceso, ya que podrías encontrarlo peculiar, dado que hacemos referencia a algo definido en el archivo de cabecera C_Controls.mqh. Sin embargo, el acceso está correctamente implementado.
Si la variable estática es falsa, esto indica que se pueden realizar lecturas ligeramente más lentas de los datos. En este caso, utilizamos el búfer del indicador de control. Atención: no digo que la lectura del búfer sea realmente más lenta, pero compara el número de operaciones necesarias para leer una propiedad del objeto presente en el gráfico.
De todos modos, una vez leído el búfer, en la línea 49 verificaremos si no estamos en TriState. Si esta condición se cumple, ejecutaremos un conjunto de operaciones en la línea 50, antes de comprobar si estamos en modo play.Esto determinará si la variable estática será verdadera o falsa. Este conjunto de operaciones, que en realidad son asignaciones, está anidado de tal manera que el comando parece mucho más complejo de lo que realmente es. Sin embargo, dado que esto no afecta al compilador y los valores se asignan como se espera, podemos hacerlo así. Si la línea 50 es verdadera, almacenaremos el valor del búfer en la variable de posición interna. Esto ocurre en la línea 51.
Este tipo de acción solo sucederá en una situación: cuando el usuario interactúa con el control deslizante y cambia la posición desde la que debe iniciarse la repetición/simulación. Es decir, cuando estamos en modo de reproducción, este código no se verifica. Sin embargo, cuando pasamos del modo pause al modo play, este código se ejecuta, lo cual será importante más adelante.
Por otro lado, si la comprobación de la línea 41 resulta falsa, se ejecutará lo que está definido entre las líneas 55 y 60. Esto disparará un evento personalizado para que podamos actualizar el módulo del indicador de control. Y así es como funcionará el sistema a partir de ahora.
Tal vez leer directamente el objeto en el gráfico no haga que el servicio sea realmente más eficiente en términos de rendimiento. Pero abre la posibilidad de realizar manipulaciones más avanzadas en los objetos del gráfico. Esto permite construir sistemas mucho más elaborados sin sobrecargar la plataforma MetaTrader 5, evitando llenar el gráfico con indicadores y otros elementos únicamente para manipular los objetos en el gráfico.
Ganamos rendimiento de forma tangible
A pesar de todo lo mencionado hasta ahora, estas modificaciones no suponen un aumento significativo del rendimiento del servicio de repetición/simulador. Al menos, no se observa un cambio drástico. Sin embargo, estas alteraciones hacen que ciertas partes del código sean más eficientes y, sobre todo, más legibles. Este último aspecto se debe a las definiciones creadas en el archivo C_Controls.mqh, que luego se utilizaron en el archivo de cabecera C_Replay.mqh. Aunque no hayas visto todo el código completo, probablemente ya tengas una idea de cómo modificar ciertas secciones para mejorar la legibilidad de la clase C_Replay.
Ahora bien, veamos algo que realmente mejora el rendimiento del código. Esto busca hacer que vuelva a ejecutarse de manera que pueda generar una barra de 1 minuto dentro del tiempo esperado.
Puede parecer extraño y, a simple vista, carecer de sentido lo que voy a mostrarte. Pero créeme, pruébalo por ti mismo y comprueba que hay una mejora sustancial en el rendimiento con un cambio sencillo. Para verlo, observemos ahora el código completo del archivo de cabecera C_Replay.mqh. A continuación, se muestra en su totalidad. La numeración será un poco diferente a la de los fragmentos anteriores, pero no te preocupes por ello. Centrémonos en el código completo.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_ConfigService.mqh" 005. #include "C_Controls.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 008. #resource "\\" + def_IndicatorControl 009. //+------------------------------------------------------------------+ 010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != "")) 011. //+------------------------------------------------------------------+ 012. #define def_ShortNameIndControl "Market Replay Control" 013. //+------------------------------------------------------------------+ 014. class C_Replay : public C_ConfigService 015. { 016. private : 017. struct st00 018. { 019. C_Controls::eObjectControl Mode; 020. uCast_Double Memory; 021. ushort Position; 022. int Handle; 023. }m_IndControl; 024. struct st01 025. { 026. long IdReplay; 027. int CountReplay; 028. double PointsPerTick; 029. MqlTick tick[1]; 030. MqlRates Rate[1]; 031. }m_Infos; 032. stInfoTicks m_MemoryData; 033. //+------------------------------------------------------------------+ 034. inline bool MsgError(string sz0) { Print(sz0); return false; } 035. //+------------------------------------------------------------------+ 036. inline void UpdateIndicatorControl(void) 037. { 038. static bool bTest = false; 039. double Buff[]; 040. 041. if (m_IndControl.Handle == INVALID_HANDLE) return; 042. if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position) 043. { 044. if (bTest) 045. m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1 ? C_Controls::ePause : C_Controls::ePlay); 046. else 047. { 048. if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1) 049. m_IndControl.Memory.dValue = Buff[0]; 050. if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState) 051. if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)) 052. m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition]; 053. } 054. }else 055. { 056. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position; 057. m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode; 058. m_IndControl.Memory._8b[7] = 'D'; 059. m_IndControl.Memory._8b[6] = 'M'; 060. EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, ""); 061. bTest = false; 062. } 063. } 064. //+------------------------------------------------------------------+ 065. void SweepAndCloseChart(void) 066. { 067. long id; 068. 069. if ((id = ChartFirst()) > 0) do 070. { 071. if (ChartSymbol(id) == def_SymbolReplay) 072. ChartClose(id); 073. }while ((id = ChartNext(id)) > 0); 074. } 075. //+------------------------------------------------------------------+ 076. inline void CreateBarInReplay(bool bViewTick) 077. { 078. bool bNew; 079. double dSpread; 080. int iRand = rand(); 081. 082. if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew)) 083. { 084. m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay]; 085. if (m_MemoryData.ModePlot == PRICE_EXCHANGE) 086. { 087. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 088. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 089. { 090. m_Infos.tick[0].ask = m_Infos.tick[0].last; 091. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 092. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 093. { 094. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 095. m_Infos.tick[0].bid = m_Infos.tick[0].last; 096. } 097. } 098. if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 099. CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate); 100. } 101. m_Infos.CountReplay++; 102. } 103. //+------------------------------------------------------------------+ 104. void AdjustViewDetails(void) 105. { 106. MqlRates rate[1]; 107. 108. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 109. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 110. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE); 111. m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 112. CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate); 113. if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE)) 114. for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++); 115. if (rate[0].close > 0) 116. { 117. if (GetInfoTicks().ModePlot == PRICE_EXCHANGE) 118. m_Infos.tick[0].last = rate[0].close; 119. else 120. { 121. m_Infos.tick[0].bid = rate[0].close; 122. m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick); 123. } 124. m_Infos.tick[0].time = rate[0].time; 125. m_Infos.tick[0].time_msc = rate[0].time * 1000; 126. }else 127. m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay]; 128. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 129. } 130. //+------------------------------------------------------------------+ 131. public : 132. //+------------------------------------------------------------------+ 133. C_Replay() 134. :C_ConfigService() 135. { 136. Print("************** Market Replay Service **************"); 137. srand(GetTickCount()); 138. SymbolSelect(def_SymbolReplay, false); 139. CustomSymbolDelete(def_SymbolReplay); 140. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 141. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 142. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 143. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 144. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 145. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 146. SymbolSelect(def_SymbolReplay, true); 147. m_Infos.CountReplay = 0; 148. m_IndControl.Handle = INVALID_HANDLE; 149. m_IndControl.Mode = C_Controls::ePause; 150. m_IndControl.Position = 0; 151. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState; 152. } 153. //+------------------------------------------------------------------+ 154. ~C_Replay() 155. { 156. IndicatorRelease(m_IndControl.Handle); 157. SweepAndCloseChart(); 158. SymbolSelect(def_SymbolReplay, false); 159. CustomSymbolDelete(def_SymbolReplay); 160. Print("Finished replay service..."); 161. } 162. //+------------------------------------------------------------------+ 163. bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate) 164. { 165. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 166. return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket."); 167. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 168. return MsgError("Asset configuration is not complete, need to declare the ticket value."); 169. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 170. return MsgError("Asset configuration not complete, need to declare the minimum volume."); 171. SweepAndCloseChart(); 172. m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1); 173. if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl")) 174. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 175. else 176. Print("Apply template: ", szNameTemplate, ".tpl"); 177. 178. return true; 179. } 180. //+------------------------------------------------------------------+ 181. bool InitBaseControl(const ushort wait = 1000) 182. { 183. Print("Waiting for Mouse Indicator..."); 184. Sleep(wait); 185. while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 186. if (def_CheckLoopService) 187. { 188. AdjustViewDetails(); 189. Print("Waiting for Control Indicator..."); 190. if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false; 191. ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle); 192. UpdateIndicatorControl(); 193. } 194. 195. return def_CheckLoopService; 196. } 197. //+------------------------------------------------------------------+ 198. bool LoopEventOnTime(void) 199. { 200. int iPos; 201. 202. while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay)) 203. { 204. UpdateIndicatorControl(); 205. Sleep(200); 206. } 207. m_MemoryData = GetInfoTicks(); 208. iPos = 0; 209. while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService)) 210. { 211. if (m_IndControl.Mode == C_Controls::ePause) return true; 212. iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0); 213. CreateBarInReplay(true); 214. while ((iPos > 200) && (def_CheckLoopService)) 215. { 216. Sleep(195); 217. iPos -= 200; 218. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxPosSlider) / m_MemoryData.nTicks); 219. UpdateIndicatorControl(); 220. } 221. } 222. 223. return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService)); 224. } 225. //+------------------------------------------------------------------+ 226. }; 227. //+------------------------------------------------------------------+ 228. #undef macroRemoveSec 229. #undef def_SymbolReplay 230. #undef def_CheckLoopService 231. //+------------------------------------------------------------------+
Código fuente del archivo C_Replay.mqh
Presta mucha, pero mucha atención a lo que voy a explicar. Porque si no comprendes los detalles involucrados, te sentirás completamente perdido con lo que haremos para mejorar el rendimiento del servicio de repetición/simulador.
En la línea 32, definimos una nueva variable privada de la clase C_Replay. Sin embargo, la estructura utilizada está definida en el archivo de cabecera C_FileTicks.mqh. Para más detalles sobre esto, consulta el artículo anterior. Esta variable se utiliza en varios puntos considerados críticos dentro de la clase C_Replay. Estos puntos se seleccionaron debido a la frecuencia con la que se invocan durante la ejecución del servicio. Uno de ellos es el procedimiento CreateBarInReplay, que se usa para convertir ticks en barras en el gráfico. Otro punto clave es la función LoopEventOnTime, donde, en la línea 207, inicializamos esta misma variable.
Un punto importante es que, cuando en la línea 207 utilizamos la función GetInfoTicks para capturar los valores presentes en la clase C_FileTicks, que también forman parte de una variable privada de esta clase, no estamos creando ningún tipo de puntero para manipular dichos datos. En su lugar, al ejecutar la línea 207, duplicamos la información contenida en la variable privada de dicha clase y la almacenamos en una nueva variable que será privada de la clase C_Replay.
Soy consciente de que este enfoque puede que no parezca ideal ni ayude directamente a mejorar la eficiencia del servicio. De hecho, implica un mayor coste debido al uso de más memoria del sistema. Sin embargo, acceder a los datos mediante variables es significativamente más rápido que a través de llamadas a procedimientos o funciones. Esto mejora el rendimiento del servicio. Si MQL5 permitiera el uso de punteros, podríamos almacenar los datos en una región común de memoria. En este caso, la clase C_FileTicks tendría acceso tanto de lectura como de escritura, mientras que otras clases o partes del código solo tendrían acceso de lectura. De este modo, se mantendría el consumo de memoria bajo y se garantizaría un rendimiento adecuado, al tiempo que se respetaría el encapsulamiento de las variables dentro de las clases.
El uso de punteros para lograr esto se implementa mejor mediante funciones. Este enfoque se mostró en la versión discutida en el artículo anterior. Sin embargo, al poner en funcionamiento el servicio de repetición/simulador, especialmente en el modo simulación, donde las barras se transforman en ticks y luego vuelven a convertirse en barras, observé una caída considerable en el rendimiento del servicio. Esto puede deberse a que, en el momento de escribir este artículo, el compilador de MQL5 no reconoce esta construcción de funciones como un puntero que permita acceder restringido a una estructura determinada. En este caso, la clase base, C_FileTicks, podría acceder a la estructura con permisos de lectura y escritura, mientras que cualquier otro código que no fuera de la clase tendría solo acceso de lectura. Es posible que, cuando se lea este artículo, los desarrolladores del compilador de MQL5 ya hayan solucionado esta cuestión. Esto permitiría lograr el mismo rendimiento que con el esquema descrito aquí. Una recomendación importante. Dado que este artículo se escribió hace algún tiempo, te sugiero que pruebes el código antes de implementarlo. Esto se debe a que es posible que ya no sea necesario realizar lo que se muestra aquí.
Sin embargo, no culpo a los creadores y desarrolladores del compilador de MQL5, ya que no he visto una construcción similar en ningún otro lugar. Sin embargo, esta limitación me obligó a cambiar de enfoque y tomar una decisión:
- Romper el encapsulamiento establecido en el artículo anterior.
- Implementar la solución duplicando los datos.
Opté por duplicar los datos, ya que la memoria es un recurso barato. Romper el encapsulamiento habría dificultado el desarrollo y mejora del código. Con este enfoque, logramos un aumento de velocidad de ejecución de aproximadamente cuatro a cinco veces, manteniendo el mismo nivel de seguridad en los datos. Durante las pruebas realizadas antes de implementar la duplicación de datos, logramos procesar cerca de 3 segundos de ticks utilizando el dólar como ejemplo. Estos resultados pueden consultarse en los artículos de la primera fase. Ahora, con la duplicación, conseguimos procesar casi 15 segundos de datos en el mismo contexto. Cabe destacar que estos resultados se aplican a la simulación. En el caso de la repetición, el sistema ahora construye la barra de 1 minuto dentro de un tiempo aceptable, es decir, cercano a los 60 segundos.
Un detalle importante. Es posible que pienses que la función Sleep, presente en la línea 216, podría estar ralentizando el sistema y que, al eliminarla, podríamos procesar más datos en el gráfico. Sin embargo, al trabajar con ticks reales, la función Sleep en la línea 216 es imprescindible. Si no estuviera, los datos se lanzarían al gráfico mucho más rápido de lo normal. Esto haría que la reproducción, y no la simulación, generara la barra de un minuto antes de que transcurrieran los 60 segundos correspondientes.
Conclusión
Ahora nos enfrentamos al siguiente problema: durante una simulación, es posible que se procesen menos ticks de los necesarios en el intervalo de 60 segundos. Sin embargo, en el caso de la repetición, los lanzamientos no se retrasan significativamente. Por lo tanto, en el contexto de la repetición, el sistema sigue operando bajo los mismos principios que en la fase anterior, cuando utilizábamos las variables globales del terminal. Para la simulación, sin embargo, será necesario realizar ciertos ajustes.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12121
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso