English Русский 中文 Deutsch 日本語 Português
Principios de programación en MQL5: Archivos

Principios de programación en MQL5: Archivos

MetaTrader 5Ejemplos | 8 noviembre 2016, 09:26
3 317 0
Dmitry Fedoseev
Dmitry Fedoseev

Contenido

Introducción

En casi todos los lenguajes existen funciones para trabajar con archivos, y MQL5, en este sentido, no es un excepción. Aunque a la hora de programar asesores e indicadores en MQL5 no siempre es necesario trabajar usando archivos (se podría decir que con muy poca frecuencia), tarde o temprano cualquier escritor de expertos se ve obligado a ello. El rango de tareas para las que puede ser necesario trabajar con archivos es bastante amplio. Se puede tratar, por ejemplo, de la generación de nuestro propio informe comercial, la creación de archivos con algunos parámetros complicados para un asesor o indicador, la lectura de datos de mercado (por ejemplo, un calendario de noticias), etcétera. En este artículo se abordarán todas las funciones para trabajar con archivos en MQL5. Para cada una de ellas se ofrecerán tareas sencillas y prácticas. Ejecutando dichas tareas, usted adquirirá habilidades útiles para trabajar con archivos. Además de ejemplos didácticos, a lo largo del artículo vamos a crear una serie de funciones útiles que pueden resultar necesarias en la práctica.

La documentación de MQL5 para describir funciones de archivo se encuentra en el siguiente enlace.  

Leyendo un archivo de texto

La función más simple y usada con mayor frecuencia es la lectura de archivos de texto. No vamos a hablar en profundidad de la teoría, intentaremos comenzar con la práctica tan pronto como sea posible. Dado que este artículo está dedicado a la programación, usted deberá tener abierto el editor MetaEditor. Si no lo ha hecho aún, ábralo. En el editor, ejecute el comando siguiente: Menú principal - Abrir el catálogo de datos. En la carpeta que se abrirá, podrá ver la carpeta MQL5. Ábrala, luego haga lo propio con la carpeta Files ubicada en ella. Precisamente allí se encuentran los archivos que pueden ser procesados con las funciones de archivo de MQL5. Esta restricción está diseñada para garantizar la seguridad de la información. Los usuarios del terminal MetaTrader intercambian activamente programas escritos en MQL5. Si esta restricción no existiese, sería muy fácil para cualquier malhechor causarle daños a su equipo: borrar o corromper archivos importantes o robar información personal.

En la carpeta MQL5/Files que acaba de abrir, crearemos un archivo de texto. Para ello, clique con el ratón en algún espacio libre de la carpeta, y en el menú contextual que se abrirá, elija el comando: Crear — Docuemento de texto. Al crear un archivo, dele el nombre "test", el nombre completo del archivo puede ser "test.txt". Será mucho más fácil si activa en su computadora el modo de visualización de extensiones de archivo.

Después de dar un nuevo nombre al archivo, clique sobre él para abrirlo: se abrirá en el editor "Bloc de notas". Escriba en el archivo 2 — 3 líneas de texto y guárdelo. Al guardar el archivo, asegúrese de que ha elegido la codificación ANSI (lista desplegable en la parte inferior de la ventana de guardado del archivo, fig. 1).


Fig. 1. Guardando un archivo de texto del "Bolc de notas" de Windows. Con una flecha roja se muestra la elección de la codificación del archivo 

Ahora vamos a leer este archivo con los recursos de MQL5. En el editor MetaEditor creamos un script, lo llamaremos "sTestFileRead".

Antes de leer un archivo o escribir en él, deberemos abrirlo, y cerrarlo al terminar de trabajar con él. La apertura del archivo se lleva a cabo con la función FileOpen(), que tiene dos parámetros obligatorios. El primer parámetro a indicar será el nombre del archivo. Indicaremos el nombre del archivo "test.txt", creado anteriormente. Notemos que no se indica la ruta completa al archivo, sino solo la ruta desde la carpeta MQL5/Files. Como segundo parámetro, se indica la combinación de banderas que determina el modo de trabajo con los archivos. Vamos a leer un archivo, así que indicamos la bandera FILE_READ. El archivo "test.txt" es de texto y está en la codificación ANSI, por lo que indicaremos dos banderas más: FILE_TXT y FILE_ANSI. Las banderas se combinan con la ejecución de la operación lógica "o", designada con el signo "|".

La función FileOpen() retorna el manejador del archivo. No vamos a prodigarnos en detalles sobre lo que es un manejador. Sencillamente diremos que se trata de un cierto valor numérico (tipo int), que en lo sucesivo se usará en lugar del nombre de línea del archivo. El nombre de línea del archivo se indica solo al abrir el archivo, y después, al realizar algunas acciones con este archivo, se usa su manejador.

Entonces, abrimos el archivo (escribimos el código en la función OnStart() del script "sTestFileRead"):

int h=FileOpen("test.txt",FILE_READ|FILE_ANSI|FILE_TXT);

Después de abrir el archivo, será necesario comprobar si el archivo se ha abierto realmente. Esto se realiza comprobando el valor del manejador recibido:

if(h==INVALID_HANDLE){
   Alert("Error al abrir el archivo");
   return; 
}

El error en la apertura de un archivo es un hecho habitual y frecuente. Si el archivo ya está abierto, entonces no se puede abrir una segunda vez. El archivo puede estar abierto también en algún programa incorporado. Por ejemplo, al abrir el archivo en el "Bloc de notas" de Windows, este archivo también se puede abrir desde MQL5. Pero si el archivo está abiero en Microsoft Excel, entonces no se podrá abrir de forma simultánea desde ninguna parte.  

La lectura de datos desde un archivo de texto (abierto con la bandera FILE_TXT) se ejecuta con la función FileReadString(). La lectura se realiza por líneas, una llamada de la función lee una línea del archivo. Leemos una línea y la mostramos en la ventana de mensajes:

string str=FileReadString(h);
Alert(str);

Cerramos el archivo:

FileClose(h);

Preste antención a que la llamada de las funciones FileReadString() y FileClose() se ejecuta indicando el manejador (variable h), obtenido al abrir el archivo con la función FileOpen().

Ahora podemos ejecutar el script "sTestFileRead". Si algo no sale bien, compare su código con el archivo "sTestFileRead" de los anexos al artículo. Como resultado del funcionamiento del script, deberá mostrarse una ventana con la primera línea del archivo "test.txt" (fig. 2).

 
Fig. 2. Resultado del funcionamiento del script "sTestFileRead"

Bien, hasta el momento hemos leído una línea del archivo "test.txt". Para leer las otras dos hay que llamar la función FileReadString() dos veces más, pero en la práctica, el número de líneas del archivo puede no ser conocido de antemano. Para resolver esta tarea, nos vendrá bien la función FileIsEnding() y el operador cíclico while. Si a medida que leemos el archivo llegamos a su final, la función FileIsEnding() retornará true. Con la ayuda de esta función, escribiremos una nuestra para leer todas las líneas del archivo y mostrarlas en la ventana de mensajes. Puede ser necesaria para diversos expirementos didácticos de trabajo con archivos. Obtenemos la siguiente función:

void ReadFileToAlert(string FileName){
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   Alert("=== Comienzo ===");   
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      Alert(str);   
   }
   FileClose(h);

 Creamos el script "sTestFileReadToAlert", copiamos en él esta función y la llamamos desde la función OnStart() del script:

void OnStart(){
   ReadFileToAlert("test.txt");
}

Como resultado del funcionamiento del script se abrirá una ventana de mensajes con la línea "=== Comienzo ===" y las tres líneas del archivo "test.txt". Ahora el archivo ha sido leído por completo (fig. 3). 


Fig. 3. Gracias a la función FileIsEnding() y el ciclo do while, el archivo ha sido leído por completo   

Creando un archivo de texto

Para crear un archivo, es necesario abrirlo con la función FileOpen(). Abrimos el archivo con el nombre "test.txt", pero en lugar de la bandera FILE_READ, hay que indicar la bandera FILE_WRITE:

int h=FileOpen("test.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);

Después de abrirlo, no olvide comprobar el manejador, como ya hemos hecho al leer el archivo. Si la función se ha ejecutado con éxito, se creará el nuevo archivo "test.txt". Si el archivo ya existe, se limpiará por entero. Tenga especial atención y cuidado al abrir archivos para escribir, no pierda información de valor.  

La escritura de un archivo de texto se ejecuta con la función FileWrite(), el primer parámetro indicado es el manejador del archivo, y el segundo, la línea escrita en el archivo. Con cada llamada de la función FileWrite() se escribe una nueva línea.

Escribimos diez líneas en el archivo en el ciclo. Obtenemos definitivamente el siguiente código del script (en los anexos tenemos el script "sTestFileCreate"):

void OnStart(){
   int h=FileOpen("test.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }
   for(int i=1;i<=10;i++){
      FileWrite(h,"Строка-"+IntegerToString(i));
   }
   FileClose(h);
   Alert("archivo creado");
}

Tras la ejecución de este código, en el archivo "test.txt" se encontrarán diez líneas. Para comprobar el contenido del archivo, podemos abrirlo en el "Bloc de notas" o ejecutar el script "sTestFileReadToAlert".

Preste atención a la función FileWrite(). Puede tener no solo dos argumentos, sino muchos más. A la función se pueden pasar varias variables de línea, y durante la escritura, estas se unirán en una sola. En el código que acabamos de mostrar, la llamada de la función FileWrite() se puede anotar de la siguiente forma:

FileWrite(h,"Línea-",IntegerToString(i));

La propia función unirá las líneas durante la escritura.

Escribiendo al final de un archivo de texto

A veces sucede que es necesario guardar el contenido de un archivo existente y terminar de escribir al final del mismo una o dos líneas más de texto. Para ejecutar estas acciones, se debe abrir el archivo simultáneamente para la escritura y para la lectura. Es decir, al llamar la función FileOpen(), es necesario indicar las dos banderas: FILE_READ Y FILE_WRITE.

int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);

Si el archivo con el nombre indicado no existe, se creará. Si ya existe, entonces se abrirá, y además todo su contenido se guardará. Sin embargo, si empezamos en este caso a escribir en el archivo, su contenido antiguo será borrado, puesto que se escribirá desde el principio del archivo.

Al trabajar con archivos existe un concepto llamado "puntero", se trata del valor numérico que define en el archivo la posición desde la que se realizará la siguiente anotación o lectura del archivo. Al abrir un archivo, el puntero se coloca automáticamente al inicio del archivo, y durante la lectura o escritura de datos se desplaza de forma automática en la magnitud de los datos leídos o escritos. En caso necesario, es posible desplazar el puntero aleatoriamente. Para ello se usa la función FileSeek().  

Para guardar el contenido antiguo y añadir el nuevo al final del archivo, antes de escribir, es necesario reubicar el puntero al final del archivo:

FileSeek(h,0,SEEK_END);

A la función FileSeek() se transmiten tres parámetros: el manejador, la magnitud del desplazamiento del puntero y la posición desde la que se lleva la cuenta del desplazamiento. En este ejemplo, SEEK_END designa el final del archivo. De esta forma, se ejecuta el desplazamiento del puntero en una magnitud de 0 bytes desde el final del archivo, es decir, al final del todo.

De forma definitiva, obtenemos el siguiente código del script para completar el archivo:

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }
   FileSeek(h,0,SEEK_END);
   FileWrite(h,"Línea adicional");
   FileClose(h);
   Alert("archivo completado");
}

Este script existe también en los anexos al artículo, su nombre es "sTestFileAddToFile". Intente iniciar el script y después compruebe el contenido del archivo "test.txt". Cada llamada del script "sTestFileAddToFile" añadirá una línea al archivo "test.txt".

Modificando una línea indicada de un archivo de texto

Aunque el desplazamiento del puntero por el archivo puede ser libre, con los archivos de texto esto solo puede utilizarse para añadir archivos, y resulta totalmente inadecuado para introducir cambios. No se pueden realizar cambios en una línea específica del archivo. El asunto es que las líneas de archivo son un concepto convencional, ya que en realidad en el archivo no hay ninguna línea, sino que existe una serie continua de datos. En esta serie a veces se encuentran símbolos especiales, que son invisibles en el editor de texto. Estos indican que los siguientes datos deben representarse con una nueva línea. Si colocamos el puntero al inicio de la línea y comenzamos a escribir, entonces, en el caso de que el tamaño de los datos escritos sea menor a la línea sustituida, en la línea permanecerán datos antiguos. Si el tamaño de la nueva línea es superior al tamaño de la línea antigua, se borrarán los símbolos de transición a la nueva línea y parte de los datos de la siguiente línea.

Vamos a experimentar un poco: probaremos a sustituir en el archivo "test.txt" la segunda línea. Para ello, abriremos el archivo para la lectura y escritura, leeremos una línea para reubicar el puntero al inicio de la segunda línea e intentaremos escribir una nueva línea de dos letras "AB" (el script "sTestFileChangeLine2-1" en los anexos):

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }
   string str=FileReadString(h);
   FileWrite(h,"AB");
   FileClose(h);
   Alert("Ejecutado");
}

Como resultado del funcionamiento de este script, el archivo "test.txt" tendrá en el "Bloc de notas" el aspecto siguiente (fig. 4):

 
Fig. 4. Resultado del intento de cambiar una línea del archivo de texto 

En lugar de la segunda línea, han aparecido dos nuevas: "AB" y "ka-2". Podemos adivinar que "ka-2" representa los restos de la segunda línea, en la que han resultado borrados cuatro símbolos. El asunto es que al escribir líneas con la función FileWrite(), esta añade al final del texto escrito símbolos de transición a una nueva línea, y en el sistema operativo Windows, estos son dos. Los dos símbolos de la línea "AB" y los dos símbolos en la nueva línea dan como suma los 4 símbolos borrados.  

Restauramos el archivo "test.txt" ejecutando el script "sTestFileCreate", y ahora probamos a sustituir la segunda línea con una más larga. Escribimos la línea "Línea-12345" (script "sTestFileChangeLine2-2" en los anexos):

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }
   string str=FileReadString(h);
   FileWrite(h,"Línea-12345");
   FileClose(h);
   Alert("Ejecutado");
}

Vemos el archivo (fig. 5):

 
Fig. 5. Resultado del intento de cambiar una línea del archivo de texto 

Como podemos ver, al ser la nueva línea más larga que la antigua, también se ve afectada la tercera línea.  

La única variante para introducir cambios en archivos de texto es leerlos y escribirlos de nuevo. Hay que leer el archivo en la matriz, introducir los cambios en los elementos necesarios de la matriz y guardarla en otro archivo, después eliminamos el archivo antiguo y renombramos el nuevo. En ciertos casos, podemos arreglárnoslas sin matriz: a medida que leamos las líneas de un archivo, las escribimos en otro, introduciendo en un cierto momento cambios en la línea necesaria y escribiéndola, y después, como en el primer caso, eliminaremos el archivo viejo y renombraremos el nuevo.

Probamos la variante sin matriz. Para ello, primero debemos crear un archivo temporal. Escribimos una función para obtener el nombre único del archivo temporal. En la función se transmitirá el nombre del archivo y su extensión, y en la propia función se ejecutará la comprobación sobre si existe ese archivo (con la función estándar FileIsExists()). Si existe, entonces se añadirá un número al nombre, hasta que no se detecte un archivo con ese nombre. Obtenemos la siguiente fórmula:

string TmpFileName(string Name,string Ext){
   string fn=Name+"."+Ext; // hemos formado el nombre
   int n=0;
   while(FileIsExist(fn)){ // si existe el archivo
      n++;
      fn=Name+IntegerToString(n)+"."+Ext; // añadimos al nombre una cifra
   }
   return(fn);
}

Creamos el script "sTestFileChangeLine2-3", copiamos en él esta función, en la función OnStart() vamos a ubicar el código siguiente.

Abrimos el archivo "test.txt" para la lectura:

int h=FileOpen("test.txt",FILE_READ|FILE_ANSI|FILE_TXT);

Obtenemos el nombre del archivo temporal y lo abrimos:

string tmpName=TmpFileName("test","txt");

int tmph=FileOpen(tmpName,FILE_WRITE|FILE_ANSI|FILE_TXT);

Leemos el archivo por líneas y contamos las líneas. Todas las líneas leídas se anotan directamente en un archivo temporal, y la segunda línea es sustituida:

   int cnt=0;
   while(!FileIsEnding(h)){
      cnt++;
      string str=FileReadString(h);
      if(cnt==2){
         // sustituimos la línea
         FileWrite(tmph,"Nueva línea-2");
      }
      else{
         // reescribimos la línea sin cambios
         FileWrite(tmph,str);
      }
   }

Cerramos ambos archivos:

FileClose(tmph);
FileClose(h);

Solo queda eliminar el archivo fuente y renombrar el temporal. Para eliminarlo, usaremos la función estándar FileDelete().

FileDelete("test.txt");

Para renombrarlo, usaremos la función estándar FileMove(), que está pensada para desplazar o renombrar los archivos. A la función se transmiten cuatro parámetros obligatorios: el nombre del archivo reubicado (archivo fuente), la bandera de ubicación del archivo, el nombre del nuevo archivo (archivo de destino), y la bandera de reescritura. En lo que respecta al nombre de los archivos, todo debería ser comprensible, por eso nos detendremos con más detalle en los parámetros dos y cuatro, las banderas. El segundo parámetro determina la ubicación del archivo fuente. Los archivos con los que se puede trabajar en MQL5 se pueden ubicar no solo en la carpeta MQL5/Files del terminal, sino también en la carpeta general de todos los terminales. Más tarde analizaremos este momento de forma más exhaustiva, por ahora, pondremos un 0. El último parámetro determina la ubicación del archivo de destino, y también puede tener una bandera adicional que defina las acciones en el caso de que el archivo de destino exista. Puesto que hemos eliminado el archivo fuente de forma autónoma (archivo de destino), indicaremos 0 como cuarto parámetro:

FileMove(tmpName,0,"test.txt",0);

Antes de ejecutar el script "sTestFileChangeLine2-3", restaure el archivo "test.txt" con el script "sTestFileCreate". Después del funcionamiento del script "sTestFileChangeLine2-3", el archivo "text.txt" debe tener el siguiente contenido (fig. 6):

 
Fig. 6. Contenido del archivo después de sustituir la línea

Volvamos a la función FileMove(). Si indicamos como cuarto parámetro la bandera FILE_REWRITE, que permite reescribir el archivo de destino:

FileMove(tmpName,0,"test.txt",FILE_REWRITE);

entonces en este caso, podemos no eliminar el archivo fuente del script. Esta variante se usa en el script "sTestFileChangeLine2-3" de los anexos al artículo. 

En lugar de la función FileMove() se puede usar otra función estándar, FileCopy(), pero entonces tendremos que eliminar el archivo temporal:

FileCopy(tmpName,0,"test.txt",FILE_REWRITE);
FileDelete(tmpName); 

Leyendo un archivo de texto en una matriz

Ya hemos descrito una función útil en este artículo (obteniendo el nombre libre de un archivo). Ahora vamos a desarrollar una función usada con frecuencia al trabajar con archivos: la lectura del archivo en la matriz. A la función se le transmite el nombre del archivo y la matriz de línea. La matriz se transmite por un enlace y es rellenada con el contenido del archivo en la función. La función retorna true/false, dependiendo del éxito de su funcionamiento. 

bool ReadFileToArray(string FileName,string & Lines[]){
   ResetLastError();
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      int ErrNum=GetLastError();
      printf("Error al abrir el archivo %s # %i",FileName,ErrNum);
      return(false);
   }
   int cnt=0; // con la ayuda de esta variable vamos a leer el número de líneas del archivo
   while(!FileIsEnding(h)){
      string str=FileReadString(h); // leemos la línea siguiente del archivo
      // cortamos los espacios en blanco a la derecha y a la izquierda, para detectar y evitar usar las líneas vacías
      StringTrimLeft(str); 
      StringTrimRight(str);
      if(str!=""){ 
         if(cnt>=ArraySize(Lines)){ // la matriz se ha rellenado por completo
            ArrayResize(Lines,ArraySize(Lines)+1024); // aumentamos el tamaño de la matriz en 1024 elementos
         }
         Lines[cnt]=str; // enviamos la línea leída a la matriz
         cnt++; // aumentamos el contador de líneas leídas
      }
   }
   ArrayResize(Lines,cnt);
   FileClose(h);
   return(true);
}

No vamos a analizar con detalle esta función: en este momento, después del material estudiado en este artículo, debería sernos comprensible. Además, ha sido profusamente comentada, así que nos detendremos solo en ciertos detalles. Después de leer una línea del archivo en la variable str se produce el borrado de los espacios en los límites de la línea con las funciones StringTrimLeft() y StringTrimRight(). A continuación, se ejecuta la comprobación sobre si existe la línea str. Esto se ha hecho para saltar las líneas vacías innecesarias. A medida que se rellena la matriz, esta aumenta no un elemento a uno, sino por bloques de 1024, así, la función funcionará mucho más deprisa. Al final se ejecuta el escalado de la matriz de acuerdo con el número real de líneas leídas.

En los anexos, esta función se encuentra en el script "sTestFileReadFileToArray".

Creando un archivo de texto con separadores

Hasta el momento, en este artículo solo hemos tratado con archivos de texto sencillos. Existe otra variedad de archivos de texto: los archivos de texto con separadores. Normalmente, tienen la extensión csv (abreviatura del ingl. "comma separated values", que se traduce como "valores separados por una coma"). En esencia, se trata de archivos de texto que se pueden abrir en los editores de texto. También es posible leerlos y editarlos manualmente. Uno de los símbolos (no debe ser obligatoriamente la coma) se usa para separar los campos en líneas. Por ello, con estos archivos se pueden ejecutar acciones un poco diferentes a las realizables con los archivos de texto. La diferencia principal reside en que mientras que en un archivo de texto sencillo al llamar la función FileRedaString() tiene lugar la lectura de una línea completa, en los archivos con separador se produce la lectura hasta el separador o hasta el final de la línea. Existe asimismo una diferencia en el funcionamiento de la función FileWrite(): todas las funciones enumeradas no solo se unen en una línea, sino que además entre ellas se añade un separador. 

Vamos a experimentar creando un archivo csv. Para crearlo, abrimos un archivo de la misma forma en que abrimos el archivo para su escritura, pero en lugar de indicar la bandera FILE_TXT, indicamos la bandera FILE_CSV. Como tercer parámetro, indicamos el símbolo usado en calidad de separador:

int h=FileOpen("test.csv",FILE_WRITE|FILE_ANSI|FILE_CSV,";");

Escribimos en el archivo diez líneas con tres campos por línea:

   for(int i=1;i<=10;i++){
      string str="Строка-"+IntegerToString(i)+"-";
      FileWrite(h,str+"1",str+"2",str+"3");
   }

Al final no hay que olvidarse de cerrar el archivo. En los anexos, el código de este ejemplo se encuentra en el script "sTestFileCreateCSV". Como resultado del funcionamiento de este ejemplo, se crea el archivo "test.csv". El contenido del archivo se muestra en la fig. 7. Como podemos ver, los parámetros de la función FileWrite() no solo se han unido en una línea, sino que además entre ellos se ha añadido un separador.

 
Fig. 7. Contenido del archivo con separadores

Leyendo un archivo de texto con separadores

Vamos a intenetar leer un archivo csv, exactamente de la misma forma que leímos el archivo de texto al inicio de este artículo. Hacemos una copia del script "sTestFileReadToAlert" con el nombre "sTestFileReadToAlertCSV". En la función ReadFileToAlert() cambiamos la primera línea: 

int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");

Renombramos la función ReadFileToAlert() como ReadFileToAlertCSV() y cambiamos el nombre transmitido a la función:

void OnStart(){
   ReadFileToAlertCSV("test.csv");
}

Según el resultado del funcionamiento del script, se ve que el archivo solo se ha leído en parte. Es conveniente determinar cuándo ha sido leído el campo de una línea y cuando comienza una nueva. Para ello, nos será muy útil la función FileIsLineEnding().

Hacemos una copia del script "sTestFileReadToAlertCSV" con el nombre "sTestFileReadToAlertCSV2", renombramos la función "ReadFileToAlertCSV" como "ReadFileToAlertCSV2" e introducimos en ella los cambios. Añadimos la función FileIsLineEnding(): si retorna true, mostraremos la línea separadora "---". 

void ReadFileToAlertCSV2(string FileName){
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   Alert("=== Comienzo ===");   
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      Alert(str);
      if(FileIsLineEnding(h)){
         Alert("---");
      }
   }
   FileClose(h);
}

 Ahora los campos mostrados por el script en el ventana de mensajes se dividirán en grupos (fig. 8).


Fig. 8. Separadores "---" entre grupos de campos de una línea del archivo 

Leyendo un archivo con separadores en la matriz

Tras aclarar las peculiaridades del trabajo con los archivos csv, escribiremos otra función útil para leer un archivo csv en la matriz. La lectura se ejecutará en la matriz de estructuras, donde cada elemento se corresponderá con una línea del archivo. En la estructura se encontrará la matriz de líneas, cada elemento de esta se corresponderá con un campo de la línea. 

Descripción de la estructura:

struct SLine{
   string line[];
};

Función:

bool ReadFileToArrayCSV(string FileName,SLine & Lines[]){
   ResetLastError();
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";");
   if(h==INVALID_HANDLE){
      int ErrNum=GetLastError();
      printf("Error al abrir el archivo %s # %i",FileName,ErrNum);
      return(false);
   }   
   int lcnt=0; // variable para el cálculo de líneas 
   int fcnt=0; // variable para el cálculo de los campos de la línea    
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      // nueva línea (nuevo elemento de la matriz de estructuras)
      if(lcnt>=ArraySize(Lines)){ // la matriz de estructuras ha sido rellenada por completo
         ArrayResize(Lines,ArraySize(Lines)+1024); // aumentamos el tamaño de la matriz en 1024 elementos
      }
      ArrayResize(Lines[lcnt].field,64);// cambiamos el tamaño de la matriz en la estructura
      Lines[lcnt].field[0]=str; // asignamos el valor del primer campo
      // comenzamos a leer el resto de los campos en la línea
      fcnt=1; // por el momento está ocupado un elemento en la matriz de campos
         while(!FileIsLineEnding(h)){ // leemos el resto de campos en la línea
            str=FileReadString(h);
            if(fcnt>=ArraySize(Lines[lcnt].field)){ // la matriz de campos está completamente rellena
               ArrayResize(Lines[lcnt].field,ArraySize(Lines[lcnt].field)+64); // aumentamos el tamaño de la matriz a 64 elementos
            }     
            Lines[lcnt].field[fcnt]=str; // asignamos el valor del siguiente campo
            fcnt++; // aumentamos el contador de campos
         }
      ArrayResize(Lines[lcnt].field,fcnt); // cambiamos el tamaño de la matriz de campos de acuerdo con la cantidad de campos real
      lcnt++; // aumentamos el contador de líneas
   }
   ArrayResize(Lines,lcnt); // cambiamos la matriz de estructuras (líneas) de acuerdo con la cantidad de líneas real
   FileClose(h);
   return(true);
}

No vamos a analizar en profundidad esta función, solo nos detendremos en sus momentos principales. Al principio del ciclo while(!FileIsEnding(h)) se realiza la lectura de un campo. Aquí se nos damos cuenta de que se ha añadido un elemento a la matriz de estrcuras. Comprobamos el tamaño de la matriz y, si es necesario, aumentamos su tamaño en 1024 elementos. Cambiamos directamente el tamaño de la matriz para los campos. A esta se le asigna de inmediato un tamaño de 64 elementos, y al elemento con el índice 0 se le adjudica el valor del primer campo de línea leído desde el archivo. Después de ello, en el ciclo while(!FileIsLineEnding(h)) leemos el resto de campos. Tras leer el campo siguiente, comprobamos el tamaño de la matriz, y si es necesario, lo aumentamos. Después enviamos a la matriz la línea leída del archivo. Tras leer la línea hasta el final (saliendo del ciclo while(!FileIsLineEnding(h)) ), modificamos el tamaño de los campos de acuerdo con la cantidad real de módulos. Al final del todo, medimos el tamaño de la matriz de líneas de acuerdo con el número real de líneas leídas. 

En los anexos, esta función se encuentra en el script "sTestFileReadFileToArrayCSV". El script lee el archivo "test.csv" en la matriz y muestra esta matriz en la ventana de mensajes. El resultado es el mismo que en la fig. 8. 

Escribiendo una matriz en un archivo de texto con separadores

Si el número de campos en la línea es conocido de antemano, no será complicado. Esta tarea ya se ha resuelto en este artículo, en el apartado "Creando un archivo de texto con separadores". En el caso de que el número de campos no nos sea conocido, podemos reunir todos los campos en un línea con ayuda de separadores en un ciclo y escribirla en un archivo abierto con la bandera FILE_TXT.

Abrimos el archivo: 

int h=FileOpen("test.csv",FILE_WRITE|FILE_ANSI|FILE_TXT);

Reunimos todos los campos (elementos de la matriz) en una línea con la ayuda de un separador. Al final de la línea no debe haber separador, de lo contrario, en la línea habrá un campo vacío sobrante:

   string str="";
   int size=ArraySize(a);
   if(size>0){
      str=a[0];
      for(int i=1;i<size;i++){
         str=str+";"+a[i]; // unimos los campos con un separador 
      }
   }

Escribimos la línea en el archivo y lo cerramos:  

FileWriteString(h,str);
FileClose(h);

Este ejemplo se encuentra en los anexos, en el script "sTestFileWriteArrayToFileCSV".

Archivos UNICODE

Hasta el momento, al abrir archivos, en todos los casos se indicaba la bandera FILE_ANSI, que define su codificación. En esta codificación, a un símbolo le corresponde un byte, y por consiguiente, todo el conjunto está limitado a una cantidad de 256 símbolos. En la actualidad se usa ampliamente la codificación UNICODE, en la que un símbolo se define con varios bytes y un archivo de texto puede contener una gran cantidad de símbolos: desde alfabetos de distintas lenguas hasta jeroglíficos y otros símbolos gráficos.

Vamos a experimentar un poco. Abra en el editor el script "sTestFileReadToAlert", guárdelo con el nombre "sTestFileReadToAlertUTF" y cambie la bandera FILE_ANSI a FILE_UNICODE:

int h=FileOpen(FileName,FILE_READ|FILE_UNICODE|FILE_TXT);

Puesto que el archivo "test.txt" se guarda en la codificación ANSI, como resultado del funcionamiento del script, se abrirá una ventana con un extraño conjunto de símbolos (fig. 9).

  
Fig. 9. Texto extraño al no coincidir la codificación del archivo y la indicada al abrir el archivo

Obviamente, esto sucede porque no coinciden la codificación del archivo y la indicada al abrir el archivo.

Abra el script "sTestFileCreate" en el editor y guárdelo con el nombre "sTestFileCreateUTF" y cambie la bandera FILE_ANSI a FILE_UNICODE:

int h=FileOpen("test.txt",FILE_WRITE|FILE_UNICODE|FILE_TXT);

Inicie el script "sTestFileCreateUTF", para crear el nuevo archivo "test.txt". Ahora el script "sTestFileReadToAlertUTF" mostrará un texto legible (fig. 10).

 
Fig. 10. Lectura con el script "sTestFileReadToAlertUTF" de un archivo creado con el script "sTestFileCreateUTF"

Abra el archivo "test.txt" en el editor "Bloc de notas" y ejecute el comando del menú principal "Guardar como". En la parte inferior de la ventana de guardado del archivo se puede ver que en la lista "Codificación" se ha elegido "Unicode". El "Bloc de notas", de alguna forma, ha determinado la codificación del archivo. Los archivos en la codificación Unicode comienzan con un conjunto estándar de símbolos, la llamada BOM (byte order mark, marca de orden de bytes). Un poco más tarde, al analizar los archivos binarios, volveremos a esta cuestión y escribiremos una función para definir el tipo de archivo de texto (ANSI o UNCODE). 

Funciones adicionales para trabajar con archivos de texto con separadores

Entre la multitud de funciones de archivo para trabajar con el contenido de archivos de texto (tanto sencillos como con separadores) podemos limitarnos en la práctica a dos funciones: FileWrite() y FileReadString(). La función FileReadString(), además, se usa también al trabajar con archivos binarios (sobre los archivos binarios hablaremos un poco más tarde). Aparte de la función FileWrite(), podemos usar la función FileWriteString(), pero esta cuestión no es esencial. 

Al trabajar con archivos de texto con separadores, es posible usar algunas funciones más, que pueden hacer el trabajo más cómodo: FileReadBool()FileReadNumber() y FileReadDatetime(). La función FileReadNumber() está pensada para leer cifras. Si conocemos de antemano que el campo a leer en el archivo incluye solo cifras, podemos usar esta función. Su acción es idéntica a la lectura de una línea con la función FileReadString() y su transformación en un número con la función StringToDouble(). De forma análoga, la función FileReadBool() se ha pensado para leer los valores del tipo bool, en la línea puede estar escrito el texto "true" o "false" o el número "0" o "1". La función FileReadDatetime() ha sido diseñada para leer la fecha en el formato de línea y transformarla en un valor numérico del tipo datetime. Su acción es análoga a la lectura de una línea y su transformación con la función StringToTime().  

Archivos binarios

Los archivos de texto vistos anteriormente son muy cómodos porque su contenido, leído con métodos programáticos, se corresponde con lo que usted puede ver en el archivo al abrirlo en un editor de texto. Es posible controlar fácilmente el funcionamiento del programa echando un vistazo al archivo en el editor. En caso necesario, podemos reubicar el archivo manualmente. Un defecto de los archivos de texto es la limitación de posibilidades al trabajar con ellos (recuerde las compliaciones con las que topamos al sustituir una línea del archivo).

Si los archivos de texto tienen un tamaño poco significativo, será muy cómodo usarlos, pero con tamaños grandes, el trabajo con ellos será muy lento. Si es necesario trabajar con grandes volúmenes de datos y hacerlo lo más rápido posible, será imprescindible usar archivos binarios.

Al abrir un archivo en el modo binario, en lugar de la bandera FILE_TXT o FILE_CSV se indica la bandera FILE_BIN. No tiene sentido indicar las banderas de codificación FILE_ANSI o FILE_UNCODE, ya que un archivo binario es una archivo con cifras.

Claro que podemos echar un vistazo por curiosidad al contenido del archivo en un editor de texto del tipo "Bloc de notas": así podremos ver las letras e incluso el texto legible, si es que existe en el archivo, pero esto está más relacionado con las peculiaridades de funcionamiento del "Bloc de notas" que con el contenido del archivo.

Mirar un archivo binario en un editor de texto es posible, pero la posibilidad de editarlo allí no es en absoluto conveniente, porque el archivo resultará al final dañado. No vamos a ahondar en los motivos por los que esto es así, simplemente lo admitiremos como un hecho. Por supuesto que existen también editores especiales para los archivos binarios, que permiten editar dichos archivos, pero de todas formas este proceso no será visual en absoluto, ni tampoco comprensible de una forma intuitiva.

Archivos binarios, variables

La mayoría de funciones para trabajar con archivos en MQL5 han sido pensadas para usarse en el modo binario. Existen funciones de escritura/lectura de tipos diferentes:  

FileReadDouble() FileWriteDouble()
FileReadFloat() FileWriteFloat()
FileReadInteger() FileWriteInteger()
FileReadLong() FileWriteLong()
FileReadString() FileWriteString()
FileReadStruct() FileWriteStruct()

No vamos a analizar todas las funciones de escritura/lectura de variables: basta con una, pues el uso del resto de las funciones es idéntico. Vamos a experimentar con las funciones FileWriteDouble() y FileReadDouble().

Primero crearemos un archivo y escribiremos en él tres variables, después las leeremos en orden aleatorio. 

Abrimos el archivo:

int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);

Escribimos en el archivo tres variables double con valores 1.2, 3.45, 6.789:

FileWriteDouble(h,1.2);
FileWriteDouble(h,3.45);
FileWriteDouble(h,6.789);

No olvide cerrar el archivo.

En los anexos, el script con este código tiene el nombre "sTestFileCreateBin". Como resultado del funcionamiento del script en la carpeta MQL5/Files aparecerá el archivo "test.bin". De todas formas, vamos a ver el contenido del archivo en el "Bloc de notas" (fig. 11). Para ello, abra el "Bloc de notas" y arrastre el archivo allí:

 
Fig. 11. Archivo binario en el "Bloc de notas"

Como podemos comprobar, no tiene sentido ver estos archivos en el bloc de notas.

Ahora vamos a leer el archivo. Es obvio que para la lectura deberá usarse la función FileReadDouble(). Abrimos el archivo:

int h=FileOpen("test.bin",FILE_READ|FILE_BIN);

Declaramos tres variables, leemos sus valores del archivo y los mostramos en la ventana de mensajes:

double v1,v2,v3;
   
v1=FileReadDouble(h);
v2=FileReadDouble(h);
v3=FileReadDouble(h);
   
Alert(DoubleToString(v1)," ",DoubleToString(v2)," ",DoubleToString(v3));
  

No olvide cerrar el archivo. En los anexos, el script con este código tiene el nombre "sTestFileReadBin". Como resultado del funcionamiento del script, recibiremos el mensaje: 1.20000000 3.45000000 6.78900000.

En los archivos binarios, conociendo su estructura, podemos introducir cambios limitados. Vamos a intentar cambiar el valor de la segunda variable sin reescribir todo el archivo.

Abrimos el archivo:

int h=FileOpen("test.bin",FILE_READ|FILE_WRITE|FILE_BIN);

Después de abrirlo, hay que desplazar el puntero a la posición necesaria. Para calcular la posición, es más cómodo usar la función sizeof(). Esta retorna el tamaño del tipo de datos indicado. También será útil familiarizarse con los tipos de datos y sus tamaños. Desplazamos el puntero al inicio de la segunda variable:

FileSeek(h,sizeof(double)*1,0);

Para mayor claridad, se ha escrito la multiplicación sizeof(double)*1, para que quede claro que es el final de la primera variable. Si fuese necesario cambiar la tercera variable, habría que multiplicar por 2.

Escribimos el nuevo valor: 

FileWriteDouble(h,12345.6789);

En los anexos este código se encuentra en el script con el nombre "sTestFileChangeBin". Después de ejecutar este script, iniciamos el script "sTestFileReadBin" y obtenemos: 1.20000000 12345.67890000 6.78900000.

Así, podemos leer una cierta variable (en lugar del archivo completo) de la misma forma. Vamos a escribir el código para leer la tercera variable double del archivo "test.bin".

Abrimos el archivo:

int h=FileOpen("test.bin",FILE_READ|FILE_BIN);

Desplazamos el puntero, leemos el valor y lo mostramos en la ventana:

FileSeek(h,sizeof(double)*2,SEEK_SET);
double v=FileReadDouble(h);
Alert(DoubleToString(v));

El script con este ejemplo se encuentra en los anexos, el nombre del archivo es "sTestFileReadBin2". Como resultado del funcionamiento del script, obtenemos: 6.78900000, el valor de la tercera variable. Cambie este código por sí mismo, de tal forma que se lea la segunda variable.

De la misma forma exactamente se pueden guardar y leer variables de otros tipos y sus combinaciones. Es importante conocer la estructura del archivo para calcular correctamente la posición para colocar el puntero. 

Archivos binarios, estructuras

Si necesita escribir en el archivo varios tipos diferentes de variables, será mucho más cómodo no escribir/leer las variables una a una, sino describir la estructura y leerla/escribirla por completo. Normalmente se hace así: el archivo comienza por la estructura que describe la ubicación de los datos en el archivo (formato del archivo), y después se ubican los datos. Sin embargo, existe una limitación: en la estructura no deberán existir matrices dinámicas y líneas, porque su tamaño es desconocido.

Vamos a experimentar con la escritura y lectura de la estructura en el archivo. A continuación, describiremos la estructura con muchas variables de diverso tipo:

struct STest{
   long ValLong;
   double VarDouble;
   int ArrInt[3];
   bool VarBool;
};

En los anexos, este código se encuentra en el script con el nombre "sTestFileWriteStructBin". En la función OnStart() declaramos dos variables y las rellenamos con valores diferentes:

STest s1;
STest s2;
   
s1.ArrInt[0]=1;
s1.ArrInt[1]=2; 
s1.ArrInt[2]=3;
s1.ValLong=12345;
s1.VarDouble=12.34;
s1.VarBool=true;
         
s2.ArrInt[0]=11;
s2.ArrInt[1]=22; 
s2.ArrInt[2]=33;
s2.ValLong=6789;
s2.VarDouble=56.78;
s2.VarBool=false;  

Ahora, como es habitual, abrimos el archivo:

int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);

Escribimos en él ambas estructuras:

FileWriteStruct(h,s1);
FileWriteStruct(h,s2);

No olvide cerrar el archivo. Ejecute este script para que el archivo se cree.

Ahora vamos a leer el archivo. Leemos la segunda línea.

Abrimos el archivo:

int h=FileOpen("test.bin",FILE_READ|FILE_BIN);

Desplazamos el puntero al inicio de la segunda estructura:

FileSeek(h,sizeof(STest)*1,SEEK_SET);

Declaramos la variable (añada al inicio del archivo la descripción de la estructura STest), leemos en ella los datos del archivo:

STest s;
FileReadStruct(h,s);

Mostramos los valores de los campos de estructura en la ventana:

Alert(s.ArrInt[0]," ",s.ArrInt[1]," ",s.ArrInt[2]," ",s.ValLong," ",s.VarBool," ",s.VarDouble);   

Como resultado del funcionamiento, en la ventana de mensajes veremos la línea: 11 22 33 6789 false 56.78. La línea correspnde a los datos de la segunda estructura.

El código del ejemplo está ubicado en los anexos, en un script con el nombre "sTestFileReadStructBin".

Lectura de estructuras según las variables

En MQL5, los campos de estructuras van uno detrás de otro sin desplazamiento (alineación), por eso se pueden leer campos de estructuras aparte sin dificultad.

Vamos a leer el valor de la variable double de la segunda estructura en el archivo "test.bin". Es importante calcular correctamente la posición para colocar el puntero: 

FileSeek(h,sizeof(STest)+sizeof(long),SEEK_SET);

Todo lo demás sucede de forma análoga a lo que ya hemos hecho muchas veces en este artículo: abrir el archivo, leerlo, cerrarlo. El código del ejemplo se encuentra en los anexos, en el script con el nombre "sTestFileReadStructBin2".

Definiendo un archivo UNICODE, la función FileReadInteger

Ya que hemos profundizado un poco en el uso de códigos binarios, podemos crear una función útil para definir un archivo UNICODE. Estos archivos se pueden distinguir por el valor del byte inicial, igual a 255. Al código 255 le corresponde un símbolo no imprimible, por eso en un archivo ANSI normal, no podrá estar presente.

Significa que es necesario leer un byte del archivo y comprobar su valor. Para leer distintas variables de tipo entero, excepto long, se usa la función FileReadInteger(). En ella se transmite un parámetro que indica el tamaño de la variable leída. Leemos del archivo un byte en la variable v:

uchar v=FileReadInteger(h,CHAR_VALUE);

Solo queda comprobar el valor de la variable. Más abajo se muestra el código completo de la función:

bool CheckUnicode(string FileName,bool & Unicode){
   ResetLastError();
   int h=FileOpen(FileName,FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      int ErrNum=GetLastError();
      printf("Error al abrir el archivo %s # %i",FileName,ErrNum);
      return(false);
   }
   uchar v=FileReadInteger(h,CHAR_VALUE);
   Unicode=(v==255);
   FileClose(h);
   return(true);
}

La función retorna true/false, dependiendo de si ha logrado realizar la comprobación o no. Como primer parámetro a la función se transmite el nombre del archivo, el segundo parámetro (transmitido a través de un enlace), contiene una variable igual a true para los archivos UNICODE y a false para los archivos ANSI después de la ejecución de la función. 

El código de esta función, con un ejemplo de su llamada, se encuentra en el script "sTestFileCheckUnicode". Inicie el script "sTestFileCreate" y compruebe su tipo con el script "sTestFileCheckUnicode", después inicie el script "sTestFileCreateUTF" e inicie otra vez el script "sTestFileCheckUnicode". Obtendrá otro resultado.  

Archivos binarios, matrices, matrices de estructuras

La principal ventaja de los archivos binarios se hace notoria al trabajar con volúmenes de datos considerables. Los datos, además, se ubican en las matrices (con variables aparte es compliacdo obtener un tamaño considerable) y en las líneas. Las matrices pueden ser no solo de las variables del tipo estándar, sino también de las estructuras que deberán corresponder a los requirimientos descritos anteriormentes. En ellas no deberán existir matrices dinámicas y líneas.

Las matrices se escriben en el archivo con la función FileWriteArray(). Como primer parámetro en la función, como siempre, se transmite el manejador del archivo, después, el nombre la matriz. Los dos siguientes parámetros no son obligatorios. Si no hay que guardar la matriz completa, se puede señalar el índice del elemento inicial de la matriz y el número de elementos guardados. 

La lectura de matrices se realiza con la función FileReadArray(), los parámetros de la función son idénticos a los parámetros de la función FileWriteArray().

Vamos a experimentar, escribiremos en el archivo una matriz int de tres elementos: 

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   int a[]={1,2,3};   
   FileWriteArray(h,a);   
   FileClose(h);
   Alert("Se ha escrito el archivo");
}

En los anexos, este código se ubica en el archivo "sTestFileWriteArray".

Ahora lo leeremos (en los anexos, el script "sTestFileReadArray") y lo mostraremos en la ventana:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   int a[];   
   FileReadArray(h,a);   
   FileClose(h);
   Alert(a[0]," ",a[1]," ",a[2]);   
}

Como resultado del funcionamiento del script, obtenemos la línea "1 2 3", que corresponde a la matriz especificada anteriormente. Preste atención: el tamaño de la matriz no ha sido definido, y al llamar la función FileReadArray() tampoco se ha indicado, se ha leído el archivo al completo. Sin embargo, en el archivo puede encontrarse no solo una matriz, sino cualquier número de ellas, y pueden tener diferentes tipos. Por eso no estaría mal guardar también en un archivo información sobre el tamaño del archivo. Vamos a escribir en el archivo dos matrices: una del tipo int, y una segunda del tipo double. Pero antes vamos a escribir la variable int con sus tamaños:

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   
   // dos matrices
   int a1[]={1,2,3}; 
   double a2[]={1.2,3.4};
   
   // definimos los tamaños de las matrices
   int s1=ArraySize(a1);
   int s2=ArraySize(a2);
   
   // escribimos la matriz 1
   FileWriteInteger(h,s1,INT_VALUE); // escribimos el tamaño de la matriz
   FileWriteArray(h,a1); // escribimos la matriz propiamente dicha
   
   // escribimos la matriz 2
   FileWriteInteger(h,s2,INT_VALUE); // escribimos el tamaño de la matriz
   FileWriteArray(h,a2); // escribimos la matriz propiamente dicha   
      
   FileClose(h);
   Alert("Se ha escrito el archivo");
}

El código del ejemplo se encuentra en los anexos, en el script "sTestFileWriteArray2". 

Ahora, al leer un archivo, primero leemos el tamaño de la matriz, después leeremos en la matriz el número indicado de elementos:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   int a1[];
   double a2[];
   int s1,s2;
   
   s1=FileReadInteger(h,INT_VALUE); // leemos el tamaño de la matriz 1
   FileReadArray(h,a1,0,s1); // leemos en la matriz el número de elementos establecido en s1 
   
   s2=FileReadInteger(h,INT_VALUE); // leemos el tamaño de la matriz 1
   FileReadArray(h,a2,0,s2); // leemos en la matriz el número de elementos establecido en s2    

   FileClose(h);
   Alert(ArraySize(a1),": ",a1[0]," ",a1[1]," ",a1[2]," :: ",ArraySize(a2),": ",a2[0]," ",a2[1]);   
}

Este código se encuentra en los anexos, en el script "sTestFileReadArray2".

Como resultado del funcionamiento, el script muestra el siguiente mensaje: 3 : 1 2 3 - 2 : 1.2 3.4, lo que se corresponde con los tamaños y el contenido de las matrices anteriormente descritas en el archivo.

Al leer las matrices con la función FileReadArray() se produce automáticamente el escalado de la matriz, pero tenga en cuenta que el escalado se realiza solo si el tamaño actual es menor a la cantidad de elementos leídos. Si el tamaño de la matriz es mayor, entonces no cambiará, solo se rellenará parte de la matriz.

El trabajo con las matrices de estructuras es totalmente idéntico al trabajo con las matrices de los tipos estándar, dado que el tamaño de la estructura está concretamente definido (no existen matrices dinámicas y líneas). No vamos a mostrar ejemplos de matrices de estructuras, puede experimentar por sí mismo.

Recordemos de nuevo: puesto que podemos desplazar libremente el puntero por el archivo, es posible leer no la matriz entera, sino solo un elemento o parte concretos de la misma. Lo importante es calcular correctamente la posición a la que hay que desplazar el puntero. Tampoco vamos a mostrar ejemplos de lectura de elementos aparte, para evitar extendernos demasiado en el artículo. Puede experimentar por sí mismo.

Archivos binarios, líneas, matrices de estructuras

Para escribir una línea en un archivo binario, se usa la función FileWriteString(), a la que se transmiten obligatoriamente dos parámtros: el manejador del archivo y la línea escrita en el archivo. El tercer parámetro no es obligatorio: podemos indicar la cantidad de símbolos a escribir, si no necesitamos escribir toda la línea al completo. 

La lectura de la línea se realiza con la función FileReadString(). En ella, con el primer parámetro se indica el manejador, y el segundo - obligatorio - se utiliza para indicar el número de símbolos a leer.

En general, el principio de escritura/lectura de líneas es muy parecido al principio de trabajo con la matriz: la línea es análoga a toda la matriz, y un símbolo de la línea es análogo a un elemento de la matriz, por eso no vamos a analizar un ejemplo de escritura/lectura de líneas. Vamos a ver un ejemplo más complejo: la escritura y lectura de una matriz. Primero escribiremos en el archivo la variable int con el tamaño de la matriz, después en el ciclo escribiremos elementos aparte, anotando antes de cada uno de ellos la variable int con su tamaño: 

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   
   string a[]={"Línea-1","Línea-2","Línea-3"}; // matriz escrita 

   FileWriteInteger(h,ArraySize(a),INT_VALUE); // escribimos el tamaño de la matriz
   
   for(int i=0;i<ArraySize(a);i++){
      FileWriteInteger(h,StringLen(a[i]),INT_VALUE); // escribimos el tamaño de la línea (un elemento de la matriz)
      FileWriteString(h,a[i]);
   }

   FileClose(h);
   Alert("Se ha escrito el archivo");
}

En los anexos, este código se encuentra en el script "sTestFileWriteStringArray".

Al realizar la lectura, en primer lugar leemos el tamaño de la matriz, después cambiamos su tamaño y leeremos elementos particulares, leyendo de forma preliminar su tamaño:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }   
   
   string a[]; // en esta matriz leeremos el archivo
   
   int s=FileReadInteger(h,INT_VALUE); // leemos el tamaño del archivo
   ArrayResize(a,s); // cambiamos el tamaño de la matriz
   
   for(int i=0;i<s;i++){ // de todos los elementos de la matriz
      int ss=FileReadInteger(h,INT_VALUE); // leemos el tamaño de la línea
      a[i]=FileReadString(h,ss); // leemos la línea
   }

   FileClose(h);

   // mostramos la matriz leída
   Alert("=== Comienzo ===");
   for(int i=0;i<ArraySize(a);i++){
      Alert(a[i]);
   }

}

En los anexos, este código se encuentra en el script "sTestFileReadStringArray". 

Carpeta común para los archivos

Hasta ahora hemos tratado con archivos ubicados en la carpeta MQL5/Files. Pero este no es el único sitio donde pueden encontrarse los archivos. En el editor MetaEditor, en el menú principal, ejecute el comando: Archivo - Abrir carpeta general de datos. Se abrirá una carpeta en la que usted podrá ver la carpeta Files. En esta carpeta también pueden ubicarse archivos accesibles desde otros programas escritos en MQL5. Preste atención a la ruta hasta ellos (fig. 12):


Fig. 12. Ruta hasta la carpeta general de datos 

La ruta a la carpeta general de datos no se relaciona en modo alguno con la ruta al terminal y la ruta a la carpeta Files, con la que hemos trabajado durante todo el artículo. No importa cuántos terminales tengamos funcionando (incluidos aquellos con la clave "/portable"), para todos ellos se abrirá una carpeta común.

La ruta a las carpetas se puede determinar de forma programática. Ruta hasta la carpeta de datos (en la que se ubica la carpeta MQL5/Files, con la que hemos trabajado durante todo el artículo):

TerminalInfoString(TERMINAL_DATA_PATH);

Ruta hasta la carpeta general de datos (en la que se ubica la carpeta Files):

TerminalInfoString(TERMINAL_COMMONDATA_PATH);

Exactamente de la misma forma, se puede definir la ruta hasta el terminal (carpeta raíz en la que está instalado el terminal):

TerminalInfoString(TERMINAL_PATH);

Al igual que con la carpeta MQL5/Files, al trabajar con archivos de la carpeta general no es necesario indicar la ruta completa, basta conañadir la bandera FILE_COMMON a la combinación de banderas transmitidas a la función FileOpen(). Algunas funciones de archivo tienen directamente un parámetro para indicar la bandera de la carpeta general. A estas funciones pertenecen, por ejemplo, FileDelete(), FileMove(), FileCopy().

Copiamos el archivo "test.txt" de la carpeta MQL5/Files a la carpeta general de datos:

   if(FileCopy("test.txt",0,"test.txt",FILE_COMMON)){
      Alert("archivo copiado");
   }
   else{
      Alert("Error al copiar el archivo");
   }

En los anexos, este código se encuentra en el script "sTestFileCopy". Después de ejecutar el script en la carpeta general Files, aparecerá el archivo "test.txt". Si iniciamos el script una segunda vez, recibiremos un mensaje de error. Para evitar el mensaje de error, es necesario permitir la reescritura del archivo, y añadir la bandera FILE_REWRITE:

FileCopy("test.txt",0,"test.txt",FILE_COMMON|FILE_REWRITE)

Ahora copiamos el archivo desde la carpeta general a esta misma carpeta con otro nombre (en los anexos, el script "sTestFileCopy2"):

FileCopy("test.txt",FILE_COMMON,"test_copy.txt",FILE_COMMON)

Y ahora desde la carpeta general a la carpeta MQL5/Files (en los anexos, el script "sTestFileCopy3"):

FileCopy("test.txt",FILE_COMMON,"test_copy.txt",0)

La función FileMove() se llama de forma idéntica, solo que no se crea una copia. El archivo se desplaza (o se renombra).

Archivos en el simulador

Todo el trabajo con archivos que hemos visto hasta este momento ha estado relacionado con el funcionamiento de programas MQL5 (scripts, expertos, indicadores) que funcionan en la cuenta (iniciados en el gráfico). Al trabajar con el experto en el simulador, todo funciona de una forma completamente distinta. El simulador en MetaTrader5 tiene la posibilidad de realizar una simulación distribuida (en la nube) usando agentes remotos. Grosso modo, las pasadas de optimización 1-10 (las cifras, por supuesto, son condicionales) se realizan en una computadora, mientras que las pasadas 11-20 tienen lugar en otra computadora. Por consiguiente, debido a ello, surgen ciertas complicaciones también al trabajar con archivos. Vamos a ver estas peculiaridades y a mostrar los principios que hay que respetar al trabajar con archivos en el simulador.

Si bien durante el trabajo habitual con archivos, la función FileOpen() recurre a los archivos ubicados en la carpeta MQL5/Files, que se encuentra en la carpeta de datos del terminal, durante la simulación se recurre a los archivos en la carpeta MQL5/Files, que se encuentra en la carpeta del agente de simulación. Si los archivos solo son necesarios durante una pasada de la optimización (o una única simulación), por ejemplo, para guardar datos sobre una posición o sobre órdenes pendientes, entonces toda estará normal, solo será necesario limpiar los archivos antes de la pasada siguiente (al inicializar el experto). Si el archivo se va a usar para determinar algunos parámetros de trabajo con el experto, lo más probable es que se cree de forma manual, entonces se ubicará en la carpeta MQL5/Files de la carpeta de datos del terminal. Esto significa que el experto a simular no lo verá. Para que el experto tenga acceso a este archivo, hay que transmitirlo al agente. Esto se hace en el experto, indicando la propiedad "#property tester_file". De esta forma, se puede transmitir cualquier número de archivos:

#property tester_file "file1.txt"
#property tester_file "file2.txt"
#property tester_file "file3.txt"

Sin embargo, si el archivo se indica a través de " "#property tester_file", entonces si escribimos en él, el experto escribirá en la copia del archivo ubicada en la carpeta del agente de simulación. El archivo en la carpeta de datos del terminal permanecerá sin cambios. La posterior lectura del archivo por parte del experto se ejecutará desde la carpeta del agente, es decir, se leerá el archivo modificado. Esto significa que si durante la simulación y optimización el experto necesita guardar ciertos datos para su posterior análisis, el guardado de datos en el archivo no será posible, para ello habrá que usar frames.

Si no se usan los agentes remotos, es posible trabajar con los archivos desde la carpeta general (indicando la bandera FILE_COMMON al cerrar el archivo). En esta caso, no hará falta indicar el nombre del archivo en las propiedades del experto, y el experto podrá escribir en el archivo. En resumen, al usar la carpeta común de datos, no existe ninguna particularidad a la hora de trabajar con los archivos, excepto que usted deberá usar agentes remotos. Además, será necesario seguir atentamente los nombres de los archivos, para que el experto simulado no estropee el archivo usado por el experto que trabaja en realidad. El trabajo en el simulador se puede definir de forma programática:

MQLInfoInteger(MQL5_TESTER)

En el caso de la simulación, es necesario usar otros nombres para los archivos.

Acceso común a los archivos

Como ya se ha mencionado en este artículo, si un archivo está abierto, no lograremos abrirlo una segunda vez: obtendremos un error. Si ya hay un programa que esté trabajando con el archivo, entonces trabajar con él en otro programa no será posible hasta que no esté cerrado. Sin embargo, MQL5 proporciona la posibilidad de usar los archivos de forma común. Para ello, al abrir un archivo es necesario indicar la bandera adicional FILE_SHARE_READ (acceso general para la lectura)  o FILE_SHARE_WRITE (acceso general para la escritura). A pesar de que dispongamos de estas posibilidades, es necesario usarlas con mucho cuidado y comprendiendo lo que se hace. Los sistemas operativos en la actualidad son multitarea, por eso nadie puede garantizar que la secuencia en la ejecución de las acciones escritura - lectura se dé correctamente. Si permitimos el acceso general en la escritura y la lectura, puede resultar que un programa escriba mientras que otro lea de inmediato lo que aún no se ha escrito por completo. De esta forma, será necesario tomar medidas para que la sincronización cuando los programas recurran al archivo sea adecuada. Esta tarea es bastante compleja y traspasa los límites del presente artículo. Además, lo más probable es que podamos arreglárnoslas sin sincronización y sin acceso conjunto a los archivos (lo que demostraremos un poco más abajo, utilizando archivos para intercambiar información entre terminales).

El único caso en el que se puede abrir un archivo tranquilamente con derechos comunes de lectura (FILE_SHARE_READ) y cuando el permiso de tal acceso está justificado, es cuando el archivo se usa para definir algunos parámetros del funcionamiento de un experto o indicador, por ejemplo, un archivo de configuración. El archivo se crea manualmente o con un script adicional, después es leído por varios ejemplares del experto o indicador durante la inicialización. Durante la inicialización, varios expertos pueden intentar abrir el archivo casi al mismo tiempo, por eso es conveniente proporcionarles tal posibilidad. Además, tendremos la garantía de que mientras un archivo es leído, no se está escribiendo en el mismo.  

Usando archivos para intercambiar datos entre terminales

Guardando los archivos en la carpeta general, se posibilitará el intercambio de datos entre terminales. Por supuesto que el uso del archivo con tales objetivos no es la mejor solución, pero en ciertos casos puede servir de ayuda. La tarea se resuelve fácilmente: no se usa ningún acceso común al archivo, el archivo se abre de la forma habitual. Mientras se escribe, nadie podrá abrir el archivo. Al finalizar la anotación, el archivo se cierra y otros ejemplares de los programas podrán leerlo. Más abajo se adjunta el código de la función de transmisión (escritura) de datos del script "sTestFileTransmitter":

bool WriteData(string str){
   for(int i=0;i<30 && !IsStopped();i++){ // varios intentos
      int h=FileOpen("data.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);
      if(h!=INVALID_HANDLE){ // se ha logrado abrir el archivo
         FileWriteString(h,str); // escribimos los datos  
         FileClose(h); // cerramos el archivo
         Sleep(100); // hacemos una pausa mayor, para que otros programas 
		     // tengan la posibilidad garantizada de leer los datos
         return(true); // en caso de éxito, retornamos true
      }
      Sleep(1); // hacemos una pausa mínima, para que otros programas 
                // finalicen la lectura del programa y puedan captar 
                // el momento en el que el archivo estará libre
   }
   return(false); // si no se ha logrado escribir los datos
}

Se hacen varios intentos de abrir el archivo. Si el archivo se ha abierto, se realiza la anotación, se cierra el archivo y se hace una pausa relativamente larga (función Sleep(100)), para que otros programas tengan tiempo de abrir el archivo. En caso de error de apertura, se ejecuta una cierta pausa (función Sleep(1)), para "captar" lo más rápidamente posible el momento cuando el archivo estará libre.

La función de recepción (lectura) funciona según el mismo principio. En los anexos, el script con la función de recepción tiene el nombre "sTestFileReceiver", el script mostrará los datos obtenidos con la función Comment(). Inicie el script transmisor en un gráfico, y el script receptor en otro gráfico (o en otro ejemplar del terminal). 

Otras funciones

Ya casi hemos analizado todas las funciones para trabajar con archivos, solo quedan las utilizadas con menor frecuencia: FileSize(), FileTell(), FileFlush(). La función FileSize() retorna el tamaño un archivo abierto en bytes:

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }
   ulong size=FileSize(h);
   FileClose(h);
   Alert("Tamaño del archivo "+IntegerToString(size)+" (byte)");
}

En los anexos, este código se encuentra en el script "sTestFileSize". Al ejecutar este script, se abrirá una ventana con un mensaje sobre el tamaño del archivo. 

La función FileTell() retorna la posición del puntero de archivo del archivo abierto. La función se usa con tan poca frecuencia, que resulta complicado incluso pensar un ejemplo para ella. Simplemente mencionaremos su existencia y la recordaremos en caso necesario.

La función FileFlush() es más útil que práctica. Como ya hemos escrito en la documentación, la función ejecuta "el reseteo en el disco de todos los datos que quedan en el búfer de archivo de entrada-salida". El efecto de la llamada de la función es el mismo que el del cierre del archivo y su nueva apertura (solo que más económico, y el puntero de archivo sigue siendo el mismo que era). Como sabemos, los archivos se almacenan en forma de entradas en el disco duro. Sin embargo, mientras el archivo está abierto, la escritura se realiza en el búfer, y no en el disco. La escritura en el disco tendrá lugar al cerrar el archivo. Por eso, si se da un cierre de emergencia del programa, como el archivo no estará cerrado, los datos no se guardarán. Si después escribir cada vez datos, llamamos a FileFlush(), los datos se guardarán en el disco, y el cierre forzoso del programa no dará problemas.

Trabajando con las carpetas

Aparte del trabajo con archivos, en MQL5 hay varias funciones para trabajr con carpetas: FolderCreate()FolderDelete()FolderClean(). La función FolderCreate se usa para crear carpetas. Todas las funciones tienen dos parámetros. El primer parámetro es obligatorio para el nombre de la carpeta. El segundo, adicional, es para la bandera FILE_COMMON (para trabajar con carpetas en la carpeta general de datos). 

FolderDelete() se usa para eliminar la carpeta indicada. Solo se puede eliminar una carpeta vacía. Pero la limpieza del contenido de una carptea no es ningún problema, para ello existe la función FolderClean(), la función limpia por completo la carpeta indicada, y elemina además las carpetas y archivos incluidos. 

Obteniendo la lista de archivos

A veces al trabajar con archivos hay que encontrar un archivo cuyo nombre no conocemos exactamente. El principio del nombre del archivo tendrá que sernos conocido, además, el nombre deberá tener un final numérico desconocido, por ejemplo, "file1.txt", "file2.txt", etc. En este caso, podemos obtener el nombre de los archivos por la máscara, usando las funciones FileFindFirst(), FileFindNext(), FileFindClose(). Los datos de la función no solo encuentran archivos, sino también carpetas. El nombre de la carpeta puede diferenciarse del nombre del archivo en la barra inversa al final.

Escribiremos información útil para obtener una lista de archivos y carpetas. En una matriz reuniremos los nombres de los archivos, en otra, los nombres de las carpetas:

void GetFiles(string folder, string & files[],string & folders[],int common_flag=0){

   int files_cnt=0; // contador de archivos
   int folders_cnt=0; // contador de carpetas   
   
   string name; // variable para obtener el nombre del archivo o carpeta 

   long h=FileFindFirst(folder,name,common_flag); // obtenemos el manejador de la búsqueda y el nombre 
                                      // del primer archivo/carpeta (si él/ella existe)
   if(h!=INVALID_HANDLE){ // hay aunque sea un archivo o carpeta
      do{
         if(StringSubstr(name,StringLen(name)-1,1)=="\\"){ // carpeta
            if(folders_cnt>=ArraySize(folders)){ // comprobamos el tamaño de la matriz, 
                                                 // lo aumentamos, si es necesario
               ArrayResize(folders,ArraySize(folders)+64);
            }
            folders[folders_cnt]=name; // enviamos el nombre de la carpeta a la matriz
            folders_cnt++; // leemos las carpetas        
         }
         else{ // archivo
            if(files_cnt>=ArraySize(files)){ // comprobamos el tamaño de la matriz, 
                                             // lo aumentamos, si es necesario
               ArrayResize(files,ArraySize(files)+64);
            }
            files[files_cnt]=name; // enviamos el nombre del archivo a la matriz
            files_cnt++; // leemos los archivos
         }
      }
      while(FileFindNext(h,name)); // obtenemos el nombre del siguiente archivo o carpeta
      FileFindClose(h); // finalizamos la búsqueda
   }
   ArrayResize(files,files_cnt); // cambiamos el tamaño de la matriz de acuerdo con 
                                 // la cantidad real de archivos
   ArrayResize(folders,folders_cnt); // cambiamos el tamaño de la matriz de acuerdo 
                                        // con la cantidad real de carpetas
}

Vamos a experimentar con esta función, la llamaremos desde el script de la siguiente forma: 

void OnStart(){

   string files[],folders[];

   GetFiles("*",files,folders);
   
   Alert("=== Comienzo ===");
   
   for(int i=0;i<ArraySize(folders);i++){
      Alert("Carpeta: "+folders[i]);
   }      
   
   for(int i=0;i<ArraySize(files);i++){
      Alert("archivo: "+files[i]);
   }

}

 En los anexos, el script tiene el nombre "sTestFileGetFiles". Preste atención a la máscara "*":

GetFiles("*",files,folders);

Con esta máscara se podrán encontrar todos los archivos y carpetas ubicados en la carpeta MQL5/Files.

Para encontrar todos los archivos y carpetas que empiecen con la palabra "test", servirá la máscara "test*". Si necesitamos solo los archivos con la extensión "txt", hará falta la máscara "*.txt", etcétera. Intente crear una carpeta y dentro de ella varios archivos, por ejemplo, una carpeta "folder1". Para obtener la lista de archivos ubicados en ella, le servirá la máscara "folder1\\*".  

Página de códigos

En este artículo se ha usado con frecuencia en los ejemplos de código la función FileOpen(). Vamos a hablar de uno de sus parámetros que hemos dejado hasta el momento sin estudiar. Se trata de codepage, la página de códigos. La página de códigos es un recuadro de correspondencias entre símbolos de texto y su valor numérico. Será más comprensible con un ejemplo con la codificación ANSI. En esta codificación, en el recuadro de símbolos hay un total de 256, es decir, para diferentes lenguajes se usa su propia página de códigos, que se define en los ajustes del sistema operativo. A esta página de códigos le corresponde la constante CP_ACP, con la que se llama por defecto la función FileOpen(). Es muy poco probable que alguien necesite algún día usar otra página de códigos, por eso no tiene sentido entrar en detalles sobre este tema, basta con familiarizarse en general.  

Trabajando con archivos sin limitaciones

A veces resulta necesario trabajar con archivos más allá de los límites del "sandbox" de archivo del terminal (fuera de la carpeta MQL5/Files y de la carpeta general). Esto puede ampliar significativamente la funcionalidad de los programas en MQL5: se podrán procesar los archivos con código fuente, introducir cambios en ellos de forma automática, generar archivos de imágenes para la interfaz gráfica, generar código, etcétera. Si usted va a hacer esto para sí mismo, o bien va a solicitar los servicios de un programador en el que usted confíe, resulta permisible. En los artículos hay material sobre este tema: el artículo Operaciones de archivo a través de WinAPI. Existe también un método más sencillo. MQL5 dispone de todos los medios para trabajar con archivos, por eso basta con trasladar el archivo necesario al "sandbox" del terminal, ejecutar con él las acciones requeridas y trasladarlo de vuelta. Para ello solo es necesaria una función WinAPI: CopyFile.

Para que el programa MQL5 pueda utilizar funciones WinAPI, es necesario permitir su uso. Dicho permiso se activa en los ajustes del terminal (Menú principal - Servicio - Ajustes - Asesores - Permitir la importación de DLL), en este caso, se activa el permiso general en el terminal para todos los programas iniciados de nuevo. Es posible, en lugar de activar el permiso general, otorgarlo para un programa iniciado. Si el programa recurre a las funciones WinAPI o DLL semejantes, en su ventana de propiedades aparecerá la pestaña "Dependencias" y en ella, la opción "permitir la importación de DLL".

Existen dos variantes de la función CopyFile: CopyFileA() y la más moderna CopyFileW(). Ambas funciones se pueden utilizar, pero para la función CopyFileA() es necesario transformar los argumentos de línea. Podemos ver cómo se hace esto en el artículo Principios de programación en MQL5: Líneas de caracteres en el apartado "Llamando funciones API". Es mejor usar la función CopyFileW()moderna, para ella los argumentos de línea se indican como son, sin transformaciones sobrantes. 

Para usar la función CopyFileW(), primero hay que importarla. Se encuentra en la biblioteca "kernel32.dll":

#import "kernel32.dll"
   int CopyFileW(string,string,int);
#import

En los anexos, este código se encuentra en el script con el nombre "sTestFileWinAPICopyFileW".

El script compilará el archivo con su propio código fuente a la carpeta MQL5/Files:

void OnStart(){
   
   string src=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Scripts\\"+MQLInfoString(MQL_PROGRAM_NAME)+".mq5";
   string dst=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\"+MQLInfoString(MQL_PROGRAM_NAME)+".mq5";
   
   if(CopyFileW(src,dst,0)==1){
      Alert("archivo copiado");
   }
   else{
      Alert("No se ha logrado copiar el archivo");   
   }
}

Si la función CopyFileW() funciona con éxito, retornará 1, de lo contrario, 0. El tercer parámetro de la función indica si se puede reescribir el archivo en el caso de que exista el archivo de destino: 0 — es posible, 1 — no es posible. Inicie el script. Si funciona correctamente, compruebe la carpeta MQL5/Files. 

Tenga en cuenta que el sistema operativo pone sus propias limitaciones en cuanto al copiado de archivos. Existen los así llamados "parámetros de control de las cuentas de usuario". Si están activados, en ciertos sitios (o desde ciertos sitios), los archivos no podrán ser copiados. Por ejemplo, no es posible copiar un archivo a la raíz de la unidad de sistema.

Algunos scripts útiles

Aparte de crear funciones útiles para trabajar con archivos, crearemos también dos scripts útiles para ilustrar el material analizado en el artículo. Vamos a crear un script para exportar cotizaciones a un archivo csv y otro script para exportar los resultados comerciales también a un archivo csv.

El script para importar las cotizaciones tendrá parámetros para definir la fecha de inicio y la fecha de finalización de los datos y parámetros que determinan si usar estas fechas o si exportar todos los datos al completo. Para que en el script se abra la ventana de propiedades, es necesario indicar la propiedad correspondiente:

#property script_show_inputs

A continuación se declaran los parámetros externos:

input bool     UseDateFrom = false; // Indicar la fecha de incio
input datetime DateFrom=0; // Fecha de inicio
input bool     UseDateTo=false; // Indicar la fecha de finalización
input datetime DateTo=0; // Fecha de finalización


Escribimos el código en la función OnStrat() del script. Definimos la fecha de acuerdo con los parámetros del script:

   datetime from,to;
   
   if(UseDateFrom){
      from=DateFrom;
   }
   else{
      int bars=Bars(Symbol(),Period());
      if(bars>0){
         datetime tm[];
         if(CopyTime(Symbol(),Period(),bars-1,1,tm)==-1){
            Alert("Error al definir el inicio de los datos, inténtelo un poco más tarde");
            return;
         }
         else{
            from=tm[0];
         }
         
      }
      else{
         Alert("El marco temporal se está construyendo, inténtelo un poco más tarde");
         return;
      }
   }
   
   if(UseDateTo){
      to=DateTo;
   }
   else{
      to=TimeCurrent();
   }   

Si se usan varibables para definir la fecha, entonces se toman sus valores, de lo contrario, se usa TimeCurrent() para la hora de la finalización, y la hora de inicio se determina con la hora de la primera barra. 

Una vez dispongamos de las fechas, copiamos las cotizaciones en una matriz de tipo MqlRates:

   MqlRates rates[];
   
   if(CopyRates(Symbol(),Period(),from,to,rates)==-1){
      Alert("Error al copiar las cotizaciones, inténtelo un poco más tarde");
   }

Guardamos los datos de esta matriz en un archivo:

   string FileName=Symbol()+" "+IntegerToString(PeriodSeconds()/60)+".csv";
   
   int h=FileOpen(FileName,FILE_WRITE|FILE_ANSI|FILE_CSV,";");
   
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }
   
   // escribimos los datos en un archivo en el formato: Time, Open, High, Low, Close, Volume, Ticks
   
   // la primera línea es para saber dónde se encuentra cada cosa
   FileWrite(h,"Time","Open","High","Low","Close","Volume","Ticks");  
   
   for(int i=0;i<ArraySize(rates);i++){
      FileWrite(h,rates[i].time,rates[i].open,rates[i].high,rates[i].low,rates[i].close,rates[i].real_volume,rates[i].tick_volume);
   }
   
   FileClose(h);

   Alert("Se ha guardado con éxito, mirar  el archivo "+FileName);   

Si se ha ejecutado con éxito, el script abrirá una ventana con un mensaje que informará sobre el guardado correcto del archivo o que dará un mensaje de error. En los anexos, el script tiene el nombre "sQuotesExport".

Ahora vamos a pasar al script de guardado de la historia comercial. El inicio es aproximadamente el mismo: primero las variables externas, solo que al definir la hora de inicio es más sencillo, al solicitar la historia, bastará con indicar la hora de inicio 0:

   datetime from,to;
   
   if(UseDateFrom){
      from=DateFrom;
   }
   else{
      from=0;
   }
   
   if(UseDateTo){
      to=DateTo;
   }
   else{
      to=TimeCurrent();
   }  

Seleccionamos la historia: 

   if(!HistorySelect(from,to)){
      Alert("Error al seleccionar la historia");
      return;
   }

Abrimos el archivo:

   string FileName="history.csv";
   
   int h=FileOpen(FileName,FILE_WRITE|FILE_ANSI|FILE_CSV,";");
   
   if(h==INVALID_HANDLE){
      Alert("Error al abrir el archivo");
      return;
   }

Escribimos la primera línea con el nombre de los campos:

   FileWrite(h,"Time","Deal","Order","Symbol","Type","Direction","Volume","Price","Comission","Swap","Profit","Comment");     

Al pasar por todas las operaciones, escribimos en el archivo las operaciones buy y sell:

   for(int i=0;i<HistoryDealsTotal();i++){
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0){         
         long type=HistoryDealGetInteger(ticket,DEAL_TYPE);         
         if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL){      
            long entry=HistoryDealGetInteger(ticket,DEAL_ENTRY);      
            FileWrite(h,(datetime)HistoryDealGetInteger(ticket,DEAL_TIME),
                        ticket,
                        HistoryDealGetInteger(ticket,DEAL_ORDER),
                        HistoryDealGetString(ticket,DEAL_SYMBOL),
                        (type==DEAL_TYPE_BUY?"buy":"sell"),
                        (entry==DEAL_ENTRY_IN?"in":(entry==DEAL_ENTRY_OUT?"out":"in/out")),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_VOLUME),2),
                        HistoryDealGetDouble(ticket,DEAL_PRICE),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_COMMISSION),2),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_SWAP),2),
                        DoubleToString(HistoryDealGetDouble(ticket,DEAL_PROFIT),2),
                        HistoryDealGetString(ticket,DEAL_COMMENT)                     
            );
         }
      }
      else{
         Alert("Error al seleccionar la operación, inténtelo de nuevo");
         FileClose(h);
         return;
      }
   }

Preste atención: para los tipos de operación (buy/sell) y las direcciones (in/out) se ejecuta la transformación de los valores en líneas, mientras que ciertos valores del tipo double se transforman en líneas con dos decimales. 

Al final cerramos el archivo y mostramos el mensaje: 

   FileClose(h);
   Alert("Se ha guardado con éxito, mirar  el archivo "+FileName); 

 En los anexos, este script tiene el nombre "sHistoryExport".

Materiales adicionales sobre el tema

En el apartado de artículos podrá encontrar una gran cantidad de materiales relacionados de una u otra forma con el trabajo con archivos, merece la pena echarle un vistazo:

Conclusión

Ya hemos estudiado todas las funciones para trabajar con archivos en MQL5. Puede parecer un tema muy limitado, pero ha resultado ser bastante amplio. Además, ciertas cuestiones relacionadas con el trabajo con archivos solo se han analizado de forma superficial, sin mostrar suficientes ejemplos prácticos. No obstante, hemos profundizado con detalle en las tareas más frecuentes de trabajo con archivos, incluidas las particularidades a la hora de operar con archivos en el simulador. Asimismo, hemos creado varias funciones útiles. Por otra parte, todos los ejemplos ilustrativos han sido orientados a la práctica y ejecutados de forma lógica. Todo el código en el artículo se adjunta en forma de scripts.

Archivos adjuntos

  1. sTestFileRead — leer una línea de un archivo de texto ANSI y mostrarla en la ventana de mensajes.
  2. sTestFileReadToAlert — leer todas las líneas de un archivo de texto ANSI y mostrarlas en la ventana de mensajes.
  3. sTestFileCreate — crear un archivo de texto ANSI.
  4. sTestFileAddToFile — añadir líneas a un archivo de texto ANSI.
  5. sTestFileChangeLine2-1 — intento incorrecto de cambio de una línea concreta en un archivo de texto ANSI.
  6. sTestFileChangeLine2-2 — nuevo intento incorrecto de cambio de una línea concreta en un archivo de texto ANSI.
  7. sTestFileChangeLine2-3 — sustitución de una línea en un archivo de texto ANSI reescribiendo todo el archivo.
  8. sTestFileReadFileToArray — función útil de lectura de un archivo de texto ANSI en la matriz.
  9. sTestFileCreateCSV — crear un archivo CSV ANSI.
  10. sTestFileReadToAlertCSV — leer un archivo CSV ANSI en la ventana de mensajes por campos.
  11. sTestFileReadToAlertCSV2 — leer un archivo CSV ANSI en la ventana de mensajes por campos con separador de líneas. 
  12. sTestFileReadFileToArrayCSV — leer un archivo CSV ANSI en la matriz de estructuras.
  13. sTestFileWriteArrayToFileCSV — escribir la matriz de una sola línea en un archivo CSV ANSI.
  14. sTestFileReadToAlertUTF — leer un archivo de texto UNICODE y mostrarlo en la ventana de mensajes.
  15. sTestFileCreateUTF — crear un archivo de texto UNICODE.
  16. sTestFileCreateBin — crear un archivo binario y escribir en él tres variables double.
  17. sTestFileReadBin — leer tres variables double del archivo binario.
  18. sTestFileChangeBin — reescribir la segunda variable double en el archivo binario.
  19. sTestFileReadBin2 — leer la tercera variable double del archivo binario. 
  20. sTestFileWriteStructBin — escribir la estructura en el archivo binario.
  21. sTestFileReadStructBin — leer una estructura del archivo binario.
  22. sTestFileReadStructBin2 — leer una variable del archivo binario con estructuras.
  23. sTestFileCheckUnicode — comprobar el tipo de archivo (ANSI o UNCODE).
  24. sTestFileWriteArray — escribir la matriz en el archivo binario.
  25. sTestFileReadArray — leer una matriz del archivo binario
  26. sTestFileWriteArray2 — escribir dos matrices en el archivo binario.
  27. sTestFileReadArray2 — leer dos matrices del archivo binario.
  28. sTestFileWriteStringArray — escribir una matriz de línea en el archivo binario.
  29. sTestFileReadStringArray — leer una matriz de línea del archivo binario.
  30. sTestFileCopy — copiar un archivo de la carpeta MQL5/Files a la carpeta general.
  31. sTestFileCopy2 — copiar un archivo en la carpeta general.
  32. sTestFileCopy3 — copiar un archivo de la carpeta general a la carpeta MQL5/Files. 
  33. sTestFileTransmitter — script-transmisor de datos a través del archivo en la carpeta general.
  34. sTestFileTransmitter — script-receptor de datos a través del archivo en la carpeta general.
  35. sTestFileSize — definir el tamaño del archivo.
  36. sTestFileGetFiles — obtener la lista de archivos según la máscara.
  37. sTestFileWinAPICopyFileW — ejemplo de uso de la función WinAPI CopyFileW().
  38. sQuotesExport — script para exportar cotizaciones.
  39. sHistoryExport — script para guardar la historia comercial.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2720

Archivos adjuntos |
files.zip (27.47 KB)
Asesor experto multiplataforma: Órdenes Asesor experto multiplataforma: Órdenes
MetaTrader 4 y MetaTrader 5 usan reglas diferentes en el procesamiento de solicitudes comerciales. En este artículo se discutirá la posibilidad de usar un objeto de clase para representar las operaciones procesadas por el servidor, para que en lo sucesivo el asesor pueda trabajar con ellas independientemente de la versión de la plataforma comercial y del modo ejecutado.
LifeHack para tráders: Optimización "silenciosa" o Trazando la distribución de trades LifeHack para tráders: Optimización "silenciosa" o Trazando la distribución de trades
Análisis de la historia comercial y la construcción de los gráficos HTML de distribuciónde de los resultados comerciales dependiendo de la hora de entrada en la posición. Los gráficos se representan en tres segmentos, por horas, días y meses.
Principios de programación en MQL5: Variables globales del terminal  MetaTrader 5 Principios de programación en MQL5: Variables globales del terminal MetaTrader 5
Las variables globales del terminal es un medio imprescindible durante la programación de los Asesores Expertos complejos y seguros. Después de aprender a trabajar con las variables globales, ya no podrá imaginar la creación de los asesores expertos en MQL5 sin usarlas.
Interfaces gráficas X: Control "Gráfico estándar" (build 4) Interfaces gráficas X: Control "Gráfico estándar" (build 4)
En este artículo vamos a analizar el control de la interfaz gráfica como «Gráfico estándar». Nos permitirá crear los arrays de objetos-gráficos con posibilidad del desplazamiento horizontal sincronizado del gráfico. Aparte de eso, continuaremos optimizando el código de la librería para reducir el consumo de los recursos de CPU.