- Principales características de los indicadores
- Evento indicador principal: OnCalculate
- Dos tipos de indicadores: para la ventana principal y para la subventana
- Ajuste del número de buffers y gráficos
- Asignación de un array como buffer: SetIndexBuffer
- Configuración de plot: PlotIndexSetInteger
- Reglas de asignación de buffers y gráficos
- Aplicación de directivas para personalizar plots
- Configuración de nombres de plots
- Visualización de las carencias de datos (elementos vacíos)
- Indicadores de subventanas independientes: tamaños y niveles
- Propiedades generales de los indicadores: precisión del título y del valor
- Coloreado de gráficos por elementos
- Omitir dibujo en barras iniciales
- Esperar datos y gestionar la visibilidad (DRAW_NONE)
- Indicadores multidivisa y multitemporal
- Seguimiento de formación de barras
- Comprobación de indicadores
- Limitaciones y ventajas de los indicadores
- Crear un borrador de indicador en el Asistente MQL
Esperar datos y gestionar la visibilidad (DRAW_NONE)
En el capítulo anterior, en la sección Trabajar con arrays de ticks reales en estructuras MqlTick, trabajamos con el script SeriesTicksDeltaVolume.mq5, que calcula el volumen delta en cada barra. En ese momento mostrábamos los resultados en un registro, pero una forma mucho más cómoda y lógica de analizar esa información técnica es un indicador. En esta sección crearemos un indicador de este tipo: IndDeltaVolume.mq5.
Aquí tendremos que abordar dos factores con los que nos encontramos a menudo a la hora de desarrollar indicadores, pero que no se han tratado en los ejemplos anteriores.
El primero de ellos es que los datos de tick no se refieren a series temporales de precios estándar, que el terminal envía al indicador en los parámetros OnCalculate. Esto significa que el propio indicador debe solicitarlos y esperar antes de que sea posible mostrar algo en la ventana.
El segundo factor está relacionado con el hecho de que los volúmenes de compra y venta, por regla general, son mucho mayores que su delta, y cuando se muestran en una ventana, será difícil distinguir entre estos últimos. No obstante, es el delta un valor indicativo, que suele analizarse junto con el movimiento del precio. Por ejemplo, hay 4 combinaciones más obvias de configuraciones de barra y volumen delta:
- Barra alcista y delta positivo = confirmación de una tendencia alcista
- Barra bajista y delta negativo = confirmación de la tendencia bajista
- Barra alcista y delta negativo = posible inversión de tendencia a la baja
- Barra bajista y delta positivo = es posible una inversión de tendencia al alza
Para ver el histograma de deltas necesitamos proporcionar un modo para desactivar los histogramas «grandes» (compras y ventas), para lo cual utilizaremos el tipo DRAW_NONE. Desactiva el dibujo de un trazado específico e impide su influencia en la escala de ventana seleccionada automáticamente (pero deja el búfer en Data Window). Así, al eliminar los trazados grandes de la consideración, conseguiremos una autoescala mayor para el diagrama delta restante. Otra forma de ocultar los búferes marcándolos como auxiliares (modo INDICATOR_CALCULATIONS) se abordará en la siguiente sección.
La idea del delta de volumen es calcular por separado los volúmenes de compra y venta en ticks, después de lo cual podemos encontrar la diferencia entre estos volúmenes. En consecuencia, obtenemos tres series temporales con volúmenes de compra, volúmenes de venta y las diferencias entre ellos. Como esta información no cabe en la escala de precios, el indicador debe mostrarse en su propia ventana, y elegiremos histogramas desde cero (DRAW_HISTOGRAM) como forma de mostrar tres series temporales.
De acuerdo con esto, vamos a describir las propiedades de los indicadores en las directivas: ubicación, número de búferes y trazados, así como sus tipos.
#property indicator_separate_window
|
Vamos a utilizar las variables de entrada del script anterior. Dado que los ticks representan datos bastante cuantiosos, limitaremos el número de barras para el cálculo en la historia (BarCount). Además, dependiendo de la presencia o ausencia de volúmenes reales en ticks de un determinado instrumento financiero, podemos calcular el delta de dos formas diferentes, para lo cual utilizaremos el parámetro tick type (la enumeración COPY_TICKS está definida en el archivo de encabezado TickEnum.mqh, que ya hemos utilizado en el script).
#include <MQL5Book/TickEnum.mqh>
|
En el manejador OnInit, cambiamos el modo de operar de los dos primeros histogramas entre DRAW_HISTOGRAM y DRAW_NONE, dependiendo del parámetro ShowBuySell seleccionado por el usuario (el valor por defecto true significa mostrar los tres histogramas). Tenga en cuenta que la configuración dinámica a través de PlotIndexSetInteger sobrescribe la configuración estática (en este caso, sólo algunos de los ajustes) incrustado en el archivo ejecutable utilizando las directivas #property.
int OnInit()
|
Pero, ¿dónde está el registro de los búferes de indicadores? Volveremos sobre ello en un par de párrafos. Ahora vamos a empezar a preparar la función OnCalculate.
int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
|
El principal problema técnico se encuentra en el bloque denominado TODO(2). El algoritmo de solicitud de ticks, que se utilizó en el script y se transferirá al indicador con cambios mínimos, los solicita utilizando la función CopyTicksRange. Una llamada de este tipo devuelve los datos disponibles en la base de datos de ticks. Pero si aún no está disponible para la barra histórica dada, la solicitud hace que los datos de ticks se descarguen y sincronicen de forma asíncrona (en segundo plano). En este caso, el código de llamada recibe 0 ticks. En este sentido, tras recibir una respuesta «vacía» de este tipo, el indicador debería interrumpir los cálculos con una señal de fallo (pero no de error) y volver a solicitar ticks al cabo de un tiempo. En una situación de mercado abierto normal recibimos regularmente ticks, por lo que la función OnCalculate probablemente debería ser llamada pronto y recalculada con la base de ticks actualizada. Pero, ¿qué hacer los fines de semana cuando no hay ticks?
Para el correcto manejo de tal situación, MQL5 proporciona un temporizador. Lo estudiaremos en uno de los capítulos siguientes, pero por ahora lo utilizaremos como «caja negra». La función especial EventSetTimer «solicita» al kernel que llame a nuestro programa MQL después de un número especificado de segundos. El punto de entrada para una llamada de este tipo es un manejador OnTimer reservado, que hemos visto en la tabla general de la sección Visión general de las funciones de gestión de eventos. Por lo tanto, si hay un retraso en la recepción de datos de tick, debe iniciar el temporizador utilizando EventSetTimer (un período mínimo de 1 segundo es suficiente) y devolver cero desde OnCalculate.
int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
|
En el manejador OnTimer, utilizamos la función EventKillTimer para detener el temporizador (si no se hace esto, el sistema seguirá llamando a nuestro manejador cada segundo). Además, necesitamos iniciar de alguna manera el recálculo del indicador. Para ello, aplicaremos otra función que aún tenemos que descubrir en el capítulo sobre gráficos: ChartSetSymbolPeriod (véase la sección Cambiar símbolo y marco temporal). Permite establecer una nueva combinación de un símbolo y un marco temporal para un gráfico con un identificador determinado (0 significa el gráfico actual). No obstante, si no se modifican pasando _Symbol y _Period (véase Variables predefinidas), entonces el gráfico simplemente se actualizará (los indicadores se recalculan).
void OnTimer()
|
Otro punto a tener en cuenta aquí es que, en el mercado abierto, el evento del temporizador y la actualización automática del gráfico pueden ser redundantes si el siguiente tick aparece antes de la llamada a OnTimer. Por lo tanto, crearemos una variable global (calcDone) para cambiar la bandera de la preparación de los cálculos. Al principio de OnCalculate, lo restableceremos en false; ; a la finalización normal del cálculo, lo estableceremos en true.
bool calcDone = false;
|
A continuación, en OnTimer, podemos iniciar la actualización automática del gráfico sólo cuando calcDone sea igual a false.
void OnTimer()
|
Pasemos ahora a los comentarios de TODO(1,2,3), donde debemos realizar los cálculos y rellenar los búferes de indicadores. Combinemos todas estas operaciones en una clase CalcDeltaVolume. Así, se asignará un método distinto para cada acción, mientras que mantendremos el manejador OnCalculate simple (aparecerán llamadas a métodos en lugar de comentarios).
En la clase, proporcionaremos variables de miembro que aceptarán las configuraciones de usuario para el número de barras históricas procesadas y el método de cálculo del delta, así como tres arrays para los búferes de indicadores. Vamos a inicializarlos en el constructor.
class CalcDeltaVolume
|
Podemos asignar arrays de miembros como búferes porque vamos a crear un objeto global de esta clase a continuación. Para que los datos se muestren correctamente, sólo tenemos que asegurarnos de que los arrays adjuntos a los gráficos existen en el momento de dibujarlos. Es posible cambiar los enlaces de búfer de forma dinámica (véase el ejemplo de IndSubChartSimple.mq5 en la siguiente sección).
Tenga en cuenta que los búferes de indicadores deben ser del tipo double, mientras que los volúmenes son del tipo ulong. Por lo tanto, para valores muy grandes (por ejemplo, en plazos muy largos), hipotéticamente puede haber una pérdida de precisión.
Se ha creado el método reset para inicializar los búferes. La mayoría de los elementos del array se rellenan con el valor vacío EMPTY_VALUE, y las últimas barras limit se rellenan con cero porque allí sumaremos los volúmenes de compras y ventas por separado.
void reset()
|
El cálculo en la i-ésima barra histórica se realiza mediante el método createDeltaBar. Su entrada recibe el número de barras y un enlace al array con las marcas de tiempo de las barras (lo recibimos como el parámetro OnCalculate). Los elementos del array i-ésima se inicializan a cero.
int createDeltaBar(const int i, const datetime &time[])
|
A continuación, necesitamos los límites de tiempo de la i-ésima barra: prev y next, donde next se cuenta a la derecha de prev sumando el valor de la función PeriodSeconds, que es nueva para nosotros. Devuelve el número de segundos en el marco temporal actual. Sumando esta cantidad, encontramos el comienzo teórico de la siguiente barra. En el historial, cuando i no es igual al número de la última barra, podríamos sustituir la búsqueda de la siguiente marca de tiempo por time[i + 1]. Sin embargo, el indicador también debería funcionar en la última barra que todavía está en proceso de formación y que no tiene una barra siguiente. Por lo tanto, en general, el uso de time[i + 1] está prohibido.
...
|
Cuando hicimos un cálculo similar en el script, no tuvimos que utilizar la función PeriodSeconds, porque no contamos la última barra actual y podíamos permitirnos buscar next y prev, como iTime(WorkSymbol, TimeFrame, i) y iTime(WorkSymbol, TimeFrame, i + 1), respectivamente.
Además, en el método createDeltaBar, solicitamos ticks dentro de las marcas de tiempo encontradas (restamos 1 milisegundo desde la derecha para no tocar la siguiente barra). Los ticks llegan al array ticks, que es procesada por el método de ayuda calc. Contiene el algoritmo de script casi sin cambios. Nos hemos visto obligados a separarlo en un método designado porque el cálculo se realizará en dos situaciones diferentes: utilizando barras históricas (recuerde el comentario TODO(2)) y utilizando ticks en la barra actual (comentario TODO(3)). Vamos a analizar a continuación la segunda situación.
ResetLastError();
|
En caso de solicitud correcta, el método devuelve el número de ticks procesados, y en caso de error, devuelve un código de error con un signo menos. Tenga en cuenta que si todavía no hay ticks para la barra en la base de datos (lo cual no es un error, estrictamente hablando, pero no permite continuar con el funcionamiento visual del indicador), el método devolverá 0 (el signo de 0 no cambia su valor). Por lo tanto, en la función OnCalculate, tenemos que comprobar que el resultado del método para «menor o igual que» 0.
El método calc consiste prácticamente en líneas de trabajo del script SeriesTicksDeltaVolume.mq5, por lo que no lo presentaremos aquí. Quienes deseen refrescarse la memoria, pueden hacerlo consultando IndDeltaVolume.mq5.
Para calcular el delta en una última barra constantemente actualizada necesitamos fijar la marca de tiempo del último tick procesado con una precisión de milisegundos. A continuación, en la siguiente llamada de OnCalculate, podremos consultar todos los ticks posteriores a esta etiqueta.
Tenga en cuenta que no hay garantía de que el sistema tenga tiempo de llamar a nuestro manejador OnCalculate en cada tick en tiempo real. Si realizamos cálculos pesados, o si algún otro programa MQL carga el terminal con cálculos, o si los ticks vienen muy rápido (por ejemplo, después de importantes comunicados de prensa), los eventos pueden no llegar a la cola del indicador (no más de un evento de cada tipo se almacena en la cola, incluyendo no más de una notificación de tick). Por lo tanto, si el programa quiere obtener todos los ticks, debe solicitarlos utilizando CopyTicksRange o CopyTicks.
No obstante, la marca de tiempo del último tick procesado por sí sola no es suficiente. Los ticks pueden tener el mismo tiempo incluso teniendo en cuenta los milisegundos. Por lo tanto, no podemos añadir 1 milisegundo a la etiqueta para excluir el tick «antiguo»: los «nuevos» ticks con la misma etiqueta pueden detrás.
A este respecto, debe recordar no sólo la etiqueta, sino también el número de los últimos ticks con esta etiqueta. Entonces, la próxima vez que solicitemos ticks, podremos hacerlo empezando desde el momento recordado (es decir, incluyendo los ticks «antiguos»), pero saltándonos exactamente tantos de ellos como ya se procesaron la última vez.
Para implementar este algoritmo, se declaran dos variables en la clase last time y last count.
ulong last time; // millisecond marker of the last processed online tick
|
A partir del array de ticks recibido del sistema, hallamos los valores para estas variables utilizando el método auxiliar updateLastTime.
void updateLastTime(const int n, const MqlTick &ticks[])
|
Ahora podemos refinar el método createDeltaBar: al procesar la última barra, llamamos por primera vez a updateLastTime.
int createDeltaBar(const int i, const datetime &time[])
|
Teniendo valores actualizados para last time y last count, podemos implementar un método para calcular deltas en la actual barra en línea.
int updateLastDelta(const int total)
|
Para implementar este modo, hemos introducido un parámetro opcional adicional skip en el método calc. Permite omitir el cálculo en un número determinado de ticks «antiguos».
void calc(const int i, const MqlTick &ticks[], const int skip = 0)
|
La clase para el cálculo está lista; ahora sólo tenemos que insertar llamadas a tres métodos públicos en OnCalculate.
int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
|
Vamos a compilar y ejecutar el indicador. Para empezar, es aconsejable elegir un marco temporal no superior a H1 y dejar el número de barras de BarCount establecido en 100 por defecto. Después de esperar un poco a que se construya el indicador, el resultado debería ser algo parecido a esto:
Indicador de volumen de delta con todos los histogramas, incluidas compras y ventas
Ahora, compárelo con lo que ocurrirá al establecer el parámetro ShowBuySell en false.
Indicador de volumen con un histograma de deltas (se ocultan las compras y ventas por separado)
Así, en este indicador, implementamos la espera de la carga de los datos de ticks para el instrumento de trabajo utilizando un temporizador, ya que los ticks pueden requerir recursos significativos. En la próxima sección analizaremos los indicadores multidivisa que trabajan a nivel de cotización, y por lo tanto una petición asíncrona simplificada para actualizar el gráfico usando ChartSetSymbolPeriod será suficiente para ellos. Más adelante tendremos que implementar otro tipo de espera para asegurarnos de que las series temporales de otro indicador están listas.