
Del básico al intermedio: Eventos (II)
Introducción
En el artículo anterior, "Del básico al intermedio: Eventos (I)", empezamos a hablar de la programación basada en eventos. Sí, mi querido lector, este enfoque es conocido por muchos programadores precisamente por este nombre: programación basada en eventos. Muchos la consideran complicada y difícil. Esto se debe precisamente al hecho de que esas personas no comprenden los mecanismos y conceptos involucrados.
Sin embargo, la programación basada en eventos nos libra de diversos problemas y detalles relacionados con la interfaz gráfica. Al ser MetaTrader 5 una interfaz gráfica, el uso de la programación basada en eventos facilita bastante el desarrollo de aplicaciones orientadas a este entorno.
Pues bien, el artículo anterior dimos los que podemos considerar como los primeros pasos dentro del Riachuelo, para sentir cómo van a fluir las cosas de ahora en adelante. Vimos cómo sortear algunos puntos para mantener un recuento de eventos ocurridos y tratados por un indicador bastante simple y modesto. Sin embargo, quedó pendiente hablar de un pequeño desafío para quienes quieren vislumbrar cuánto están logrando comprender y poner en práctica ciertos conceptos de programación.
Como el desafío propuesto no era realmente complicado, creo que quienes le dedicaran un poco más de tiempo y esfuerzo habrían logrado idear una forma de resolverlo, ya que, basándose en todo lo mostrado hasta ahora, sí es posible resolverlo de forma relativamente simple.
A continuación, voy a mostrar dos maneras de resolver la misma cuestión. Explicaré los pros y los contras de cada una de las formas de resolver el problema. Así que es hora de alejarse de las distracciones y concentrarse en lo que se explicará en este artículo. Y, como de costumbre, empezaremos un nuevo tema para organizar mejor las cosas.
Posible primera solución
Para comenzar a explicar, de manera correcta, lo que se va a ver, vamos a empezar observando cuál era el código del indicador, visto en el artículo anterior.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. uint gl_Counter; 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. gl_Counter = 0; 09. 10. Print(__FUNCTION__); 11. 12. return INIT_SUCCEEDED; 13. }; 14. //+------------------------------------------------------------------+ 15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 16. { 17. if (!gl_Counter) 18. { 19. uint arr[]; 20. 21. if (FileLoad(_Symbol, arr) > 0) 22. gl_Counter = arr[0]; 23. } 24. 25. Print(__FUNCTION__, " :: ", ++gl_Counter); 26. 27. return rates_total; 28. }; 29. //+------------------------------------------------------------------+ 30. void OnDeinit(const int reason) 31. { 32. uint arr[1]; 33. 34. arr[0] = gl_Counter; 35. FileSave(_Symbol, arr); 36. 37. Print(__FUNCTION__); 38. }; 39. //+------------------------------------------------------------------+
Código 01
El desafío propuesto consistía en crear una forma de hacer que la variable declarada en la línea cuatro de este código mantuviera su valor de conteo actual si y solo si se modificaba el período gráfico. En cualquier otro caso, el valor de dicha variable debería ponerse a cero. De esta manera, el conteo se reiniciaba.
Pues bien, aunque este desafío es relativamente fácil de cumplir, puede indicarte cuán preparado estás para cosas más elaboradas. Sin embargo, si lo intentaste y aun así te atascaste en algún punto, no te preocupes. Lo importante es que lo intentaste y buscaste la forma de resolver el problema. Eso es lo que importa. Lograr resolver el problema siendo principiante sería como recibir un bono, pero no demostraría que ya sabes programar. Solo has logrado encontrar una solución. En ese caso, mis más sinceras felicitaciones.
Vale, entonces vamos a pensar un poco. Cada vez que cambiamos el tiempo gráfico, MetaTrader 5 dispara un evento Deinit. Este, a su vez, genera una llamada dirigida a un gráfico determinado. En el futuro, profundizaremos más en este mecanismo para explorarlo mejor. Por ahora, solo necesitas entender hasta dónde MetaTrader 5 hará las cosas por ti y cómo podemos y debemos lidiar con ello. Cuando este evento llega a la ventana gráfica, se dirige a TODAS LAS APLICACIONES PRESENTES EN EL GRÁFICO. TODAS. Depende de cada una capturar y tratar este evento Deinit de la mejor manera posible.
Muy bien, ¿y quién recibe este evento? Bueno, mi querido lector, este evento será capturado única y exclusivamente por el procedimiento OnDeinit, y es aquí donde las cosas empiezan a ponerse interesantes. Esto se debe a que, si observas la línea 30 del código 01, verás que este procedimiento recibe un valor. Este valor lo completa MetaTrader 5 para informar del motivo por el que la aplicación se está eliminando del gráfico. Pero espera un momento, ¿cómo que el indicador está siendo eliminado del gráfico? A primera vista, no tiene mucho sentido. Eso no tiene sentido, ya que solo estamos pidiendo que se modifique el período del gráfico, no que se elimine el indicador. En cierto modo, tienes razón en esto, querido lector. Sin embargo, debido a diversos motivos, principalmente relacionados con la implementación de la propia plataforma MetaTrader 5, es mucho más sencillo eliminar todos los elementos del gráfico, ordenar que se repinte el gráfico y, solo después, volver a colocar los elementos eliminados en él. Claro que, debido a esto, los scripts no se recolocan, ya que no pueden manejar eventos. Sin embargo, los indicadores y asesores expertos, al poder manejar eventos, se reenvían de manera automática nuevamente al gráfico. Sucede lo mismo que cuando inicias la plataforma. Si hubiera algún gráfico abierto que contuviera algún indicador o asesor experto, este se colocaría automáticamente en el gráfico por MetaTrader 5. Justo después de que todos estos elementos hayan sido colocados en el gráfico, MetaTrader 5 emite un nuevo evento para ese mismo gráfico. El evento en cuestión es Init, que a su vez será capturado por la función OnInit. Esta función se puede observar en la línea seis del código 01. Este es todo el trabajo que MetaTrader 5 va a hacer por nosotros. A partir de ahí, depende de nosotros implementar cómo nuestra aplicación va a lidiar con dichos eventos.
Esto es muy importante, querido lector, así que te ruego que lo entiendas y asimiles muy bien. Es la base de la programación basada en eventos en MQL5 para producir aplicaciones que se ejecuten en MetaTrader 5. Como nuestra aplicación solo conoce el valor de conteo presente en gl_Counter durante la ejecución, no podemos saber cuál era el valor anterior a la llegada del evento Init. Esto se debe a que, durante el proceso de eliminación del gráfico de la plataforma y su posterior recreación, MetaTrader 5 limpia toda la región de la memoria que estaba siendo utilizada por nuestra aplicación en el gráfico. Lo único que, dependiendo de la situación, puede mantenerse son las variables estáticas. Sin embargo, las variables estáticas en aplicaciones destinadas a funcionar como indicadores plantean algunos problemas. Se pueden utilizar, pero hay que hacerlo con ciertas reservas y precauciones. En el futuro hablaremos más sobre esto. Por ahora, olvidemos las variables estáticas en los códigos destinados a servir como indicadores. En el caso de los asesores expertos, la situación es diferente, ya que existen otros mecanismos que debemos comprender para poder utilizarlos. Entonces, centrémonos primero en la cuestión de los indicadores, precisamente por ser más sencillos.
Es cierto que en este momento no podemos trabajar con variables estáticas y que el valor de gl_Counter solo es válido mientras el indicador existe en el gráfico. Además, cuando pedimos a MetaTrader 5 que modifique el período del gráfico, se disparará un evento que será capturado por el procedimiento de la línea 30, cuyo valor del parámetro reason nos indica el motivo del evento Deinit. Ahora solo debemos ir a la documentación y ver qué valor informa el tipo de evento que necesitamos para guardar gl_Counter. Con esto tendremos nuestra solución.
Perfecto, entonces, al consultar la documentación, vemos que la constante REASON_CHARTCHANGE cumple nuestro criterio, que es precisamente el cambio en el período gráfico. Ahora bien, si también se modifica el símbolo, se transmitirá la misma constante. Pero primero enfoquémonos en la cuestión del período gráfico. Así, añadiendo una pequeña prueba en el procedimiento OnDeinit, obtenemos nuestro nuevo código, que mostramos a continuación.
. . . 29. //+------------------------------------------------------------------+ 30. void OnDeinit(const int reason) 31. { 32. uint arr[1]; 33. 34. arr[0] = (reason == REASON_CHARTCHANGE ? gl_Counter : 0); 35. FileSave(_Symbol, arr); 36. 37. Print(__FUNCTION__); 38. }; 39. //+------------------------------------------------------------------+
Código 02
Como todo el resto del código permaneció idéntico a lo que puede verse en el código 01, me centré solo en lo que cambió. El cambio es muy simple y fácil de entender: solo tuve que modificar la línea 34 para verificar si la constante era la que quería utilizar. Si es así, el valor del recuento se guardará. En caso contrario, el valor se pondrá a cero, lo que forzará una reinicialización del recuento cuando volvamos a utilizar el indicador en el mismo símbolo donde se estaba llevando a cabo el recuento.
Ahora vamos a ver las ventajas y desventajas de implementar una solución de esta forma. Y sí, querido lector, no todo son ventajas o desventajas a la hora de implementar algo. Por eso, comprender conceptos y practicar forma parte del aprendizaje.
Para no extenderme demasiado, citaré solo una ventaja y una desventaja. El resto corre de tu cuenta, querido lector, y el conocimiento irá surgiendo a medida que ganes práctica y estudies cada escenario específico de uso de esta o aquella forma de implementación.
Como ventaja, podemos decir que el proceso es relativamente limpio y fácil de controlar en diversos aspectos. Por ejemplo, puedes querer saber cuántas veces se disparó un evento determinado durante un plazo preestablecido.
Como desventaja, podemos decir que el hecho de utilizar archivos para almacenar los valores nos obliga a analizarlos, lo que, en muchos casos, es un problema. Por ejemplo, si tenemos el mismo símbolo abierto en la misma sesión de MetaTrader 5 con este mismo indicador, los valores pueden ser inconsistentes si cambiamos constantemente el período gráfico. Este tipo de problema se conoce en programación como condición de carrera y es bastante difícil de resolver, de hecho, se han escrito tesis doctorales y libros enteros sobre este tema. Créeme, como principiante, no querrás tener que lidiar con una condición de carrera en tu código.
Pues bien, mi querido lector, esta sería la solución número uno, precisamente por requerir pocos cambios en el código: solo una breve búsqueda en la documentación y la adopción de elementos ya mostrados en artículos anteriores.
Ahora veremos una solución un poco diferente que delegará parte del trabajo de cuidar el valor del recuento en MetaTrader 5. Pero, como ya te imaginarás, haremos esto en un nuevo tema.
Posible segunda solución
Esta segunda solución es mucho más divertida, loca e interesante, tanto desde el punto de vista práctico como por el hecho de dejar en manos de MetaTrader 5 la responsabilidad de mantener el valor del contador. Sin embargo, antes de que te emociones y pienses: «¿Por qué no vimos esta solución antes?, voy a hacer las cosas de forma inversa». Primero, hablaremos de las ventajas y desventajas de utilizar esta segunda propuesta de solución para poder ver después cómo se implementa. Al igual que en la solución anterior, mencionaré una ventaja y una desventaja para no alargar demasiado la explicación.
Como ventaja de esta segunda solución, podemos mencionar el hecho de que se elimina parte de nuestra responsabilidad de mantener el valor de la variable en algún lugar, lo más probable es que se utilice un archivo para ello. Como desventaja, cabe señalar que al delegar la responsabilidad de mantener el valor de la variable, la cosa se vuelve un poco más complicada en función de lo que se intenta hacer. Ya que la responsabilidad pasa a ser de MetaTrader 5 y no nuestra.
Pero ¿cómo es posible? Estás loco. ¿Cómo puede la misma cosa ser considerada una ventaja y una desventaja al mismo tiempo? Bueno, mi querido lector, para entender el motivo por el que estoy poniendo esto como una ventaja y una desventaja, necesitamos ver en qué consiste esta solución para la que una ventaja y una desventaja prácticamente son lo mismo y, al mismo tiempo, dan la impresión de ser algo improbable. Esta solución es un recurso presente en MetaTrader 5 del que prácticamente no se oye hablar. En el pasado, lo utilicé para otros fines, algo que, con toda seguridad, los desarrolladores de MetaTrader 5 no pensaron que alguien utilizaría ese recurso para ese propósito.
Pues bien, puedes verlo en "Desarrollo de un EA de trading desde cero (Parte 17): Acceso a los datos en la web (III). En aquella época, imaginé que los recursos de MetaTrader 5 estaban mucho más explorados por una amplia gama de programadores. Pero, terminé notando que había muchas más personas con conocimientos muy superficiales, intentando hacer las cosas a la fuerza con MQL5. Por eso hice una pausa, reflexioné y maduré la idea de crear estos artículos, que ahora puedes aprovechar para aprender MQL5 de una manera divertida y placentera. Mi intención era atender a ese público sediento de conocimiento, pero con tan pocas referencias orientadas a los principiantes.
Espero que estos artículos te estén ayudando a empezar con buen pie. Cualquier duda o sugerencia para mejorar este contenido será bienvenida.
Bien, pero volviendo a nuestra cuestión. La idea es usar variables globales de terminal. Se trata de un recurso muy interesante al que podemos acceder mediante funciones y procedimientos definidos en la biblioteca MQL5. Es algo simple, práctico y bastante útil en diversas situaciones. Así pues, veamos cómo queda el código del indicador con el objetivo de utilizar dicho recurso. Puedes verlo justo a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. union u_01 05. { 06. double d_value; 07. uint u_Counter; 08. }gl_Union; 09. //+------------------------------------------------------------------+ 10. int OnInit() 11. { 12. ZeroMemory(gl_Union); 13. 14. if (!GlobalVariableGet(_Symbol, gl_Union.d_value)) 15. if (!GlobalVariableTemp(_Symbol)) 16. return INIT_FAILED; 17. 18. GlobalVariableSet(_Symbol, gl_Union.d_value); 19. 20. Print(__FUNCTION__); 21. 22. return INIT_SUCCEEDED; 23. }; 24. //+------------------------------------------------------------------+ 25. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 26. { 27. Print(__FUNCTION__, " :: ", ++gl_Union.u_Counter); 28. 29. return rates_total; 30. }; 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. switch(reason) 35. { 36. case REASON_CHARTCHANGE: 37. GlobalVariableSet(_Symbol, gl_Union.d_value); 38. break; 39. default: 40. GlobalVariableDel(_Symbol); 41. } 42. 43. Print(__FUNCTION__); 44. }; 45. //+------------------------------------------------------------------+
Código 03
Cuando ejecutes este código 03, podrás ver algo parecido a lo que se muestra en la animación a continuación.
Animación 01
Observa que, en un momento dado, cambiamos el período gráfico y, al hacerlo, se generó la misma secuencia de eventos explicada en el tema anterior. Esto puede observarse en la información que se imprime en el terminal. Pero no es lo único que sucede aquí. Observa que, a diferencia de lo que se hacía hasta ahora, en este código 03 no utilizamos un archivo para almacenar de manera temporal alguna información —en este caso, el valor del contador—, sino que le pedimos a MetaTrader 5 que almacene esa misma información de una manera muy específica.
Para comprobarlo, podemos visualizar si existe y, en caso afirmativo, cuáles son las variables globales de terminal presentes en MetaTrader 5, antes incluso de que haya una explicación sobre el código 03. Para ello, basta con utilizar la tecla de acceso rápido F3 o seguir la ruta mostrada en la imagen de abajo.
Imagen 01
Si el indicador del código 03 se está ejecutando, veremos una variable en la ventana que se abrirá, como se muestra en la imagen siguiente.
Imagen 02
Si el indicador del código 03 no se está ejecutando, es posible que esta misma ventana que se ve en la imagen 02 no contenga ninguna información, ya que, por lo general, y que yo sepa, pocas personas utilizan variables globales de terminal, sea cual sea el motivo. Pero, si el recurso existe y está disponible, ¿por qué no utilizarlo de forma creativa?
Con la información que hemos visto en las imágenes y en la animación, ya tenemos suficientes elementos para entender cómo funciona el código 03. En cierto modo, no veo la necesidad de explicar cómo funciona, ya que se trata de una simple modificación del código 02. Sin embargo, no voy a cometer esa maldad, ya que las variables globales de terminal son un tipo de recurso cuyo uso es muy específico. Voy a aprovechar la oportunidad para explicar cómo puedes pensar en formas de utilizarlas en otras situaciones.
Para empezar, si realmente quieres utilizar una variable global de terminal, sea cual sea el motivo, debes saber que solo pueden contener valores del tipo double. Es decir, no podemos colocar texto ni valores enteros en tal variable. Esto, en principio. Sin embargo, dado que MQL5 permite crear uniones, que ya he explicado que pueden ser muy útiles en el día a día, creamos una unión en la línea cuatro para poder colocar un valor que no sea de punto flotante dentro de una variable global de terminal.
Al hacer esto, abrimos diversas posibilidades de uso de dichas variables, ya que, en este caso, podremos incluir pequeños fragmentos de texto dentro de las mismas, siempre y cuando el texto pueda incluirse dentro de un tipo double. Si no tienes claro de qué estoy hablando, te recomiendo que revises los artículos anteriores para informarte mejor sobre este tema.
Para evitar accesos innecesarios a las variables globales de terminal, declaramos la unión como una variable global. Esto puede observarse en la línea ocho. Ahora, presta atención, querido lector: lo que estamos haciendo no es obligatorio. Podríamos implementar este código de modo que no tuviéramos ninguna variable global. Sin embargo, esto nos obligaría a mover esta variable global, que se declararía en nuestro código, fuera de él, convirtiéndola en una variable global de terminal. En cierto modo, esto acabaría perjudicando el rendimiento general. No obstante, en algunos escenarios muy específicos puede llegar a ser interesante y necesario adoptar este tipo de implementación. Cada caso es un caso. Así que no generalices ni pienses que necesitamos hacer las cosas de esta manera y no de otra. Porque no siempre será así.
Por cuestiones prácticas, utilizamos la línea 12 para limpiar la región de memoria donde estará nuestro contador. Podríamos hacerlo de otras maneras. Así que piensa en otras formas de hacerlo y pruébalas para ver el resultado. Justo después, en la línea 14, intentamos leer la variable que se puede ver en la imagen 02. Si esta variable no existe, en la línea 15 intentamos crearla. Observa que el nombre de la variable será el nombre del símbolo en el que se está utilizando el indicador. Si este intento falla, el indicador devolverá un valor de error a MetaTrader 5, que disparará un nuevo evento, en este caso un evento Deinit, ya que la inicialización ha fallado. Esto hará que se ejecute el procedimiento de la línea 32 y que el indicador desaparezca del gráfico.
Es curioso cómo las cosas funcionan y pueden implementarse de manera muy simple cuando se entiende lo que se está haciendo y cómo se generan las cosas. Sin embargo, lo que necesito que entiendas, querido lector, es que esta función de la línea 15 intentará crear una variable global de terminal temporal, y es importante que esto se haga ANTES de la llamada de la línea 18. De lo contrario, la variable no será temporal. Aunque, aun así, la variable seguirá siendo global por otros criterios, pero no por el que deseamos utilizar.
Cuando creamos una variable global de terminal temporal, esta solo existirá mientras la plataforma MetaTrader 5 esté abierta. Si se cierra por cualquier motivo, todas las variables globales de terminal creadas por la función de la línea 15 se eliminarán. Esta situación es muy interesante cuando queremos mantener valores durante todo el tiempo que MetaTrader 5 esté abierto, independientemente de lo que se esté ejecutando, y que esa información se olvide cuando se cierra la plataforma. Sin embargo, esto solo ocurre si la variable global de terminal ha sido creada por la función GlobalVariableTemp.
Tú también puedes crear la misma variable, aunque sin la condición de que se elimine, si utilizas la función de la línea 18. En ese caso, MetaTrader 5 la eliminará después de un determinado período. Consulta la documentación para obtener más información al respecto, ya que es posible que necesites que una información esté presente durante más tiempo, pero no quieras guardarla en un archivo. En este caso, las variables globales del terminal pueden resultar bastante interesantes.
Vale, en cuanto a la función OnCalculate, no tenemos nada que añadir. Sin embargo, sí tenemos algo que añadir en relación con el procedimiento OnDeinit. Observa el siguiente detalle: debido a la línea 34, verificaremos qué tipo de situación provocó el evento Deinit. Si se trata de un cambio de período gráfico, como se esperaba, usaremos la línea 37 para almacenar el valor actual de nuestro contador. Ahora bien, si se trata de cualquier otra situación, usaremos la línea 40 para eliminar la variable global del terminal que haya sido creada. ¿Pero por qué hacer esto si la variable que se va a crear es temporal? El motivo es simplemente didáctico, querido lector. Es posible que quieras experimentar con este código 03 y crear tus propios tipos de situaciones. Nada más justo que mostrar cómo podemos crear y eliminar una variable global de terminal directamente a través del código. Por eso está la línea 40.
Entonces, la idea es mostrar que podemos utilizar diferentes medios para conseguir el mismo resultado. El medio que adoptemos dependerá del tipo de escenario o situación al que nos enfrentemos.
Consideraciones finales
Muy bien, querido lector, hemos llegado al final de otro artículo. Este artículo es un complemento de lo visto en el artículo anterior, por lo que es necesario que los veas como un todo. Estudiar lo que se mostró en el artículo anterior y aplicar los conocimientos adquiridos en los demás artículos de esta serie te permitirá comprender en profundidad lo explicado y demostrado en este artículo. Aunque no haya dado muchos detalles sobre cómo funciona cada línea de código, ese no es mi objetivo. Quiero que aprendas a pescar. Te proporciono el conocimiento y el material para que puedas practicar y estudiar la mejor manera de alcanzar tus objetivos. Depende de ti practicar y estudiar cada detalle mostrado.
Por ello, en el próximo artículo veremos cómo se podría concebir un indicador real, de modo que podamos aplicar todo lo mostrado hasta ahora. Sin embargo, para comprender adecuadamente lo que se mostrará en el próximo artículo, es necesario que hayas comprendido cómo se generan los eventos y hasta dónde puede ayudarnos MetaTrader 5, porque, si no, muchas puertas seguirán cerradas para ti.
En el anexo tienes acceso a los códigos que se han visto en este artículo. Así que, ¡buenos estudios y nos vemos en el próximo artículo!
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/15733





- 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