- Métodos de almacenamiento de la información: texto y binario
- Escritura y lectura de archivos en modo simplificado
- Abrir y cerrar archivos
- Gestión de descriptores de archivo
- Seleccionar una codificación para el modo texto
- Escritura y lectura de arrays
- Escritura y lectura de estructuras (archivos binarios)
- Escritura y lectura de variables (archivos binarios)
- Escritura y lectura de variables (archivos de texto)
- Gestión de la posición en un expediente
- Obtención de las propiedades de un archivo
- Forzar escritura de caché en disco
- Eliminación de un archivo y comprobación de su existencia
- Copia y desplazamiento de archivos
- Búsqueda de archivos y carpetas
- Trabajar con carpetas
- Cuadro de diálogo de selección de archivos o carpetas
Forzar escritura de caché en disco
La escritura y lectura de archivos en MQL5 se realiza en la caché, lo que significa que se mantiene un cierto búfer en memoria para los datos y ello aumenta la eficacia del trabajo. Así, los datos transferidos mediante llamadas a funciones durante la escritura llegan al búfer de salida, y sólo después de que esté lleno, tiene lugar la escritura física en el disco. Al leer, por el contrario, se leen más datos del disco en el búfer que los que el programa solicitó mediante funciones (si no es el final del archivo), y las operaciones de lectura posteriores (que son muy probables) son más rápidas.
El almacenamiento en caché es una tecnología estándar utilizada en la mayoría de las aplicaciones y al nivel del propio sistema operativo. No obstante, además de sus pros, el almacenamiento en caché también tiene sus contras.
En concreto, si los archivos se utilizan como medio de intercambio de datos entre programas, el retraso en la escritura puede ralentizar considerablemente la comunicación y hacerla menos predecible, ya que el tamaño del búfer puede ser bastante grande y la frecuencia de su «volcado» al disco puede ajustarse según algunos algoritmos.
Por ejemplo, en MetaTrader 5 existe toda una categoría de programas MQL para copiar señales de trading de una instancia del terminal a otra. Suelen utilizar archivos para transferir información, y para ellos es muy importante que el almacenamiento en caché no ralentice las cosas. Para este caso, MQL5 proporciona la función FileFlush.
void FileFlush(int handle)
La función realiza un vaciado forzado a disco de todos los datos que quedan en el búfer de E/S del archivo con el descriptor handle.
Si no utiliza esta función, entonces parte de los datos «enviados» desde el programa pueden, en el peor de los casos, llegar al disco sólo cuando se cierra el archivo.
Esta función ofrece mayores garantías de seguridad de los datos valiosos en caso de imprevistos (como un bloqueo del sistema operativo o del programa). No obstante, por otro lado, no se recomiendan las llamadas frecuentes a FileFlush durante la grabación masiva, ya que pueden afectar negativamente al rendimiento.
Si el archivo se abre en modo mixto, simultáneamente para escritura y lectura, se debe llamar a la función FileFlush entre lecturas y escrituras en el archivo.
Como ejemplo, analicemos el script FileFlush.mq5, en el que implementamos dos modos que simulan el funcionamiento de la copiadora de transacciones. Tendremos que ejecutar dos instancias del script en gráficos diferentes, con una de ellas como emisora de datos y la otra como receptora.
El script tiene dos parámetros de entrada: EnableFlashing permite comparar las acciones de los programas utilizando la función FileFlush y sin ella, y UseCommonFolder indica la necesidad de crear un archivo que actúe como medio de transferencia de datos, a elegir entre hacerlo en la carpeta de la instancia actual del terminal o en una carpeta compartida (en este último caso, se puede probar la transferencia de datos entre diferentes terminales).
#property script_show_inputs
|
Recuerde que, para que aparezca un diálogo con variables de entrada cuando se lanza el script, debe establecer adicionalmente la propiedad script_show_inputs.
El nombre del archivo de tránsito se especifica en la variable dataport. La opción UseCommonFolder controla la bandera FILE_COMMON añadida al conjunto de conmutadores de modo para archivos abiertos en la función File Open.
const string dataport = "MQL5Book/dataport";
|
La función principal OnStart consta en realidad de dos partes: la configuración del archivo abierto y un bucle que envía o recibe datos periódicamente.
Necesitaremos ejecutar dos instancias del script, y cada una tendrá su propio descriptor de archivo apuntando al mismo archivo en disco pero abierto en diferentes modos.
void OnStart()
|
Al principio, estamos intentando abrir el archivo en modo FILE_WRITE, sin compartir el permiso de escritura (FILE_SHARE_WRITE), por lo que la primera instancia del script en ejecución capturará el archivo e impedirá que la segunda trabaje en modo escritura. La segunda instancia obtendrá un error e INVALID_HANDLE después de la primera llamada a FileOpen e intentará abrir el archivo en modo lectura (FILE_READ) con la segunda llamada a FileOpen utilizando la bandera de escritura paralela FILE_SHARE_WRITE lo que, idealmente, debería funcionar. A continuación, la variable modeWriter se establecerá en false para indicar la función real del script.
El bucle operativo principal tiene la siguiente estructura:
while(!IsStopped())
|
El bucle se ejecutará hasta que el usuario elimine manualmente el script del gráfico: esto se indicará mediante la función IsStopped. Dentro del bucle, la acción se activa cada 5 segundos llamando a la función Sleep, que «congela» el programa durante el número de milisegundos especificado (5000 en este caso). Esto se hace para facilitar el análisis de los cambios en curso y evitar registros de estado demasiado frecuentes. En un programa real sin registros detallados, puede enviar datos cada 100 milisegundos o incluso con mayor frecuencia.
Los datos transmitidos incluirán la hora actual (un valor datetime, 8 bytes). En la primera rama de la instrucción if(modeWriter), donde se escribe el archivo, llamamos a FileWriteLong con la última cuenta (obtenida de la función TimeLocal), aumentamos el contador de operaciones en 1 (count++) y enviamos el estado actual al registro.
long temp = TimeLocal(); // get the current local time datetime
|
Es importante señalar que la llamada a la función FileFlush después de cada entrada sólo se realiza si el parámetro de entrada EnableFlashing se establece en true.
En la segunda rama del operador if, en la que leemos los datos, primero reiniciamos la bandera de error interno llamando a ResetLastError. Esto es necesario porque vamos a leer los datos del archivo mientras haya datos. Cuando no haya más datos para leer, el programa obtendrá un código de error específico 5015 (ERR_FILE_READERROR).
Dado que los temporizadores integrados en MQL5, incluida la función Sleep, tienen una precisión limitada (aproximadamente 10 ms), no podemos excluir que se produjeran dos escrituras consecutivas entre dos intentos consecutivos de leer un archivo. Por ejemplo, una lectura se produjo a las 10:00:00'200 y la segunda, a las 10:00:05'210 (en la notación "hours:minutes:seconds'milliseconds"). En este caso se produjeron dos grabaciones en paralelo: una a las 10:00:00'205 y la segunda a las 10:00:05'205, y ambas cayeron en el periodo mencionado. Esta situación es poco probable pero posible. Incluso con intervalos de tiempo absolutamente precisos, el sistema en tiempo ejecución de MQL5 puede verse obligado a elegir entre dos scripts en ejecución (cuál invocar antes que el otro) si el número total de programas es grande y no hay suficientes núcleos de procesador para todos ellos.
MQL5 proporciona temporizadores de alta precisión (hasta microsegundos), pero esto no es crítico para la tarea actual.
El bucle anidado es necesario por otra razón más. Inmediatamente después de lanzar el script como «receptor» de datos, debe procesar todos los registros del archivo que se hayan acumulado desde el lanzamiento del «emisor» (es poco probable que ambos scripts puedan lanzarse simultáneamente). Probablemente alguien preferiría un algoritmo diferente: omitir todos los registros «antiguos» y registrar sólo los nuevos. Esto puede hacerse, pero aquí se aplica la opción «sin pérdidas».
ResetLastError();
|
Tenga en cuenta lo siguiente: los metadatos sobre el archivo abierto para lectura, como su tamaño, devueltos por la función FileSize (véase Obtención de las propiedades de un archivo) no cambian después de abrir el archivo. Si más tarde otro programa añade algo al archivo que abrimos para lectura, la longitud «detectable» del mismo no se actualizará aunque llamemos a FileFlash para el descriptor de lectura. Sería posible cerrar y volver a abrir el archivo (antes de cada lectura, pero esto no es eficaz), ya que entonces aparecería la nueva longitud para el nuevo descriptor. Pero prescindiremos de él, con la ayuda de otro truco.
El truco consiste en seguir leyendo datos utilizando funciones de lectura (en nuestro caso FileReadLong) mientras devuelvan datos sin errores. Es importante no utilizar otras funciones que operen sobre metadatos. En concreto, debido al hecho de que el fin de archivo de sólo lectura permanece constante, la comprobación con la función FileIsEnding (véase Control de posición dentro de un archivo) dará true en la posición antigua, a pesar de la posible reposición del archivo desde otro proceso. Además, un intento de mover el puntero del archivo interno hasta el final (FileSeek(handle, 0, SEEK_END); véase para la función FileSeek la misma sección) no saltará al final real de los datos, sino a la posición obsoleta en la que se encontraba el final en el momento de la apertura.
La función nos indica la posición real dentro del archivo FileTell (véase la misma sección). A medida que se añada información al archivo desde otra instancia del script y se lea en este bucle, el puntero se moverá cada vez más a la derecha, superando, por extraño que sea, FileSize. Para una demostración visual de cómo el puntero se mueve más allá del tamaño del archivo, vamos a guardar sus valores antes y después de llamar a FileReadLong, y luego la salida de los valores junto con el tamaño del registro.
Cuando la lectura con FileReadLong genere algún error, el bucle interno se romperá. La salida del bucle normal implica el error 5015 (ERR_FILE_READERROR). En concreto, se produce cuando no hay datos disponibles para la lectura en la posición actual del archivo.
Los últimos datos leídos con éxito se envían al registro, y es fácil compararlos con lo que el script remitente produce allí.
Vamos a ejecutar un nuevo script dos veces. Para distinguir entre sus copias, lo haremos en los gráficos de diferentes instrumentos.
Al ejecutar ambos scripts, es importante observar el mismo valor del parámetro UseCommonFolder. Dejémoslo en nuestras pruebas igual a false, ya que lo haremos todo en un solo terminal. Se sugiere la transferencia de datos entre terminales diferentes con UseCommonFolder configurado en true para realizar pruebas independientes.
En primer lugar, vamos a ejecutar la primera instancia en el gráfico EURUSD,H1, dejando todos los ajustes por defecto, incluido EnableFlashing=false. A continuación, ejecutaremos la segunda instancia en el gráfico XAUUSD,H1 (también con la configuración predeterminada). El registro será el siguiente (su tiempo será diferente):
(EURUSD,H1) * (EURUSD,H1) FileOpen(dataport,FILE_BIN|FILE_WRITE|FILE_SHARE_READ|flag)=1 / ok (EURUSD,H1) Written[1]: 1629652995 (XAUUSD,H1) * (XAUUSD,H1) FileOpen(dataport,FILE_BIN|FILE_WRITE|FILE_SHARE_READ|flag)=-1 / CANNOT_OPEN_FILE(5004) (XAUUSD,H1) FileOpen(dataport,FILE_BIN|FILE_READ|FILE_SHARE_WRITE|FILE_SHARE_READ|flag)=1 / ok (EURUSD,H1) Written[2]: 1629653000 (EURUSD,H1) Written[3]: 1629653005 (EURUSD,H1) Written[4]: 1629653010 (EURUSD,H1) Written[5]: 1629653015 |
El remitente abrió correctamente el archivo para escritura y comenzó a enviar datos cada 5 segundos, según las líneas con la palabra «Escrito» y los valores crecientes. Menos de 5 segundos después de que se iniciara el emisor, también se inició el receptor. Dio un mensaje de error porque no podía abrir el archivo para escribir. Pero luego abrió con éxito el archivo para su lectura. Sin embargo, no hay constancia de que haya podido encontrar en el archivo los datos transmitidos. Los datos se quedaban «colgados» en la caché del remitente.
Vamos a detener ambos scripts y a ejecutarlos de nuevo en la misma secuencia: primero, ejecutamos el emisor en EURUSD, y luego el receptor en XAUUSD. Pero esta vez especificaremos EnableFlashing=true para el remitente.
Esto es lo que ocurre en el registro:
(EURUSD,H1) * (EURUSD,H1) FileOpen(dataport,FILE_BIN|FILE_WRITE|FILE_SHARE_READ|flag)=1 / ok (EURUSD,H1) Written[1]: 1629653638 (XAUUSD,H1) * (XAUUSD,H1) FileOpen(dataport,FILE_BIN|FILE_WRITE|FILE_SHARE_READ|flag)=-1 / CANNOT_OPEN_FILE(5004) (XAUUSD,H1) FileOpen(dataport,FILE_BIN|FILE_READ|FILE_SHARE_WRITE|FILE_SHARE_READ|flag)=1 / ok (XAUUSD,H1) Read[1]: 1629653638 (size=8, before=0(false), after=8) (EURUSD,H1) Written[2]: 1629653643 (XAUUSD,H1) Read[2]: 1629653643 (size=8, before=8(true), after=16) (EURUSD,H1) Written[3]: 1629653648 (XAUUSD,H1) Read[3]: 1629653648 (size=8, before=16(true), after=24) (EURUSD,H1) Written[4]: 1629653653 (XAUUSD,H1) Read[4]: 1629653653 (size=8, before=24(true), after=32) (EURUSD,H1) Written[5]: 1629653658 |
El mismo archivo se abre de nuevo con éxito en diferentes modos en ambos scripts, pero esta vez los valores escritos son leídos regularmente por el receptor.
Es interesante observar que antes de cada siguiente lectura de datos, excepto la primera, la función FileIsEnding devuelve true (que aparece en la misma cadena que los datos recibidos, entre paréntesis después de la cadena «antes»). Así, hay una señal de que estamos al final del archivo, pero entonces FileReadLong lee con éxito un valor supuestamente fuera del límite del archivo y desplaza la posición hacia la derecha. Por ejemplo, la entrada «size=8, before=8(true), after=16» significa que el tamaño del archivo se declara al programa MQL como 8, el puntero actual antes de la llamada a FileReadLong también es igual a 8 y el signo de fin de archivo se habilita. Después de llamar con éxito a FileReadLong, el puntero se mueve a 16. Sin embargo, en la siguiente iteración y en todas las demás, volvemos a ver «size=8», y el puntero se desplaza gradualmente cada vez más lejos del archivo.
Dado que la escritura en el emisor y la lectura en el receptor se producen una vez cada 5 segundos, en función de sus fases de desfase de bucle, podemos observar el efecto de un retraso diferente entre las dos operaciones, de hasta casi 5 segundos en el peor de los casos. Sin embargo, esto no significa que el vaciado de la caché sea tan lento. De hecho, es un proceso casi instantáneo. Para garantizar una detección de cambios más rápida, puede reducir el periodo de espera en los bucles (tenga en cuenta que esta prueba, si el retraso es demasiado corto, llenará rápidamente el registro: a diferencia de un programa real, aquí siempre se generan nuevos datos, ya que se trata de la hora actual del emisor al segundo más cercano).
Por cierto: puede ejecutar varios destinatarios, a diferencia del remitente, que debe ser sólo uno. El siguiente registro muestra el funcionamiento de un emisor en EURUSD y de dos receptores en los gráficos XAUUSD y USDRUB.
(EURUSD,H1) * (EURUSD,H1) FileOpen(dataport,FILE_BIN|FILE_WRITE|FILE_SHARE_READ|flag)=1 / ok (EURUSD,H1) Written[1]: 1629671658 (XAUUSD,H1) * (XAUUSD,H1) FileOpen(dataport,FILE_BIN|FILE_WRITE|FILE_SHARE_READ|flag)=-1 / CANNOT_OPEN_FILE(5004) (XAUUSD,H1) FileOpen(dataport,FILE_BIN|FILE_READ|FILE_SHARE_WRITE|FILE_SHARE_READ|flag)=1 / ok (XAUUSD,H1) Read[1]: 1629671658 (size=8, before=0(false), after=8) (EURUSD,H1) Written[2]: 1629671663 (USDRUB,H1) * (USDRUB,H1) FileOpen(dataport,FILE_BIN|FILE_WRITE|FILE_SHARE_READ|flag)=-1 / CANNOT_OPEN_FILE(5004) (USDRUB,H1) FileOpen(dataport,FILE_BIN|FILE_READ|FILE_SHARE_WRITE|FILE_SHARE_READ|flag)=1 / ok (USDRUB,H1) Read[1]: 1629671658 (size=16, before=0(false), after=8) (USDRUB,H1) Read[2]: 1629671663 (size=16, before=8(false), after=16) (XAUUSD,H1) Read[2]: 1629671663 (size=8, before=8(true), after=16) (EURUSD,H1) Written[3]: 1629671668 (USDRUB,H1) Read[3]: 1629671668 (size=16, before=16(true), after=24) (XAUUSD,H1) Read[3]: 1629671668 (size=8, before=16(true), after=24) (EURUSD,H1) Written[4]: 1629671673 (USDRUB,H1) Read[4]: 1629671673 (size=16, before=24(true), after=32) (XAUUSD,H1) Read[4]: 1629671673 (size=8, before=24(true), after=32) (EURUSD,H1) Written[5]: 1629671678 |
Cuando se lanzó el tercer script en USDRUB, ya había dos registros de 8 bytes en el archivo, por lo que el bucle interno realizó inmediatamente dos iteraciones desde FileReadLong, y el tamaño del archivo «parece» ser igual a 16.