Visión general de las funciones Copy para obtener arrays de cotizaciones

La API de MQL5 contiene varias funciones para leer series temporales de cotizaciones en arrays. Sus nombres figuran en la siguiente tabla.

Función

Acción

CopyRates

Obtener el historial de cotizaciones en un array de estructuras MqlRates

CopyTime

Obtener el histórico de horarios de apertura de barras en un array del tipo datetime

CopyOpen

Obtener el histórico de precios de apertura de barras en un array del tipo double

CopyHigh

Obtener el historial de precios máximos de barra en un array del tipo double

CopyLow

Obtener el historial de precios de barras bajas en un array del tipo double

CopyClose

Obtener el histórico de precios de cierre de barras en un array del tipo double

CopyTickVolume

Obtener el historial de volúmenes de ticks en un array del tipo long

CopyRealVolume

Obtener el histórico de volúmenes de intercambio en un array del tipo long

CopySpread

Obtener el historial de diferenciales en un array del tipo int

Todas las funciones toman como los dos primeros parámetros el nombre del símbolo y el período deseados, que pueden representarse condicionalmente mediante el siguiente pseudocódigo:

int Copy***(const string symbolENUM_TIMEFRAMES timeframe, ...)

Además, todas las funciones tienen tres variantes del prototipo, que difieren en la forma de establecer el rango solicitado:

  • Índice inicial de barras y número de barras: Copy***(..., int offset, int count, ...)
  • Hora de inicio del intervalo y número de barras: Copy***(..., datetime start, int count, ...)
  • Horas de inicio y fin del intervalo: Copy***(..., datetime start, datetime stop, ...)

Al mismo tiempo, la notación de parámetros implica que los datos solicitados tienen una dirección de indexación como en una serie temporal, es decir, la posición offset con índice 0 almacena los datos de la barra incompleta actual, y el aumento de índices corresponde a moverse más profundamente en el historial de precios. Debido a ello, en particular a la segunda opción, el número indicado de barras count contará hacia atrás desde el inicio del intervalo offset, es decir, en el sentido de disminución del tiempo.

La tercera opción proporciona flexibilidad adicional: no importa en qué orden se especifiquen las fechas de inicio y fin (start/stop), ya que las funciones devolverán en cualquier caso datos en el intervalo que va de la fecha menor a la mayor. Las barras adecuadas se seleccionan de forma que su tiempo de apertura se encuentre entre los recuentos de tiempo start/stop o sea igual a uno de ellos, es decir, se considera el rango [start; stop] incluyendo los límites.

El desarrollador determina qué opción de función elegir en función de lo que sea más importante: obtener un número garantizado de elementos (por ejemplo, para algoritmos de aprendizaje automático) o cubrir un intervalo de fechas específico (por ejemplo, con un comportamiento uniforme predeterminado del mercado).

La precisión de representación del tiempo en el tipo datetime es de 1 segundo. Los valores start/stop no tienen que redondearse al tamaño del punto. Por ejemplo, el intervalo de 14:59 a 16:01 le permitirá seleccionar dos barras en el marco temporal H1 para las 15:00 y las 16:00. Un intervalo degenerado con etiquetas iguales y redondeadas, por ejemplo, 15:00 en cotizaciones H1, corresponde a una barra.

Puede solicitar barras en el marco temporal diario aunque haya horas/minutos/segundos distintos de cero en los parámetros de inicio/parada (a pesar de que las etiquetas de las barras en el marco temporal D1 tengan la hora 00:00). En este caso, sólo aquellas barras D1 que tengan una hora de apertura posterior al mínimo de start/stop y hasta el máximo de start/stop (la igualdad con las etiquetas de las barras diarias es imposible en este caso, ya que el tiempo requerido contiene horas/minutos/segundos). Por ejemplo, entre D'2021.09.01 12:00' y D'2021.09.03 07:00', hay dos horarios de apertura de barras D1: D'2021.09.02' y D'2021.09.03'. Estas barras se incluirán en el resultado. La barra D'2021.09.01' tiene una hora de apertura de 00:00, que es anterior al inicio del intervalo, por lo que se descarta. La barra D'2021.09.03' se incluye en el resultado, a pesar de que sólo 7 horas de la mañana de ese día entraron en el intervalo. En cambio, una solicitud de varias horas dentro de un día, por ejemplo, entre D'2021.09.01 12:00' y D'2021.09.01 15:00', no cubrirá una única barra del día (la hora de apertura de la barra D'2021.09.01' no entra en este intervalo), y por tanto el array receptor estará vacío.

La única diferencia entre todas las funciones de la tabla es el tipo de array que recibe los datos, que se pasa como último parámetro por referencia. Por ejemplo, la función CopyRates coloca los datos solicitados en un array de estructuras MqlRates, y la función CopyTime coloca los horarios de apertura de las barras en un array del tipo datetime, y así sucesivamente.

Así, los prototipos de funciones comunes pueden representarse de la siguiente manera:

int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, int offset, int count, type &result[])

int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, datetime start, int count, type &result[])

int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, datetime start, datetime stop, type &result[])

Aquí, el type coincide con cualquiera de los tipos MqlRates, datetime, double, long o int, dependiendo de la función específica.

Las funciones devuelven el número de elementos copiados en el array o -1 en caso de error. En concreto, obtendremos -1 si no hay datos en el servidor en el intervalo solicitado, o el intervalo está fuera del número máximo de barras del gráfico (TerminalInfoInteger(TERMINAL_MAXBARS)).

Es importante señalar que, en el array receptor, los datos recibidos siempre se colocan físicamente en orden cronológico, del pasado al futuro. Así, si se utiliza la indexación estándar para el array receptor (es decir, la función ArraySetAsSeries), entonces el elemento del índice 0 será el más antiguo y el último el más reciente. Si la instrucción se ejecutó para el array ArraySetAsSeries(result, true), entonces la numeración se realizará en orden inverso, como en una serie temporal: el elemento 0º será el más nuevo del rango, y el último elemento será el más antiguo. Esto se ilustra en la siguiente figura.

Series temporales terminales y array receptor

Series temporales terminales y array receptor

Si tiene éxito, el número especificado de elementos de la propia serie temporal (interna) del terminal se copiará en el array de destino. Cuando se solicitan datos por intervalo de fechas (start/stop), el número de elementos del array resultante se determinará indirectamente, en función del contenido del historial de este intervalo. Por lo tanto, para copiar un número desconocido de valores, se recomienda utilizar arrays dinámicos: las funciones de copia asignan de forma independiente el tamaño necesario de los arrays de destino (el tamaño puede aumentar o disminuir).

Si necesita copiar un número conocido de elementos o lo hace con frecuencia, como cada vez que llama a OnTick en Asesores Expertos u OnCalculate en los indicadores, es mejor utilizar arrays distribuidos estáticamente. El hecho es que las operaciones de asignación de memoria para arrays dinámicos requieren tiempo adicional y pueden afectar al rendimiento, especialmente durante las pruebas y la optimización.

Se accede a las series temporales de forma diferente para los distintos tipos de programas MQL si los datos solicitados aún no están listos. Por ejemplo, en las funciones de indicadores, Copy personalizados devuelven inmediatamente un error, ya que los indicadores se ejecutan en el hilo de interfaz común del terminal y no pueden esperar a recibir datos (se supone que los indicadores solicitarán datos durante las siguientes llamadas de sus manejadores de eventos, y las series temporales ya se habrán descargado y construido para entonces). Además, en el capítulo dedicado a los indicadores, aprenderemos que para acceder a las cotizaciones del gráfico «nativo» sobre el que se sitúa el indicador, no es necesario utilizar las funciones Copy, ya que todas las series temporales se pasan automáticamente a través de los parámetros del array del manejador OnCalculate.

Cuando se accede desde Asesores Expertos y scripts se realizan varios intentos de recibir datos con una breve pausa (con una espera dentro de la función), que da tiempo a cargar y calcular las series temporales que faltan. La función devolverá la cantidad de datos que estarán listos para cuando expire este tiempo de espera, pero la carga del historial continuará, y la siguiente petición similar devolverá más datos.

En cualquier caso, debe estar preparado para que la función Copy devuelva un error en lugar de datos (existen diferentes motivos: fallo de conexión, falta de datos solicitados, carga del procesador si se solicitan muchas series temporales nuevas en paralelo): analice la causa del problema en el código (_LastError) y vuelva a intentarlo más tarde, corrija la configuración o informe al usuario.

La presencia de un símbolo en Market Watch no es una condición necesaria para solicitar series temporales utilizando las funciones de Copy; no obstante, para los símbolos incluidos en esta ventana, las consultas tienden a ejecutarse más rápidamente porque algunos datos ya han sido descargados del servidor y probablemente calculados para los periodos solicitados. Aprenderemos a añadir caracteres a Market Watch mediante programación en la sección Editar la lista de Observación de Mercado.

Para explicar el funcionamiento de las funciones en la práctica, veamos el script SeriesCopy.mq5. Contiene múltiples llamadas a la función CopyTime, que le permite ver visualmente cómo se correlacionan las marcas de tiempo y los números de barra.

El script define un array dinámico times para recibir datos. Todas las solicitudes se realizan para el símbolo «EURUSD» y el marco temporal H1.

void OnStart()
{
   datetime times[];

Para empezar, se solicitan 10 barras, desde el 5 de septiembre de 2021 hacia el pasado. Como ese día es domingo, las barras anteriores fueron el viernes 3 (véase el registro más abajo).

   PRTF(CopyTime("EURUSD"PERIOD_H1D'2021.09.05', 10times)); // 10 / ok
   ArrayPrint(times);
   /*
   [0] 2021.09.03 14:00 2021.09.03 15:00 2021.09.03 16:00 2021.09.03 17:00 2021.09.03 18:00
   [5] 2021.09.03 19:00 2021.09.03 20:00 2021.09.03 21:00 2021.09.03 22:00 2021.09.03 23:00
   */

La salida del array se realiza por defecto en orden cronológico (a pesar de que los parámetros de la función se establecen en el sistema de coordenadas inverso: como en una serie temporal). Cambiemos el orden de indexación en el array receptor y obtengamos una nueva salida.

   PRTF(ArraySetAsSeries(timestrue)); // true / ok
   ArrayPrint(times);
   /*
   [0] 2021.09.03 23:00 2021.09.03 22:00 2021.09.03 21:00 2021.09.03 20:00 2021.09.03 19:00
   [5] 2021.09.03 18:00 2021.09.03 17:00 2021.09.03 16:00 2021.09.03 15:00 2021.09.03 14:00
   */

Para los próximos experimentos, restableceremos el orden habitual.

   PRTF(ArraySetAsSeries(timesfalse)); // true / ok

Ahora vamos a solicitar un número indefinido de barras entre dos puntos temporales (el número es desconocido, porque las vacaciones pueden estar en el intervalo, por ejemplo). Lo haremos de dos maneras: en el primer caso, indicaremos el intervalo del futuro al pasado, y en el segundo, del pasado al futuro. Los resultados coinciden.

   //                                      FROM                 TO
   PRTF(CopyTime("EURUSD"PERIOD_H1D'2021.09.06 03:00', D'2021.09.05 03:00', times));
   ArrayPrint(times)  //                   FROM                 TO
   PRTF(CopyTime("EURUSD"PERIOD_H1D'2021.09.05 03:00', D'2021.09.06 03:00', times));
   ArrayPrint(times);
   /*
   CopyTime(EURUSD,PERIOD_H1,D'2021.09.06 03:00',D'2021.09.05 03:00',times)=4 / ok
   2021.09.06 00:00 2021.09.06 01:00 2021.09.06 02:00 2021.09.06 03:00
   CopyTime(EURUSD,PERIOD_H1,D'2021.09.05 03:00',D'2021.09.06 03:00',times)=4 / ok
   2021.09.06 00:00 2021.09.06 01:00 2021.09.06 02:00 2021.09.06 03:00
   */

Al imprimir los arrays podemos ver que son idénticos. Volvamos al modo de indexación de series temporales y discutamos una cuestión más.

   PRTF(ArraySetAsSeries(timestrue)); // true / ok
   ArrayPrint(times);
   // 2021.09.06 03:00 2021.09.06 02:00 2021.09.06 01:00 2021.09.06 00:00

Aunque las dos marcas de tiempo están separadas por 24 horas, lo que implica obtener 25 elementos en el array (recuerde que el principio y el final se procesan de forma inclusiva), el resultado sólo contiene 4 barras. El hecho es que el 5 de septiembre cae en domingo y, por lo tanto, de todo el intervalo, el trading se ha llevado a cabo únicamente en las horas matinales del día 6.

Observe también que el tamaño del array receptor se ha reducido automáticamente de 10 a 4 elementos.

Por último, solicitaremos 10 barras, a partir de la barra 100ª (los resultados obtenidos dependerán de su hora actual y del historial disponible).

   PRTF(CopyTime("EURUSD"PERIOD_H110010times)); // 10 / ok
   ArrayPrint(times);
   /*
   [0] 2021.10.04 19:00 2021.10.04 18:00 2021.10.04 17:00 2021.10.04 16:00 2021.10.04 15:00
   [5] 2021.10.04 14:00 2021.10.04 13:00 2021.10.04 12:00 2021.10.04 11:00 2021.10.04 10:00
   */
}

Debido a la indexación como en una serie temporal, el array se muestra en orden cronológico inverso.