
Desarrollo de un sistema de repetición (Parte 59): Un nuevo futuro
Introducción
En el artículo anterior Desarrollo de un sistema de repetición (Parte 58): Volvemos a trabajar en el servicio, mencioné que el sistema había pasado por algunos cambios y que existía una razón para incluir un pequeño retraso entre la aplicación de la plantilla en el gráfico y la actualización del gráfico por parte del servicio. Si esto no ocurriera, los módulos obligarían al servicio a cerrarse de forma prematura. Tal vez muchos de ustedes se hayan quedado con la duda de cómo es posible. Pero aquí lo veremos con más detalle.
Para comenzar a explicar y para que tengas la debida noción de lo que abordaré, mira el Video 01 que está presente más abajo.
Vídeo de demostración de cómo pueden fallar las cosas
En este video muestro exactamente lo que ocurre, sin cortes ni trucos. Pero, ¿por qué ocurre esto? Para entenderlo, es necesario recapitular lo que se abordó en el artículo anterior. A continuación, haré un breve resumen de lo que se explicó en él.
Entendamos el motivo del cierre del servicio
Hay una razón por la que el servicio se cierra de forma prematura justo después de que MetaTrader 5 aplique la plantilla. La razón es simple: el indicador, o más específicamente, el módulo de control, está provocando que el gráfico se cierre. ¿Todavía no lo comprendes? De acuerdo, veamos el código del módulo de control que está causando este comportamiento. Esto puede verse en el siguiente fragmento.
53. void OnDeinit(const int reason) 54. { 55. switch (reason) 56. { 57. case REASON_TEMPLATE: 58. Print("Modified template. Replay // simulation system shutting down."); 59. case REASON_INITFAILED: 60. case REASON_PARAMETERS: 61. case REASON_REMOVE: 62. case REASON_CHARTCLOSE: 63. ChartClose(user00); 64. break; 65. } 66. delete control; 67. }
Fragmento de código del módulo de control
Observa que en la línea 63 hay una llamada que le indica a MetaTrader 5 que debe cerrar el gráfico. De acuerdo, pero ¿quién está realmente ejecutando esta llamada? Esto se debe a que, si fuera el cambio de plantilla, el mensaje presente en la línea 58 debería aparecer en la caja de mensajes de MetaTrader 5, pero no se imprimió. Por tanto, puedes pensar que no es el cambio de plantilla en MetaTrader 5 lo que está causando que el gráfico se cierre.
Entiendo tu duda y posible incredulidad respecto a esto. Sin embargo, lo que está ocurriendo es que la aplicación de la plantilla en el gráfico por parte de MetaTrader 5 es efectivamente quien elimina el indicador de control del gráfico, provocando que este se cierre. No obstante, como esto se hace de forma asincrónica, las cosas no suceden como podrías imaginar.
Lo que ocurre es que, en un momento dado, MetaTrader 5 completa la aplicación de la plantilla en el gráfico. Cuando esto sucede, MetaTrader 5 envía un evento Deinit al indicador. Este evento será gestionado por la función OnDeinit, pero, a diferencia de lo que podrías pensar, el valor presente en la variable reason no es REASON_TEMPLATE, sino REASON_REMOVE. Esto ocurre porque MetaTrader 5 está eliminando el indicador de control del gráfico, completando así la aplicación de la plantilla. Por esta razón, el mensaje de la línea 58 no se imprime en la caja de mensajes del terminal.
Pero ahora surge la pregunta: ¿por qué MetaTrader 5 no aplica la plantilla de forma síncrona? Es decir, ¿por qué la plantilla no se aplica completamente en el gráfico tan pronto como se ejecuta la llamada ChartApplyTemplate, ubicada en la línea 87 del archivo de cabecera C_Replay.mqh? El motivo es la velocidad. Dentro de una plantilla, pueden existir múltiples indicadores o incluso un EA que requiera que un número determinado de barras esté presente en el gráfico para proporcionar indicaciones útiles.
Si MetaTrader 5 esperara a que la llamada ChartApplyTemplate se completara de forma síncrona, la plataforma podría bloquearse durante preciosos segundos e incluso detenerse si algo en la plantilla causara una falla grave. Al realizar este trabajo de forma asincrónica, la plataforma gana velocidad, pero complica las cosas a los programadores con menos experiencia. Una de esas complicaciones es: El programador no es consciente de que algunas llamadas se ejecutarán de forma asincrónica y otras lo harán de inmediato.
Dado que forzar una actualización del gráfico mediante la llamada ChartRedraw no hace que la plantilla se aplique inmediatamente, debemos hacer algo diferente. Esto es exactamente lo que se implementó, y se puede observar en el video de demostración.
Otra cuestión es: ¿por qué no resolverlo de otra manera? Por ejemplo, podrías preguntarte por qué no colocar el indicador de control dentro de la plantilla directamente. Aunque esto resolvería el problema, generaría otro. En versiones anteriores, utilizábamos una variable global del terminal para evitar que el indicador de control apareciera en un gráfico no deseado. Sin embargo, ya no utilizamos este enfoque. La forma de bloqueo ha cambiado. Por lo tanto, no podemos permitir que la plantilla contenga el indicador de control, ya que esto podría causar problemas difíciles de resolver.
En el artículo anterior mencioné que el código de ambos módulos, tanto el de control como el del mouse, había cambiado. El motivo fue un giro inesperado al retomar nuestra atención en el servicio de repetición/simulación. A continuación, veremos cómo es el nuevo código de ambos indicadores. Nos centraremos solo en los puntos donde realmente hubo cambios.
Nuevo código para el servicio de repetición/simulación
A continuación, se presenta el código fuente del indicador o, más precisamente, del módulo de control.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property description "Control indicator for the Replay-Simulator service." 05. #property description "This one doesn't work without the service loaded." 06. #property version "1.59" 07. #property link "https://www.mql5.com/pt/articles/12075" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. #property indicator_buffers 1 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Service Graphics\C_Controls.mqh> 13. //+------------------------------------------------------------------+ 14. C_Controls *control = NULL; 15. //+------------------------------------------------------------------+ 16. input long user00 = 0; //ID 17. //+------------------------------------------------------------------+ 18. double m_Buff[]; 19. int m_RatesTotal = 0; 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. ResetLastError(); 24. if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID) 25. SetUserError(C_Terminal::ERR_PointerInvalid); 26. if ((_LastError != ERR_SUCCESS) || (user00 == 0)) 27. { 28. Print("Control indicator failed on initialization."); 29. return INIT_FAILED; 30. } 31. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 32. ArrayInitialize(m_Buff, EMPTY_VALUE); 33. 34. return INIT_SUCCEEDED; 35. } 36. //+------------------------------------------------------------------+ 37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 38. { 39. return m_RatesTotal = rates_total; 40. } 41. //+------------------------------------------------------------------+ 42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 43. { 44. (*control).DispatchMessage(id, lparam, dparam, sparam); 45. if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown) 46. { 47. Print("Internal failure in the messaging system..."); 48. ChartClose(user00); 49. } 50. (*control).SetBuffer(m_RatesTotal, m_Buff); 51. } 52. //+------------------------------------------------------------------+ 53. void OnDeinit(const int reason) 54. { 55. switch (reason) 56. { 57. case REASON_TEMPLATE: 58. Print("Modified template. Replay // simulation system shutting down."); 59. case REASON_INITFAILED: 60. case REASON_PARAMETERS: 61. case REASON_REMOVE: 62. case REASON_CHARTCLOSE: 63. ChartClose(user00); 64. break; 65. } 66. delete control; 67. } 68. //+------------------------------------------------------------------+ 69.
Código fuente del indicador de control
Notarás que no mostraré el código del archivo de cabecera, ya que no se ha modificado. Aunque podemos consultar todo el código del módulo de control más arriba, nos centraremos en los fragmentos que realmente han sido modificados. En él tenemos una única diferencia que hay está en la línea 26. Se evalúan dos condiciones en esa línea. Una es el valor de _LastError, y la otra es el valor de user00. Este user00 se define en la línea 16 y es el encargado de recibir, desde el servicio de repetición/simulador, el valor del ID del gráfico donde debe estar presente el módulo de control.
Ahora presta mucha atención. Normalmente, tú, como usuario, no podrás definir realmente el valor que se asignará en la entrada de la línea 16. Esto ocurre de manera natural. Pero, ¿y si intentaras engañar al sistema para guardar toda la configuración como un archivo de plantilla? Esto podría hacer que todo funcionara y nos evitara el inconveniente causado por la aplicación de la plantilla, ¿no crees? Incorrecto. No funcionará como esperas. de la forma que esperas.
Si, después de que el servicio haya cargado todo y MetaTrader 5 haya estabilizado el gráfico, guardas este gráfico ya listo como una plantilla, podrías notar algo si abres el archivo de plantilla. Este algo se puede ver en el fragmento mostrado a continuación.
01. <indicator> 02. name=Custom Indicator 03. path=Services\Market Replay.ex5::Indicators\Market Replay.ex5 04. apply=0 05. show_data=1 06. scale_inherit=0 07. scale_line=0 08. scale_line_percent=50 09. scale_line_value=0.000000 10. scale_fix_min=0 11. scale_fix_min_val=0.000000 12. scale_fix_max=0 13. scale_fix_max_val=0.000000 14. expertmode=1610613824 15. fixed_height=-1 16. 17. <graph> 18. name= 19. draw=0 20. style=0 21. width=1 22. color= 23. </graph> 24. <inputs> 25. user00=130652731570824061 26. </inputs> 27. </indicator>
Fragmento del archivo de plantilla
La posición exacta de este fragmento no es importante aquí. Por lo tanto, la numeración de las líneas es solo una ayuda para referenciar los elementos y facilitar la explicación.
Observa que en la línea 1 hay una etiqueta de apertura y que en la línea 27 hay una etiqueta que cierra la estructura. Entre estas dos etiquetas hay diversa información, como la ubicación del indicador que debe colocarse en el gráfico. Esto se observa en la línea 3. Perfecto. Ahora presta atención a la línea 24. Allí tenemos una etiqueta que abre la sección de entradas esperadas por el indicador declarado en la línea 3. De manera similar, la línea 26 cierra la parte correspondiente a las entradas.
En la línea 25 referenciamos la entrada que, en el código fuente del indicador, está declarada en la línea 16 para recibir el ID del gráfico. Nota que en esta línea 25, del fragmento, estamos enviando un valor al indicador de control para que la condición evaluada en la línea 26, del código fuente del indicador, sea falsa. Así, el indicador se cargaría y todo funcionaría correctamente. ¿Es así? De nuevo, estás equivocado. Tan pronto como el servicio de repetición/simulación coloque el indicador de control en el gráfico de la forma correcta, el código del indicador detectará que ya existe otra instancia ejecutándose allí. Cuando esto ocurra, la línea 24 del código del indicador de control generará un resultado que hará que se ejecute la línea 25. Esto provocará que _LastError ya no tenga el valor esperado en la línea 26. Es decir, todo el sistema fallará y MetaTrader 5 generará un evento Deinit. Este evento será gestionado por el procedimiento presente en la línea 53, es decir, OnDeinit. Ahí se ejecutará un flujo que parte de REASON_INITFAILED, que cerrará el gráfico y hará que el servicio de repetición/simulador se detenga.
Mira cómo todo funciona maravillosamente cuando hacemos un uso adecuado de todas las capacidades que la plataforma MetaTrader 5 nos ofrece. Pero esa fue la cuestión del indicador de control. ¿Y qué pasó con el indicador del mouse? ¿Qué ocurrió con él?
En cuanto al indicador de mouse, la situación es un poco más interesante. Esto se debe a que se decidió permitir que realizara algo, pero no solo él. Sin embargo, se utilizará para iniciar la implementación de esta funcionalidad. Por lo tanto, fue necesario añadir y eliminar algunas cosas del módulo responsable del indicador de mouse. Así que, para delimitar adecuadamente las cosas, lo colocaremos en el próximo tema.
Pequeños cambios, para grandes intereses
Aunque el módulo de mouse inicialmente estaba diseñado para ser colocado en la ventana principal, junto con otras funcionalidades, MQL5 nos permite hacer un poco más que eso. Sinceramente, no había considerado hacer ciertas cosas. Pero, dado que MetaTrader 5 puede configurarse y ajustarse para permitirnos realizar ciertas tareas rápidamente, se decidió que sería necesario añadir y modificar algunas cosas en el código. Por lo tanto, sigue las modificaciones y adiciones realizadas, comenzando con la clase C_Terminal, que puede verse a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. protected: 011. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 012. //+------------------------------------------------------------------+ 013. struct st_Terminal 014. { 015. ENUM_SYMBOL_CHART_MODE ChartMode; 016. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 017. long ID; 018. string szSymbol; 019. int Width, 020. Height, 021. nDigits, 022. SubWin; 023. double PointPerTick, 024. ValuePerPoint, 025. VolumeMinimal, 026. AdjustToTrade; 027. }; 028. //+------------------------------------------------------------------+ 029. private : 030. st_Terminal m_Infos; 031. struct mem 032. { 033. long Show_Descr, 034. Show_Date; 035. bool AccountLock; 036. }m_Mem; 037. //+------------------------------------------------------------------+ 038. void CurrentSymbol(void) 039. { 040. MqlDateTime mdt1; 041. string sz0, sz1; 042. datetime dt = macroGetDate(TimeCurrent(mdt1)); 043. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 044. 045. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 046. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 047. switch (eTS) 048. { 049. case DOL : 050. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 051. case IND : 052. case WIN : sz1 = "GJMQVZ"; break; 053. default : return; 054. } 055. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 056. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 057. } 058. //+------------------------------------------------------------------+ 059. public : 060. //+------------------------------------------------------------------+ 061. C_Terminal(const long id = 0, const uchar sub = 0) 062. { 063. m_Infos.ID = (id == 0 ? ChartID() : id); 064. m_Mem.AccountLock = false; 065. m_Infos.SubWin = (int) sub; 066. CurrentSymbol(); 067. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 068. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 069. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 070. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 071. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 072. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 073. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 074. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 075. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 076. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 077. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 078. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 079. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 080. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 081. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 082. ResetLastError(); 083. } 084. //+------------------------------------------------------------------+ 085. ~C_Terminal() 086. { 087. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 088. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 089. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 090. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 091. } 092. //+------------------------------------------------------------------+ 093. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 094. { 095. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 096. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 097. } 098. //+------------------------------------------------------------------+ 099. inline const st_Terminal GetInfoTerminal(void) const 100. { 101. return m_Infos; 102. } 103. //+------------------------------------------------------------------+ 104. const double AdjustPrice(const double arg) const 105. { 106. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 107. } 108. //+------------------------------------------------------------------+ 109. inline datetime AdjustTime(const datetime arg) 110. { 111. int nSeconds= PeriodSeconds(); 112. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 113. 114. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 115. } 116. //+------------------------------------------------------------------+ 117. inline double FinanceToPoints(const double Finance, const uint Leverage) 118. { 119. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 120. 121. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 122. }; 123. //+------------------------------------------------------------------+ 124. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 125. { 126. static string st_str = ""; 127. 128. switch (id) 129. { 130. case CHARTEVENT_CHART_CHANGE: 131. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 132. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 133. break; 134. case CHARTEVENT_OBJECT_CLICK: 135. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 136. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 137. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 138. break; 139. case CHARTEVENT_OBJECT_CREATE: 140. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 141. st_str = sparam; 142. break; 143. } 144. } 145. //+------------------------------------------------------------------+ 146. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const 147. { 148. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 149. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 150. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 151. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 152. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 153. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 154. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 155. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 156. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 157. } 158. //+------------------------------------------------------------------+ 159. bool IndicatorCheckPass(const string szShortName) 160. { 161. string szTmp = szShortName + "_TMP"; 162. 163. if (_LastError != ERR_SUCCESS) return false; 164. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 165. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin); 166. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 167. { 168. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 169. Print("Only one instance is allowed..."); 170. SetUserError(C_Terminal::ERR_NoMoreInstance); 171. 172. return false; 173. } 174. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 175. ResetLastError(); 176. 177. return true; 178. } 179. //+------------------------------------------------------------------+ 180. };
Código fuente del archivo de cabecera C_Terminal.mqh
Al revisar el código del archivo de cabecera que contiene la clase C_Terminal, puede que no notes a simple vista que hay algunos cambios aquí. Esto se debe a que las modificaciones son muy sutiles, pero nos permiten realizar muchas tareas, principalmente aquellas que necesitaremos ejecutar en un futuro cercano.
Básicamente, todos los cambios que ocurrieron se deben a la línea 22. Esta variable no existía antes. Pero ahora ha sido añadida para permitirnos realizar algo muy específico, aunque bastante interesante: dirigir objetos a una subventana específica.
Dado que esta variable fue añadida y puede ser leída fuera de la clase, debemos garantizar que tenga un valor adecuado desde el principio. Por esta razón, en la línea 61, hicimos una modificación en el constructor de la clase. Observa que ahora el constructor también recibirá un parámetro extra. Dicho parámetro puede aceptar valores entre 0 y 255, lo cual es mucho más que suficiente, ya que rara vez colocamos más de 2 o 3 subventanas en el gráfico. Pero hay un detalle, y al observar la línea 65, resolvemos este problema con una declaración explícita de conversión de tipo. ¿Y por qué no usamos directamente el tipo uchar en la variable, en lugar de realizar esta conversión aquí, en la línea 65? El motivo es la retrocompatibilidad. MQL5 espera recibir un tipo entero con signo. Si declaramos la variable con este tipo, simplifica mucho nuestro trabajo posteriormente. Del mismo modo, el hecho de que el parámetro pasado sea del tipo uchar nos permite delimitar fácilmente la cantidad de subventanas a 255. Por esta razón, las cosas se están haciendo de esta manera.
Ahora, yendo a la línea 149, podemos ver dónde se utiliza este valor. Observa que este valor será empleado para informar a MetaTrader 5 en qué ventana del gráfico deberá colocarse el objeto. Este tipo de cosas no es novedad para muchos que ya tienen algún conocimiento sobre MQL5. Siendo algo bastante común, dicho sea de paso. No obstante, la situación cambia al observar la función IndicadorCheckPass, que comienza en la línea 159.
Normalmente, cuando colocamos un indicador en una ventana, no es necesario especificar en cuál ventana será trazado. Sin embargo, cuando usamos objetos gráficos, la cuestión se vuelve un poco más complicada. Esto se debe a que necesitamos saber en qué ventana será colocado el objeto. Sin este conocimiento, la llamada realizada en la línea 149 siempre colocará el objeto en la ventana incorrecta. Por lo tanto, presta atención a cómo descubriremos en qué ventana se encuentra el indicador.
Para determinar rápidamente cuál es la ventana, usamos la línea 164, donde asignamos un nombre temporal al indicador que estamos colocando. Luego, en la línea 165, utilizamos una llamada de MQL5, ChartWindowFind, para que MetaTrader 5 nos informe exactamente en qué ventana se encuentra el indicador. Recuerda que estamos usando un nombre temporal y que ningún otro indicador debe tener un nombre similar. De lo contrario, podríamos obtener un falso positivo aquí. Si no obtenemos un valor positivo, ajustamos la indicación para que apunte a la ventana principal, es decir, la ventana con índice cero. Después de esto, en la línea 166, garantizamos que solo habrá una única instancia presente en el gráfico. El resto de la función permanece igual que antes, por lo que no merece más comentarios.
Muy bien, ahora ya podemos colocar el módulo del indicador de mouse en cualquier subventana. Pero para que funcione mínimamente bien, será necesario modificar algunas cosas en el código de las clases. Veamos rápidamente lo que cambió en el archivo de cabecera C_Mouse.mqh. Este puede verse en su totalidad a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_" 007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0)) 008. //+------------------------------------------------------------------+ 009. class C_Mouse : public C_Terminal 010. { 011. public : 012. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 013. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 014. struct st_Mouse 015. { 016. struct st00 017. { 018. short X_Adjusted, 019. Y_Adjusted, 020. X_Graphics, 021. Y_Graphics; 022. double Price; 023. datetime dt; 024. }Position; 025. uchar ButtonStatus; 026. bool ExecStudy; 027. }; 028. //+------------------------------------------------------------------+ 029. protected: 030. //+------------------------------------------------------------------+ 031. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 032. { 033. if (!m_OK) return; 034. CreateObjectGraphics(szName, OBJ_BUTTON); 035. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 036. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 037. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 038. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 039. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 042. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 046. } 047. //+------------------------------------------------------------------+ 048. private : 049. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 050. struct st01 051. { 052. st_Mouse Data; 053. color corLineH, 054. corTrendP, 055. corTrendN; 056. eStudy Study; 057. }m_Info; 058. struct st_Mem 059. { 060. bool CrossHair, 061. IsFull; 062. datetime dt; 063. string szShortName, 064. szLineH, 065. szLineV, 066. szLineT, 067. szBtnS; 068. }m_Mem; 069. bool m_OK; 070. //+------------------------------------------------------------------+ 071. void GetDimensionText(const string szArg, int &w, int &h) 072. { 073. TextSetFont("Lucida Console", -100, FW_NORMAL); 074. TextGetSize(szArg, w, h); 075. h += 5; 076. w += 5; 077. } 078. //+------------------------------------------------------------------+ 079. void CreateStudy(void) 080. { 081. if (m_Mem.IsFull) 082. { 083. CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH); 084. CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH); 085. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2); 086. CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy); 087. } 088. m_Info.Study = eStudyCreate; 089. } 090. //+------------------------------------------------------------------+ 091. void ExecuteStudy(const double memPrice) 092. { 093. double v1 = GetInfoMouse().Position.Price - memPrice; 094. int w, h; 095. 096. if (!CheckClick(eClickLeft)) 097. { 098. m_Info.Study = eStudyNull; 099. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 100. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 101. }else if (m_Mem.IsFull) 102. { 103. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 104. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)); 105. GetDimensionText(sz1, w, h); 106. ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1); 107. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 108. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w); 109. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h); 110. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 112. ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 113. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 114. } 115. m_Info.Data.ButtonStatus = eKeyNull; 116. } 117. //+------------------------------------------------------------------+ 118. inline void DecodeAlls(int xi, int yi) 119. { 120. int w = 0; 121. 122. xi = (xi > 0 ? xi : 0); 123. yi = (yi > 0 ? yi : 0); 124. 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); 125. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 126. m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price); 127. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi); 128. yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin); 129. m_Info.Data.Position.X_Adjusted = (short) xi; 130. m_Info.Data.Position.Y_Adjusted = (short) yi; 131. } 132. //+------------------------------------------------------------------+ 133. public : 134. //+------------------------------------------------------------------+ 135. C_Mouse(const long id, const string szShortName) 136. :C_Terminal(id), 137. m_OK(false) 138. { 139. m_Mem.szShortName = szShortName; 140. } 141. //+------------------------------------------------------------------+ 142. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 143. :C_Terminal(id) 144. { 145. if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) SetUserError(C_Terminal::ERR_Unknown); 146. if (_LastError != ERR_SUCCESS) return; 147. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 148. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 149. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 150. ZeroMemory(m_Info); 151. m_Info.corLineH = corH; 152. m_Info.corTrendP = corP; 153. m_Info.corTrendN = corN; 154. m_Info.Study = eStudyNull; 155. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 156. CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH); 157. ChartRedraw(GetInfoTerminal().ID); 158. } 159. //+------------------------------------------------------------------+ 160. ~C_Mouse() 161. { 162. if (!m_OK) return; 163. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 164. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1); 165. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 166. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 167. } 168. //+------------------------------------------------------------------+ 169. inline bool CheckClick(const eBtnMouse value) 170. { 171. return (GetInfoMouse().ButtonStatus & value) == value; 172. } 173. //+------------------------------------------------------------------+ 174. inline const st_Mouse GetInfoMouse(void) 175. { 176. if (!m_OK) 177. { 178. double Buff[]; 179. uCast_Double loc; 180. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 181. 182. ZeroMemory(m_Info.Data); 183. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 184. { 185. loc.dValue = Buff[0]; 186. m_Info.Data.ButtonStatus = loc._8b[0]; 187. DecodeAlls((int)loc._16b[1], (int)loc._16b[2]); 188. } 189. IndicatorRelease(handle); 190. } 191. 192. return m_Info.Data; 193. } 194. //+------------------------------------------------------------------+ 195. inline void SetBuffer(const int rates_total, double &Buff[]) 196. { 197. uCast_Double info; 198. 199. info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0); 200. info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics; 201. info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics; 202. Buff[rates_total - 1] = info.dValue; 203. } 204. //+------------------------------------------------------------------+ 205. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 206. { 207. int w = 0; 208. static double memPrice = 0; 209. 210. if (m_OK) 211. { 212. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 213. switch (id) 214. { 215. case (CHARTEVENT_CUSTOM + evHideMouse): 216. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE); 217. break; 218. case (CHARTEVENT_CUSTOM + evShowMouse): 219. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH); 220. break; 221. case CHARTEVENT_MOUSE_MOVE: 222. DecodeAlls((int)lparam, (int)dparam); 223. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price); 224. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0); 225. m_Info.Data.ButtonStatus = (uchar) sparam; 226. if (CheckClick(eClickMiddle)) 227. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 228. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 229. { 230. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 231. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 232. m_Info.Study = eStudyExecute; 233. } 234. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 235. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 236. break; 237. case CHARTEVENT_OBJECT_DELETE: 238. if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH)) 239. CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH); 240. break; 241. } 242. } 243. } 244. //+------------------------------------------------------------------+ 245. }; 246. //+------------------------------------------------------------------+ 247. #undef macro_NameObjectStudy 248. //+------------------------------------------------------------------+
Código fuente del archivo de cabecera C_Mouse.mqh
Lo primero que llama la atención está en las líneas seis y siete. Nota que, a diferencia de lo que ocurría antes, aquí generamos una definición de nombres un poco diferente. Esto es necesario para evitar conflictos entre los nombres de objetos ya presentes en el gráfico. De esta forma, el nombre pasa a depender tanto de la ventana como del número de objetos presentes en el gráfico. Es decir, cada nombre se vuelve único.
Existen algunas diferencias menores en este código, pero nada que realmente merezca un gran destaque. Sin embargo, entre las líneas 64 y 67, se declaran nuevas variables. Estas se usan para almacenar los nombres de los objetos que serán creados. Para que entiendas cómo se llevará a cabo esto, observa en la línea 83 un ejemplo de asignación de un nombre a uno de los objetos. En esta línea queda bastante claro cómo se utiliza la macro para generar y asignar un nombre a una de las variables.
Ahora bien, aunque gran parte del código no merezca mayor énfasis, esto se debe a que las modificaciones fueron mínimas y realizadas únicamente para mejorar el soporte a lo que necesitamos. Existe una parte del código que, en efecto, merece una explicación. Aunque no esté 100% como nos gustaría, ya es suficiente para lograr lo que queremos. Estoy hablando del procedimiento que comienza en la línea 118, DecodeAlls.
Este procedimiento funciona maravillosamente bien cuando el módulo del indicador de mouse se encuentra en la ventana principal, es decir, en la ventana con índice cero. Sin embargo, cuando colocamos el módulo de mouse en otra ventana, comenzaron a surgir problemas. Aunque hemos resuelto muchos de ellos, aún persisten algunos, y podrás observar esto en el video que se incluirá al final de este artículo.
Pero el gran detalle, y este puede dejarte, querido lector, completamente sorprendido y desorientado, está en la línea 128. ¿Por qué existe esta línea? ¿Y por qué no existía antes? Para entender esto, necesitas comprender otra cosa El módulo del indicador de mouse, inicialmente, estaba diseñado para mostrarse únicamente en la ventana con índice cero. Esta ventana tiene su punto inicial en el eje Y en la parte superior de la misma. Es decir, el valor de Y siempre comienza en 0. Sin embargo, al añadir ventanas adicionales, el Y de la ventana principal no cambia, pero el de las ventanas extras sí. Estos valores son desplazados por un margen dado. Para el sistema operativo, es decir, Windows, este desplazamiento no tiene la menor importancia, y reporta a MetaTrader 5 la posición exacta del mouse.
Por su parte, MetaTrader 5 ajusta el valor de la posición del mouse de manera que quede dentro de la ventana del gráfico. De este modo, los valores fuera de la ventana pueden ser negativos o positivos. El valor será negativo si el mouse se encuentra por encima del área de trabajo de la ventana. ¿Qué es el área de trabajo de la ventana? Consulta la Imagen 01, donde toda la región que contiene el bitmap se considera área de trabajo.
Imagen 01 - Entendiendo el área de trabajo
Observa que los bordes y la barra de título no forman parte del área de trabajo de la ventana. Entonces, si el puntero del mouse entra en la región de la barra de título, será MetaTrader 5, y no el sistema operativo, quien corregirá el valor para que sea negativo. Es crucial entender esto: el valor de Y se vuelve negativo al entrar en la región de la barra de título, no porque el sistema operativo lo haya hecho, sino porque MetaTrader 5 ajusta el valor para que permanezca dentro del área de trabajo.
Cuando añades algo que crea una región en la ventana principal, como una franja donde el gráfico deja de estar presente, MetaTrader 5 no lo interpreta como una nueva ventana. Aunque tú la definas como tal, para MetaTrader 5 esto no sucede, y continuará viendo la ventana como completa, considerando que toda el área de trabajo sigue dentro de los bordes de la ventana principal. Por fortuna, MetaTrader 5 nos permite saber dónde comienza esta nueva región. Sin embargo, no nos informa dónde termina; esto depende de que nosotros encontremos una manera de determinarlo. Pero el simple hecho de saber dónde comienza ya nos proporciona una gran ventaja.
Para saber dónde comienza la región, realizamos una llamada a ChartGetInteger, utilizando como parámetros la constante CHART_WINDOW_YDISTANCE y el número de la subventana. Nuevamente, nos referimos a ella como una subventana para simplificar las cosas; sin embargo, esta denominación no es del todo precisa.
El valor retornado por la llamada en la línea 128 se resta del valor convertido. Recordemos que el valor convertido es el que MetaTrader 5 reporta como parte de la ventana. Sin esta corrección, realizada en la línea 128, cuando el puntero del mouse entrara en una posición que MetaTrader 5 identificara como parte de la región de la subventana, tendríamos una falsa indicación de la posición de la línea horizontal. Pero con esta corrección, tal problema no ocurrirá. Al menos, no de la manera en que ocurriría normalmente.
Un detalle adicional importante se encuentra en la línea 145, donde almacenamos el nombre corto del indicador. ¿Por qué hacemos esto? La razón se revela en la línea 164. Sin conocer el nombre del indicador de mouse, no podríamos probar ni verificar si ya podemos informar a MetaTrader 5 que deje de enviar eventos de mouse. Algunos podrían preguntar si no podríamos simplemente mantener el evento activado todo el tiempo. Sin embargo, dejar el evento activado sin usarlo sería, en cierto sentido, un desperdicio. El motivo es que cada vez que el mouse se moviera, MetaTrader 5 tendría que disparar un evento de mouse, y si nadie realmente lo utiliza, esto solo añadiría elementos innecesarios a la cola de eventos. Para evitar esto, desactivamos el envío de eventos de mouse tan pronto como ya no sean necesarios. Pero, sin conocer el nombre del módulo del indicador de mouse, ¿cómo podríamos saber si existe un indicador que necesita recibir el evento? Simplemente no sería posible. Por esta razón, almacenamos el nombre del indicador y lo usamos para verificar si podemos desactivar el evento de mouse.
Además de las modificaciones mencionadas, que realmente merecen ser destacadas, hay otras. Sin embargo, como son relativamente más simples, no entraré en detalles sobre ellas. Para encontrarlas, solo necesitas comparar este código del archivo de cabecera C_Mouse.mqh con versiones anteriores. Esto quedará como una tarea para que puedas aprender más sobre cómo codificar y modificar elementos sin generar grandes problemas en el código final.
Como era de esperarse, el código del archivo C_Study.mqh también pasó por algunas modificaciones. Sin embargo, al igual que las modificaciones menores no mencionadas en C_Mouse.mqh, el contenido de C_Study.mqh no será comentado aquí. Solo lo proporcionaré en su totalidad para que puedas observar cómo el código fue modificado.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. //+------------------------------------------------------------------+ 008. class C_Study : public C_Mouse 009. { 010. private : 011. //+------------------------------------------------------------------+ 012. struct st00 013. { 014. eStatusMarket Status; 015. MqlRates Rate; 016. string szInfo, 017. szBtn1, 018. szBtn2, 019. szBtn3; 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, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 049. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, 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, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 055. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 056. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, 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, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 063. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, 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, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise); 073. m_Info.bvT = true; 074. break; 075. case evShowDailyVar: 076. C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 077. m_Info.bvD = true; 078. break; 079. case evShowPriceVar: 080. C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 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 = m_Info.szBtn1; 094. m_Info.bvT = false; 095. break; 096. case evHideDailyVar: 097. sz = m_Info.szBtn2; 098. m_Info.bvD = false; 099. break; 100. case evHidePriceVar: 101. sz = m_Info.szBtn3; 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_ExpansionPrefix 187. #undef def_MousePrefixName 188. //+------------------------------------------------------------------+
Código fuente del archivo de cabecera C_Study.mqh
De cualquier forma, lo que realmente me interesa mostrar en este artículo es el siguiente código: Es decir, el código fuente del indicador, que se encuentra justo a continuación.
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.59" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/12075" 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 color user02 = clrBlack; //Price Line 18. input color user03 = clrPaleGreen; //Positive Study 19. input color user04 = clrLightCoral; //Negative Study 20. //+------------------------------------------------------------------+ 21. C_Study::eStatusMarket m_Status; 22. int m_posBuff = 0; 23. double m_Buff[]; 24. //+------------------------------------------------------------------+ 25. int OnInit() 26. { 27. ResetLastError(); 28. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 29. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 30. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 31. { 32. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 33. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 34. m_Status = C_Study::eCloseMarket; 35. }else 36. m_Status = C_Study::eInReplay; 37. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 38. ArrayInitialize(m_Buff, EMPTY_VALUE); 39. 40. return INIT_SUCCEEDED; 41. } 42. //+------------------------------------------------------------------+ 43. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 44. { 45. m_posBuff = rates_total; 46. (*Study).Update(m_Status); 47. 48. return rates_total; 49. } 50. //+------------------------------------------------------------------+ 51. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 52. { 53. (*Study).DispatchMessage(id, lparam, dparam, sparam); 54. (*Study).SetBuffer(m_posBuff, m_Buff); 55. 56. ChartRedraw((*Study).GetInfoTerminal().ID); 57. } 58. //+------------------------------------------------------------------+ 59. void OnBookEvent(const string &symbol) 60. { 61. MqlBookInfo book[]; 62. C_Study::eStatusMarket loc = m_Status; 63. 64. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 65. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 66. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 67. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 68. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 69. if (loc != m_Status) (*Study).Update(m_Status); 70. } 71. //+------------------------------------------------------------------+ 72. void OnDeinit(const int reason) 73. { 74. if (reason != REASON_INITFAILED) 75. { 76. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 77. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 78. } 79. delete Study; 80. } 81. //+------------------------------------------------------------------+ 82.
Código fuente del indicador de Mouse
En este código, de entrada, puedes notar que ya no estamos utilizando algunas entradas (inputs); es decir, ahora la concepción de este indicador ha cambiado. El motivo de esto es darte la libertad de colocar este indicador donde mejor te parezca que debería estar. Dado que podría ser utilizado para otros propósitos además de los que imagino, no voy a imponer un lugar específico donde deberías colocarlo. Por lo tanto, ya no será necesario que las entradas que informaban la ID del gráfico, así como el estado del símbolo, sean configuradas por el usuario.
Observa que en la línea 36 realizamos un pequeño ajuste. Si este módulo se coloca en el gráfico del símbolo que está siendo utilizado por el servicio de repetición/simulador, la línea 36 ajustará las configuraciones automáticamente. Con esto, el usuario ya no tendrá que realizar las modificaciones que antes eran necesarias, como, por ejemplo, informar la ID del gráfico. Nota adicional Aunque ya no sea necesario informar la ID, todo lo que se explicó en los artículos anteriores sobre las llamadas sigue siendo válido, al menos hasta el momento de escribir este artículo.
Conclusión
Con un poco de esfuerzo y utilizando conocimientos previos, ha sido posible mostrarte cómo funcionan algunas cosas en MetaTrader 5. Es cierto que, si has estado siguiendo los artículos, podrías tener la impresión de que aparentemente no estamos progresando. Pero la verdad es que estamos avanzando, aunque de una forma más lenta. Esto se debe a que estoy desarrollando y probando cosas que no sabía si realmente eran posibles en MetaTrader 5.
Gran parte de las personas que se autodenominan programadores simplemente afirman que ciertas cosas no se pueden hacer en MetaTrader 5. Dicen que la plataforma carece de esta o aquella funcionalidad. Sin embargo, he comprobado que muchas de estas personas no saben realmente de lo que están hablando.
Para concluir este artículo, incluyo un video donde muestro cómo está funcionando el módulo del indicador de mouse. Presta atención al video: verás que existe una falla. Soy consciente de ella. Sin embargo, dado que no es tan grave, la corregiré en otro momento, cuando tenga un mayor nivel de conocimiento sobre cómo funciona MetaTrader 5 en ciertos aspectos que todavía no he logrado comprender completamente.
Quiero que tú, querido lector, participes en este aprendizaje. Por lo tanto, continuaré mostrando cómo se desarrolla el sistema que será utilizado para el servicio de repetición/simulador, además de ser compatible con el mercado real y las cuentas demo.
Video de demostración del nuevo indicador de mouse en funcionamiento
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12075





- 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