English Русский 中文 Español Deutsch 日本語
MQL5 Programações Básicas: Arquivos

MQL5 Programações Básicas: Arquivos

MetaTrader 5Exemplos | 2 dezembro 2016, 15:43
10 427 0
Dmitry Fedoseev
Dmitry Fedoseev

Conteúdos

Introdução

Como em muitas outras linguagens de programação, o MQL5 apresenta funções para trabalhar com arquivos. Embora trabalhar com arquivos não seja uma tarefa muito comum, ao desenvolver Expert Advisors e indicadores no MQL5, cada desenvolvedor irá ter que lidar com eles mais cedo ou mais tarde, porém a variedade de problemas ao trabalhar com grande quantidade de arquivos é enorme. Inclui a geração de um relatório de negociação personalizado, a criação de arquivos especiais com parâmetros complexos para um EA ou indicador, a leitura de dados de mercado (por exemplo, um calendário de notícias), etc. Este artigo abrange todas as funções para trabalhar com arquivos no MQL5, Cada uma delas é acompanhada por uma simples tarefa prática destinada a aprimorar suas habilidades. Além das tarefas, o artigo considera uma série de funções úteis que podem ser aplicadas na prática.

Clique aqui para a documentação do MQL5 contendo a descrição das funções de arquivo.  

Ler um arquivo de texto

A função mais simples e mais utilizada é a leitura dos arquivos de texto. Pularemos direto para a prática. Abra o MetaEditor. Selecione Arquivo — Abrir Pasta de Dados. Abra a pasta MQL5 na nova janela. Em seguida, abra a pasta Arquivos. Esta pasta contém arquivos disponíveis para o processamento das funções de arquivo MQL5. Essa restrição garante a segurança dos dados. Os usuários do MetaTrader compartilham ativamente aplicativos do MQL5. Sem a restrição, seria muito fácil para intrusos causarem danos ao seu PC, eliminando/corrompendo arquivos importantes ou roubando dados pessoais.

Crie um arquivo de texto na recém-inaugurada pasta MQL5/Arquivo. Para fazer isso, clique em algum lugar dentro da pasta e selecione Novo — Documento de Texto. Nomeie o arquivo "test". Seu nome completo deve ser "test.txt". Eu recomendo que você habilite a exibição de extensões de arquivos em seu PC.

Em seguida, renomeie o arquivo, e abra-o. Ele será aberto no editor do bloco de notas. Escreva 2-3 linhas de texto no arquivo e salve-o. Verifique se a codificação ANSI está selecionada na lista suspensa na parte inferior da janela Salvar (Fig. 1).


Fig. 1. Salvar um arquivo de texto no bloco de notas do Windows. A seta vermelha mostra a codificação do arquivo selecionado 

Agora, vamos ler o arquivo por meio do MQL5. Crie um script no MetaEditor e nomeie-o como sTestFileRead.

O arquivo deve ser aberto antes de ser lido ou escrito, e em seguida deverá ser fechado. O arquivo é aberto pela função FileOpen() com dois parâmetros obrigatórios. O primeiro é o nome do arquivo. Devemos especificar o "test.txt" aqui. Observe que especificamos o caminho da pasta MQL5/Arquivos, e não o caminho completo. O segundo parâmetro é a combinação de flags, definindo o modo de como trabalhar com arquivos. O arquivo será lido, portanto, deve-se especificar a flag FILE_READ. O "test.txt" é um arquivo de texto que aplica a codificação ANSI, issol significa que deve-se utilizar mais duas flags: FILE_TXT e FILE_ANSI. As flags são combinadas a partir da operação lógica "ou" e são indicadas pelo símbolo "|".

A função FileOpen() retorna o handle de arquivo. Não entraremos em detalhes sobre a função handle. Apenas diremos que é um valor numérico (int) a ser usado em vez da string do nome do arquivo. A string do nome do arquivo é especificada quando um arquivo é aberto, enquanto o handle é usado posteriormente para executar ações com esse mesmo arquivo.

Vamos abrir o arquivo (escreva o código na função OnStart() do script sTestFileRead):

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

Depois disso, verifique se o arquivo está realmente aberto. Isso é feito ao verificar o valor do handle recebido:

if(h==INVALID_HANDLE){
   Alert("Error opening file");
   return; 
}

O erro de abertura do arquivo é bastante comum. Se o arquivo já estiver aberto, ele não poderá ser aberto pela segunda vez. O arquivo pode ser aberto em alguns aplicativo de terceiros. Por exemplo, o arquivo pode ser aberto no Bloco de notas do Windows e no MQL5 simultaneamente. Mas se ele é aberto no Microsoft Excel, então ele pode ser aberto em qualquer outro lugar.  

A leitura de dados de um arquivo de texto (aberto com a flag FILE_TXT) é feito pela função FileReadString(). A leitura é realizada linha a linha. Uma função de chamada lê uma única linha do arquivo. Leremos uma linha, e o exibiremos na caixa de mensagem:

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

Feche o arquivo:

FileClose(h);

Observe que ao chamar as funções FileReadString() e FileClose(), elas são executada especificando o handle recebido (variável h), quando se abre o arquivo pela função FileOpen().

Agora, você pode executar o script sTestFileRead. Se acontecer algo de errado, compare seu código com o arquivo sTestFileRead anexado abaixo. A janela com a primeira linha do arquivo "test.txt" deve aparecer como resultado da operação do script (Fig. 2).

 
Fig. 2. O resultado da operação do script  sTestFileRead

Até o momento, somente lemos uma linha do arquivo "test.txt". Para ler os dois restantes, podemos chamar a função FileReadString() mais duas vezes, porém na prática o número de linhas de arquivo geralmente não é conhecido com antecedência. Para resolver este problema, devemos aplicar oa função FileIsEnding() e o operador while. Se chegar ao final de um arquivo, quando lê-lo, a função FileIsEnding() retorna 'verdadeira'. Vamos escrever uma função personalizada para ler todas as linhas de arquivo e exibi-las na caixa de mensagem usando a função FileIsEnding(). Ou seja, isto pode ser útil para uma variedade de experiências educacionais sobre o trabalho com arquivos. Obtemos a seguinte função:

void ReadFileToAlert(string FileName){
   int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   Alert("=== Start ===");   
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      Alert(str);   
   }
   FileClose(h);

 Vamos criar o script sTestFileReadToAlert, copiar a função para ele chamá-lo a partir da função OnStart() do script:

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

A caixa de mensagem contendo a linha "=== Start ===" e as três linhas do arquivo "test.txt". O arquivo agora é lido completamente (Fig. 3). 


Fig. 3. Nós aplicamos a função FileIsEnding() e o loop 'do while' para ler o arquivo inteiro   

Criar um arquivo de texto

Para criar um arquivo, abra-o usando a função FileOpen(). Abra o arquivo "test.txt" usando a flag FILE_READ em vez do FILE_WRITE:

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

Após abrir o arquivo, certifique-se de verificar o handle ao ler o arquivo. Se a função é executada com êxito, o novo arquivo "test.txt" será criado. Se o arquivo já existir, ele será completamente cancelado. Tenha cuidado ao abrir arquivos para escrever e não perder dados valiosos.  

A escrita no arquivo de texto é executada pela função FileWrite(). Enquanto o primeiro parâmetro define o handle de arquivo, o segundo define a linha escrita no arquivo. Uma nova linha é escrita em cada chamada da função FileWrite().

Vamos escrever no arquivo, dez linhas num loop. O código de script final (sTestFileCreate) tem o seguinte visual:

void OnStart(){
   int h=FileOpen("test.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   for(int i=1;i<=10;i++){
      FileWrite(h,"Line-"+IntegerToString(i));
   }
   FileClose(h);
   Alert("File created");
}

Após executar o código, o arquivo "test.txt" deve conter dez linhas. Para verificar o conteúdo do arquivo, abra-o no Bloco de Notas ou execute o script sTestFileReadToAlert.

Observe a função FileWrite(), pode ter mais de dois argumentos. Você pode passar multiplas variáveis string para a função e elas são combinados numa linha quando escritas. No código especificado, a chamada da função FileWrite() pode ser escrita da seguinte forma:

FileWrite(h,"Line-",IntegerToString(i));

A função irá combinar automaticamente as linhas ao escrever.

Escrever no final de um arquivo de texto

Às vezes, é necessário adicionar uma ou várias linhas de texto novas no arquivo existente, deixando o restante do conteúdo intacto. Para executar tais ações, o arquivo deve ser aberto, simultaneamente, para leitura e escrita. Isso significa que as duas flags (FILE_READ e FILE_WRITE) devem ser especificadas ao chamar a função FileOpen().

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

Caso não exista o arquivo com o nome especificado, ele será criado. Se já existir, ele é aberto enquanto seu conteúdo é mantido intacto. No entanto, se começar a escrever no arquivo de uma vez, seu conteúdo anterior é excluído, desde que a escrita seja realizada pelo começo do arquivo.

Quando se trabalha com arquivos, existe uma coisa chamada "ponteiro" — o valor numérico que indica a posição, a partir da qual a próxima entrada, ou leitura do arquivo é realizada. Ao abrir o arquivo, o ponteiro é definido automaticamente no início do arquivo. Durante a leitura ou escrita dos dados, ele é deslocada automaticamente pelo tamanho de dados lidos ou escritos. Você mesmo pode mudar o ponteiro caso seja necessário. Para fazer isso, use a função FileSeek().  

Para salvar o conteúdo anterior e adicionar o novo ao final do arquivo, realoque o ponteiro para o final do arquivo antes de escrever:

FileSeek(h,0,SEEK_END);

Os três parâmetros são enviados para a função FileSeek(): handle, valor de realocação de ponteiro e a posição da mudança são calculados. Neste exemplo, a constante SEEK_END significa o final do arquivo. Assim, o ponteiro é deslocado por 0 bytes a partir do final do arquivo (significa o deslocamento para o final).

O código de script final que deverá ser adicionado ao arquivo é o seguinte:

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   FileSeek(h,0,SEEK_END);
   FileWrite(h,"Additional line");
   FileClose(h);
   Alert("Added to file");
}

Este script também está anexado abaixo (sTestFileAddToFile). Inicie o script e verifique o conteúdo do arquivo test.txt. Cada chamada do script sTestFileAddToFile adiciona uma linha para test.txt.

Alterar uma linha específica de um arquivo de texto

No caso de arquivos de texto, a capacidade de mover livremente o ponteiro, em todo o arquivo, pode ser usado apenas para fazer adições ao arquivo e não é aplicável para fazer alterações. É impossível fazer alterações numa determinada linha de arquivo, uma vez que uma linha de arquivo é apenas um conceito de como o arquivo realmente contém uma série contínua de dados. Às vezes, tais séries contêm caracteres especiais invisíveis num editor de texto, elas indicam que as seguintes informações devem ser exibidas numa nova linha. Se definimos o ponteiro no início da linha e começamos a escrever, os dados anteriores na linha permanecerão se o tamanho dos dados escritos for menor que a linha existente. Caso contrário, os novos símbolos de linha serão eliminados, juntamente com uma parte dos dados da linha seguinte.

Tentaremos substituir a segunda linha no test.txt. Abra o arquivo para ler e escrever, leia uma linha para realocar o ponteiro para o início da segunda linha, e escrever uma nova linha composta de duas letras "AB" (script sTestFileChangeLine2-1 abaixo):

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

O arquivo test.txt obtido agora tem a seguinte aparência (Fig. 4):

 
Fig. 4. O conteúdo do arquivo de texto após uma tentativa de alterar uma linha  

Agora, temos duas linhas em vez de uma: "AB" e "-2". A "-2" é o que resta da segunda linha, onde quatro caracteres foram apagados. O motivo é que, ao escrever uma linha usando a função FileWrite(), é adicionado novos símbolos de linha ao final do texto escrito. Nos sistemas operacionais Windows, o novo símbolo de linha consiste em dois caracteres. Se adicionarmos a eles dois caracteres na linha "AB", podemos entender por que quatro caracteres foram excluídos no arquivo resultante.  

Execute o script sTestFileCreate para restaurar o test.txt, e tente substituir a segunda linha por uma mais longa. Vamos escrever "Line-12345" (script sTestFileChangeLine2-2 anexado abaixo):

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   string str=FileReadString(h);
   FileWrite(h,"Line-12345");
   FileClose(h);
   Alert("Done");
}

Vamos ver o arquivo resultante (Fig. 5):

 
Fig. 5. Resultados da segunda tentativa de alterar uma linha única do arquivo de texto  

Como a nova linha é mais longa do que a anterior, a terceira linha também foi afetada.  

A única maneira de fazer alterações em arquivos de texto é lê-los e reescrevê-los completamente. Devemos ler o arquivo no array, fazer alterações necessárias nos elementos do array, salvá-lo, linha por linha, para outro arquivo, é preciso excluir o arquivo antigo e renomear o novo. Às vezes, o array não é necessário: ao ler as linhas de um arquivo, eles podem ser gravadas em outro arquivo. Num certo ponto no tempo, as mudanças são feitas e salvas na linha necessária. Em seguida, o arquivo antigo é excluído e o novo é renomeado.

Aplicaremos a última opção (mudar sem o array). Primeiro, devemos criar um arquivo temporário. Vamos escrever a função para receber o nome original do arquivo temporário. O nome do arquivo e a extensão são transmitidos para a função. A verificação da existência do arquivo é executada (pela função padrão FileIsExists()) dentro da própria função. Se o arquivo existir, um número será adicionado até que nenhum arquivo com esse nome seja detectado. A função tem a seguinte apresentação:

string TmpFileName(string Name,string Ext){
   string fn=Name+"."+Ext; // formando nome
   int n=0;
   while(FileIsExist(fn)){ // se o arquivo existir
      n++;
      fn=Name+IntegerToString(n)+"."+Ext; // adicionar um número ao nome
   }
   return(fn);
}

Vamos criar o script sTestFileChangeLine2-3, copiar a função para ele e colocar o seguinte código na função OnStart().

Abra test.txt para leitura:

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

Receber o nome do arquivo temporário e abri-lo:

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

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

Leia o arquivo linha por linha contando-os. Todas as linhas de leitura são enviadas para o arquivo temporário e a segunda linha é substituída:

   int cnt=0;
   while(!FileIsEnding(h)){
      cnt++;
      string str=FileReadString(h);
      if(cnt==2){
         // substituir a linha
         FileWrite(tmph,"New line-2");
      }
      else{
         // reescrever a linha sem alterações
         FileWrite(tmph,str);
      }
   }

Feche ambos os arquivos:

FileClose(tmph);
FileClose(h);

Agora, tudo o que temos a fazer é excluir o arquivo original e renomear o temporário. A função padrão FileDelete() é usada para exclusão.

FileDelete("test.txt");

Para renomear o arquivo, devemos usar a função padrão FileMove() projetada para mover ou renomear arquivos. A função recebe os quatro parâmetros obrigatórios: nome do arquivo realocado (arquivo de origem), localização de arquivo flag, novo nome de arquivo (flag de destino) e substituir flag. Estando familiarizados com os nomes dos arquivos, então vamos dar uma olhada mais profunda no segundo e no quarto parâmetro — flags. O segundo parâmetro define a localização do arquivo de origem. Os arquivos disponíveis para processamento no MQL5 podem ser localizados não apenas na pasta MQL5/Files do terminal, mas também na pasta comum de todos os terminais. Consideraremos detalhadamente isto mais tarde, pdor agora, apenas definiremos 0. O último parâmetro define o local do arquivo de destino. Caso o arquivo exista, ele também pode ter uma flag adicional definindo ações. Como eliminamos o arquivo de origem (arquivo de destino), o quarto parâmetro é definido como 0:

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

Antes de executar o script sTestFileChangeLine2-3, restaure o test.txt usando o script sTestFileCreate. Após a operação do script sTestFileChangeLine2-3, o text.txt deve ter o seguinte conteúdo (Fig. 6):

 
Fig. 6. O conteúdo do arquivo após substituir a linha

Vamos voltar para a função FileMove(). Se definimos a flag FILE_REWRITE (que nos permite reescrever o arquivo de destino) como o quarto parâmetro:

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

Não é necessário excluir o arquivo de origem do script. Esta opção é usada no script sTestFileChangeLine2-3 abaixo. 

Em vez da função FileMove(), podemos usar outra função padrão FileCopy(), porém, neste caso, precisamos apagar o arquivo temporário:

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

Ler um arquivo de texto num array

Uma função útil já foi descrita neste artigo (recebendo um nome de arquivo desocupado). Agora, vamos desenvolver outra função que é freqüentemente usada ao trabalhar com arquivos — lendo um arquivo para um array. O nome do arquivo e a linha do array são transmitidos para a função. O array é transmitido através do link, e preenchido com o conteúdo do arquivo na função. A função retorna verdadeiro/falso dependendo de seus resultados de operação. 

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("Erro ao abrir o arquivo %s # %i",FileName,ErrNum);
      return(false);
   }
   int cnt=0; // use a variável para contar o número de linhas de arquivo
   while(!FileIsEnding(h)){
      string str=FileReadString(h); // leia a próxima linha do arquivo
      // remover espaços à esquerda, e à direita para detectar e evitar o uso de linhas vazias
      StringTrimLeft(str); 
      StringTrimRight(str);
      if(str!=""){ 
         if(cnt>=ArraySize(Lines)){ // array completamente preenchido
            ArrayResize(Lines,ArraySize(Lines)+1024); // aumentar o tamanho do array em 1024 elementos
         }
         Lines[cnt]=str; // Enviar a linha de leitura para o array
         cnt++; // aumentar o contador de linhas de leitura
      }
   }
   ArrayResize(Lines,cnt);
   FileClose(h);
   return(true);
}

Não detalharemos está função, uma vez que tudo já deve estar claro, a partir dos dados fornecidos até o momento. Além disso, já foi comentado em detalhes. Devemos apenas mencionar algumas nuances. Após ler a linha do arquivo para a variável str, os espaços nas bordas da linha são apagados pelas funções StringTrimLeft() e StringTrimRight(). Em seguida, a verificação é realizada se a string str não estiver vazia. Isso é feito para pular linhas vázias desnecessárias. Enquanto o array é preenchido, ele é aumentado em blocos por 1024 elementos, ao invés de um único. A função funciona muito mais rápida desta forma. Finalmente, o array é dimensionado de acordo com o número real de linhas de leitura.

A função pode ser encontrada no script sTestFileReadFileToArray anexado a seguir.

Criar um arquivo de texto com separadores

Até agora, consideramos apenas arquivos de texto simples. No entanto, há um outro tipo de arquivos de texto — os com separadores. Geralmente, eles possuem a extensão .csv (abreviação de "comma separated values" ou "valores separados por vírgulas"). Na verdade, estes são arquivos de texto simples que podem ser abertos em editores de texto, bem como lidos e editados manualmente. Um certo caractere (não necessariamente uma vírgula) é usado como um separador de campo em linhas. Assim, você pode executar algumas ações diferentes com eles, em comparação com arquivos de texto simples. A principal diferença é que num arquivo de texto simples, uma linha inteira é lida quando a função FileRedaString() é chamada, enquanto em arquivos com separadores, a leitura é realizada ou até um separador ou até o fim de linha. A função FileWrite() também funciona de forma diferente: todas as variáveis escritas enumeradas na função não são simplesmente conectadas a uma linha. Em vez disso, um separador é adicionado entre eles. 

Vamos tentar criar um arquivo csv. Abra o arquivo de texto, como já fizemos, para escrever especificando o sinalizador FILE_CSV em vez do FILE_TXT. O terceiro parâmetro é um símbolo usado como um separador:

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

Vamos escrever no arquivo dez linhas com três campos por linha:

   for(int i=1;i<=10;i++){
      string str="Line-"+IntegerToString(i)+"-";
      FileWrite(h,str+"1",str+"2",str+"3");
   }

Certifique-se de fechar o arquivo no final. O código pode ser encontrado no script sTestFileCreateCSV anexado abaixo. O arquivo "Test.csv" é criado como resultado. O conteúdo do arquivo é mostrado na Fig. 7. Como podemos ver, os parâmetros de função FileWrite() agora formam uma única linha com um separador entre eles.

 
Fig. 7. Conteúdo de um arquivo com separadores

Ler um arquivo de texto com separadores

Agora, vamos tentar ler o arquivo csv da mesma forma que o arquivo de texto no início deste artigo. Vamos fazer uma cópia do script sTestFileReadToAlert chamado sTestFileReadToAlertCSV. Alterar a primeira sequência de caracteres na função ReadFileToAlert(): 

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

Renomeie a função ReadFileToAlert() em ReadFileToAlertCSV() e altere o nome do arquivo transmitido para a função:

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

O resultado da operação de script mostra que o arquivo foi lido por um campo. Seria bom determinar quando os campos de uma linha são lidos, assim como quando a nova linha começa. A função FileIsLineEnding() é aplicada para isso.

Vamos fazer uma cópia do script sTestFileReadToAlertCSV chamado sTestFileReadToAlertCSV2, renomear a função ReadFileToAlertCSV para ReadFileToAlertCSV2 e alterá-lo. Adicione a função FileIsLineEnding(): se retornar 'verdadeira', exibir a linha de divisão "---". 

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

 Agora, os campos enviados para a janela de mensagem pelo script são divididos em grupos (Fig. 8).


Fig. 8. Separadores "---" entre grupos de campos de uma única linha de arquivo 

Ler um arquivo com separadores para um array

Agora que nos familiarizamos com o trabalho dos arquivos csv, vamos desenvolver ainda outra função útil para ler um arquivo csv num array. A leitura é executada para um array de estrutura, onde cada elemento corresponde a uma linha de arquivo. A estrutura conterá um array de linhas com todos os seus elementos correspondentes a um único campo de linha. 

Estrutura:

struct SLine{
   string line[];
};

Função:

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("Erro ao abrir o arquivo %s # %i",FileName,ErrNum);
      return(false);
   }   
   int lcnt=0; // variável para calcular linhas 
   int fcnt=0; // variável para calcular campos de linha   
   while(!FileIsEnding(h)){
      string str=FileReadString(h);
      // nova linha (novo elemento da estrutura array)
      if(lcnt>=ArraySize(Lines)){ // array de estrutura completamente preenchido
         ArrayResize(Lines,ArraySize(Lines)+1024); // aumentar o tamanho do array em 1024 elementos
      }
      ArrayResize(Lines[lcnt].field,64);// alterar o tamanho do array na estrutura
      Lines[lcnt].field[0]=str; // atribuir o primeiro valor de campo
      // começar a ler outros campos na linha
      fcnt=1; // até que um elemento no array de linhas esteja ocupado
         while(!FileIsLineEnding(h)){ // ler o resto dos campos na linha
            str=FileReadString(h);
            if(fcnt>=ArraySize(Lines[lcnt].field)){ // campo do array é completamente preenchido
               ArrayResize(Lines[lcnt].field,ArraySize(Lines[lcnt].field)+64); // aumentar o tamanho da array em 64 elementos
            }     
            Lines[lcnt].field[fcnt]=str; // atribuir o valor do campo seguinte
            fcnt++; // aumentar o contador de linhas
         }
      ArrayResize(Lines[lcnt].field,fcnt); // alterar o tamanho do campo do array, de acordo com o número real de campos
      lcnt++; // aumentar o contador de linhas
   }
   ArrayResize(Lines,lcnt); // mudar o array de estruturas (linhas) de acordo com o número real de linhas
   FileClose(h);
   return(true);
}

Não consideraremos esta função em detalhes, apenas nos pontos mais críticos. Um campo é lido no início do loop while (!FileIsEnding(h)). Aqui descobrimos sobre um elemento a ser adicionado ao array de estrutura. Verifique o tamanho do array e aumente-o em 1024 elementos, se necessário. Altere o tamanho do campo array de uma só vez. Um tamanho de 64 elementos é definido para ele imediatamente, e o valor do campo de primeira linha lida, a partir do arquivo é atribuído ao elemento, com 0 índice. Depois disso, leia os campos restantes no loop While (!FileIsLineEnding(h)). Após ler outro campo, verifique o tamanho do array e aumente-o se necessário, envie a linha lida do arquivo para o array. Após ler a linha até o fim (sair do loop while(!FileIsLineEnding(h)), altere o tamanho do campo de array, de acordo com o seu número real. No final, redimensione o array de linhas de acordo com o número real de linhas de leitura. 

A função pode ser encontrada no script sTestFileReadFileToArrayCSV anexado adiante. O script lê o arquivo test.csv para o array, e o exibe na janela de mensagem. O resultado é o mesmo mostrado na Fig. 8. 

Escrever um array para um arquivo de texto com separadores

A tarefa é bastante simples se o número de campos na linha for conhecido com antecedência. Uma tarefa semelhante já foi resolvida na seção "Criar um arquivo de texto com separadores". Se o número de campos for desconhecido, todos os campos podem ser reunidos numa única linha com separadores num loop, enquanto a linha é gravada no arquivo aberto com a flag FILE_TXT.

Abra o arquivo: 

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

Reúna todos os campos (elementos do array) numa única linha utilizando um separador. Não deve haver separador no final da linha, caso contrário, haverá um campo vazio redundante na mesma:

   string str="";
   int size=ArraySize(a);
   if(size>0){
      str=a[0];
      for(int i=1;i<size;i++){
         str=str+";"+a[i]; // campos de mesclagem utilizando um separador 
      }
   }

Escreva a linha no arquivo e feche-a:  

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

Este exemplo pode ser encontrado no script sTestFileWriteArrayToFileCSV anexado abaixo.

Arquivos UNICODE

Até agora, a flag FILE_ANSI sempre foi especificada ao abrir arquivos para definir sua codificação. Nesta codificação, um caractere corresponde a um byte, portanto, o conjunto inteiro é limitado a 256 símbolos. No entanto, a codificação UNICODE é amplamente utilizada nos dias de hoje. Nesta codificação, um símbolo é definido por vários bytes e um arquivo de texto pode conter um número muito grande de caracteres, incluindo letras de alfabetos diferentes, hieróglifos e outros símbolos gráficos.

Faremos algumas experiências. Abra o script sTestFileReadToAlert no editor, salve-o sob o nome sTestFileReadToAlertUTF e substitua o sinalizador FILE_ANSI com o FILE_UNICODE:

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

Uma vez que test.txt é salvo em ANSI, a nova janela vai conter um texto ilegível (Fig. 9).

  
Fig. 9. O texto ilegível que pode ser visto quando a codificação original de um arquivo não corresponde à especificada ao abrir um arquivo

Aparentemente, isso acontece porque a codificação original do arquivo não coincide com a especificada ao abri-lo.

Abra o script sTestFileCreate no editor, salve-o sob o nome sTestFileCreateUTF, e substitua a flag FILE_ANSI com o FILE_UNICODE:

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

Inicie o script sTestFileCreateUTF para criar um novo arquivo test.txt. Agora, o sTestFileReadToAlertUTF exibe o texto legível (Fig. 10).

 
Fig. 10. Usando o script sTestFileReadToAlertUTF para ler o arquivo gerado pelo outro script sTestFileCreateUTF

Abra test.txt no bloco de notas e execute o comando "Salvar como ..." no menu principal. Observe que o Unicode é selecionado na lista Codificação na parte inferior da janela "Salvar como". O bloco de notas tem de alguma forma, definido a codificação do arquivo. Os arquivos Unicode começam com o conjunto padrão de símbolos, chamado BOM (marca de ordem de bytes). Mais tarde, voltaremos a isso e escreveremos a função para definir um tipo de arquivo de texto (ANSI ou UNCODE). 

Funções adicionais para trabalhar com arquivos de texto contendo separadores

Da variedade de funções de arquivo para trabalhar com o conteúdo de arquivos de texto (tanto os simples como os com separadores), nós realmente precisamos apenas de dois: FileWrite() e FileReadString(). Além de outras coisas, a função FileReadString() também é utilizada para trabalhar com arquivos binários (mais sobre eles abaixo). Para além da função FileWrite(), a função FileWriteString() pode ser utilizada, embora isso não seja crítico. 

Ao trabalhar com arquivos de texto com separadores, algumas outras funções podem ser usadas tornando o trabalho mais conveniente: FileReadBool()FileReadNumber()FileReadDatetime(). A função FileReadNumber() é usada para ler os números. Se soubermos, antecipadamente que o campo lido a partir de um arquivo contém apenas um número, podemos aplicar essa função. Seu efeito é idêntico ao ler uma linha com a função FileReadString() e convertê-la num número com a função StringToDouble(). Da mesma forma, a função FileReadBool() é usada para ler valores de tipo bool. A string pode conter verdadeiro/falso ou 0/1. A função FileReadDatetime() é utilizada para ler os dados no formato de linha e convertê-lo para um valor numérico do tipo datetime. Seu efeito é semelhante à leitura de uma linha, convertendo-a utilizando a função StringToTime().  

Arquivos binários

Os arquivos de texto discutidos anteriormente são bastante convenientes, pois seu conteúdo lido por meio do programa é consistente com o que você vê no arquivo quando o abre num editor de texto. Você pode gerenciar facilmente os resultados da operação do programa examinando um arquivo no editor. Se necessário, o arquivo pode ser alterado manualmente. As desvantagens dos arquivos de texto incluem as opções limitadas ao trabalhar com eles (isso é evidente se pensarmos nas dificuldades que enfrentamos ao substituir uma única linha de arquivo).

Se um arquivo de texto é pequeno, é bastante confortável o seu uso. Mas, quanto maior o seu tamanho, mais tempo leva para trabalhar com ele. Se você precisar processar rapidamente grandes volumes de dados, use arquivos binários.

Ao abrir um arquivo no modo binário, a flag FILE_BIN é especificada em vez de FILE_TXT ou FILE_CSV. Não há nenhum ponto em especificar arquivos de codificação FILE_ANSI ou FILE_UNCODE, uma vez que o arquivo binário é um arquivo com números.

Claro, podemos ter um olhar para o conteúdo do arquivo binário no editor de texto do bloco de notas. Às vezes, podemos ver letras ou até mesmo um texto legível, no entanto, isso tem mais a ver com o Bloco de Notas em si, em vez do conteúdo do arquivo.

De qualquer modo, você definitivamente não deve editar o arquivo num editor de texto, porque pode ser danificado no processo. Não entraremos em detalhes sobre as suas causas, apenas aceitaremos esse fato. Naturalmente, existem editores de arquivos binários especiais, entretanto o processo de edição ainda não é intuitivo.

Arquivos binários e variáveis

A maioria das funções para trabalhar com arquivos em MQL5 são projetadas para o modo binário. Existem funções variáveis para leitura/escrita de diferentes tipos:

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

Não descreveremos todas as funções variáveis de escrita/leitura aqui. Precisamos apenas de um deles, enquanto o resto é usado da mesma maneira. Vamos experimentar as funções FileWriteDouble() e FileReadDouble().

Primeiro, crie um arquivo, escreva três variáveis e leia-os em ordem aleatória. 

Abra o arquivo:

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

Escreva três variáveis duplas com os valores 1.2, 3.45, 6.789 para o arquivo:

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

Não se esqueça de fechar o arquivo.

O código pode ser encontrado no script sTestFileCreateBin anexado. Assim como o resultado, o arquivo test.bin aparece na pasta MQL5/Files. Dê uma olhada em seu conteúdo no Bloco de notas (Fig. 11). Abra o bloco de notas e arraste o arquivo para ele:

 
Fig. 11. Arquivo binário no Bloco de notas

Como podemos ver, não há nenhum ponto em visualizar esses arquivos no Bloco de notas.

Agora, vamos ler o arquivo. Obviamente, a função FileReadDouble() deve ser utilizada para leitura. Abra o arquivo:

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

Declare três variáveis, leia seus valores do arquivo e mostre-os na caixa de mensagem:

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

Não se esqueça de fechar o arquivo. O código pode ser encontrado no script sTestFileReadBin anexado. Como resultado, recebemos a seguinte mensagem: 1.20000000 3.45000000 6.78900000.

Conhecendo a estrutura de arquivos binários, é possível fazer algumas mudanças limitadas neles. Vamos tentar alterar a segunda variável sem reescrever o arquivo inteiro.

Abra o arquivo:

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

Após abrir, mova o ponteiro para a posição especificada. A função sizeof() é recomendada para o cálculo da posição. Retorna o tamanho do tipo de dados especificado. Também seria bom estar bem familiarizado com o tipos de dados e seus tamanhos. Mova o ponteiro para o início da segunda variável:

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

Para mais clareza, implementamos a multiplicação sizeof(double)*1, de modo claro, este é o final da primeira variável. Se fosse necessário mudar a terceira variável, precisaríamos multiplicar por 2.

Escreva o novo valor: 

FileWriteDouble(h,12345.6789);

O código pode ser encontrado no script sTestFileChangeBin anexado. Após executar o script, inicie o script sTestFileReadBin e receba: 1.20000000 12345.67890000 6.78900000.

Você pode ler uma determinada variável (em vez de todo o arquivo) da mesma maneira. Vamos ler o código para ler a terceira variável dupla do test.bin.

Abra o arquivo:

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

Mova o ponteiro, leia o valor e mostre-o na caixa de mensagem:

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

Este exemplo pode ser encontrado no script sTestFileReadBin2 anexado abaixo. Como resultado, recebemos a seguinte mensagem: 6.78900000 — a terceira variável. Altere o código para ler a segunda variável.

Você pode salvar e ler as variáveis de outros tipos e suas combinações da mesma maneira. É importante conhecer a estrutura do arquivo para calcular corretamente a posição do ponteiro. 

Arquivos binários e estruturas

Se você precisar gravar várias variáveis de diferentes tipos num arquivo, é muito mais conveniente descrever a estrutura e ler/gravar toda a estrutura em vez de ler/gravar as variáveis uma a uma. O arquivo normalmente começa com a estrutura descrevendo o local dos dados no arquivo (formato de arquivo) seguido pelos dados. No entanto, há uma limitação: a estrutura não deve ter arrays dinâmicos e linhas, uma vez que seu tamanho é desconhecido.

Vamos experimentar com a escrita e a leitura da estrutura para o arquivo. Descreva a estrutura com várias variáveis de diferentes tipos:

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

O código pode ser encontrado no script sTestFileWriteStructBin anexado. Declare duas variáveis e as preencha com valores diferentes na função OnStart():

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;  

Agora, abra o arquivo:

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

Escreva as duas estruturas para ele:

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

Não se esqueça de fechar o arquivo. Execute o script para criar um arquivo.

Agora, vamos ler o arquivo. Leia a segunda estrutura.

Abra o arquivo:

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

Mova o ponteiro para o início da segunda estrutura:

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

Declare a variável (adicione a descrição da estrutura STest ao início do arquivo) e leia os dados do arquivo para ele:

STest s;
FileReadStruct(h,s);

Descreva os valores dos campos de estrutura na janela:

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

Como resultado, vamos ver a seguinte linha na caixa de mensagem: 11 22 33 6789 false 56.78. A linha corresponde aos dados da segunda estrutura.

O código do exemplo pode ser encontrado no script sTestFileReadStructBin anexado abaixo.

Escrever estruturas de variáveis

No MQL5, os campos de estrutura seguem, uns aos outros, sem um deslocamento (alinhamento), portanto, é possível ler certos campos de estrutura sem quaisquer dificuldades.

Leia o valor da variável dupla da segunda estrutura no arquivo test.bin. É importante calcular uma posição para definir um ponteiro: 

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

O resto é semelhante ao que já fizemos muitas vezes neste artigo: abrir o arquivo, ler, fechar. O código do exemplo pode ser encontrado no script sTestFileReadStructBin2 anexado abaixo.

Definir o arquivo UNICODE e a função FileReadInteger

Após nos familiarizarmos um pouco com os arquivos binários, podemos criar uma função útil para definir um arquivo UNICODE. Esses arquivos podem ser distinguidos pelo valor do byte inicial, no qual é igual a 255. Ou seja, o código 255 corresponde a um símbolo não imprimível, portanto, ele não pode estar presente num arquivo ANSI comum.

Isso significa que devemos ler um byte do arquivo e verificar seu valor. A função FileReadInteger() é utilizada para ler várias variáveis inteiras, exceto o long, uma vez que recebe o parâmetro especificando o tamanho de uma variável de leitura. Ler um byte para a variável v do arquivo:

uchar v=FileReadInteger(h,CHAR_VALUE);

Agora, só temos de verificar o valor da variável. O código de função completo é mostrado abaixo:

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

A função retorna verdadeira/falsa, caso a verificação seja bem-sucedida. O nome do arquivo é transmitido para a função como o primeiro parâmetro, enquanto o segundo (transmitido através de um link) contém a variável igual a verdadeiro para arquivos UNICODE e falsopara arquivos ANSI depois que a função é executada. 

O código da função e o exemplo de sua chamada podem ser encontrados no script sTestFileCheckUnicode anexado abaixo. Inicie o script sTestFileCreate e verifique seu tipo usando o script sTestFileCheckUnicode. Depois disso, inicie o script sTestFileCreateUTF e execute o script sTestFileCheckUnicode novamente. Você obterá um resultado diferente.  

Arquivos binários, arrays e arrays de estrutura

A principal vantagem dos arquivos binários torna-se perceptível ao trabalhar com grande quantidade de dados. Os dados são geralmente localizados em arrays (uma vez que é difícil receber grandes quantidades usando variáveis separadas) e em strings. As arrays podem consistir em variáveis padrão, e estruturas que devem atender aos requisitos mencionados acima. Eles não devem conter arrays dinâmicas nem strings.

Os Arrays são gravados no arquivo usando a funçãoFileWriteArray(). O handle de arquivo é transmitido para a função, como o primeiro parâmetro seguido pelo nome da array. Os dois parâmetros a seguir são opcionais. Se você não precisa salvar o array inteiro, especifique o índice do elemento inicial do array e o número de elementos salvos. 

Os arrays são lidos e usam a função FileReadArray(), os parâmetros desta função são idênticos aos parâmetros de função FileWriteArray().

Vamos escrever a variável int composta por três elementos do arquivo: 

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   int a[]={1,2,3};   
   FileWriteArray(h,a);   
   FileClose(h);
   Alert("File written");
}

O código pode ser encontrado no arquivo sTestFileWriteArray anexado abaixo.

Agora, leia (script sTestFileReadArray) e o exiba na janela:

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

Como resultado, obtemos a linha "1 2 3" correspondente à array previamente especificada. Observe que o tamanho do array não está definido e não foi especificado ao chamar a função FileReadArray(). Em vez disso, o arquivo inteiro foi lido. Porém, o arquivo pode ter vários arrays de diferentes tipos. Portanto, seria razoável salvar o tamanho do arquivo também. Vamos escrever os arrays int e double para o arquivo ambos começando com uma variavel int contendo seu tamanho:

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   
   // dois arrays
   int a1[]={1,2,3}; 
   double a2[]={1.2,3.4};
   
   // definir o tamanho dos arrays
   int s1=ArraySize(a1);
   int s2=ArraySize(a2);
   
   // escreva o array 1
   FileWriteInteger(h,s1,INT_VALUE); // escreva o tamanho do array
   FileWriteArray(h,a1); // escreva o array
   
   // escreva o array 2
   FileWriteInteger(h,s2,INT_VALUE); // escreva o tamanho do array
   FileWriteArray(h,a2); // escreva o array   
      
   FileClose(h);
   Alert("File written");
}

O código pode ser encontrado no script sTestFileWriteArray2 anexado abaixo. 

Após ler o arquivo, lemos o tamanho do primeiro array, e depois, lemos para o array o número especificado de elementos:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   int a1[];
   double a2[];
   int s1,s2;
   
   s1=FileReadInteger(h,INT_VALUE); // leia o tamanho do array 1
   FileReadArray(h,a1,0,s1); // leia o número de elementos definidos em s1 para o array
   
   s2=FileReadInteger(h,INT_VALUE); // Leia o tamanho do array 2
   FileReadArray(h,a2,0,s2); // Leia o número de elementos definidos em s2 para o array    

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

O código pode ser encontrado no script sTestFileReadArray2 anexado abaixo.

Como um resultado, o script mostra a mensagem: 3: 1 2 3 - 2: 1,2 3,4 correspondente ao tamanho e conteúdo dos arrays anteriores gravadas no arquivo.

Quando lemos os arrays usando a função FileReadArray(), o mesmo é automaticamente dimensionado. No entanto, o dimensionamento é executado somente se o tamanho atual for menor que o número de elementos lidos. Se o tamanho do array exceder o número, ele permanece inalterado. Apenas uma parte do array é preenchido em vez disso.

Trabalhar com arrays de estrutura é completamente idêntico ao trabalho com arrays de tipos padrão, uma vez que o tamanho da estrutura é definida corretamente (não há arrays dinâmicos e nem strings). Não forneceremos um exemplo contendo os arrays de estrutura aqui. Você pode, por conta própria, experimentar com eles.

Além disso, note que, uma vez que somos capazes de mover o ponteiro em todo o arquivo, é possível ler apenas um dos elementos do array, ou uma parte do mesmo. É importante calcular a posição ponteiro futuro corretamente. Um exemplo de leitura de elementos separados também não é exibido aqui para encurtar o comprimento do artigo. Você pode tentar experimentá-lo.

Arquivos binários, strings e linha de arrays

A função FileWriteString() é utilizada para escrever uma string num arquivo binário. Os dois parâmetros obrigatórios são transmitidos para a função: handle de arquivo e uma linha gravada no arquivo. O primeiro parâmetro é opcional: você pode definir o número de símbolos escritos, se apenas uma parte da linha for escrita. 

A linha é lida pela função FileReadString(). Nesta função, o primeiro parâmetro é um handle, enquanto o segundo (opcional) é usado para definir o número de caracteres de leitura.

Em geral, escrever/ler as linhas é muito semelhante ao trabalho com um array: uma linha é semelhante à array inteira, enquanto um caractere de linha tem muito em comum com um único elemento de array, portanto, não será exibido um exemplo de uma única linha escrita/leitura. Em vez disso, vamos considerar o exemplo mais complexo: escrever e ler uma string array. Primeiro, vamos escrever no arquivo a variável int com o tamanho da array, então escreva elementos separados numa variável int com seu tamanho no início de cada um deles: 

void OnStart(){
   int h=FileOpen("test.bin",FILE_WRITE|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   
   string a[]={"Line-1","Line-2","Line-3"}; // array escrita 

   FileWriteInteger(h,ArraySize(a),INT_VALUE); // escreva o tamanho do array
   
   for(int i=0;i<ArraySize(a);i++){
      FileWriteInteger(h,StringLen(a[i]),INT_VALUE); // escreva o tamanho da linha (um único elemento do array)
      FileWriteString(h,a[i]);
   }

   FileClose(h);
   Alert("File written");
}

O código pode ser encontrado no script sTestFileWriteStringArray anexado.

Ao ler, leia primeiro o tamanho do array, depois mude seu tamanho e leia os elementos separados lendo seu tamanho:

void OnStart(){
   int h=FileOpen("test.bin",FILE_READ|FILE_BIN);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }   
   
   string a[]; // leia o arquivo para este array
   
   int s=FileReadInteger(h,INT_VALUE); // leia o tamanho do array
   ArrayResize(a,s); // alterar o tamanho do array
   
   for(int i=0;i<s;i++){ // por todos os elementos do array
      int ss=FileReadInteger(h,INT_VALUE); // leia o tamanho da linha
      a[i]=FileReadString(h,ss); // leia a linha
   }

   FileClose(h);

   // exibir o array de leitura
   Alert("=== Start ===");
   for(int i=0;i<ArraySize(a);i++){
      Alert(a[i]);
   }

}

O código pode ser encontrado no script sTestFileReadStringArray anexado. 

Pasta compartilhada para arquivos

Até agora, lidamos com os arquivos localizados no diretório MQL5/Files. No entanto, este não é o único local onde os arquivos podem ser localizados. No menu principal do MetaEditor, execute File - Open Common Data Folder. A pasta com o diretório Files será aberta. Também pode conter arquivos disponíveis em aplicativos desenvolvidos no MQL5. Observe o caminho até ele (Fig. 12):


Fig. 12. Caminho para a pasta de dados comuns  

O caminho para a pasta de dados comum não tem nada a ver com o caminho para o terminal, e nem para o diretório de arquivos que lidamos em todo o artigo. Não importa quantos terminais você lançou (incluindo aqueles executando com a chave "/portátil"), a mesma pasta compartilhada é aberta para todos eles.

O caminho para as pastas pode ser definido programaticamente. O caminho para a pasta de dados (contendo o diretório MQL5/Files com o qual trabalhamos em todo o artigo):

TerminalInfoString(TERMINAL_DATA_PATH);

Caminho para a pasta de dados compartilhada (contendo o Arquivo diretório):

TerminalInfoString(TERMINAL_COMMONDATA_PATH);

Da mesma forma, você pode definir o caminho para o terminal (diretório raiz, onde o terminal está instalado):

TerminalInfoString(TERMINAL_PATH);

Semelhante ao diretório MQL5/Files, não há necessidade de especificar o caminho completo ao trabalhar com arquivos da pasta compartilhada. Em vez disso, você só precisará adicionar a flag FILE_COMMON à combinação de flags transmitidos para a função FileOpen(). Algumas funções de arquivo têm um determinado parâmetro para especificar a pasta compartilhada da flag. Esses são FileDelete(), FileMove(), FileCopy() e alguns outros.

Copie test.txt da pasta MQL5/Files para a pasta de dados comuns:

   if(FileCopy("test.txt",0,"test.txt",FILE_COMMON)){
      Alert("Arquivo copiado");
   }
   else{
      Alert("Erro ao copiar arquivo");
   }

O código pode ser encontrado no script sTestFileCopy anexado. Após de executar o script, o arquivo test.txt aparece na pasta Arquivos compartilhados. Se lançarmos o script pela segunda vez, receberemos a mensagem de erro. Para evitá-lo, permita que o arquivo sobrescreva adicionando a flag FILE_REWRITE:

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

Agora, copie o arquivo da pasta compartilhada para a mesma pasta com um nome diferente (script sTestFileCopy2):

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

Finalmente, copie o arquivo da pasta comum para MQL5/Files (script sTestFileCopy3):

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

A função FileMove() é chamada da mesma forma, embora a cópia não é criada. Em vez disso, o arquivo é movido (ou renomeado).

Arquivos no testador

Até o momento, nosso trabalho com arquivos estava relacionado apenas aos programas MQL5 (scripts, EAs, indicadores) executados numa conta (lançado num gráfico). No entanto, tudo é diferente quando lançamos um EA no testador. O testador do MetaTrader 5 tem a capacidade de realizar testes distribuídos (em nuvem) usando agentes remotos. Resumidamente, as execuções de otimização 1-10 (os números são condicionais) são realizadas num PC, as execuções de 11-20 são realizadas em outro, etc. Isso pode causar dificuldades, e afetar o trabalho com arquivos. Consideraremos esses recursos, e formaremos os princípios que você deve seguir quando se trabalhar com arquivos no testador.

Ao trabalhar com arquivos, a função FileOpen() acessa os arquivos localizados no diretório MQL5/Files dentro da pasta de dados do terminal. Ao testar, a função acessa os arquivos do diretório MQL5/Files dentro da pasta do agente de teste. Se os arquivos forem necessários durante uma única execução de otimização (ou teste único), por exemplo, para armazenar dados numa posição ou pedidos pendentes, tudo o que você precisa fazer é limpar os arquivos antes da próxima execução (ao inicializar o EA). Se o arquivo for gerado manualmente e também for utilizado para determinar quaisquer parâmetros de operação EA, ele será localizado no diretório MQL5/Files da pasta de dados do terminal. Isso significa que o testador não será capaz de visualizá-lo. Para permitir que o EA acesse o arquivo, ele deve ser transmitiddo para o agente. Isso é feito no EA através da configuração da propriedade "#property tester_file". Assim, é possível enviar qualquer quantidade de arquivos:

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

No entanto, mesmo se o arquivo for especificado usando "#property tester_file", o EA ainda grava na cópia do arquivo localizada no diretório do agente de teste. O arquivo na pasta de dados do terminal permanece inalterado. A leitura adicional do arquivo, pelo EA é realizada, a partir da pasta do agente. Em outras palavras, um arquivo alterado é lido. Portanto, se você precisa salvar alguns dados para uma análise mais aprofundada durante o teste EA e a otimização, salvar os dados no arquivo não é aplicável. Você deve usar frames em vez disso.

Se você não usar agentes remotos, trabalhe com os arquivos da pasta compartilhada (defina a flag FILE_COMMON ao abrir um arquivo). Nesse caso, não há necessidade de especificar o nome do arquivo nas propriedades EA e o mesmo é capaz de gravar no arquivo. Em suma, ao usar a pasta de dados comuns, trabalhar com os arquivos do testador é muito mais simples, além do fato de que você não deve usar agentes remotos. Além disso, observe os nomes dos arquivos, para que o EA testado não corrompa o arquivo usado, no qual o mesmo está realmente trabalhando. Trabalhar no testador pode ser definido programaticamente:

MQLInfoInteger(MQL5_TESTER)

Ao testar, use outros nomes de arquivo.

Compartilhando acessos para arquivos

Como mencionado anteriormente, se o arquivo já está aberto, ele não pode ser aberto novamente. Se um arquivo já é processado por um aplicativo, outro programa não tem acesso a ele até que o arquivo seja fechado. No entanto, o MQL5 fornece a capacidade de compartilhar arquivos. Ao abrir um arquivo, defina as flag adicionais FILE_SHARE_READ (leitura compartilhada) ou FILE_SHARE_WRITE (escrita compartilhada). Use as flags com cuidado. Os sistemas operacionais atuais apresentam multitarefas. Portanto, não há garantia de que a sequência escrita — leitura sera executada corretamente. Se você permitir a escrita e leitura compartilhada, pode acontecer de um programa gravar os dados, enquanto outro lê os mesmos dados (inacabados) ao mesmo tempo. Portanto, devemos tomar medidas adicionais para sincronizar o acesso ao arquivo de diferentes programas. Esta é uma tarefa complicada, que se estende consideravelmente, além do alcance deste artigo. Além disso, é mais provável que seja possível sem sincronização e compartilhamento de arquivos (isso será mostrado abaixo ao usar arquivos para trocar dados entre os terminais).

A única vez que você pode abrir com segurança o arquivo com leitura compartilhada (FILE_SHARE_READ) e quando esse compartilhamento é justificado, é quando o arquivo é usado para definir parâmetros de operação do EA, ou do indicador, como o arquivo de configuração, por exemplo. O arquivo é criado manualmente, ou por um script adicional e, em seguida, lido por várias instâncias de um EA, ou indicador durante a inicialização. Durante a inicialização, vários EAs podem tentar abrir o arquivo quase simultaneamente, consequentemente você deve permitir que eles façam isso. Ao mesmo tempo, há uma garantia de que a leitura e a escrita não estejam ocorrendo simultaneamente.  

Usando arquivos para troca de dados entre terminais

Você pode organizar uma troca de dados entre os terminais salvando arquivos para a pasta compartilhada. Naturalmente, usar os arquivos para tais finalidades não é a melhor solução, porém é útil em alguns casos. A solução é bastante simples: nenhum acesso compartilhado ao arquivo é utilizado. Em vez disso, o arquivo é aberto da maneira usual. Ninguém mais pode abrir o arquivo até que a escrita esteja em progresso. Após concluída a escrita, o arquivo é fechado e outras instâncias do programa não podem lê-lo. Abaixo está o código da função de gravação de dados do script sTestFileTransmitter:

bool WriteData(string str){
   for(int i=0;i<30 && !IsStopped();i++){ // várias tentativas
      int h=FileOpen("data.txt",FILE_WRITE|FILE_ANSI|FILE_TXT);
      if(h!=INVALID_HANDLE){ // arquivo aberto com êxito
         FileWriteString(h,str); // escreva os dados 
         FileClose(h); // escreva os arquivos
         Sleep(100); // aumentar a pausa para deixar outros programas 
		     // leia os dados
         return(true); // retornar 'verdadeiro' se bem-sucedido
      }
      Sleep(1); // aumentar a pausa para impedir outros programas 
                // terminar de ler o arquivo e capiturar 
                // o momento em que o arquivo está disponível
   }
   return(false); // se escreverem os dados de gravação
}

Várias tentativas para abrir o arquivo são realizadas. A abertura do arquivo é seguida pela escrita, fechamento e uma pausa relativamente longa (função Sleep (100)) para permitir que outros programas abram o arquivo. No caso de um erro de abertura de arquivo, uma pequena pausa é feita (função Sleep(1)) para capturar o momento em que o arquivo esteja disponível.

A função de aceitação (leitura) segue o mesmo princípio. O script sTestFileReceiver anexado abaixo apresenta essa função. Os dados obtidos são exibidos pela função Comment(). Inicie o script do transmissor num gráfico e o script do receptor em outro (ou em outra instância do terminal) 

Algumas funções extras

Já consideramos quase todas as funções para trabalhar com arquivos, exceto para alguns raramente usados: FileSize(), FileTell() e FileFlush(). A função FileSize() retorna o tamanho de um arquivo aberto em bytes:

void OnStart(){
   int h=FileOpen("test.txt",FILE_READ|FILE_ANSI|FILE_TXT);
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   ulong size=FileSize(h);
   FileClose(h);
   Alert("Dimensão do arquivo "+IntegerToString(dimensão)+" (bytes)");
}

O código pode ser encontrado no script sTestFileSize anexado. Ao executar o script, a janela de mensagens com a dimensão de um arquivo é aberta. 

A função FileTell() retorna a posição do ponteiro de arquivo de um arquivo aberto. A função é utilizada tão raramente que é difícil pensar em qualquer exemplo apropriado. Basta observar a sua existência e lembrar-se em caso de necessidade.

A função FileFlush() é mais útil. Conforme indicado na documentação, a função envia todos os dados restantes no buffer de entrada/saída de arquivo para o disco. O efeito da chamada de função é semelhante ao fechamento e reabertura do arquivo (embora seja mais eficiente de recursos e o ponteiro de arquivo permanece em sua localização inicial). Como sabemos, os arquivos são armazenados como entradas num disco. No entanto, a escrita é executada pelo buffer, ao invés do disco até o arquivo ser aberto. Escreva para o disco para ser executado, quando o ficheiro é fechado. Portanto, os dados não são salvos no caso do fechamento de emergência do programa. Se chamamos FileFlush() após cada gravação no arquivo, os dados são salvos no disco e os bloqueios do programa não causam nenhum problema.

Trabalhando com pastas

Além de trabalhar com arquivos, o MQL5 apresenta uma série de funções para trabalhar com pastas:FolderCreate()FolderDelete()FolderClean(). A função FolderCreate é usada para criar uma pasta. Todas as funções têm dois parâmetros. O primeiro é obrigatório para um nome de pasta. O segundo é adicional ao FILE_COMMON flag (para trabalhar com pastas, na pasta de dados comuns). 

FolderDelete() exclui uma pasta especificada. Somente uma pasta vazia pode ser excluída. No entanto, limpar o conteúdo da pasta não é um problema uma vez que a função FolderClean() é usada para isso. Todo o conteúdo, incluindo subpastas e arquivos, é excluído. 

Receber a lista de arquivos

Às vezes, você não se lembra exatamente o nome de um arquivo que precisa. Você pode se lembrar de um começo, mas não de um final numérico, por exemplo, file1.txt, file2.txt, etc. Nesse caso, os nomes dos arquivos podem ser obtidos usando as funções FileFindFirst(), FileFindNext() e FileFindClose(). Essas funções pesquisam arquivos e pastas. Um nome de pasta pode ser distinguido de um nome de arquivo por uma barra invertida no final.

Vamos escrever uma função útil para obter uma lista de arquivos e pastas. Vamos coletar nomes de arquivos num array, enquanto os nomes de pasta em outra:

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

   int files_cnt=0; // contador de arquivos
   int folders_cnt=0; // contador de pastas  
   
   string name; // variável para receber um nome de arquivo ou pasta 

   long h=FileFindFirst(pasta,nome,common_flag); // receber um handle de pesquisa e um nome 
                                      // do primeiro arquivo/pasta (se presente)
   if(h!=INVALID_HANDLE){ // pelo menos um único arquivo ou pasta está presente
      do{
         if(StringSubstr(name,StringLen(name)-1,1)=="\\"){ // pasta
            if(folders_cnt>=ArraySize(folders)){ // verifique o tamanho do array, 
                                                 // aumentá-lo se necessário
               ArrayResize(folders,ArraySize(folders)+64);
            }
            folders[folders_cnt]=name; // Enviar o nome da pasta para o array
            folders_cnt++; // conte as pastas        
         }
         else{ // arquivos
            if(files_cnt>=ArraySize(files)){ // Verifique o tamanho do array, 
                                             // aumentá-lo se necessário
               ArrayResize(files,ArraySize(files)+64);
            }
            files[files_cnt]=name; // enviar o nome do arquivo para o array
            files_cnt++; // contar os arquivos
         }
      }
      while(FileFindNext(h,name)); // receber o nome do próximo arquivo ou pasta
      FileFindClose(h); // fim da pesquisa
   }
   ArrayResize(files,files_cnt); // alterar o tamanho do array de acordo com 
                                 // o número real de arquivos
   ArrayResize(folders,folders_cnt); // alterar o tamanho do array de acordo com 
                                        // o número real de pastas
}

Experimente este recurso. Chamaremos o script da seguinte maneira: 

void OnStart(){

   string files[],folders[];

   GetFiles("*",files,folders);
   
   Alert("=== Start ===");
   
   for(int i=0;i<ArraySize(folders);i++){
      Alert("Folder: "+folders[i]);
   }      
   
   for(int i=0;i<ArraySize(files);i++){
      Alert("File: "+files[i]);
   }

}

 O script sTestFileGetFiles está anexado a seguir. Observe a máscara de pesquisa "*":

GetFiles("*",files,folders);

A máscara permite pesquisar todos os arquivos e pastas no diretório MQL5/Files.

Para encontrar todos os arquivos e pastas começando com "teste", você pode usar a máscara "test*". Se você precisar apenas de arquivos txt, você precisará da máscara "*.txt", etc. Crie uma pasta (por exemplo, "folder1") com um número de arquivos. Você pode usar a máscara "folder1\\*" para receber a lista de arquivos que ela contém.  

Сódigo da página

No presente artigo, a função FileOpen() é frequentemente aplicada em códigos de exemplo. Consideremos um de seus parâmetros que ainda não descrevemos — página de códigos. Uma página de código é uma tabela de conversão de símbolos de texto, e seus valores numéricos. Vamos dar uma olhada na codificação ANSI para ficar mais claro. A tabela de caracteres de codificação contém apenas 256 caracteres, o que significa que uma página de códigos separada definida nas configurações do sistema operacional é usada para cada idioma. A constante CP_ACP, da qual a função FileOpen() é chamada por padrão, corresponde à página de códigos. É muito improvável que alguém precise usar uma página de código diferente, portanto, não faz sentido entrar em detalhes sobre esse assunto. O conhecimento geral seria bastante suficiente.  

Trabalhando com arquivos sem limitações

Às vezes, você pode querer trabalhar com arquivos fora do arquivo do terminal "sandbox" (fora do MQL5/Files, ou pasta compartilhada). Isso pode expandir, significativamente a funcionalidade das aplicações MQL5, permitindo que você processe arquivos de código-fonte, alterá-los automaticamente, gerar arquivos de imagem para a interface gráfica rapidamente, assim como gerar o código, etc. Se você fizer isso por si mesmo, ou contratar um programador confiável, então você pode fazer isso. Você pode ler mais sobre isso no artigo "Operações de Arquivo via WinAPI". Há também há uma maneira mais fácil. O MQL5 tem todos os meios necessários para trabalhar com arquivos, portanto, você pode mover o arquivo para o terminal "sandbox", executar todas as ações necessárias e movê-lo de volta. Uma única função WinAPI (CopyFile) é suficiente para isso.

A aplicação das funções WinAPI deve ser permitida para que um aplicativo MQL5 pode usá-los. A permissão é ativada nas configurações do terminal (Menu principal - Ferramentas - Opções - Expert Advisors - Permitir importações de DLL). Neste caso, a permissão está habilitada para todos os programas lançados posteriormente. Em vez da permissão geral, você pode habilitar a permissão somente para um programa que você vai lançar. Se o aplicativo for acessar funções WinAPI ou outras DLLs, a guia Dependências com a opção "Permitir importação de DLL" será exibida em sua janela de configurações.

Existem duas versões da função CopyFile: CopyFileA() e mais CopyFileW(). Você pode usar qualquer um deles. No entanto, ao usar a função CopyFileA(), você precisará converter os argumentos da string primeiro. Leia a seção "Chamada de funções API" do artigo "Fundamentos Básicos da Programação MQL%: Strings" para conhecer os detalhes. Eu recomendo usar a função CopyFileW() mais atualizada. Nesse caso, os argumentos de sequência são especificados como está, portanto, nao é necessária nenhuma conversão. 

Para usar a função CopyFileW(), você deve importá-la primeiro. Você pode encontrá-lo na biblioteca kernel32.dll:

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

O código pode ser encontrado no script sTestFileWinAPICopyFileW anexado.

O script copia o arquivo que contém seu código fonte para 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("Arquivo copiado");
   }
   else{
      Alert("Falha ao copiar o arquivo");   
   }
}

Se bem-sucedido, CopyFileW() retorna 1, caso contrário, 0. O parâmetro da terceira função indica se um arquivo pode ser substituído se houver um arquivo de destino: 0 — habilitado, 1 — desabilitado. Inicie o script. Se ele funcionar com êxito, verifique a pasta MQL5/Files. 

Observe que o sistema operacional impõe limitações na cópia de arquivos. Existem os chamados "parâmetros de controle de conta do usuário". Se estiverem habilitados, os arquivos não podem ser copiados de/para alguns locais. Por exemplo, é impossível copiar um arquivo para a raiz de uma unidade do sistema.

Alguns scripts úteis

Além de criar funções úteis para trabalhar com arquivos, vamos criar um par de scripts úteis para mais prática. Desenvolveremos os scripts para exportar as cotações para um arquivo csv, e exportamos os resultados da negociação.

O script de exportação de citação terá parâmetros para definir datas de início e fim dos dados, tais como parâmetros que definem se as datas devem ser usadas, ou se todos os dados devem ser exportados. Defina a propriedade necessária para abrir a janela de propriedades do script:

#property script_show_inputs

Os parâmetros externos são declarados posteriormente:

input bool     UseDateFrom = false; // Definir a data de início
input datetime DateFrom=0; // Data de início
input bool     UseDateTo=false; // Definir a data de término
input datetime DateTo=0; // Data final


Escreva o código na função do script OnStrat(). Defina as datas de acordo com os parâmetros do script:

   datetime de,para;
   
   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("Erro ao definir dados iniciais, tente novamente mais tarde");
            return;
         }
         else{
            from=tm[0];
         }
         
      }
      else{
         Alert("O prazo está em construção, tente novamente mais tarde");
         return;
      }
   }
   
   if(UseDateTo){
      to=DateTo;
   }
   else{
      to=TimeCurrent();
   }   

Se as variáveis definidoras de data forem utilizadas, seus valores serão usados. Caso contrário, o TimeCurrent() para a data de término é utilzado, enquanto a data de início é definida pelo primeiro tempo da barra. 

Agora que temos as datas, copie as cotações para o array do tipo MqlRates:

   MqlRates rates[];
   
   if(CopyRates(Symbol(),Period(),from,to,rates)==-1){
      Alert("Erro ao copiar cotações. Tente novamente mais tarde");
   }

Salve os dados do array para o arquivo:

   string FileName=Symbol()+" "+IntegerToString(PeriodSeconds()/60)+".csv";
   
   int h=FileOpen(FileName,FILE_WRITE|FILE_ANSI|FILE_CSV,";");
   
   if(h==INVALID_HANDLE){
      Alert("Error opening file");
      return;
   }
   
   // gravar dados no arquivo em formato: Tempo, Aberto, Alto, Baixo, Fechado, Volume, Ticks
   
   // a primeira linha para conhecer a localização
   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("Salvo, veja o arquivo "+FileName);   

Se bem-sucedido, o script abre a mensagem apropriada informando que o arquivo foi salvo com êxito. Caso contrário, será exibida a mensagem de erro. O script sQuotesExport pronto para uso, está anexado abaixo.

Agora, vamos desenvolver um script para guardar o histórico da negociação. O início é aproximadamente o mesmo: as variáveis externas vêm em primeiro lugar, embora a definição de tempo seja implementada de maneira muito mais simples, já que o tempo de início 0 é suficiente ao solicitar o histórico:

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

Distribuir histórico: 

   if(!HistorySelect(from,to)){
      Alert("Error allocating history");
      return;
   }

Abra o arquivo:

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

Escreva a primeira linha com nomes dos campos:

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

Enquanto percorrer todas as negociações, escreva negociações de compra e venda para o arquivo:

   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("Erro ao alocar uma negociação, tente novamente");
         FileClose(h);
         return;
      }
   }

Nota: para um tipo de negociação (compra/venda) e direção (entrada/saída), os valores são convertidos em sequências de caracteres, enquanto alguns valores de tipo duplo são transformados em sequências de caracteres com dois pontos decimais. 

No final, feche o arquivo, e será exibida uma mensagem: 

   FileClose(h);
   Alert("Salvo, veja o arquivo "+FileName); 

 O script sHistoryExport está anexado abaixo.

Mais sobre o tópico

A seção de Artigos contém uma enorme quantidade de materiais notáveis relacionados de alguma forma ao trabalho com arquivos:

Conclusão

Neste artigo, foi considerado todas as funções para trabalhar com arquivos no MQL5. Apesar do tópico, aparentemente estreito, o artigo acabou por ser bastante grande. No entanto, algumas questões temáticas foram examinadas de forma superficial e sem exemplos práticos suficientes. De qualquer forma, as tarefas mais comuns são discutidas em detalhes, incluindo trabalhar com arquivos no testador. Além disso, desenvolvemos várias funções úteis e todos os exemplos são práticos e logicamente completos. Todos os códigos são anexados abaixo como scripts.

Anexos

  1. sTestFileRead — ler uma única linha do arquivo de texto ANSI, e exibi-lo na caixa de mensagem.
  2. sTestFileReadToAlert — ler todas as linhas do arquivo de texto ANSI e exibi-los na caixa de mensagem.
  3. sTestFileCreate — criando o arquivo de texto ANSI.
  4. sTestFileAddToFile — adicionando uma linha ao arquivo de texto ANSI.
  5. sTestFileChangeLine2-1 — tentativa inválida de alterar uma única linha no arquivo de texto ANSI.
  6. sTestFileChangeLine2-2 — outra tentativa inválida de alterar uma única linha no arquivo de texto ANSI.
  7. sTestFileChangeLine2-3 — substituindo uma única linha no arquivo de texto ANSI, reescrevendo o arquivo inteiro.
  8. sTestFileReadFileToArray — a função útil para ler o arquivo de texto ANSI para o array.
  9. sTestFileCreateCSV — criando o arquivo ANSI CSV.
  10. sTestFileReadToAlertCSV — lendo o arquivo ANSI CSV para a caixa de mensagem por campos.
  11. sTestFileReadToAlertCSV2 — lendo o arquivo ANSI CSV para a caixa de mensagem por campos com separação de linhas. 
  12. sTestFileReadFileToArrayCSV — lendo o arquivo ANSI CSV para o array de estrutura.
  13. sTestFileWriteArrayToFileCSV — escrevendo o array como uma única linha para o arquivo CSV ANSI.
  14. sTestFileReadToAlertUTF — ler o arquivo de texto UNICODE, e exibi-lo na caixa de mensagem.
  15. sTestFileCreateUTF — criando o arquivo de texto UNICODE.
  16. sTestFileCreateBin — criando o arquivo binário, e gravando três variáveis ​​duplas para ele.
  17. sTestFileReadBin — leitura de três variáveis ​​duplas do arquivo binário.
  18. sTestFileChangeBin — reescrever a segunda variável dupla no arquivo binário.
  19. sTestFileReadBin2 — lendo a terceira variável dupla a partir do arquivo binário. 
  20. sTestFileWriteStructBin — escrevendo a estrutura para o arquivo binário.
  21. sTestFileReadStructBin — lendo a estrutura a partir do arquivo binário.
  22. sTestFileReadStructBin2 — lendo uma única variável do arquivo binário com estruturas.
  23. sTestFileCheckUnicode — verificando o tipo de arquivo (ANSI ou UNCODE).
  24. sTestFileWriteArray — escrevendo o array para o arquivo binário.
  25. sTestFileReadArray — lendo o array do arquivo binário.
  26. sTestFileWriteArray2 — escrevendo dois arrays para o arquivo binário.
  27. sTestFileReadArray2 — lendo dois arrays do arquivo binário.
  28. sTestFileWriteStringArray — escrevendo o string array para o arquivo binário.
  29. sTestFileReadStringArray — lendo o array da string do arquivo binário.
  30. sTestFileCopy — copiar o arquivo de MQL5/Files para a pasta compartilhada.
  31. sTestFileCopy2 — copiar o arquivo para a pasta compartilhada.
  32. sTestFileCopy3 — copiar o arquivo da pasta compartilhada para MQL5/Files. 
  33. sTestFileTransmitter — script para transmitir dados através do arquivo na pasta compartilhada.
  34. sTestFileReceiver — script para receber dados através do arquivo na pasta de dados compartilhada.
  35. sTestFileSize — definindo o tamanho do arquivo.
  36. sTestFileGetFiles — recebendo a lista de arquivos pela máscara.
  37. sTestFileWinAPICopyFileW — exemplo de uso da função WinAPI CopyFileW().
  38. sQuotesExport — script para exportar cotações.
  39. sHistoryExport — script para salvar o histórico das negociação.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2720

Arquivos anexados |
files.zip (27.47 KB)
Interfaces Gráficas X: O Controle Gráfico Padrão (build 4) Interfaces Gráficas X: O Controle Gráfico Padrão (build 4)
Desta vez, nós vamos discutir o controle gráfico padrão. Ele permitirá criar arrays de objetos gráficos com a possibilidade de sincronizar o deslocamento horizontal. Além disso, nós continuaremos a otimizar o código da biblioteca para reduzir o consumo de recursos do CPU.
ZigZag universal ZigZag universal
O Zigzag é um dos indicadores mais populares entre os usuários MetaTrader 5. No artigo, foram analisadas as possibilidades de criar diferentes variações do ZigZag. Como resultado, obtivemos um indicador universal com amplas possibilidades para estender recursos de fácil uso durante o desenvolvimento de Expert Advisor e outros indicadores.
LifeHack para traders: relatório comparativo de vários testes LifeHack para traders: relatório comparativo de vários testes
No artigo, é tratada a execução simultânea do teste de Experts em quatro símbolos diferentes. A comparação final dos quatro relatórios respetivos é realizada numa tabela, como seria feito durante a seleção de produtos numa loja. Uma vantagem adicional consiste na geração automática de gráficos de distribuição para cada símbolo.
Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast(Build 3) Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast(Build 3)
Apresentamos neste artigo a próxima versão da biblioteca Easy And Fast (build 3). Foi corrigido certas falhas e adicionado novos recursos. Para maiores informações leia a continuação do artigo.