- Diseño de programas MQL de varios tipos
- Hilos
- Visión general de las funciones de gestión de eventos
- Funciones de inicio y parada de programas de varios tipos
- Eventos de referencia de indicadores y Asesores Expertos: OnInit y OnDeinit
- Función principal de scripts y servicios: OnStart
- Eliminación programática de Asesores Expertos y scripts: ExpertRemove
Visión general de las funciones de gestión de eventos
La transferencia de control a los programas MQL, es decir, su ejecución, se produce mediante la llamada a funciones especiales por parte de los agentes de pruebas o del terminal, que el desarrollador MQL define en su código de aplicación para procesar eventos predefinidos. Dichas funciones deben tener un prototipo especificado, que incluya un nombre, una lista de parámetros (número, tipos y orden) y un tipo de retorno.
El nombre de cada función corresponde al significado del evento, con la adición del prefijo On. Por ejemplo, OnStart es la función principal para «iniciar» scripts y servicios; es llamada por el terminal en el momento en que se coloca el script en el gráfico o se lanza la instancia del servicio.
Para los propósitos de este libro, nos referiremos a un evento y a su correspondiente manejador por el mismo nombre.
En la siguiente tabla se enumeran todos los tipos de eventos y los programas que los admiten (: indicador;
: Asesor Experto;
: script;
: servicio). En las secciones de los respectivos tipos de programas se ofrece una descripción detallada de los eventos. Muchos factores pueden causar eventos de inicialización y desinicialización: colocar el programa en el gráfico, cambiar su configuración, cambiar el símbolo o marco temporal del gráfico (o plantilla, o perfil), cambiar la cuenta, etc. (véase el capítulo Funciones de inicio y parada de programas de varios tipos).
Tipo de programa Evento/manejador |
Descripción |
||||
---|---|---|---|---|---|
- |
- |
● |
● |
Iniciar/Ejecutar |
|
+ |
+ |
- |
- |
Inicialización tras la carga (véanse los detalles en la sección Funciones de inicio y parada de programas de varios tipos) |
|
+ |
+ |
- |
- |
Desinicialización antes de parar y descargar |
|
- |
+ |
- |
- |
Obtener un nuevo precio (tick) |
|
● |
- |
- |
- |
Solicitud para recalcular el indicador debido a la recepción de un nuevo precio o a la sincronización de precios antiguos. |
|
+ |
+ |
- |
- |
Activación del temporizador con una frecuencia determinada |
|
- |
+ |
- |
- |
Finalización de una operación de trading en el servidor |
|
- |
+ |
- |
- |
Modificación del estado de la cuenta de trading (órdenes, transacciones, posiciones) |
|
+ |
+ |
- |
- |
Variación en el libro de órdenes |
|
+ |
+ |
- |
- |
Acción del usuario o del programa MQL sobre el gráfico |
|
- |
+ |
- |
- |
Fin de un único pase de comprobador |
|
- |
+ |
- |
- |
Inicialización antes de la optimización |
|
- |
+ |
- |
- |
Desinicialización tras la optimización |
|
- |
+ |
- |
- |
Recepción de datos de optimización del agente de pruebas |
Los manejadores obligatorios están marcados con el símbolo '●', y los manejadores opcionales están marcados con '+'.
Aunque las funciones del manejador están pensadas principalmente para ser llamadas por tiempo de ejecución, también puede llamarlas desde su propio código fuente. Por ejemplo, si un Asesor Experto necesita realizar algún cálculo basado en las cotizaciones disponibles inmediatamente después del inicio, e incluso en ausencia de ticks (por ejemplo, los fines de semana), puede llamar a OnTick antes de salir de OnInit. Como alternativa, sería lógico separar el cálculo en una función independiente y llamarla tanto desde OnInit como desde OnTick. No obstante, es deseable realizar el trabajo de la función de inicialización rápidamente, y si el cálculo es largo, debería realizarse en un temporizador.
Todos los programas MQL (excepto las bibliotecas) deben tener al menos un manejador de eventos. De lo contrario, el compilador generará un error «función de gestión de eventos no encontrada».
La presencia de algunas funciones de manejador determina el tipo del programa en ausencia de directivas #property que establezcan otro tipo. Por ejemplo, tener el manejador OnCalculate conduce a la generación del indicador (incluso si se encuentra en otra carpeta, por ejemplo, scripts o Asesores Expertos). La presencia del manejador OnStart (si no existe OnCalculate) significa la creación de un script. Al mismo tiempo, si el indicador, además de OnCalculate, va a enfrentarse a OnStart, obtenemos una advertencia del compilador «función OnStart definida en el programa que no sea script».
El libro incluye dos archivos: AllInOne.mq5 y AllInOne.mqh. El archivo de encabezado describe plantillas casi vacías de todos los manejadores de eventos principales. No contienen nada excepto la salida del nombre del manejador al registro. Examinaremos la sintaxis y los detalles específicos del uso de cada uno de los manejadores en las secciones sobre tipos específicos de programas MQL. El propósito de este archivo es proporcionar un campo para experimentar con la compilación de diferentes tipos de programas, dependiendo de la presencia de ciertos manejadores y directivas de propiedades (#property).
Algunas combinaciones pueden dar lugar a errores o advertencias.
Si la compilación se ha realizado correctamente, el tipo de programa resultante se registra automáticamente después de cargarlo mediante la siguiente línea:
const string type = |
Estudiamos el enum ENUM_PROGRAM_TYPE y la función MQLInfoInteger en la sección Tipo de programa y licencia.
El archivo AllInOne.mq5, que incluye AllInOne.mqh, se encuentra inicialmente en el directorio MQL5Book/Scripts/p5/, pero puede copiarse a cualquier otra carpeta, incluidas las ramas vecinas de Navigator (por ejemplo, a una carpeta de Asesores Expertos o indicadores). Dentro del archivo, en los comentarios, se dejan opciones para conectar determinadas configuraciones de ensamblado del programa. Por defecto, si no edita el archivo, apostará por un Asesor Experto.
//+------------------------------------------------------------------+
|
Si adjuntamos el programa al gráfico, obtendremos una entrada en el registro:
EnumToString((ENUM_PROGRAM_TYPE)MQLInfoInteger(MQL_PROGRAM_TYPE))=PROGRAM_EXPERT / ok
|
Además, lo más probable es que se genere un flujo de registros desde el manejador OnTick si el mercado está abierto.
Si duplica el archivo mq5 con un nombre diferente y, por ejemplo, anula el comentario de la directiva #property service, el compilador generará el servicio, pero devolverá algunas advertencias.
no OnStart function defined in the script
|
La primera de ellas, sobre la ausencia de la función OnStart, es realmente significativa, porque cuando se crea una instancia de servicio, no se llamará a ninguna función en ella, sino que sólo se inicializarán las variables globales. No obstante, debido a esto, el diario (pestaña Experts del terminal) seguirá imprimiendo el tipo PROGRAM_SERVICE. Sin embargo, por regla general, en los servicios, así como en los scripts, se supone que la función OnStart está presente.
Las otras dos advertencias surgen porque nuestro archivo de encabezado contiene manejadores para todas las ocasiones, y el compilador nos recuerda que OnInit y OnDeinit no tienen sentido (no serán llamados por el terminal y ni siquiera se incluirán en la imagen binaria del programa). Por supuesto, en los programas reales no debería haber tales advertencias, es decir, todos los manejadores deberían estar implicados, y todo lo superfluo debería eliminarse del código fuente, ya sea física o lógicamente, utilizando directivas de preprocesador para la compilación condicional.
Si crea otra copia de AllInOne.mq5 y activa no sólo la directiva #property service sino también la macro #define _OnStart OnStart, obtendrá como resultado de su compilación un servicio totalmente operativo. Cuando se lance, no sólo mostrará el nombre de su tipo, sino también el nombre del manejador activado OnStart.
La macro debía poder activar o desactivar el manejador estándar OnStart si así lo deseaban. En el texto AllInOne.mqh, esta función se describe del siguiente modo:
void _OnStart() // "extra" underline makes the function customized
|
El nombre que comienza con un guion bajo hace que no sea un manejador estándar, sino simplemente una función definida por el usuario con un prototipo similar. Cuando incluimos una macro, durante la compilación el compilador sustituye _OnStart por OnStart, y el resultado ya es un manejador estándar. Si nombramos explícitamente la función OnStart, entonces, según las prioridades de las características que determinan el tipo del programa MQL (véase la sección Características de programas MQL de varios tipos), no le permitiría obtener una plantilla de Asesor Experto (porque OnStart identifica el programa como un script o servicio).
Una compilación personalizada similar con macros _OnCalculate1 o _OnCalculate2 requiere «ocultar» opcionalmente el manejador con un nombre estándar OnCalculate: de lo contrario, si estuviera presente, siempre obtendríamos un indicador.
Si en la siguiente copia del programa activa la macro #define _OnCalculate1 OnCalculate, obtendrá un indicador de ejemplo (aunque esté vacío y no haga nada). Como veremos más adelante, existen dos formas diferentes del manejador OnCalculate para los indicadores, en relación con las cuales se presentan bajo nombres numerados (_OnCalculate1 y _OnCalculate2). Si ejecuta el indicador en el gráfico, podrá ver en el registro los nombres de los eventos OnCalculate (a la llegada de los ticks) y OnChartEvent (por ejemplo, al hacer clic con el ratón).
Al compilar el indicador, el compilador generará dos advertencias:
no indicator window property is defined, indicator_chart_window is applied
|
Esto se debe a que los indicadores, como herramientas de visualización de datos, requieren algunos ajustes específicos en su código que no están aquí. En esta fase en que empezamos a familiarizarnos un poco con los distintos tipos de programas, esto no es importante, pero más adelante aprenderemos a describir sus propiedades y arrays en los indicadores, que determinan qué y cómo debe visualizarse en el gráfico. Entonces estos avisos desaparecerán.
Cuando se produce un nuevo evento, debe enviarse a todos los programas MQL que se ejecutan en el gráfico correspondiente. Debido al modelo de ejecución de un solo hilo de los programas MQL (véase la sección Hilos), puede ocurrir que el siguiente evento llegue cuando todavía se está procesando el anterior. Para estos casos, el terminal mantiene una cola de eventos para cada programa MQL interactivo. Todos los eventos que contiene se procesan uno tras otro por orden de recepción.
Las colas de eventos tienen un tamaño limitado. Por lo tanto, un programa escrito de forma irracional puede provocar un desbordamiento de la cola a causa de acciones lentas. En caso de desbordamiento, los nuevos eventos se descartan sin ser puestos en cola.
No procesar los eventos lo suficientemente rápido puede afectar negativamente a la experiencia del usuario o a la calidad de los datos (imagine que graba cambios de Profundidad de Mercado y se salta algunos mensajes). Para resolver este problema, puede buscar algoritmos más eficientes o utilizar el funcionamiento en paralelo de varios programas MQL interconectados (por ejemplo, asignar cálculos a un indicador, y leer sólo datos ya preparados en un Asesor Experto).
Hay que tener en cuenta que el terminal no coloca todos los eventos en la cola, sino que opera de forma selectiva. Algunos tipos de eventos se procesan según el principio «no más de un evento de este tipo en la cola». Por ejemplo, si ya existe el evento OnTick en la cola, o se está procesando, entonces un nuevo evento OnTick no se pone en cola. Si ya existe el evento OnTimer o un evento de cambio de gráfico en la cola, los nuevos eventos de estos tipos también se descartan (ignoran). Se trata de una instancia específica del programa. Otros programas menos «ocupados» recibirán este mensaje.
No proporcionamos una lista completa de dichos tipos de eventos porque esta optimización al omitir eventos «solapados» puede ser modificada por los desarrolladores del terminal.
El enfoque para organizar el trabajo de los programas en respuesta a eventos entrantes se denomina dirigido por eventos. También se puede denominar asíncrono, ya que la puesta en cola de un evento en la cola del programa y su extracción (junto con el procesamiento) se producen en momentos diferentes (idealmente, separados por un intervalo microscópico, pero lo ideal no siempre es alcanzable). Sin embargo, de los cuatro tipos de programas MQL, sólo los indicadores y los Asesores Expertos siguen plenamente este enfoque. Los scripts y servicios tienen, de hecho, sólo la función principal, que, cuando es llamada, debe realizar rápidamente la acción requerida y completarla o iniciar un bucle sin fin para mantener alguna actividad (por ejemplo, leer datos de la red) hasta que el usuario se detenga. Hemos visto ejemplos de este tipo de bucles:
while(!IsStopped())
|
En este tipo de bucles es importante no olvidar utilizar Sleep con algún periodo para compartir los recursos de la CPU con otros programas. El valor del periodo se selecciona en función de la intensidad estimada de la actividad que se está llevando a cabo.
Este enfoque puede denominarse cíclico o síncrono, o incluso en tiempo real, ya que se puede seleccionar el periodo de reposo para proporcionar una frecuencia constante de tratamiento de datos, por ejemplo:
int rhythm = 100; // 100 ms, 10 times per sec
|
Por supuesto, el «código útil» debe caber en el marco asignado.
En cambio, con el enfoque por eventos, no se sabe de antemano cuándo funcionará la próxima vez el trozo de código (manejador). Por ejemplo, en un mercado rápido, durante las noticias, los ticks pueden llegar en lotes, y por la noche pueden estar ausentes durante segundos enteros. En el caso límite, tras el último tick del viernes por la noche, el siguiente cambio de precio de algún instrumento financiero no puede emitirse hasta el lunes por la mañana, por lo que los eventos OnTick estarán ausentes durante dos días. En otras palabras: en los eventos (y en los momentos de activación de los manejadores de eventos) no hay regularidad, no hay un horario claro.
Pero si es necesario, puede combinar ambos viajes. En concreto, el evento temporizador (OnTimer) proporciona regularidad, y el desarrollador puede generar periódicamente eventos personalizados para un gráfico dentro de un bucle (por ejemplo, la intermitencia de una etiqueta de advertencia).