Armazenamento e visualização de informações

18 fevereiro 2016, 11:42
Andrey Khatimlianskii
0
853

1. Introdução

Alguma vez você já passou horas tentando encontrar alguma informação importante, escrita precavidamente pelo seu expert em um arquivo log? Ou talvez você esteja cansado de encarar as pequenas letras monocromáticas exibidas pela função Comment()? E, ainda assim, você sabe que elas podem ser de grande importância para a realização de transações. Caso você saiba do que eu estou falando, este artigo é destinado a você.

Eu apresento abaixo os problemas principais levantados por mim ao escrever este artigo:

  • Como criar uma função que possa ser usada pelo expert para escrever informações no seu arquivo log nativo (o equivalente à função Print())?
  • Como criar uma ferramenta capaz de fazer com que as informações exibidas tenham um impacto visual melhor, com alterações na cor e no tamanho da fonte?

2. O arquivo log nativo do expert

Como foi mencionado acima, a função Print() nem sempre é conveniente para a escrita de informações geradas pelo expert durante a sua operação. Isso fica especialmente evidente quando vários experts realizam transações no mesmo terminal simultaneamente. Cada expert escreve as suas próprias informações no arquivo log, e portanto se torna bastante difícil encontrar uma determinada informação nele. Nesse caso, a análise construtiva de tais informações fica absolutamente fora de questão: ela é muito trabalhosa e complicada.

Este problema pode ser resolvida de forma bastante simples. Todo expert deve ter o seu próprio arquivo log. E as informações serão escritas neste arquivo log separado do expert, e não no arquivo log geral. Para tornar este processo o mais simples possível, vamos escrever o código como uma função.

Temos abaixo a descrição daquilo que a função deve realizar:

  • criar um arquivo com um nome único
  • escrever informações neste arquivo, conforme o necessário
  • fechar o arquivo assim que o expert tiver terminado a operação, de modo que o arquivo se torne disponível para outras aplicações

Tudo está claro. A única parte discutível é a necessidade de se fechar o arquivo após cada registro. Por um lado, isso permitiria que o arquivo fosse aberto em outra aplicação durante a operação do expert. Mas, por outro lado, isso poderia trazer problemas caso o expert não fosse capaz de abrir o arquivo para criar o próximo registro em consequência do uso deste arquivo por outra aplicação. Neste caso, informações poderiam simplesmente ser perdidas. Não podemos permitir que isso ocorra, especialmente dado que há programas capazes de abrir arquivos apenas para leitura, sem impedir que o MetaTrader trabalhe com eles.

Como a abertura e o fechamento do arquivo ocorre apenas uma vez, o código responsável será colocado nas funções init() e deinit(), respectivamente. Para fazer com que ele ocupe o mínimo espaço possível, vamos representá-lo também como funções:

int log_handle = -1;
 
//+------------------------------------------------------------------+
// void log_open( string ExpertName = "Expert" )
//
// Function that opens the expert's separate log file.
// Directory, in which the file will be created:
// "...\MetaTrader 4\experts\files\logs\ExpertName\"
// The file name is the date of the file record appeared as "YYYY.MM.DD"
//+------------------------------------------------------------------+
void log_open( string ExpertName = "Expert" )
 {
     string log_name = "logs\\" + ExpertName + " (" + Symbol() + ", " + 
                   strPeriod( Period() ) + ")\\" + TimeToStr( LocalTime(),
                   TIME_DATE ) + ".txt";
  log_handle = FileOpen ( log_name, FILE_READ | FILE_WRITE, " " );
    
  if ( log_handle < 0 )
     { 
         int _GetLastError = GetLastError();
    Print( "FileOpen( ", log_name, ", FILE_READ | FILE_WRITE, \" \" ) - Error #", 
                     _GetLastError );
    return(-1);
   }
 }
string strPeriod( int intPeriod )
 {
     switch ( intPeriod )
     {
          case PERIOD_MN1: return("Monthly");
    case PERIOD_W1:  return("Weekly");
    case PERIOD_D1:  return("Daily");
    case PERIOD_H4:  return("H4");
    case PERIOD_H1:  return("H1");
    case PERIOD_M30: return("M30");
    case PERIOD_M15: return("M15");
    case PERIOD_M5:  return("M5");
    case PERIOD_M1:  return("M1");
    default:        return("UnknownPeriod");
   }
 }
 
//+------------------------------------------------------------------+
// log_close()
//
// Function that closes the expert's native log file.
//+------------------------------------------------------------------+
void log_close()
 {
     if ( log_handle > 0 ) FileClose( log_handle );
 }


Agora nós temos um arquivo aberto, e informações podem ser escritas nele. Para fazer isso:

  • mova o cursor até o fim do arquivo, de modo a não perder informações;
  • substitua o tempo do registro para o início da linha; isso será útil para a análise;
  • escreva o texto no arquivo;
  • armazene o arquivo completo no disco; caso o expert termine a sua operação inesperadamente, isso irá evitar a perda de dados

A seguinte função deve resultar dos passos acima:

//+------------------------------------------------------------------+
// log( string text )
//
// The function that writes the text line into the expert's native log file.
//+------------------------------------------------------------------+
void log( string text )
 {
     int _GetLastError = 0;
  if ( log_handle < 0 )
     {
         Print( "Log write error! Text: ", text );
    return(-1);
   }
    
     //---- Move the file pointer to the end of file
    if ( !FileSeek ( log_handle, 0, SEEK_END ) )
      {
         _GetLastError = GetLastError();
    Print( "FileSeek ( " + log_handle + ", 0, SEEK_END ) - Error #", 
          _GetLastError );
    return(-1);
  }
    //---- If the line to be written by the expert is not the line feed character, 
    //---- add the recording time at the beginning of the line
    if( text != "\n" && text != "\r\n" )
         text = StringConcatenate( TimeToStr( LocalTime(), TIME_SECONDS ), 
                             " - - - ", text );
    if( FileWrite ( log_handle, text ) < 0 )
          {
             _GetLastError = GetLastError();
     Print( "FileWrite ( ", log_handle, ", ", text, " ) - Error #", 
           _GetLastError );
     return(-1);
    }
    
    //---- Save the written text on the disk
    FileFlush( log_handle );
 }


Agora a palavra Print pode ser facilmente substituída pela palavra log em todos os experts, sendo que chamadas de log_open e log_close não devem ser deixadas de lado.

Este é um expert exemplar simples usando o arquivo log.mq4 incluído:

#include <log.mq4>
 
int init()
  {
    log_open( "log_test" );
    log( "The log file has been successfully opened, the expert starts working..." );
    return(0);
  }
int deinit()
  {
    log( "Close the log file, the expert finishes work..." );
    log_close();
    return(0);
  }
int start()
  {
    log( "New tick: Bid = " + DoubleToStr( Bid, Digits ) );
    return(0);
  }

3. Exibição de informações

Agora que nós resolvemos o problema do arquivo log, nós podemos começar a "decorar" as informações exibidas.

Primeiramente, vamos considerar todas as formas possíveis de se implementar esta tarefa. No MQL4, a função Comment() é responsável por exibir as informações, mas ela não é adequada pelas razões mencionadas acima. Portanto nós temos que encontrar uma outra solução. Um bom exemplo podem ser objetos contendo textos. Há apenas dois deles: "Text" ("texto") e "Text Label" ("etiqueta de texto"). A diferença fundamental entre eles é que o "texto" é ancorado às coordenadas do gráfico (preço e tempo), e a "etiqueta de texto" é ancorada às coordenadas das janelas. Como é desejável que as informações permaneçam em posição quando o gráfico se move ou a sua escala é alterada, nós iremos utilizar a "etiqueta de texto".

Há algumas funções no MQL4 que criam e controlam objetos, e todos os seus nomes começam com a palavra "Object" ("objeto"). Vamos ver quais delas podem ser úteis para os nossos objetivos:

  • bool ObjectCreate(...) – para criar um objeto;
  • bool ObjectDelete(...) – para deletar objetos após o uso;
  • bool ObjectSet(...) – para alterar as propriedades do objeto, como a sua âncora (x,y);
  • bool ObjectSetText(...) – para exibir o texto;
  • void ObjectsRedraw() – para redesenhar objetos após o texto ter sido alterado.

É isso que nós temos que fazer:

  • na função do expert chamada init(), criar objetos que exibirão informações;
  • na função do expert chamada deinit(), deletar todos os objetos criados;
  • na função start(), ser capaz de alterar o texto, a cor e o tamanho da fonte de todos os objetos criados.

Nós teremos 3 funções novamente, cada uma cumprindo a sua própria tarefa.


Antes de escrever o código, eu gostaria de mencionar uma limitação desagradável relativa ao uso da "etiqueta de texto". Ela só pode conter uma linha, ou seja, não pode conter quaisquer caracteres de avanço de linha. Mas as informações são compreendidas muito mais facilmente se exibidas ao longo de várias linhas. É por isso que nós vamos criar vários objetos e distribuir os dados entre eles. Eu criei cinco "linhas", mas você pode usar qualquer outra quantidade de linhas.

Além disso, há uma limitação relativa ao comprimento do texto exibido. Portanto eu adicionei uma segunda "coluna", ou seja, mais cinco linhas no lado direito.

Essa é a aparência da função info_init(), que cria objetos:

/////////////////////////////////////////////////////////////////////////////////
// void info_init()
//
// Creation of objects to display information
/////////////////////////////////////////////////////////////////////////////////
void info_init()
  {
    for( int row = 0; row <= 4; row ++ )
      {
        _LabelCreate( StringConcatenate( "InfoLabel_0", row ),   4, 15 + 15*row );
        _LabelCreate( StringConcatenate( "InfoLabel_1", row ), 270, 15 + 15*row );
      }
  }
 
/////////////////////////////////////////////////////////////////////////////////
// void _LabelCreate ( string _Name, int _XDistance, int _YDistance, int _Corner = 0 )
//
// Creation of "Text Label" named as _Name.
// Coordinates: х = _XDistance, у = _YDistance, corner = _Corner.
/////////////////////////////////////////////////////////////////////////////////
void _LabelCreate ( string _Name, int _XDistance, int _YDistance, int _Corner = 0 )
  {
    int _GetLastError;
 
    if( !ObjectCreate( _Name, OBJ_LABEL, 0, 0, 0 ) )
      {
        _GetLastError = GetLastError();
        if ( _GetLastError != 4200 )
          {
            Print( "ObjectCreate( \"", _Name, "\", OBJ_LABEL,0,0,0 ) - Error #",
                   _GetLastError );
            return(-1);
          }
      }
    if( !ObjectSet( _Name, OBJPROP_CORNER, _Corner ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSet( \"", _Name, "\", OBJPROP_CORNER, ", _Corner, 
                                       " ) - Error #", _GetLastError );
      }
    if( !ObjectSet( _Name, OBJPROP_XDISTANCE, _XDistance ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSet( \"", _Name, "\", OBJPROP_XDISTANCE, ", _XDistance, 
                                             " ) - Error #", _GetLastError );
      }
    if( !ObjectSet( _Name, OBJPROP_YDISTANCE, _YDistance ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSet( \"", _Name, "\", OBJPROP_YDISTANCE, ", _YDistance, 
                                             " ) - Error #", _GetLastError );
      }
    if( !ObjectSetText ( _Name, "", 10 ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSetText( \"", _Name, "\", \"\", 10 ) - Error #", _GetLastError );
      }
  }

Como você pode ver, os objetos serão chamados de "InfoLabel_" + número do objeto (de 00 a 04 para a "coluna" esquerda e de 10 a 14 para a direita). Os objetos são ancorados ao canto superior esquerdo. Um espaço é criado antes dele de modo intencional, pois muitos usuários estão acostumados a ver a OHLC (as informações da barra atual) ali. Eu criei um espaço vertical entre as linhas igual a 15, o que é suficiente para um texto de tamanho normal. Portanto a inicialização está terminada, vamos criar a deinicialização. Ela vai ser bastante semelhante:

/////////////////////////////////////////////////////////////////////////////////
// void info_deinit()
//
// Deletion of objects created by the info_init() function
/////////////////////////////////////////////////////////////////////////////////
void info_deinit()
 {
     int _GetLastError;
   for ( int row = 0; row <= 4; row ++ )
      {
          if ( !ObjectDelete( StringConcatenate( "InfoLabel_0", row ) ) )
           {
                   _GetLastError = GetLastError();
       Print( "ObjectDelete( \"", StringConcatenate( "InfoLabel_0", row ), 
                                        "\" ) - Error #", _GetLastError );
      }
          if( !ObjectDelete( StringConcatenate( "InfoLabel_1", row ) ) )
               {
                  _GetLastError = GetLastError();
       Print( "ObjectDelete( \"", StringConcatenate( "InfoLabel_1", row ), 
                                       "\" ) - Error #", _GetLastError );
      }
       }
 }


A coisa mais interessante é que a informação exibe a si mesma.

Portanto nós temos 10 objetos, que estão em suas posições e prontos para "receber" o texto a ser exibido. Vamos pensar como podemos tornar o uso da função de exibição o mais simples possível.

Em primeiro lugar, é mais simples especificar o número, e não o nome do objeto.

Em segundo lugar, apenas dois parâmetros devem ser usados: o número do objeto e o número do texto exibido. Outros parâmetros podem ser opcionais, pois a cor e o tamanho do texto nem sempre precisam ser alterados. Neste ponto, nós temos que imaginar o que fazer caso os parâmetros forem ignorados. Existem duas opções:

  • usar parâmetros por padrão;
  • ou usar os últimos parâmetros utilizados.

Eu acredito que a segunda opção é mais útil: Seria necessário alterar os parâmetros padrão no código de todo expert, e os últimos parâmetros utilizados são salvos automaticamente durante cada uso da função.

Portanto, essa será a aparência da nossa função:

void info( int LabelNumber, string Text, color Color = -1, 
              double FontSize = -1.0, string Font = "-1" )
 {
     //---- define the object name
      string LabelName;
  if ( LabelNumber < 10 )
          LabelName = StringConcatenate( "InfoLabel_0", LabelNumber );
  else
         LabelName = StringConcatenate( "InfoLabel_" , LabelNumber );
 
  //---- if the additional parameters were not specified, 
    //---- set the last used values for them
    if ( Color < 0 ) 
          Color = lastusedColor;
  if ( FontSize < 0 ) 
     FontSize = lastusedFontSize;
  if ( Font == "-1" ) 
     Font = lastusedFont;
 
  //---- save the last used values
    lastusedColor = Color;
  lastusedFontSize = FontSize;
  lastusedFont = Font;

Para evitar situações nas quais as últimas variáveis utilizadas contenham um valor nulo, vamos atribuir os seus valores imediatamente na declaração.

color lastusedColor = Black;
double lastusedFontSize = 9.0;
string lastusedFont = "Arial";


Você provavelmente pode notar que as últimas variáveis utilizadas são declaradas sem funções. Isso evita que os seus valores "zeroizem" a cada chamada de função info().

E agora, vamos exibir o novo texto e redesenhar os objetos. Isso deve ser feito para que as mudanças que nós fizemos em relação ao objeto sejam exibidas imediatamente:

 //---- display the new text
    if( !ObjectSetText( LabelName, Text, FontSize, Font, Color ) )
      {
        int _GetLastError = GetLastError();
        Print( "ObjectSetText( \"", LabelName,"\", \"", Text, "\", ", FontSize, ", ", Font, 
                                             ", ", Color, " ) - Error #", _GetLastError );
      }
    //---- redraw the objects
    ObjectsRedraw();
  }

O código completo de todas as três funções pode ser encontrado no arquivo anexo chamado info.mq4.

Agora, vamos examinar o que criamos:




Eu acredito que esteja bastante bom.

Por fim, nós podemos criar mais uma conveniência – uma função que eliminaria todas as informações. Ela será chamada info_clear(). Estou certo de que você sabe como usá-la.

void info_clear()
 {
     for ( int n = 0;  n < 5;  n ++ ) 
       info( n, "" );
  for (     n = 10; n < 15; n ++ ) 
       info( n, "" );
 }

4. Resumo

O artigo descreveu métodos alternativos de manter arquivos log e exibir informações. Foram criados dois arquivos, aqui inclusos, a serem mantidos no diretório "experts/include": log.mq4 e info.mq4. Eles não podem ser utilizados a partir de nenhum expert.

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/1405

Arquivos anexados |
info.mq4 (4.5 KB)
info_test_eng.mq4 (1.24 KB)
log.mq4 (3.19 KB)
log_test_eng.mq4 (0.76 KB)
Gerenciamento de pedidos - É simples Gerenciamento de pedidos - É simples

O artigo trata das várias formas de se controlar posições abertas e pedidos pendentes. Ele tem como objetivo simplificar a escrita de Expert Advisors.

Como avaliar os resultados dos testes do Expert Como avaliar os resultados dos testes do Expert

O artigo fornece fórmulas e a ordem de cálculo relativas aos dados exibidos no relatório do verificador.

Comentador do sistema do expert. Uso prático dos indicadores integrados a um programa MQL4 Comentador do sistema do expert. Uso prático dos indicadores integrados a um programa MQL4

O artigo descreve o uso de indicadores técnicos na programação com o MQL4.

Gráficos "sem buracos" Gráficos "sem buracos"

O artigo trata da realização de gráficos sem barras puladas.