
Desarrollo de un sistema de repetición (Parte 68): Ajuste del tiempo (I)
Introducción
En el artículo "Desarrollo de un sistema de repetición (Parte 66): Presionando play en el servicio (VII)", mostré cómo hacer que el indicador del mouse nos informe cuánto tiempo falta para que finalice la barra actual y comience a trazarse una nueva en el gráfico. Sin embargo, aunque este método funciona muy bien y de manera bastante eficiente, tiene un inconveniente. Aunque muchos piensen que el problema está en el método, en realidad radica en la liquidez del símbolo. Si has prestado atención a la explicación del artículo, habrás notado que el indicador del mouse solo actualiza la información cuando se produce una nueva cotización.
El motivo es que necesitamos que MetaTrader 5 genere una llamada al evento OnCalculate, de modo que el indicador reciba un nuevo valor en términos de segundos y pueda calcular el tiempo restante de la barra. Este valor se coloca en el spread, como habrás visto en el artículo. Sin embargo, justamente porque dependemos del valor proporcionado por el servicio en el spread, no podemos informar correctamente del tiempo restante de la barra en un símbolo o momento de baja liquidez. Y si el símbolo entra en subasta, tendremos un gran problema, ya que podemos permanecer varios minutos en estado de suspensión. No tenemos la capacidad de gestionar adecuadamente la situación en lo que respecta a la subasta. Incluso en símbolos con buena liquidez, debido a la subasta, no se generará ningún evento OnCalculate. Por lo tanto, el servicio de repetición/simulación debe actualizarse para que el usuario reciba la información correcta en estos dos casos. El primero es cuando la liquidez es baja y se generan pocas operaciones por segundo. El segundo problema ocurre cuando el símbolo entra en subasta. Necesitamos resolver estos dos casos antes de pasar a la siguiente etapa de desarrollo.
Implementamos la solución cuando la liquidez es baja
El primer caso, en el que tenemos una baja liquidez, es relativamente sencillo y fácil de resolver, ya que solo será necesario modificar el código del servicio. Bueno, al menos en principio. Quizás sea necesario modificar algo más. De todos modos, intentemos resolverlo trabajando únicamente en el código fuente del servicio de repetición/simulación.
Para comprobar si realmente será posible hacerlo, vamos a abandonar temporalmente nuestro código fuente original. Crearemos otro código únicamente para probar si es posible hacer que, cuando el servicio esté detenido, el indicador del mouse se actualice respecto al tiempo restante para que se cierre la barra actual.
El código que necesitaremos crear será lo más sencillo posible. Nada demasiado complicado ni elaborado. La idea es probar y verificar si podemos enviar datos del servidor al indicador sin que esto modifique la información presente en el gráfico. Por lo tanto, el código fuente que se usará se presenta a continuación.
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property description "Data synchronization demo service." 05. //+------------------------------------------------------------------+ 06. #include <Market Replay\Defines.mqh> 07. //+------------------------------------------------------------------+ 08. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != "")) 09. //+------------------------------------------------------------------+ 10. void OnStart() 11. { 12. long id; 13. int handle; 14. MqlRates Rate[1]; 15. 16. Print("Starting Test Service..."); 17. SymbolSelect(def_SymbolReplay, false); 18. CustomSymbolDelete(def_SymbolReplay); 19. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 20. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5); 21. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5); 22. Rate[0].close = 105; 23. Rate[0].open = 100; 24. Rate[0].high = 110; 25. Rate[0].low = 95; 26. Rate[0].tick_volume = 5; 27. Rate[0].spread = 1; 28. Rate[0].real_volume = 10; 29. Rate[0].time = D'10.03.2023 09:00'; 30. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 31. Rate[0].time = D'10.03.2023 09:30'; 32. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 33. SymbolSelect(def_SymbolReplay, true); 34. id = ChartOpen(def_SymbolReplay, PERIOD_M30); 35. Sleep(1000); 36. if ((handle = iCustom(NULL, 0, "\\Indicators\\Mouse Study.ex5")) != INVALID_HANDLE) 37. ChartIndicatorAdd(id, 0, handle); 38. IndicatorRelease(handle); 39. Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS)); 40. while (def_Loop) 41. { 42. CustomRatesUpdate(def_SymbolReplay, Rate, 1); 43. Sleep(250); 44. Rate[0].spread++; 45. if (Rate[0].spread == 60) 46. { 47. Rate[0].time += 60; 48. Rate[0].spread = 0; 49. Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS)); 50. EventChartCustom(id, evSetServerTime, (long)(Rate[0].time), 0, ""); 51. } 52. } 53. ChartClose(id); 54. SymbolSelect(def_SymbolReplay, false); 55. CustomSymbolDelete(def_SymbolReplay); 56. Print("Finished Test Service..."); 57. } 58. //+------------------------------------------------------------------+
Código fuente del servicio de prueba
Recordemos que, para que la explicación se lleve a cabo correctamente, debemos asegurarnos de que el indicador del mouse esté compilado y ubicado en la ubicación que mostraré durante la explicación. De lo contrario, la prueba fallará. Así que veamos qué debe ejecutarse y cómo podremos usar esto a nuestro favor en caso de que la prueba demuestre ser eficaz para lo que pretendemos hacer.
Las cuatro primeras líneas contienen declaraciones y propiedades sobre el tipo de ejecutable que se generará. En la línea seis, incluimos un archivo de cabecera únicamente para evitar tener que declarar cosas adicionales aquí. En la línea ocho, definimos una directiva de control para el bucle que realizaremos a continuación. De cierta forma, la única línea realmente importante entre estas ocho es la línea dos. El resto podrían suprimirse y los códigos necesarios colocarse en el lugar adecuado. Sin embargo, la línea dos es importante para informar a MetaTrader 5 de que el ejecutable generado debe ser un servicio. De lo contrario, sería un script y no es esto lo que queremos probar.
A partir de la línea 10 empiezan a suceder cosas interesantes. Entre las líneas 12 y 14 declaramos las variables que utilizaremos. Luego, entre las líneas 17 y 21, creamos el símbolo personalizado. Estos pasos son estándar, así que podemos avanzar un poco más. A continuación, entre las líneas 22 y 28, creamos un RATE para la barra. Lo que realmente nos importa es la línea 29, donde definimos cuándo se creará la barra. En la línea 30, le indicamos a MetaTrader 5 los datos de dicha barra. Ahora presta atención. En la línea 31 crearemos una segunda barra con los mismos datos, pero esta vez estará desplazada 30 minutos. El motivo de desplazarla 30 minutos es que el gráfico se abrirá con un TimeFrame de 30 minutos. Si colocas otro valor de tiempo en esta barra que estamos creando en la línea 31, el indicador del ratón calculará cuánto tiempo falta para que se cierre. Por eso, presta atención a este detalle. Otro detalle a tener en cuenta es NO informar ningún valor en segundos.. El valor debe expresarse en minutos y nunca en segundos. La siguiente acción que realizamos es crear la barra en la línea 32.
En la línea 34 abrimos el gráfico. Como se vio en un artículo anterior de esta misma secuencia, antes de que el servicio agregue cualquier cosa, ya sea objetos o incluso un indicador, al gráfico, necesitamos hacer que el servicio espere un poco. Esto es para que la plataforma MetaTrader 5 construya realmente el gráfico y lo coloque en la pantalla. Esta espera se lleva a cabo en la línea 35, donde esperamos un segundo antes de continuar con la ejecución del programa.
Y nuevamente, necesitamos prestar atención a la nueva información que aparece en la línea 36. En esta línea, estamos intentando crear un manejador para colocar el indicador del ratón en el gráfico. La función iCustom nos devuelve el valor que necesitamos para añadir el indicador, siempre que este se encuentre. Si no se encuentra, obtendremos un valor inválido que no utilizaremos. Por lo tanto, el indicador del mouse deberá estar obligatoriamente en la ubicación indicada. De lo contrario, la prueba fallará. Si logramos encontrar el indicador, lo añadiremos al gráfico en la línea 37, por lo que, dado que el manejador devuelto por la llamada a iCustom ya no será necesario, lo eliminamos en la línea 38.
Ahora viene la parte de la prueba. La parte que realmente nos interesa: verificar si funcionará o no. Si funciona, podremos solucionar el problema en el caso de símbolos con baja liquidez. Si no funciona, tendremos que buscar otra solución. Entendamos entonces cómo se lleva a cabo esta prueba, que está contenida dentro de un bucle que comienza en la línea 40 y termina en la línea 52. El resto del código, entre las líneas 53 y 56, son únicamente para finalizar la prueba, por lo que no tienen mayor relevancia en este artículo. Entonces, veamos cómo funciona el test.
Entendemos cómo funciona la prueba
Todo comienza en la línea 40, donde se ingresa al bucle. Si el gráfico está abierto y el usuario no ha detenido el servicio, el bucle se ejecutará indefinidamente. Una vez dentro, veremos la línea 42. Esta línea actualizará de manera forzada los datos de la barra. Pero debes tener en cuenta que no se transferirá ninguna nueva información real a la barra, sino solo lo que estamos modificando en esta prueba. Está bien. En la línea 43, generamos un pequeño retardo para que la actualización no sea demasiado rápida. Presta atención al valor del retardo: no estamos esperando cerca de un segundo. El valor es de un cuarto de segundo, lo cual será importante más adelante, cuando observes los resultados en MetaTrader 5.
En la línea 44 incrementamos el valor del spread. De este modo, el indicador del mouse tendrá la impresión de que ha pasado un segundo, cuando en realidad el tiempo transcurrido es mucho menor debido al retardo que estamos utilizando. Hasta este punto, el funcionamiento debería ser el siguiente: cuando se abra el gráfico y se muestre el indicador del mouse, MetaTrader 5 debería generar los eventos OnCalculate, lo que permitiría reducir el tiempo restante para el cierre de la barra. Puedes verificar si esto está ocurriendo. Sin embargo, como ya he comprobado que efectivamente sucede, he tenido la paciencia y la curiosidad de investigar qué ocurriría en el momento en que el valor del spread superase el valor de 60. ¿Por qué este valor de 60 es importante para nosotros? La razón es que, cuando se alcanza este valor, debería aparecer una nueva barra de un minuto, incluso si se utiliza un gráfico con un marco temporal mensual. Esto no importa. Para que tengamos una indicación correcta, es necesario que MetaTrader 5 sea informado de que ha surgido una nueva barra de un minuto.
De todos modos, tenemos que asegurarnos de que nuestro código pueda manejar esto. De lo contrario, MetaTrader 5 no podrá resolver este tipo de problema por sí solo. Por lo tanto, en la línea 45 verificamos el valor de 60 iteraciones del bucle. Cuando se alcanza este valor, en la línea 47 sumamos un minuto (es decir, 60 segundos) al tiempo original de la barra. Así, cuando se ejecute la función de la línea 42, MetaTrader 5 sabrá que existe una nueva barra de un minuto. Sin embargo, esto no es del todo suficiente. En la línea 48 reiniciamos el contador de iteraciones para que se analice nuevamente un nuevo ciclo. Para comprobarlo, en la línea 49 imprimimos el valor que le informamos a MetaTrader 5.
Ahora viene un detalle: hasta este momento, solo MetaTrader 5 ha sido informado de la existencia de esta nueva barra. Sin embargo, como debes haber notado en el artículo donde se implementó el sistema que nos informa del tiempo restante de la barra, no podemos utilizar directamente el valor de tiempo proporcionado por la función OnCalculate. Al menos por ahora. Estoy pensando en una forma de superar esta limitación para que no sea necesario ejecutar la línea 50. No obstante, hasta que logremos superar esta limitación, necesitamos que la línea 50 obligue a MetaTrader 5 a generar un evento personalizado. Esto permitirá informar al indicador de mouse de que se ha formado una nueva barra. De esta manera, el indicador sabrá en qué punto temporal estamos trabajando realmente y podrá informarnos del tiempo que falta para que se cierre la barra que se está trazando en ese momento y se inicie una nueva.
Si no deseas compilar este servicio y probarlo en MetaTrader 5, no hay problema. En el video siguiente muestro cómo se comportó el servicio y si es posible utilizar este código de prueba en el servicio de repetición/simulación. Desde mi punto de vista, la prueba funcionó muy bien, lo que demuestra que es posible y viable incorporar el mecanismo probado en la aplicación de repetición/simulador. Esto permitirá que, en momentos o símbolos con baja liquidez, tengamos información sobre cuánto tiempo falta para que aparezca una nueva barra en el gráfico.
Para lograr esto, añadir el mecanismo probado a la aplicación de repetición/simulador requerirá un leve cambio en una parte muy específica del código. Esta parte se encuentra en el archivo de cabecera C_Replay.mqh. Pero antes de proceder a implementar las modificaciones, es importante comprender otro aspecto. Para separar las explicaciones, avancemos a un nuevo tema.
Tiempo, tiempo, tiempo. Cómo podemos entender el tiempo
Aunque pueda parecer confuso realizar ciertas tareas, no hay otra opción: tendré que mostrar algo que puede ser extremadamente complejo, pero que es perfectamente posible y razonable de hacer. Y todo está relacionado con la longitud de los datos.
Si observas la función OnCalculate, verás que el valor del spread se proporciona como un entero, lo que significa que, en procesadores de 64 bits, se utilizan 32 bits. Al hacer una simple verificación, también notarás que la longitud en términos de bits de datetime es de 64 bits. Está bien. De acuerdo, pero ¿cómo pueden ayudarnos estos datos? Con calma, querido lector. Con calma. Ahora necesitamos hacer algunos cálculos, pero no son complicados. Es algo sencillo.
Es necesario que el servicio obligue a MetaTrader 5 a generar algún tipo de evento para actualizar el cronómetro del indicador del mouse. Esto es un hecho y se está logrando de dos maneras diferentes. Una es mediante la llamada a OnCalculate y la otra, a través de un evento personalizado que se dispara de vez en cuando. Para ver dónde ocurre esto en el servicio, revisa el fragmento siguiente, perteneciente al archivo de cabecera C_Replay.mqh:
068. //+------------------------------------------------------------------+ 069. inline void CreateBarInReplay(bool bViewTick) 070. { 071. bool bNew; 072. double dSpread; 073. int iRand = rand(); 074. 075. if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew)) 076. { 077. m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay]; 078. if (m_MemoryData.ModePlot == PRICE_EXCHANGE) 079. { 080. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 081. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 082. { 083. m_Infos.tick[0].ask = m_Infos.tick[0].last; 084. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 085. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 086. { 087. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 088. m_Infos.tick[0].bid = m_Infos.tick[0].last; 089. } 090. } 091. if (bViewTick) 092. { 093. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 094. if (bNew) EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)m_Infos.Rate[0].time, 0, ""); 095. } 096. m_Infos.Rate[0].spread = (int)macroGetSec(m_MemoryData.Info[m_Infos.CountReplay].time); 097. CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate); 098. } 099. m_Infos.CountReplay++; 100. } 101. //+------------------------------------------------------------------+
Fragmento del archivo C_Replay.mqh
Esto ya fue explicado en el artículo "Desarrollo de un sistema de repetición (Parte 66): Presionando play en el servicio (VII), pero aquí reforzaremos la explicación. Cada vez que se llama a este procedimiento y se añade un nuevo tick a la barra que se está trazando en el gráfico, en la línea 96 se incluye el valor en segundos del spread. Esto permite calcular el tiempo restante de la barra. Muy bien. Y, siempre que aparece una nueva barra de un minuto, en la línea 94 se realiza una prueba que resulta verdadera, lo que obliga a MetaTrader 5 a generar un evento personalizado. Este evento permite que el indicador del mouse reajuste el tiempo para identificar cuándo se generó la nueva barra. Hacemos esto para evitar añadir una llamada extra que sería ejecutada de forma intensiva en el indicador del mouse. Expliqué esto en el artículo mencionado anteriormente.
De todos modos, estamos malgastando tiempo y espacio haciendo las cosas de esta forma. Podríamos ser un poco más eficientes y transmitir más información que con el método actual. Ahora viene la parte de los cálculos. Necesitamos sincronizar los eventos cada segundo; no es necesario que sea más rápido. Ya tenemos un punto de partida: hay 60 segundos en un minuto, 60 minutos en una hora y 24 horas en un día. Está bien. Al hacer las cuentas, esto nos da 86 400 segundos por día (aproximadamente, ya que un día no siempre tiene exactamente 24 horas). Sin embargo, para nuestros propósitos, asumiremos que un día tiene 24 horas. Muy bien. Un valor de 32 bits puede contener un valor máximo de 4 294 967 295 si no tiene signo. Una vez más, asumiremos que no estamos tratando con un valor con signo. Esto significa que en 32 bits podemos almacenar aproximadamente 49 días y 12 horas en términos de segundos. Presta atención a este dato. En un único valor de spread, cuyo tamaño es de 32 bits, podemos almacenar aproximadamente 49 días. Sin embargo, nuestra aplicación de repetición/simulador difícilmente se utilizará para análisis y estudios que superen uno o dos días consecutivos. Por lo tanto, podemos sincronizar perfectamente los datos utilizando únicamente la información presente en el spread, sin necesidad de forzar a MetaTrader 5 a realizar una llamada personalizada para informar al indicador del mouse de que ha surgido una nueva barra.
Ahora las cosas se han puesto interesantes, ¿verdad? Pero para hacerlo aún más interesante, voy a implementar un modelo más ambicioso, de modo que no tengamos el límite de 49 días. Aunque dudo que alguien realmente alcance este límite, de todas formas implementaré algo diferente. Usaré los bits de una manera un poco más controlada, por así decirlo. Sin embargo, esto nos obligará a realizar algunos cambios en el indicador del mouse. Nada demasiado complicado, considerando lo que podríamos lograr con estas modificaciones.
¿Pero será que hacer esto nos proporcionará el tipo de solución que pretendemos implementar? Para saber si es así, necesitamos observar otro fragmento, también perteneciente al archivo de cabecera C_Replay.mqh. Este se puede ver a continuación:
207. //+------------------------------------------------------------------+ 208. bool LoopEventOnTime(void) 209. { 210. int iPos; 211. 212. while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay)) 213. { 214. UpdateIndicatorControl(); 215. Sleep(200); 216. } 217. m_MemoryData = GetInfoTicks(); 218. AdjustPositionToReplay(); 219. EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)macroRemoveSec(m_MemoryData.Info[m_Infos.CountReplay].time), 0, ""); 220. iPos = 0; 221. while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService)) 222. { 223. if (m_IndControl.Mode == C_Controls::ePause) return true; 224. 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); 225. CreateBarInReplay(true); 226. while ((iPos > 200) && (def_CheckLoopService)) 227. { 228. Sleep(195); 229. iPos -= 200; 230. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks); 231. UpdateIndicatorControl(); 232. } 233. } 234. 235. return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService)); 236. } 237. }; 238. //+------------------------------------------------------------------+
Fragmento del archivo C_Replay.mqh
Muy bien, nuestro problema surge cuando el símbolo tiene poca liquidez. Es decir, no se generan ticks cada segundo de forma continua. Puede que en algunos momentos la liquidez permita que se genere un tick por segundo, lo cual es deseable para nuestro propósito. Sin embargo, en otros momentos esta liquidez no se alcanza. Aun así, puedes observar que en la línea 225 se realiza la llamada al fragmento mostrado anteriormente. Incluso si el tiempo entre ticks supera un segundo, esta llamada en la línea 225 seguirá ocurriendo. Así que, sí, usar el spread como medio para informar el tiempo es factible. No obstante, implementar todos los aspectos relacionados puede ser un poco complicado. Por esta razón, no será posible explicar todos los detalles en este artículo. Pero comenzaremos aquí.
Iniciamos una nueva etapa
Lo primero que haremos será modificar el archivo de cabecera Defines.mqh. Las modificaciones se pueden ver a continuación:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. //+------------------------------------------------------------------+ 17. union uCast_Double 18. { 19. double dValue; 20. long _long; // 1 Information 21. datetime _datetime; // 1 Information 22. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 23. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 24. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 25. }; 26. //+------------------------------------------------------------------+ 27. enum EnumEvents { 28. evHideMouse, //Hide mouse price line 29. evShowMouse, //Show mouse price line 30. evHideBarTime, //Hide bar time 31. evShowBarTime, //Show bar time 32. evHideDailyVar, //Hide daily variation 33. evShowDailyVar, //Show daily variation 34. evHidePriceVar, //Hide instantaneous variation 35. evShowPriceVar, //Show instantaneous variation 36. evSetServerTime, //Replay/simulation system timer 37. evCtrlReplayInit, //Initialize replay control 38. }; 39. //+------------------------------------------------------------------+
Código fuente del archivo Defines.mqh
Nota que la línea 36 está tachada, lo que significa que ha dejado de existir en el archivo final. Del mismo modo, ahora, en la línea 15, tenemos una nueva definición que funcionará como una máscara para que podamos trabajar con mayor facilidad. El hecho de que la línea 36, que era el evento responsable de actualizar el cronómetro, haya sido eliminada implica que realizaremos algunos cambios para que el nuevo código sea compatible con la nueva política de mensajes.
De manera similar a lo que se hizo en el archivo Defines.mqh, también realizaremos algunos cambios en otro archivo de cabecera: el archivo Macros.mqh. El nuevo archivo se puede ver a continuación:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define macroRemoveSec(A) (A - (A % 60)) 05. #define macroGetDate(A) (A - (A % 86400)) 06. #define macroGetSec(A) (A - (A - (A % 60))) 07. #define macroGetTime(A) (A % 86400) 08. //+------------------------------------------------------------------+ 09. #define macroColorRGBA(A, B) ((uint)((B << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16))) 10. #define macroTransparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0) 11. //+------------------------------------------------------------------+
Código fuente del archivo Macros.mqh
En este caso, solo añadimos la línea siete, que es una macro diseñada para extraer el valor de la hora desde dentro de la estructura de una variable de tipo datetime. Como es algo muy sencillo, podemos continuar con el análisis de otro archivo de cabecera. Esta vez se trata del archivo C_Stuty.mqh, que se puede ver completo a continuación. Este archivo forma parte del indicador de mouse.
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. void Draw(void) 028. { 029. double v1; 030. 031. if (m_Info.bvT) 032. { 033. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 034. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo); 035. } 036. if (m_Info.bvD) 037. { 038. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 039. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 040. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 041. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 042. } 043. if (m_Info.bvP) 044. { 045. v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 046. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 047. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 048. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 049. } 050. } 051. //+------------------------------------------------------------------+ 052. inline void CreateObjInfo(EnumEvents arg) 053. { 054. switch (arg) 055. { 056. case evShowBarTime: 057. C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise); 058. m_Info.bvT = true; 059. break; 060. case evShowDailyVar: 061. C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 062. m_Info.bvD = true; 063. break; 064. case evShowPriceVar: 065. C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 066. m_Info.bvP = true; 067. break; 068. } 069. } 070. //+------------------------------------------------------------------+ 071. inline void RemoveObjInfo(EnumEvents arg) 072. { 073. string sz; 074. 075. switch (arg) 076. { 077. case evHideBarTime: 078. sz = m_Info.szBtn1; 079. m_Info.bvT = false; 080. break; 081. case evHideDailyVar: 082. sz = m_Info.szBtn2; 083. m_Info.bvD = false; 084. break; 085. case evHidePriceVar: 086. sz = m_Info.szBtn3; 087. m_Info.bvP = false; 088. break; 089. } 090. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 091. ObjectDelete(GetInfoTerminal().ID, sz); 092. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 093. } 094. //+------------------------------------------------------------------+ 095. public : 096. //+------------------------------------------------------------------+ 097. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 098. :C_Mouse(IdParam, szShortName, corH, corP, corN) 099. { 100. if (_LastError != ERR_SUCCESS) return; 101. ZeroMemory(m_Info); 102. m_Info.Status = eCloseMarket; 103. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 104. m_Info.corP = corP; 105. m_Info.corN = corN; 106. CreateObjInfo(evShowBarTime); 107. CreateObjInfo(evShowDailyVar); 108. CreateObjInfo(evShowPriceVar); 109. } 110. //+------------------------------------------------------------------+ 111. void Update(const eStatusMarket arg) 112. { 113. int i0; 114. datetime dt; 115. 116. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 117. { 118. case eCloseMarket : 119. m_Info.szInfo = "Closed Market"; 120. break; 121. case eInReplay : 122. case eInTrading : 123. i0 = PeriodSeconds(); 124. dt = (m_Info.Status == eInReplay ? (datetime) m_Info.TimeDevice + GL_TimeAdjust : TimeCurrent()); 125. dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent()); 126. m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time); 127. if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS); 128. break; 129. case eAuction : 130. m_Info.szInfo = "Auction"; 131. break; 132. default : 133. m_Info.szInfo = "ERROR"; 134. } 135. Draw(); 136. } 137. //+------------------------------------------------------------------+ 138. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 139. { 140. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 141. switch (id) 142. { 143. case CHARTEVENT_CUSTOM + evHideBarTime: 144. RemoveObjInfo(evHideBarTime); 145. break; 146. case CHARTEVENT_CUSTOM + evShowBarTime: 147. CreateObjInfo(evShowBarTime); 148. break; 149. case CHARTEVENT_CUSTOM + evHideDailyVar: 150. RemoveObjInfo(evHideDailyVar); 151. break; 152. case CHARTEVENT_CUSTOM + evShowDailyVar: 153. CreateObjInfo(evShowDailyVar); 154. break; 155. case CHARTEVENT_CUSTOM + evHidePriceVar: 156. RemoveObjInfo(evHidePriceVar); 157. break; 158. case CHARTEVENT_CUSTOM + evShowPriceVar: 159. CreateObjInfo(evShowPriceVar); 160. break; 161. case (CHARTEVENT_CUSTOM + evSetServerTime): 162. m_Info.TimeDevice = (datetime)lparam; 163. break; 164. case CHARTEVENT_MOUSE_MOVE: 165. Draw(); 166. break; 167. } 168. ChartRedraw(GetInfoTerminal().ID); 169. } 170. //+------------------------------------------------------------------+ 171. }; 172. //+------------------------------------------------------------------+ 173. #undef def_ExpansionPrefix 174. #undef def_MousePrefixName 175. //+------------------------------------------------------------------+
Código fuente del archivo C_Study.mqh
Todas las líneas tachadas en este archivo deben eliminarse del archivo original. Aunque las líneas tachadas formaban parte del código antiguo, que era el responsable de permitir que el evento personalizado mantuviera el cronómetro del indicador sincronizado, esto ya no será necesario, porque ahora utilizaremos otro método para realizar este trabajo. Sin embargo, es importante que observes un detalle en este archivo de cabecera. Mira la línea 124 y compárala con la línea 125. Aunque se indica que toda la línea se sustituyó, lo que realmente ocurrió fue la eliminación de la variable m_Info.TimeDevice. Esto se debe a que ya no será necesaria, ya que se usaba para mantener sincronizado el cronómetro. Ahora bien, tenemos un problema, por así decirlo, en el código del indicador del mouse. A continuación, se puede ver el código completo del indicador:
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.68" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. #property indicator_applied_price PRICE_CLOSE 13. //+------------------------------------------------------------------+ 14. double GL_PriceClose; 15. datetime GL_TimeAdjust; 16. //+------------------------------------------------------------------+ 17. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 18. //+------------------------------------------------------------------+ 19. C_Study *Study = NULL; 20. //+------------------------------------------------------------------+ 21. input color user02 = clrBlack; //Price Line 22. input color user03 = clrPaleGreen; //Positive Study 23. input color user04 = clrLightCoral; //Negative Study 24. //+------------------------------------------------------------------+ 25. C_Study::eStatusMarket m_Status; 26. int m_posBuff = 0; 27. double m_Buff[]; 28. //+------------------------------------------------------------------+ 29. int OnInit() 30. { 31. ResetLastError(); 32. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 33. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 34. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 35. { 36. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 37. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 38. m_Status = C_Study::eCloseMarket; 39. }else 40. m_Status = C_Study::eInReplay; 41. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 42. ArrayInitialize(m_Buff, EMPTY_VALUE); 43. 44. return INIT_SUCCEEDED; 45. } 46. //+------------------------------------------------------------------+ 47. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 48. const double& high[], const double& low[], const double& close[], const long& tick_volume[], 49. const long& volume[], const int& spread[]) 50. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[]) 51. { 52. GL_PriceClose = close[rates_total - 1]; 53. GL_PriceClose = price[rates_total - 1]; 54. GL_TimeAdjust = (spread[rates_total - 1] < 60 ? spread[rates_total - 1] : 0); 55. if (_Symbol == def_SymbolReplay) 56. GL_TimeAdjust = iSpread(NULL, PERIOD_M1, 0) & (~def_MaskTimeService); 57. m_posBuff = rates_total; 58. (*Study).Update(m_Status); 59. 60. return rates_total; 61. } 62. //+------------------------------------------------------------------+ 63. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 64. { 65. (*Study).DispatchMessage(id, lparam, dparam, sparam); 66. (*Study).SetBuffer(m_posBuff, m_Buff); 67. 68. ChartRedraw((*Study).GetInfoTerminal().ID); 69. } 70. //+------------------------------------------------------------------+ 71. void OnBookEvent(const string &symbol) 72. { 73. MqlBookInfo book[]; 74. C_Study::eStatusMarket loc = m_Status; 75. 76. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 77. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 78. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 79. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 80. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 81. if (loc != m_Status) (*Study).Update(m_Status); 82. } 83. //+------------------------------------------------------------------+ 84. void OnDeinit(const int reason) 85. { 86. if (reason != REASON_INITFAILED) 87. { 88. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 89. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 90. } 91. delete Study; 92. } 93. //+------------------------------------------------------------------+
Código fuente del Indicador de Mouse
Este código, en ciertos momentos, ha tenido un comportamiento que me ha causado frustración durante las pruebas, debido a ciertos aspectos que no funcionaban como deberían. También he probado otras cosas en él, pero entraré en detalles sobre esto más adelante. Algunas situaciones no tienen sentido: deberían funcionar, pero, por alguna razón que se nos escapa, no lo hacen. Sin embargo, dejaré estas explicaciones para otro momento, cuando tenga más calma. Aquí quiero llamar tu atención, querido lector, hacia algunos cambios necesarios.
Empecemos desde el final para entender el principio. Observa que las líneas entre las posiciones 47 y 49 han sido tachadas y reemplazadas por la línea 50, lo que significa que el manejador de eventos OnCalculate ya no recibirá todos esos parámetros. Quizá te preguntes por qué he cambiado de táctica, querido lector. Esto se debe a que, hace poco, mencioné que no sería una buena idea hacer lo que estoy haciendo ahora. Intentaré explicar el motivo de este cambio, pero no aquí, sino cuando explique el código del servicio. Observa que la línea 52 fue reemplazada por la línea 53 y que la línea 54 fue sustituida por las líneas 55 y 56. Además de estos cambios, hay otro importante: la aparición de la línea 12. Pero antes de hablar de esta línea 12, analicemos lo que ocurre en las líneas 55 y 56.
Cuando el servicio actualice el RATE —veremos cómo se hace esto más adelante— MetaTrader 5 disparará un evento Calculate, que será procesado en la función OnCalculate. Está bien. De acuerdo. Sin embargo, si el marco temporal del símbolo es diferente de un minuto, el valor de rates_total NO SE ACTUALIZARÁ hasta que aparezca una nueva barra. Dado que el spread solo se utilizará cuando estemos en el modo de repetición/simulador, verificamos si el símbolo corresponde al esperado. Si esto es así, necesitaremos capturar el valor del spread mediante la función iSpread. El motivo se explicará en el próximo artículo, al igual que el resto del cálculo que se realiza en la línea 56, ya que en este momento puede que no tenga sentido.
De todos modos, dado que utilizaremos la función de biblioteca iSpread para obtener el valor correcto, no será necesario usar la función OnCalculate con todos esos parámetros anteriores. Por consiguiente, la línea 53 capturará directamente el precio. Y ahora surge la pregunta: ¿qué precio? Porque existen varios que podríamos usar. Es aquí donde entra en juego la línea 12. Esta línea declara, como una propiedad del indicador, cuál será exactamente el precio que utilizaremos. Allí definimos que el precio será el de cierre de la barra. De esta forma, mantenemos la compatibilidad del indicador del mouse con los valores que anteriormente se trabajaban en el gráfico.
Consideraciones finales
En este artículo abordé algunas cuestiones que quedaron pendientes y que se explicarán con mayor profundidad en los próximos artículos. Hay diversos aspectos mostrados aquí que pueden no tener sentido si te centras únicamente en lo expuesto en este artículo. Entre ellos, el motivo por el que utilizamos la función iSpread en lugar de capturar el valor del spread directamente desde la llamada a OnCalculate, ya que esta última recibe el valor del spread de MetaTrader 5. Además, hay otro tema que aún no he mostrado ni explicado: qué sucede cuando el símbolo entra en subasta. Este tema es bastante peculiar cuando utilizamos un símbolo personalizado. No porque el indicador del mouse no pueda mostrarnos que el símbolo está en subasta, sino por otras razones.
La forma de lograrlo ya está implementada y funcionando. Solo es necesario observar el código de la función OnBookEvent del indicador del mouse. Verás que bastaría con informar los valores BOOK_TYPE_BUY_MARKET o BOOK_TYPE_SELL_MARKET para que el indicador del mouse nos muestre que el símbolo está en subasta. Sin embargo, explicaré cómo hacer esto en otro momento. Por ahora, lo dejamos aquí y nos veremos en el próximo artículo.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12309
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- 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