Evento indicador principal: OnCalculate

La función OnCalculate es el principal punto de entrada al código de indicador MQL5. Se llama cada vez que se produce el evento OnCalculate, que se genera cuando cambian los datos del precio. Por ejemplo, puede ocurrir cuando llega un nuevo tick para un símbolo o cuando cambian los precios antiguos (rellenando un hueco en el historial o descargando los datos que faltan del servidor).

Existen dos variantes de la función, que difieren en el material de partida para los cálculos:

  • Completa: proporciona un conjunto de series temporales de precios estándar en los parámetros (precios OHLC, volúmenes, diferenciales).
  • Reducida: para una serie temporal arbitraria (no necesariamente estándar).

Un indicador debe utilizar sólo una de las dos opciones, mientras que es imposible combinarlas en un indicador.

En el caso de utilizar la forma reducida de OnCalculate, al colocar un indicador en un gráfico, aparece una pestaña adicional en su cuadro de diálogo de propiedades. Proporciona una lista desplegable Apply to, en la que debe seleccionar la serie temporal inicial en base a la cual se calculará el indicador. Por defecto, si no se selecciona ninguna serie temporal, el cálculo se basa en los valores de precios de Close.

Selección de la serie temporal inicial para el indicador con la forma abreviada OnCalculate

Selección de la serie temporal inicial para el indicador con la forma abreviada OnCalculate

La lista siempre ofrece tipos estándar de precios, pero si hay otros indicadores en el gráfico, esta configuración le permite seleccionar uno de ellos como fuente de datos para otro indicador, construyendo así una cadena de procesamiento a partir de indicadores. Intentaremos construir un indicador a partir de otro en la sección Omitir dibujo en barras iniciales. Cuando se utiliza el formulario completo, esta opción no está disponible.

Está prohibido aplicar indicadores a los siguientes indicadores integrados: Fractales, Gator, Ichimoku y SAR Parabólico.

La forma abreviada de OnCalculate tiene el siguiente prototipo.

int OnCalculate(const int rates_total, const int prev_calculated, const int begin,
const double &data[])

El array data contiene los datos iniciales para el cálculo. Puede ser una de las series temporales de precios o un búfer calculado de otro indicador. El parámetro rates_total especifica el tamaño del array data. ArraySize(data) o las llamadas a iBars(NULL, 0) deberían dar el mismo valor que rates_total.

El parámetro prev_calculated está diseñado para recalcular efectivamente el indicador en un pequeño número de barras nuevas (normalmente en una, la última), en lugar de un cálculo completo en todas las barras. El valor prev_calculated es igual al resultado de la función OnCalculate devuelto al tiempo de ejecución desde una llamada de función anterior. Por ejemplo, si al recibir el siguiente tick, el indicador ha calculado la fórmula para todas las barras, debería devolver el valor de rates_totalA desde OnCalculate (aquí el índice A significa el momento inicial). A continuación, en el siguiente tick, al recibir el evento OnCalculate, el terminal fijará prev_calculated al valor anterior rates_totalA. No obstante, el número de barras durante este tiempo puede haber cambiado ya, y el nuevo valor rates_total aumentará; llamémoslo rates_totalB. Por lo tanto, sólo las barras de prev_calculated (alias rates_totalA) hasta rates_totalB se calculará.

No obstante, la situación más común es cuando los nuevos ticks encajan en la barra cero actual, es decir, rates_total no cambia, y por lo tanto en la mayoría de las llamadas a OnCalculate, tenemos la igualdad prev_calculated == rates_total. ¿Tenemos que recalcular algo en este caso? Depende de la naturaleza de los cálculos. Por ejemplo, si el indicador se calcula a partir de los precios de apertura de la barra, que no cambian, no tiene sentido recalcular nada. Sin embargo, si el indicador utiliza el precio de cierre (de hecho, el precio del último tick conocido) o cualquier otro precio de resumen que dependa de Close, entonces siempre deberá recalcularse la última barra.

La primera vez que se llama a la función OnCalculate, el valor de prev_calculated es igual a 0.

Si desde la última llamada a la función OnCalculate, los datos de precios han cambiado (por ejemplo, se ha cargado un historial más profundo o se han rellenado huecos), entonces el valor del parámetro prev_calculated también será puesto a 0 por el terminal. Así, el indicador recibirá una señal para un recálculo completo sobre todo el historial disponible.

Si la función OnCalculate devuelve un valor nulo, el indicador no se dibuja, y los nombres y valores de sus búferes en Data window se ocultarán.

Tenga en cuenta que la devolución del número completo de barras rates_total es la única forma estándar de indicar al terminal y a otros programas MQL que utilizarán el indicador que sus datos están listos. Aunque un indicador esté diseñado para calcular y mostrar sólo una cantidad limitada de datos, debería devolver rates_total.

La dirección de indexación del array data puede seleccionarse llamando a ArraySetAsSeries (el valor por defecto es false, que puede verificarse llamando a ArrayGetAsSeries). Al mismo tiempo, si aplicamos la función ArrayIsSeries al array, devolverá true. Esto significa que este array es un array interno, gestionado por el terminal. El indicador no puede modificarlo de ninguna manera, sino sólo leerlo, sobre todo porque hay un modificador const en la descripción del parámetro.

El parámetro begin informa del número de valores iniciales del array data que deben excluirse del cálculo. El parámetro lo establece el sistema cuando nuestro indicador es configurado por el usuario de forma que recibe data de otro indicador (ver imagen superior). Por ejemplo, si el indicador de fuente de datos seleccionado calcula un período de media móvil N, entonces las primeras barras N - 1, por definición, no contienen datos fuente, ya que allí es imposible calcular la media sobre las barras N. Si el desarrollador ha establecido una propiedad especial en este indicador fuente, se nos pasará correctamente en el parámetro begin. Pronto comprobaremos este aspecto en la práctica (véase la sección Omitir dibujo en barras iniciales).

Vamos a intentar crear un indicador vacío con una forma abreviada de OnCalculate. Aún no podrá hacer nada, pero servirá de preparación para futuros experimentos. El archivo original IndStub.mq5 se encuentra en la carpeta MQL5/Indicators/MQL5Book/p5/. Para asegurarnos de que el indicador funciona, vamos a añadir lo siguiente a OnCalculate: la posibilidad de mostrar los valores prev_calculated y rates_total en el registro y de contar el número de llamadas a la función.

int OnCalculate(const int rates_total
                const int prev_calculated
                const int begin
                const double &data[])
{
   static int count = 0;
   ++count;
   // compare the number of bars on the previous call and the current one
   if(prev_calculated != rates_total)
   {
      // signal only if there is a difference
      PrintFormat("calculated=%d rates=%d; %d ticks"
         prev_calculatedrates_totalcount);
   }
   return rates_total// return the number of processed bars
}

La condición para la desigualdad de prev_calculated y rates_total garantiza que el mensaje sólo aparecerá la primera vez que se coloque el indicador en el gráfico, y también cuando aparezcan nuevas barras. Todos los ticks que lleguen durante la formación de la barra actual no cambiarán el número de barras, y por lo tanto prev_calculated y rates_total serán iguales. Sin embargo, contaremos el número total de ticks en la variable count.

El resto de parámetros siguen sin funcionar, pero poco a poco iremos utilizando todas las posibilidades.

Este código fuente se compila correctamente, pero genera dos advertencias.

no indicator window property is defined, indicator_chart_window is applied
no indicator plot defined for indicator

Indican la ausencia de algunas directivas #property que, aunque no son obligatorias, establecen las propiedades básicas del indicador. En concreto, la primera advertencia dice que no se ha seleccionado ningún método de vinculación para el indicador: la ventana principal o la subventana, y por lo tanto se utilizará por defecto la ventana principal del gráfico. La segunda advertencia está relacionada con el hecho de que no hemos establecido el número de gráficos que se van a mostrar. Como ya se ha mencionado, algunos indicadores están diseñados a propósito sin búferes porque se han diseñado para realizar otras acciones, pero en nuestro caso se trata de un recordatorio para añadir una parte visual más adelante.

Nos ocuparemos de la eliminación de las advertencias en un par de párrafos, pero por ahora, lanzaremos el indicador en el gráfico EURUSD,M1. Utilizamos el marco temporal M1 porque así podemos ver rápidamente la formación de nuevas barras y la aparición de mensajes en el registro.

calculated=0 rates=10002; 1 ticks
calculated=10002 rates=10003; 30 ticks
calculated=10003 rates=10004; 90 ticks
calculated=10004 rates=10005; 167 ticks
calculated=10005 rates=10006; 240 ticks

Así, vemos que se llama al manejador OnCalculate como se esperaba, y se pueden realizar cálculos en él, tanto en cada tick como en barras. El indicador puede eliminarse del gráfico llamando al cuadro de diálogo Indicator List desde el menú contextual del gráfico: seleccione el indicador deseado y pulse Delete.

Ahora volvamos a otro prototipo de la función OnCalculate. Hemos visto probar en la práctica una versión reducida, pero podríamos aplicar exactamente el mismo espacio en blanco para el formulario completo.

El formulario completo está diseñado para el cálculo basado en series temporales de precios estándar y tiene el siguiente prototipo:

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
const double &open[], const double &high[], const double &low[], const double &close[],
const long &tick_volume[], const long &volume[], const int &spread[])

Los parámetros rates_total y prev_calculated tienen el mismo significado que en el formulario simple de OnCalculate: rates_total establece el tamaño de la serie temporal transmitida (todos los arrays tienen la misma longitud, ya que este es el número total de barras del gráfico), y prev_calculated contiene el número de barras procesadas en la llamada anterior (es decir, el valor que la función OnCalculate devolvió anteriormente al terminal mediante la sentencia return).

Los arrays open, high, low y close contienen los precios relevantes para las barras del gráfico actual: las series temporales del símbolo de trabajo y el marco temporal. El array time contiene la hora de apertura de cada barra, y tick_volume y volume contienen los volúmenes de trading (tick y exchange) por barra.

En el capítulo anterior estudiamos las series temporales con los tipos de precio y volumen estándar proporcionados por el terminal para los programas MQL mediante un conjunto de funciones. Por lo tanto, para la conveniencia de calcular el indicador, estas series temporales se pasan al manejador OnCalculate directamente por referencia como arrays. Esto elimina la necesidad de llamar a estas funciones y copiar (duplicar) cotizaciones en arrays internos. Por supuesto, esta técnica sólo es adecuada para aquellos indicadores que se calculan en una combinación de un símbolo de trabajo y un marco temporal que coincide con el gráfico actual. Sin embargo, MQL5 permite crear indicadores multidivisa y de marco temporal múltiple, así como indicadores para símbolos y periodos distintos a los del gráfico actual. En todos estos casos ya es imposible prescindir de las funciones de acceso a las series temporales. Un poco más adelante veremos cómo se hace.

Si comprobamos para todos los arrays pasados si pertenecen al terminal utilizando ArrayIsSeries, esta función devolverá true. Todos los arrays son de sólo lectura. El modificador const en la descripción del parámetro también lo subraya.

Elija entre la forma completa y la reducida en función de los datos que necesite el algoritmo de cálculo. Por ejemplo, para suavizar un array utilizando el algoritmo de media móvil, sólo se requiere un array de entrada y, por lo tanto, el indicador puede construirse para cualquier tipo de precio que el usuario elija. Sin embargo, los indicadores conocidos ParabolicSAR o ZigZag exigen los precios High y Low, y por lo tanto, deben utilizar la versión completa de OnCalculate. En las siguientes secciones veremos ejemplos de indicadores tanto para la versión simple de OnCalculate, como para la versión completa.