- Activar y desactivar temporizador
- Evento temporizador: OnTimer
- Temporizador de alta precisión: EventSetMillisecondTimer
Evento temporizador: OnTimer
El evento OnTimer es uno de los eventos estándar admitidos por los programas MQL5 (véase la sección Visión general de las funciones de gestión de eventos). Para recibir eventos de temporizador en el código del programa, debe describir una función con el siguiente prototipo:
void OnTimer(void)
El evento OnTimer es generado periódicamente por el terminal cliente para un Asesor Experto o un indicador que ha activado el temporizador utilizando la función EventSetTimer o EventSetMillisecondTimer (véase la sección siguiente).
¡Atención! En indicadores dependientes creados llamando a iCustom o IndicatorCreate desde otros programas, el temporizador no funciona, y el evento OnTimer no se genera. Esta es una limitación arquitectónica de MetaTrader 5.
Debe entenderse que la presencia de un manejador OnTimer y un temporizador habilitado no hace que el programa MQL sea multihilo. No se asigna más de un hilo por programa MQL (un indicador puede incluso compartir un hilo con otros indicadores en el mismo símbolo), por lo que la llamada de OnTimer y otros manejadores siempre se produce secuencialmente, de acuerdo con la cola de eventos. Si uno de los manejadores, incluido OnTimer, va a iniciar cálculos largos, esto suspenderá la ejecución de todos los demás eventos y secciones del código del programa.
Si necesita organizar el procesamiento de datos en paralelo, debe ejecutar varios programas MQL de forma simultánea (quizás, instancias del mismo programa en diferentes gráficos u objetos gráficos) e intercambiar comandos y datos entre ellos utilizando su propio protocolo; por ejemplo, utilizando eventos personalizados.
Como ejemplo, vamos a crear clases que pueden organizar varios temporizadores lógicos en un programa. Los periodos de todos los temporizadores lógicos se establecerán como un multiplicador del periodo base, es decir, el periodo de un único temporizador de hardware que suministra eventos al manejador estándar OnTimer. En este manejador, debemos llamar a cierto método de nuestra nueva clase MultiTimer que manejará todos los temporizadores lógicos.
void OnTimer()
|
La clase MultiTimer y las clases relacionadas de temporizadores individuales se combinarán en un archivo, MultiTimer.mqh.
La clase base para los temporizadores de trabajo será TimerNotification. En términos estrictos, podría tratarse de una interfaz, pero es conveniente volcar en ella algunos detalles de la implementación general: en concreto, almacenar la lectura del contador chronometer, mediante el cual nos aseguraremos de que el temporizador se dispara con un cierto multiplicador del periodo relativo del temporizador principal, así como un método para comprobar el momento en que el temporizador debe dispararse isTimeCome. Por eso TimerNotification es una clase abstracta. Carece de la implementación de dos métodos virtuales: notify, para las acciones cuando se dispara el temporizador, y getInterval, para obtener un multiplicador que determina el periodo de un temporizador concreto en relación con el periodo del temporizador principal.
class TimerNotification
|
Toda la lógica se proporciona en el método isTimeCome. Cada vez que se llama, el contador chronometer se incrementa, y si alcanza la última iteración según el método getInterval, se llama al método notify para notificar el código de la aplicación.
Por ejemplo, si el temporizador principal se inicia con un periodo de 1 segundo (EventSetTimer(1)), entonces el objeto hijo TimerNotification,, que devolverá 5 de getInterval, recibirá llamadas a su método notify cada 5 segundos.
Como ya hemos dicho, dichos objetos temporizadores serán gestionados por el objeto administrador MultiTimer. Sólo necesitamos un objeto de este tipo. Por lo tanto, su constructor se declara protegido y se crea una única instancia de forma estática dentro de la clase.
class MultiTimer
|
Dentro de esta clase, organizamos el almacenamiento del array de objetos TimerNotification (veremos cómo se rellena dentro de pocos párrafos). Una vez que tenemos el array podemos escribir fácilmente el método checkTimers que hace un bucle a través de todos los temporizadores lógicos. Para el acceso externo, este método está duplicado por el método estático público onTimer, que ya hemos visto en el manejador global OnTimer. Como la única instancia del administrador se crea estáticamente, podemos acceder a ella desde un método estático.
...
|
El objeto TimerNotification se añade al arrayz subscribers mediante el método bind.
void bind(TimerNotification &tn)
|
El método está protegido contra la adición repetida del objeto y, si es posible, el puntero se coloca en un elemento vacío del array, si lo hay, lo que elimina la necesidad de ampliar el array. Pueden aparecer elementos vacíos en un array si se ha eliminado alguno de los objetos de TimerNotification mediante el método unbind (se pueden utilizar temporizadores de forma ocasional).
void unbind(TimerNotification &tn)
|
Tenga en cuenta que el administrador no se apropia del objeto temporizador y no intenta llamar a delete. Si va a registrar objetos temporizadores asignados dinámicamente en el administrador, puede añadir el siguiente código dentro de if antes de la puesta a cero:
if(CheckPointer(subscribers[i]) == POINTER_DYNAMIC) delete subscribers[i]; |
Ahora queda entender cómo podemos organizar convenientemente las llamadas a bind/unbind, para no cargar el código de la aplicación con estas operaciones utilitarias. Si lo hace «manualmente», es fácil que olvide crear o, por el contrario, borrar el temporizador en algún sitio.
Vamos a desarrollar la clase SingleTimer derivada de TimerNotification, en la que implementamos las llamadas bind y unbind del constructor y destructor, respectivamente. Además, describimos en él la variable multiplier para almacenar el periodo del temporizador.
class SingleTimer: public TimerNotification
|
El segundo parámetro del constructor (paused) permite crear un objeto, pero no iniciar el temporizador inmediatamente. Dicho temporizador retardado puede activarse mediante el método start.
El esquema de suscripción de unos objetos a eventos en otros es uno de los patrones de diseño populares en la POO y se denomina «publisher/subscriber» (publicador/suscriptor).
Es importante señalar que esta clase también es abstracta porque no implementa el método notify. Basándonos en SingleTimer, vamos a describir las clases de temporizadores con funcionalidad adicional.
Empecemos por la clase CountableTimer. Permite especificar cuántas veces debe dispararse, tras lo cual se detendrá automáticamente. Con ella, en concreto, es fácil organizar una única acción diferida. El constructor CountableTimer tiene parámetros para establecer el periodo del temporizador, la bandera de pausa y el número de reintentos. Por defecto, el número de repeticiones no está limitado, por lo que esta clase se convertirá en la base de la mayoría de los temporizadores de aplicación.
class CountableTimer: public MultiTimer::SingleTimer
|
Para utilizar CountableTimer tenemos que describir la clase derivada en nuestro programa de la siguiente manera:
// MultipleTimers.mq5
|
En esta implementación del método notify, sólo registramos el período del temporizador y el número de veces que se disparó. Por cierto, este es un fragmento del indicador MultipleTimers.mq5, que utilizaremos como ejemplo de trabajo.
Llamemos a la segunda clase derivada de SingleTimer FunctionalTimer. Su propósito es proporcionar una implementación sencilla del temporizador para aquellos a los que les gusta el estilo funcional de programación y no les apetece escribir clases derivadas. El constructor de la clase FunctionalTimer tomará, además del punto, un puntero a una función de un tipo especial, TimerHandler.
// MultiTimer.mqh
|
En esta implementación del método notify, el objeto llama a la función por el puntero. Con una clase de este tipo, podemos definir una macro que, cuando se coloca delante de un bloque de sentencias entre llaves, lo «convertirá» en el cuerpo de la función del temporizador.
// MultiTimer.mqh
|
Entonces, en el código de la aplicación, puede escribir algo así:
// MultipleTimers.mq5
|
Esta construcción declara un temporizador con un período de 3 y un conjunto de instrucciones dentro de paréntesis (aquí, sólo la impresión a un registro). Si esta función devuelve false, este temporizador se detendrá.
Vamos a analizar más detenidamente el indicador MultipleTimers.mq5. Como no proporciona visualización, especificaremos el número de diagramas igual a cero.
#property indicator_chart_window
|
Para utilizar las clases de temporizadores lógicos, incluimos el archivo de encabezado MultiTimer.mqh y añadimos una variable de entrada para el periodo base (global) del temporizador.
#include <MQL5Book/MultiTimer.mqh>
|
El temporizador base se inicia en OnInit.
void OnInit()
|
Recordemos que el funcionamiento de todos los temporizadores lógicos está garantizado por la interceptación del evento global OnTimer.
void OnTimer()
|
Además de la clase de aplicación del temporizador MyCountableTimer anterior, vamos a describir otra clase del temporizador suspendido MySuspendedTimer.
class MySuspendedTimer: public CountableTimer
|
Un poco más abajo veremos cómo empieza. También es importante señalar aquí que después de alcanzar el número especificado de operaciones, este temporizador apagará todos los temporizadores llamando a EventKillTimer.
Ahora vamos a mostrar cómo (en el contexto global) se describen los objetos de diferentes temporizadores de estas dos clases.
MySuspendedTimer st(1, 5);
|
El temporizador st de la clase MySuspendedTimer tiene periodo 1 (1*BaseTimerPeriod) y debe detenerse después de 5 operaciones.
Los temporizadores t1 y t2 de la clase MyCountableTimer tienen periodos 2 (2 * BaseTimerPeriod) y 4 (4 * BaseTimerPeriod), respectivamente. Con el valor por defecto BaseTimerPeriod = 1 todos los periodos representan segundos. Estos dos temporizadores se ponen en marcha inmediatamente después del inicio del programa.
También crearemos dos temporizadores de estilo funcional.
bool OnTimerCustom(5)
|
Tenga en cuenta que OnTimerCustom5 sólo tiene una tarea: 5 periodos después del inicio del programa, necesita iniciar un temporizador retardado st y terminar su propia ejecución. Teniendo en cuenta que el temporizador retardado debe desactivar todos los temporizadores después de 5 periodos, obtenemos 10 segundos de actividad del programa con los ajustes por defecto.
El temporizador OnTimerCustom3 debe activarse tres veces durante este periodo.
Así, tenemos 5 temporizadores con diferentes períodos: 1, 2, 3, 4, 5 segundos.
Analicemos un ejemplo de lo que se envía al registro (las marcas de tiempo se muestran esquemáticamente a la derecha).
// time
|
El primer mensaje del temporizador de dos segundos llega, como era de esperar, unos 2 segundos después del inicio (decimos «unos» porque el temporizador de hardware tiene una limitación en la precisión y, además, otra carga del ordenador afecta a la ejecución). Un segundo después, el temporizador de tres segundos se dispara por primera vez. El segundo golpe del temporizador de dos segundos coincide con la primera salida del temporizador de cuatro segundos. Tras una única ejecución del temporizador de cinco segundos, los mensajes del temporizador de un segundo comienzan a aparecer en el registro regularmente (su contador aumenta de 0 a 4). En su última iteración, detiene todos los temporizadores.