- 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
Escritura y lectura de variables (archivos binarios)
Si una estructura contiene campos de tipos prohibidos para las estructuras simples (cadenas, arrays dinámicos, punteros), no será posible escribirla en un archivo ni leerla de un archivo mediante las funciones consideradas anteriormente. Lo mismo ocurre con los objetos de clase. Sin embargo, estas entidades suelen contener la mayor parte de los datos de los programas y también requieren guardar y restaurar su estado.
Con el ejemplo de la estructura de encabezado de la sección anterior se ha demostrado claramente que las cadenas (y otros tipos de longitud variable) pueden evitarse, pero en este caso hay que inventar implementaciones alternativas y más engorrosas de los algoritmos (por ejemplo, sustituir una cadena por un array de caracteres).
Para escribir y leer datos de complejidad arbitraria, MQL5 proporciona conjuntos de funciones de nivel inferior que operan sobre un único valor de un tipo determinado: double, float, int/uint, long/ulong o string. Todos los demás tipos integrados de MQL5 son equivalentes a enteros de diferentes tamaños: char/uchar es de 1 byte, short/ushort es de 2 bytes, color es de 4 bytes, las enumeraciones son de 4 bytes y datetime es de 8 bytes. Estas funciones pueden denominarse atómicas (es decir, indivisibles), porque las funciones de lectura y escritura en archivos a nivel de bits ya no existen.
Por supuesto, la escritura o lectura elemento por elemento también elimina la restricción de las operaciones de archivo con arrays dinámicos.
En cuanto a los punteros a objetos, siguiendo el espíritu del paradigma de la programación orientada a objetos podemos permitir que guarden y restauren objetos: basta con implementar en cada clase una interfaz (un conjunto de métodos) que se encargue de transferir contenidos importantes a archivos y viceversa, y utilizar funciones de bajo nivel. Entonces, si nos encontramos con un campo puntero a otro objeto como parte del objeto, simplemente delegamos en él el guardado o la lectura, y a su vez, él se ocupará de sus campos, entre los que puede haber otros punteros, y la delegación seguirá profundizando hasta cubrir todos los elementos.
Tenga en cuenta que en esta sección examinaremos las funciones atómicas para archivos binarios. Sus equivalentes para los archivos de texto se presentarán en la sección siguiente. Todas las funciones de esta sección devuelven el número de bytes escritos, o 0 en caso de error.
uint FileWriteDouble(int handle, double value)
uint FileWriteFloat(int handle, float value)
uint FileWriteLong(int handle, long value)
Las funciones escriben el valor del tipo correspondiente pasado en el parámetro value (double, float, long) en un archivo binario con el descriptor handle.
uint FileWriteInteger(int handle, int value, int size = INT_VALUE)
La función escribe el entero value en un archivo binario con el descriptor handle. El tamaño del valor en bytes se establece mediante el parámetro size y puede ser una de las constantes predefinidas: CHAR_VALUE (1), SHORT_VALUE (2), INT_VALUE (4, por defecto), que corresponden a los tipos char, short y int (con y sin signo).
La función admite un modo de escritura no documentado de un entero de 3 bytes. No se recomienda su uso.
El puntero del archivo se mueve por el número de bytes escritos (no por el tamaño de int).
uint FileWriteString(int handle, const string value, int length = -1)
La función escribe una cadena del parámetro value en un archivo binario con el descriptor handle. Puede especificar el número de caracteres para escribir el parámetro length. Si es inferior a la longitud de la cadena, sólo se incluirá en el archivo la parte especificada de la cadena. Si length es -1 o no se especifica, la cadena completa se transfiere al archivo sin el nulo terminal. Si length es mayor que la longitud de la cadena, los caracteres sobrantes se rellenan con ceros.
Tenga en cuenta que al escribir en un archivo abierto con la bandera FILE_UNICODE (o sin la bandera FILE_ANSI), la cadena se guarda en formato Unicode (cada carácter ocupa 2 bytes). Cuando se escribe en un archivo abierto con la bandera FILE_ANSI, cada carácter ocupa 1 byte (los caracteres de idiomas extranjeros pueden aparecer distorsionados).
La función FileWriteString también puede trabajar con archivos de texto. Este aspecto de su aplicación se describe en la siguiente sección.
double FileReadDouble(int handle)
float FileReadFloat(int handle)
long FileReadLong(int handle)
Las funciones leen un número del tipo apropiado, double, float o long, de un archivo binario con el descriptor especificado. Si es necesario, convierta el resultado a ulong (si se espera un unsigned long en el archivo en esa posición).
int FileReadInteger(int handle, int size = INT_VALUE)
La función lee un valor entero de un archivo binario con el descriptor handle. El tamaño del valor en bytes se especifica en el parámetro size.
Dado que el resultado de la función es del tipo int, debe convertirse explícitamente al tipo de destino requerido si es diferente de int (es decir, a uint, o short/ushort, o char/uchar). De lo contrario, obtendrá como mínimo una advertencia del compilador y como máximo, una pérdida de signo.
El hecho es que al leer CHAR_VALUE o SHORT_VALUE, el resultado por defecto es siempre positivo (es decir, corresponde a uchar y ushort, que «se ajustan» totalmente en int). En estos casos, si los números son realmente de los tipos uchar y ushort, las advertencias del compilador son puramente nominales, puesto que ya estamos seguros de que dentro del valor del tipo int sólo se rellenan 1 o 2 bytes bajos, y son sin signo. Esto ocurre sin distorsión.
No obstante, cuando se almacenan valores con signo (tipos char y short) en el archivo, la conversión se hace necesaria porque, sin ella, los valores negativos se convertirán en positivos inversos con la misma representación de bits (véase el apartado «Enteros con y sin signo» en la sección Conversiones aritméticas de tipo ).
En cualquier caso, es mejor evitar las advertencias mediante la conversión explícita de tipos.
La función admite el modo de lectura de enteros de 3 bytes. No se recomienda su uso.
El puntero del archivo se mueve por el número de bytes leídos (no por el tamaño int).
string FileReadString(int handle, int size = -1)
La función lee una cadena del tamaño especificado en caracteres de un archivo con el descriptor handle. El parámetro size debe establecerse cuando se trabaja con un archivo binario (el valor por defecto sólo es adecuado para archivos de texto que utilizan caracteres separadores). En caso contrario, la cadena no se lee (la función devuelve una cadena vacía) y el código de error interno _LastError es 5016 (FILE_BINSTRINGSIZE).
Por lo tanto, incluso en la fase de escritura de una cadena en un archivo binario, es necesario pensar en cómo se leerá la cadena. Hay tres opciones principales:
- Escribir cadenas con un carácter terminal nulo al final. En este caso, habrá que analizarlas carácter por carácter en un bucle y combinar los caracteres en una cadena hasta encontrar el 0.
- Escribir siempre una cadena de longitud fija (predefinida). La longitud debe elegirse con un margen para la mayoría de los escenarios, o de acuerdo con la especificación (términos de referencia, protocolo, etc.), pero esto no es económico y no garantiza al 100 % que alguna cadena rara no se acorte al escribir en un archivo.
- Escribir la longitud como un entero antes de la cadena.
La función FileReadString también puede trabajar con archivos de texto. Este aspecto de su aplicación se describe en la siguiente sección.
Observe también que si el parámetro size es 0 (lo que puede ocurrir durante algunos cálculos), la función no lee: el puntero del archivo permanece en el mismo lugar y la función devuelve una cadena vacía.
Como ejemplo para esta sección, mejoraremos el script FileStruct.mq5 de la sección anterior. El nuevo nombre del programa es FileAtomic.mq5.
La tarea sigue siendo la misma: guardar un número determinado de estructuras MqlRates truncadas con cotizaciones a un archivo binario. Pero ahora la estructura FileHeader se convertirá en una clase (y la firma de formato se almacenará en una cadena, no en un array de caracteres). Un encabezado de este tipo y un array de cotizaciones formarán parte de otra clase de control Candles, y ambas clases se heredarán de la interfaz Persistent para escribir objetos arbitrarios en un archivo y leer de un archivo.
Esta es la interfaz:
interface Persistent
|
En la clase FileHeader implementaremos el guardado y la comprobación de la firma de formato (vamos a cambiarla a «CANDLES/1.1») y los nombres del símbolo actual y el marco temporal del gráfico (más información sobre _Symbol y _Period).
La escritura se realiza en la implementación del método write heredado de la interfaz.
class FileHeader : public Persistent
|
La firma se escribe exactamente según su longitud, ya que la muestra se almacena en el objeto y la misma longitud se establecerá al leer.
Para el instrumento del gráfico actual, primero guardamos la longitud de su nombre en el archivo (1 byte es suficiente para longitudes de hasta 255), y sólo entonces guardamos la cadena en sí.
El nombre del marco temporal nunca supera los tres símbolos, si se excluye de él el prefijo constante «PERIOD_», por lo que se elige una longitud fija para esta cadena. El nombre del marco temporal sin prefijo se obtiene en la función auxiliar PeriodToString: se encuentra en un archivo de encabezado independiente Periods.mqh (ello se abordará con más detalle en la sección Símbolos y marcos temporales).
La lectura se realiza en el método read en orden inverso (por supuesto, se supone que la lectura se llevará a cabo en un objeto nuevo y diferente).
bool read(int handle) override
|
Si alguna de las propiedades (firma, símbolo, marco temporal) no coincide en el archivo y en el gráfico actual, la función devuelve false para indicar un error.
La transformación inversa del nombre del marco temporal en la enumeración ENUM_TIMEFRAMES la realiza la función StringToPeriod, también desde el archivo Periods.mqh.
La clase Candles principal para solicitar, guardar y leer el archivo de cotizaciones es la siguiente:
class Candles : public Persistent
|
Los campos son el encabezado del tipo FileHeader, el número de barras solicitado limit y un array que recibe las estructuras MqlRates de MetaTrader 5. El array se rellena en el constructor. En caso de error, el campo limit se pone a cero.
Al derivar de la interfaz Persistent, la clase Candles requiere la implementación de los métodos write y read. En el método write, primero ordenamos al objeto de encabezado que se guarde a sí mismo y, a continuación, añadimos al archivo el número de cotizaciones, el intervalo de fechas (como referencia) y el propio array.
bool write(int handle) override
|
La lectura se realiza en orden inverso:
bool read(int handle) override
|
En un programa real para archivar cotizaciones, la presencia de un rango de fechas permitiría construir su secuencia correcta a lo largo de una larga historia mediante los encabezados de los archivos y, en cierta medida, protegería contra el cambio arbitrario de nombre de los archivos.
Existe un sencillo método print para controlar el proceso:
void print() const
|
En la función principal del script, creamos dos objetos Candles, y utilizando uno de ellos, primero guardamos el archivo de cotizaciones y luego lo restauramos con la ayuda del otro. Los archivos se administran mediante el envoltorio FileHandle que ya conocemos (véase la sección Gestión de descriptores de archivos).
const string filename = "MQL5Book/atomic.raw";
|
Aquí hay un ejemplo de registros de datos iniciales para XAUUSD,H1:
FileOpen(filename,FILE_BIN|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ)=1 / ok CopyRates(_Symbol,_Period,0,limit,rates)=10 / ok FileWriteString(handle,signature,StringLen(signature))=11 / ok FileWriteInteger(handle,StringLen(_Symbol),CHAR_VALUE)=1 / ok FileWriteString(handle,_Symbol)=6 / ok FileWriteString(handle,PeriodToString(),3)=3 / ok FileWriteInteger(handle,limit)=4 / ok FileWriteLong(handle,rates[0].time)=8 / ok FileWriteLong(handle,rates[limit-1].time)=8 / ok [hora] [apertura] [máximo] [mínimo] [cierre] [volumen_tic] [diferencial] [volumen_real] [0] 2021.08.17 15:00:00 1791.40 1794.57 1788.04 1789.46 8157 5 0 [1] 2021.08.17 16:00:00 1789.46 1792.99 1786.69 1789.69 9285 5 0 [2] 2021.08.17 17:00:00 1789.76 1790.45 1780.95 1783.30 8165 5 0 [3] 2021.08.17 18:00:00 1783.30 1783.98 1780.53 1782.73 5114 5 0 [4] 2021.08.17 19:00:00 1782.69 1784.16 1782.09 1782.49 3586 6 0 [5] 2021.08.17 20:00:00 1782.49 1786.23 1782.17 1784.23 3515 5 0 [6] 2021.08.17 21:00:00 1784.20 1784.85 1782.73 1783.12 2627 6 0 [7] 2021.08.17 22:00:00 1783.10 1785.52 1782.37 1785.16 2114 5 0 [8] 2021.08.17 23:00:00 1785.11 1785.84 1784.71 1785.80 922 5 0 [9] 2021.08.18 01:00:00 1786.30 1786.34 1786.18 1786.20 13 5 0 |
Y aquí tiene un ejemplo de los datos recuperados (recuerde que las estructuras se guardan truncadas según nuestra tarea técnica hipotética):
FileOpen(filename,FILE_BIN|FILE_READ|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE)=2 / ok FileReadString(handle,StringLen(signature))=CANDLES/1.1 / ok FileReadInteger(handle,CHAR_VALUE)=6 / ok FileReadString(handle,len)=XAUUSD / ok FileReadString(handle,3)=H1 / ok FileReadInteger(handle)=10 / ok FileReadLong(handle)=1629212400 / ok FileReadLong(handle)=1629248400 / ok [hora] [apertura] [máximo] [mínimo] [cierre] [volumen_tic] [diferencial] [volumen_real] [0] 2021.08.17 15:00:00 1791.40 1794.57 1788.04 1789.46 0 0 0 [1] 2021.08.17 16:00:00 1789.46 1792.99 1786.69 1789.69 0 0 0 [2] 2021.08.17 17:00:00 1789.76 1790.45 1780.95 1783.30 0 0 0 [3] 2021.08.17 18:00:00 1783.30 1783.98 1780.53 1782.73 0 0 0 [4] 2021.08.17 19:00:00 1782.69 1784.16 1782.09 1782.49 0 0 0 [5] 2021.08.17 20:00:00 1782.49 1786.23 1782.17 1784.23 0 0 0 [6] 2021.08.17 21:00:00 1784.20 1784.85 1782.73 1783.12 0 0 0 [7] 2021.08.17 22:00:00 1783.10 1785.52 1782.37 1785.16 0 0 0 [8] 2021.08.17 23:00:00 1785.11 1785.84 1784.71 1785.80 0 0 0 [9] 2021.08.18 01:00:00 1786.30 1786.34 1786.18 1786.20 0 0 0 |
Es fácil asegurarse de que los datos se almacenen y lean correctamente. Y ahora veamos cómo se ven dentro del archivo:

Visualización de la estructura interna de un archivo binario con un archivo de cotizaciones en un programa externo
Aquí se resaltan con color varios campos de nuestro encabezado: firma, longitud del nombre del símbolo, nombre del símbolo, nombre del marco temporal, etc.