- Conceptos básicos del calendario
- Obtener la lista y las descripciones de los países disponibles
- Consultar tipos de eventos por país y moneda
- Obtener descripciones de eventos por ID
- Obtener registros de eventos por país o moneda
- Obtener registros de eventos de un tipo específico
- Leer registros de sucesos por ID
- Seguimiento de los cambios de eventos por país o moneda
- Seguimiento de los cambios de eventos por tipo
- Filtrar eventos por múltiples condiciones
- Transferir la base de datos de calendarios al probador
- Operar con el calendario
Filtrar eventos por múltiples condiciones
Como sabemos por las secciones anteriores de este capítulo, la API de MQL5 permite solicitar eventos de calendario en función de varias condiciones:
- por países (CalendarValueHistory, CalendarValueLast)
- por frecuencias (CalendarValueHistory, CalendarValueLast)
- por ID de tipo de evento (CalendarValueHistoryByEvent, CalendarValueLastByEvent)
- por intervalo de tiempo (CalendarValueHistory, CalendarValueHistoryByEvent)
- por cambios desde la encuesta del calendario anterior (CalendarValueLast, CalendarValueLastByEvent)
- por ID de noticia específica (CalendarValueById)
Esto se puede resumir en la siguiente tabla de funciones (de todas las funciones de CalendarValue, aquí sólo falta CalendarValueById para obtener un valor específico).
Condiciones |
Intervalo de tiempo |
Últimos cambios |
---|---|---|
Países |
CalendarValueHistory |
CalendarValueLast |
Divisas |
CalendarValueHistory |
CalendarValueLast |
Eventos |
CalendarValueHistoryByEvent |
CalendarValueLastByEvent |
Este conjunto de herramientas abarca los principales escenarios de análisis de calendarios, aunque no todos. Por lo tanto, en la práctica, a menudo es necesario implementar mecanismos de filtrado personalizados en MQL5, incluyendo, en concreto, las solicitudes de eventos por:
- varios países
- varias divisas
- varios tipos de eventos
- valores de propiedades arbitrarias de los eventos (importancia, sector de la economía, periodo de referencia, tipo, presencia de una previsión, impacto estimado en la tasa, subcadena en el nombre del evento, etc.)
Para resolver estos problemas, hemos creado la clase CalendarFilter (CalendarFilter.mqh).
Debido a las especificidades de las funciones integradas de la API, algunos de los atributos de las noticias tienen mayor prioridad que el resto. Esto incluye el país, la divisa y el intervalo de fechas. Se pueden especificar en el constructor de la clase, y entonces la propiedad correspondiente no se puede cambiar dinámicamente en las condiciones del filtro.
Esto se debe a que la clase de filtro se ampliará posteriormente con las capacidades de almacenamiento en caché de noticias para permitir la lectura desde el probador, y las condiciones iniciales del constructor en realidad definen el contexto de almacenamiento en caché dentro del cual es posible el filtrado posterior. Por ejemplo, si al crear un objeto especificamos el código de país «UE», obviamente no tiene sentido solicitar a través de él noticias sobre Estados Unidos o Brasil. Esto es similar al intervalo de fechas: especificarlo en el constructor hará imposible recibir noticias fuera del intervalo.
También podemos crear un objeto sin condiciones iniciales (porque todos los parámetros del constructor son opcionales), y entonces será capaz de almacenar en caché y filtrar noticias en toda la base de datos del calendario (en el momento de guardarlo).
Además, dado que los países y las divisas se muestran ahora de forma casi única (con la excepción de la Unión Europea y EUR), se pasan al constructor a través de un único parámetro context: si se especifica una cadena con una longitud de 2 caracteres, el código del país (o una combinación de países) está implícito, y si la longitud es de 3 caracteres, el código de la moneda está implícito. Para los códigos «UE» y «EUR», la zona del euro es un subconjunto de la «UE» (dentro de los países con tratados formales). En casos especiales, en los que interesan países de la UE no pertenecientes a la zona euro, también pueden describirse en el contexto de «UE». Si es necesario pueden añadirse al filtro condiciones más estrictas para las noticias sobre las divisas de estos países (BGN, HUF, DKK, ISK, PLN, RON, HRK, CZK, SEK) de forma dinámica utilizando métodos que presentaremos más adelante. No obstante, debido a condiciones exóticas, no hay garantías de que esas noticias entren en el calendario.
Empecemos a estudiar la clase.
class CalendarFilter
|
Se asignan dos arrays para los países y las divisad: country y currency. Si no se rellenan desde context durante la creación del objeto, el programa MQL podrá añadir condiciones para varios países o divisas con el fin de realizar una consulta de noticias combinada en ellos.
Para almacenar las condiciones de todos los demás atributos de las noticias, el array selectors se describe en el objeto CalendarFilter, con la segunda dimensión igual a 3. Podemos decir que se trata de un tipo de tabla en la que cada fila tiene 3 columnas.
long selectors[][3]; // [0] - property, [1] - value, [2] - condition |
En el índice 0 se localizarán los identificadores de las propiedades de las noticias. Dado que los atributos están repartidos en tres tablas base (MqlCalendarCountry, MqlCalendarEvent, MqlCalendarValue) se describen utilizando los elementos de la enumeración generalizada ENUM_CALENDAR_PROPERTY (CalendarDefines.mqh).
enum ENUM_CALENDAR_PROPERTY
|
El índice 1 almacenará valores para compararlos con ellos en las condiciones de selección de registros de noticias. Por ejemplo, si desea establecer un filtro por sector de la economía, entonces escribimos CALENDAR_PROPERTY_EVENT_SECTOR en selectors[i][0] y uno de los valores de la enumeración estándar ENUM_CALENDAR_EVENT_SECTOR en selectors[i][1].
Por último, la última columna (bajo el 2º índice) está reservada a la operación de comparación del valor del selector con el valor del atributo de la noticia: todas las operaciones admitidas se resumen en la enumeración IS.
enum IS
|
Vimos un planteamiento similar en TradeFilter.mqh. Así, podremos establecer condiciones no sólo para la igualdad de valores, sino también para la desigualdad o las relaciones más/menos. Por ejemplo, es fácil imaginar un filtro en el campo CALENDAR_PROPERTY_EVENT_IMPORTANCE, que debería ser MAYOR que CALENDAR_IMPORTANCE_LOW (se trata de un elemento de la enumeración estándar ENUM_CALENDAR_EVENT_IMPORTANCE), lo que significa una selección de noticias de importancia media y alta.
La siguiente enumeración definida específicamente para el calendario es ENUM_CALENDAR_SCOPE. Dado que el filtrado de calendarios suele asociarse a intervalos de tiempo, aquí se enumeran los más solicitados.
#define DAY_LONG (60 * 60 * 24)
|
Todas las enumeraciones se colocan en un archivo de encabezado independiente CalendarDefines.mqh.
Pero volvamos a la clase CalendarFilter. El tipo del array selectors es long, que es adecuado para almacenar valores de casi todos los tipos implicados: enumeraciones, fechas y horas, identificadores, números enteros e incluso valores de indicadores económicos porque se almacenan en el calendario en forma de números long (en millonésimas de valores reales). Sin embargo, ¿qué hacer con las propiedades de las cadenas?
Este problema se resuelve utilizando el array de cadenas stringCache, a la que se añadirán todas las líneas mencionadas en las condiciones del filtro.
class CalendarFilter
|
Entonces, en lugar del valor de la cadena en selectors[i][1], podemos guardar fácilmente el índice de un elemento en el array stringCache.
Para rellenar el array selectors con las condiciones de filtrado, se proporcionan varios métodos let, en concreto, para las enumeraciones:
class CalendarFilter
|
Para los valores reales de los indicadores:
// the following fields are processed here:
|
Y para las cadenas:
// conditions for all string properties can be found here (abbreviated)
|
En la sobrecarga del método para cadenas, observe que las cadenas largas de 2 o 3 caracteres (si no llevan el asterisco de plantilla '*', que es un sustituto de una secuencia arbitraria de caracteres) entran en los arrays de países y símbolos, respectivamente, y todas las demás cadenas se tratan como fragmentos del nombre o de la fuente de noticias, y en ambos campos intervienen stringCache y selectors.
De forma especial, la clase también admite el filtrado por tipo (identificador) de eventos.
protected:
|
Así, el número de filtros prioritarios (que se procesan fuera del array de selectores) incluye no sólo países, divisas e intervalos de fechas, sino también identificadores de tipo de evento. Esta decisión constructiva se debe a que estos parámetros pueden pasarse como entrada a determinadas funciones de la API de calendario. Obtenemos todos los demás atributos de las noticias como valores de campo de salida en arrays de estructuras (MqlCalendarValue, MqlCalendarEvent, MqlCalendarCountry). Es por ellos que realizaremos el filtrado adicional, de acuerdo con las reglas en el array de selectores.
Todos los métodos de let devuelven un puntero a un objeto, lo que permite encadenar sus llamadas. Por ejemplo, así:
CalendarFilter f;
|
Las condiciones del país y de la divisa pueden, en teoría, combinarse. No obstante, tenga en cuenta que los valores múltiples sólo pueden establecerse para países o divisas, pero no para ambos. Uno de estos dos aspectos del contexto (cualquiera de los dos) en la implementación actual sólo admite uno o ninguno de los valores (es decir, no tiene filtro). Por ejemplo, si se selecciona la divisa EUR, es posible limitar el contexto de búsqueda a las noticias de Alemania y Francia (códigos de país «DE» y «FR»). En consecuencia, se descartarán las noticias del BCE y de Eurostat, así como, en concreto, las de Italia y España. No obstante, la indicación de EUR en este caso es redundante, ya que no existen otras divisas en Alemania y Francia.
Dado que la clase utiliza funciones integradas en las que los parámetros country y currency se aplican a las noticias mediante la operación lógica AND, compruebe la coherencia de las condiciones de filtrado.
Una vez que el código de llamada establece las condiciones de filtrado, es necesario seleccionar las noticias en función de ellas. Esto es lo que hace el método público select (dado con simplificaciones).
public:
|
Dependiendo de cuál de los arrays de atributos de prioridad esté lleno, el método llama a diferentes funciones de la API para sondear el calendario:
- Si el array ids está lleno, se llama a CalendarValueHistoryByEvent en un bucle para todos los identificadores
- Si el array country está lleno y es mayor que el array de divisas, llame a CalendarValueHistory y recorra los países mediante un bucle.
- Si el array currency está lleno y es mayor o igual que el tamaño del array de países, llame a CalendarValueHistory y recorra las divisas mediante un bucle.
Cada llamada a una función rellena un array temporal de estructuras MqlCalendarValue temp[], que se acumula secuencialmente en el array de parámetros result. Después de escribir en él todas las noticias relevantes según las condiciones principales (fechas, países, divisas, identificadores), si las hay, entra en juego un método auxiliar filter, que filtra el array basándose en las condiciones de selectors. Al final del método select, las noticias se clasifican por orden cronológico, que puede romperse combinando los resultados de varias consultas de las funciones «calendario». La ordenación se lleva a cabo mediante la macro SORT_STRUCT, de la que ya se habló en la sección Comparar, ordenar y buscar en arrays.
Para cada elemento del array de noticias, el método filter llama al método match, que devuelve un indicador booleano de si la noticia coincide con las condiciones del filtro. En caso contrario, el elemento se elimina del array.
protected:
|
Por último, el método match analiza nuestro array selectors y la compara con los campos de la estructura pasada MqlCalendarValue. Aquí se proporciona el código de forma abreviada.
bool match(const MqlCalendarValue &v)
|
Los métodos equal y greater copian casi por completo los utilizados en nuestros desarrollos anteriores con clases de filtros.
En este caso, el problema de filtrado se resuelve en general, es decir, el programa MQL puede utilizar el objeto CalendarFilter de la siguiente manera:
CalendarFilter f;
|
De hecho, el método select puede hacer algo más importante que dejamos para un estudio opcional independiente.
En primer lugar, en la lista de noticias resultante, conviene insertar de algún modo un separador (delimiter) entre el pasado y el futuro, para que el ojo lo capte. En teoría, esta característica es extremadamente importante para los calendarios, pero por alguna razón, no está disponible en la interfaz de usuario de MetaTrader 5 y en el sitio web mql5.com. Nuestra implementación es capaz de insertar una estructura vacía entre el pasado y el futuro, que debemos mostrar visualmente (de lo que nos ocuparemos a continuación).
En segundo lugar, el tamaño del array resultante puede ser bastante grande (especialmente en las primeras etapas de selección de ajustes), por lo que el método select ofrece además la posibilidad de limitar el tamaño del array (limit). Para ello, se eliminan los elementos más alejados de la hora actual.
Así, el prototipo completo del método tiene este aspecto:
bool select(MqlCalendarValue &result[],
|
Por defecto, no se inserta ningún delimitador y el array no se trunca.
Un par de párrafos más arriba mencionamos una subtarea adicional del filtrado que es la visualización del array resultante. La clase CalendarFilter tiene un método especial format, que convierte el array de estructuras pasado MqlCalendarValue &data[] en un array de cadenas legibles string &result[]. El código del método se encuentra en el archivo adjunto CalendarFilter.mqh.
bool format(const MqlCalendarValue &data[],
|
Los campos de MqlCalendarValue que queremos mostrar se especifican en el array props. Recordemos que la enumeración ENUM_CALENDAR_PROPERTY contiene campos de las tres estructuras de calendario dependientes para que un programa MQL pueda mostrar automáticamente no sólo los indicadores económicos de un registro de evento concreto, sino también su nombre, características, país o código de divisa. Todo ello se implementa mediante el método format.
Cada fila del array de salida result contiene una representación textual del valor de uno de los campos (número, descripción, elemento de enumeración). El tamaño del array result es igual al producto del número de estructuras en la entrada (en data) y el número de campos visualizados (en props). El parámetro opcional header permite añadir una fila con los nombres de los campos (columnas) al principio del array de salida. El parámetro padding controla la generación de espacios adicionales en el texto para que sea conveniente mostrar la tabla en un tipo de letra monoespaciado (por ejemplo, en una revista).
La clase CalendarFilter tiene otro método público importante: update.
bool update(MqlCalendarValue &result[]); |
Su estructura repite casi por completo select. Sin embargo, en lugar de llamar a las funciones CalendarValueHistoryByEvent y CalendarValueHistory, el método llama a CalendarValueLastByEvent y CalendarValueLast. El propósito del método es obvio: consulta el calendario en busca de cambios recientes que coincidan con las condiciones de filtrado. Pero para su funcionamiento requiere una identificación de los cambios. Dicho campo sí está definido en la clase: la primera vez se rellena dentro del método select.
class CalendarFilter
|
Algunos matices de la clase CalendarFilter todavía están «tras el telón», pero abordaremos algunos de ellos en las siguientes secciones.
Probemos el filtro en acción: primero en un sencillo script CalendarFilterPrint.mq5 y después en un indicador más práctico CalendarMonitor.mq5.
En los parámetros de entrada del script puede establecer el contexto (código de país o divisa), el intervalo de tiempo y la cadena para la búsqueda de texto completo por nombres de eventos, así como limitar el tamaño de la tabla de noticias resultante.
input string Context; // Context (country - 2 characters, currency - 3 characters, empty - no filter)
|
Dados los parámetros, se crea un objeto filtro global.
CalendarFilter f(Context, TimeCurrent() - Scope, TimeCurrent() + Scope); |
A continuación, en OnStart, configuramos un par de condiciones constantes adicionales (importancia media y alta de los eventos) y la presencia de una previsión (el campo no es igual a LONG_MIN), así como el paso y una cadena de búsqueda al objeto.
void OnStart()
|
A continuación, se llama al método select y el array resultante de estructuras MqlCalendarValue se formatea en una tabla con 9 columnas utilizando el método format.
MqlCalendarValue records[];
|
Las celdas de la tabla se unen en filas y se envían al registro.
Con la configuración por defecto (es decir, para todos los países y divisas, con la parte «granja» en el nombre de los eventos de importancia media y alta), puede obtener algo como este calendario.
Selecting calendar records... country[i]= / ok calendarValueHistory(temp,from,to,country[i],c)=2372 / ok Filtering 2372 records Got 9 records TIME | CUR⁞ | NAME | IMPORTAN⁞ | ACTU⁞ | FORE⁞ | PREV⁞ | IMPACT | SECT⁞ 2022.06.02 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +128 | -225 | +202 | POSITIVE | JOBS 2022.06.02 15:30 | USD | Nonfarm Productivity q/q | MODERATE | -7.3 | -7.5 | -7.5 | POSITIVE | JOBS 2022.06.03 15:30 | USD | Nonfarm Payrolls | HIGH | +390 | -19 | +436 | POSITIVE | JOBS 2022.06.03 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +333 | +8 | +405 | POSITIVE | JOBS 2022.06.09 08:30 | EUR | Nonfarm Payrolls q/q | MODERATE | +0.3 | +0.3 | +0.3 | NA | JOBS | | | | | | | | 2022.07.07 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +nan | -263 | +128 | NA | JOBS 2022.07.08 15:30 | USD | Nonfarm Payrolls | HIGH | +nan | -229 | +390 | NA | JOBS 2022.07.08 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +nan | +51 | +333 | NA | JOBS
|
Ahora echemos un vistazo al indicador CalendarMonitor.mq5. Su objetivo es mostrar al usuario la selección actual de eventos en el gráfico de acuerdo con los filtros especificados. Para visualizar la tabla, utilizaremos la ya conocida clase de marcador (Tableau.mqh, véase la sección Cálculo del margen para una orden futura). El indicador no tiene búferes ni gráficos.
Los parámetros de entrada permiten establecer el rango de la ventana de tiempo (scope), así como el contexto global para el objeto CalendarFilter, que es la divisa o el código de país en Context (vacío por defecto, es decir, sin restricciones) o mediante una bandera booleana UseChartCurrencies. Está activada por defecto, y se recomienda utilizarla para recibir automáticamente noticias de aquellas divisas que conforman la herramienta de trabajo del gráfico.
input string Context; // Context (country - 2 chars, currency - 3 chars, empty - all)
|
Se pueden aplicar filtros adicionales por tipo de evento, sector y gravedad.
input ENUM_CALENDAR_EVENT_TYPE_EXT Type = TYPE_ANY;
|
La importancia establece el límite inferior de la selección, no la coincidencia exacta. Así, el valor por defecto de IMPORTANCE_MODERATE captará no sólo la importancia moderada, sino también la alta.
Un lector atento observará que aquí se utilizan enumeraciones desconocidas: ENUM_CALENDAR_EVENT_TYPE_EXT, ENUM_CALENDAR_EVENT_SECTOR_EXT, ENUM_CALENDAR_EVENT_IMPORTANCE_EXT. Se encuentran en el archivo ya mencionado CalendarDefines.mqh, y coinciden (casi uno a uno) con enumeraciones integradas similares. La única diferencia es que han añadido un elemento que significa «cualquier» valor. Necesitamos describir tales enumeraciones para simplificar la introducción de condiciones: ahora el filtro de cada campo se configura mediante una lista desplegable en la que se puede seleccionar uno de los valores o desactivar el filtro. Si no fuera por el elemento de enumeración añadido, tendríamos que introducir una bandera lógica «on/off» en la interfaz para cada campo.
Además, los parámetros de entrada permiten consultar los eventos por la presencia en ellos de indicadores reales, de previsión y anteriores, así como por la búsqueda de una cadena de texto (Text).
input string Text;
|
Los objetos CalendarFilter y tableau se describen a nivel global.
CalendarFilter f(Context);
|
Tenga en cuenta que el filtro se crea una vez, mientras que la tabla está representada por un autoselector y se volverá a crear dinámicamente en función del tamaño de los datos recibidos.
La configuración de los filtros se realiza en OnInit mediante llamadas consecutivas a los métodos de let en función de los parámetros de entrada.
int OnInit()
|
Al final, se pone en marcha un segundo temporizador. Todo el trabajo se realiza en OnTimer.
void OnTimer()
|
Si ejecutamos el indicador en el gráfico EURUSD con la configuración predeterminada, podemos obtener la siguiente imagen.
Noticias filtradas y formateadas en el gráfico