Almacenamiento y visualización de la información

Andrey Khatimlianskii | 4 febrero, 2016

1. Introducción

¿Alguna vez se ha pasado horas y horas buscando información importante en el archivo de log, escrita por alguno de sus asesores expertos? O tal vez ya se ha cansado de permanecer enganchado a la pantalla, mirando de cerca la información que proporciona la función Comment(). Sin embargo, son muy importantes para el trading. Si sabe a lo que me estoy refiriendo, este artículo es para usted.

A continuación voy a exponer los principales problemas que me he encontrado en el momento de escribir este artículo:


2. El archivo log nativo del Asesor Experto

Como dije anteriormente, no siempre conviene utilizar la función Print() para escribir la información que genera un experto mientras opera. Esto es evidente cuando hay varios expertos que operan en el terminal simultáneamente. Cada uno de ellos escribe su información en el archivo de log, y en consecuencia es bastante complicado encontrar en dicho archivo la información que nos interesa. No cabe duda de que el análisis constructivo de la información es muy laborioso y engorroso.

Pero este problema se puede solucionar de una forma bastante sencilla. Cada experto tiene que tener su propio archivo de log. Entonces la información se escribe en cada archivo de log por separado, y no en el archivo general. Escribamos el código en forma de función y simplifiquemos las cosas.

Esta es la descripción de la función:

La idea es sencilla. Cabe señalar que hay que cerrar el archivo después de cada registro. Por un lado se permite que otra aplicación pueda abrirlo mientras el experto está operando. Pero por otro lado, puede ocurrir que dicha aplicación utilice el archivo, y eso no es bueno para el experto porque no podrá abrirlo para crear el siguiente registro. En este caso la información se perderá. Es algo que no nos podemos permitir, sobre todo porque hay programas que pueden abrir archivos solo en modo lectura, sin impedir que MetaTrader pueda trabajar con ellos.

Como la apertura y el cierre del archivo se realizan una sola vez, el código responsable se coloca en las funciones init() y deinit(), respectivamente. Implementamos el código en funciones para que el resultado final ocupe lo menos posible:

int log_handle = -1;
 
//+------------------------------------------------------------------+
// void log_open( string ExpertName = "Expert" )
//
// Función que abre el archivo log separado del experto.
// Directorio donde se crea el archivo:
// "...\MetaTrader 4\experts\files\logs\ExpertName\"
// El nombre del archivo es la fecha del registro del archivo como en "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("Mensual");
    case PERIOD_W1:  return("Semanal");
    case PERIOD_D1:  return("Diario");
    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("Periodo desconocido");
   }
 }
 
//+------------------------------------------------------------------+
// log_close()
//
// Función que cierra el archivo de log nativo del experto.
//+------------------------------------------------------------------+
void log_close()
 {
     if ( log_handle > 0 ) FileClose( log_handle );
 }
Ahora tenemos un archivo abierto y podemos escribir información en el mismo. Para ello:

Esta es la función correspondiente:

//+------------------------------------------------------------------+
// log( string text )
//
// La función que escribe la línea de texto en el archivo log nativo del experto.
//+------------------------------------------------------------------+
void log( string text )
 {
     int _GetLastError = 0;
  if ( log_handle < 0 )
     {
         Print( "Error al escribir en el log! Texto: ", text );
    return(-1);
   }
    
     //---- Mover el puntero al final del archivo
    if ( !FileSeek ( log_handle, 0, SEEK_END ) )
      {
         _GetLastError = GetLastError();
    Print( "FileSeek ( " + log_handle + ", 0, SEEK_END ) - Error #", 
          _GetLastError );
    return(-1);
  }
    //---- Si la línea que tiene que escribir el experto no es el carácter de avance de línea, 
    //---- añadimos la hora de grabación al comienzo de la línea
    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);
    }
    
    //---- Guardamos el texto escrito en el disco
    FileFlush( log_handle );
 }

Ahora, la palabra Print se puede sustituir fácilmente en todos los expertos por la palabra log, no olvidar las llamadas a las funciones log_open y log_close.

Este es un experto de ejemplo muy sencillo que incluye el archivo log.mq4:

#include <log.mq4>
 
int init()
  {
    log_open( "log_test" );
    log( "El archivo de log se ha abierto correctamente, el experto comienza a trabajar..." );
    return(0);
  }
int deinit()
  {
    log( "Cerramos el archivo de log, el experto termina de trabajar..." );
    log_close();
    return(0);
  }
int start()
  {
    log( "Nuevo tick: Bid = " + DoubleToStr( Bid, Digits ) );
    return(0);
  }

3. Visualización de la información

Una vez resuelto el problema del archivo de log, podemos comenzar a "decorar" la información visualizada.

Primero de todo tenemos en cuenta todas las formas posibles de implementar esta tarea. En MQL4 la información se puede mostrar por medio de la función Comment(), pero esta función no es suficiente por los motivos que hemos descrito aquí. Tenemos que encontrar otra solución. Los objetos que contienen texto son un buen ejemplo. Solo hay dos: "Text" y "Text Label". La diferencia fundamental entre los dos objetos es que "Text" está anclado a las coordenadas del gráfico (precio y hora), mientras que "Text Label" lo está a las coordenadas de la ventana. Utilizaremos el objeto "Text Label" porque necesitamos que la información se quede cuando el gráfico se mueve o cambia de escala.

Hay algunas funciones en MQL4 que permiten crear y controlar objetos, los nombres de las cuales comienzan con la palabra Object. Veamos cuáles nos interesan ahora:

Pues bien, esto es lo que tenemos que hacer:

De nuevo obtendremos tres funciones, cada una de las cuales realizará su propia tarea.

Antes de escribir el código me gustaría mencionar una limitación un tanto incómoda que presenta el objeto "Text Label". Solo puede tener una línea, es decir, no puede tener ningún carácter de avance de línea. Pero la información se obtiene mejor si se muestra en varias líneas. Por esto crearemos varios objetos y luego distribuiremos los datos entre ellos. He creado cinco líneas pero usted puede utilizar cualquier otro número.

Además, existe una limitación en la longitud del texto mostrado. Así que he añadido la segunda "columna", es decir, cinco líneas más en la parte derecha.

Así pues, esta es la función info_init() que crea los objetos:

/////////////////////////////////////////////////////////////////////////////////
// void info_init()
//
// Creación de los objetos que muestran la información
/////////////////////////////////////////////////////////////////////////////////
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 )
//
// Creación de "Text Label" con el nombre _Name.
// Coordenadas: х = _XDistance, у = _YDistance, esquina = _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 puede ver, los objetos se llaman "InfoLabel_" + número de objeto (de 00 a 04 para la "columna" izquierda y de 10 a 14 para la derecha). Los objetos están anclados a la esquina superior izquierda. Se pone un espacio antes de forma intencionada porque muchos usuarios están acostumbrados a ver ahí el OHLC, la información de la barra actual. El espacio vertical que existe entre las líneas es igual a 15, este valor es suficiente para un texto de tamaño normal. Una vez terminada la inicialización, echemos un vistazo a la desinicialización. Son bastante parecidas:

/////////////////////////////////////////////////////////////////////////////////
// void info_deinit()
//
// Eliminación de objetos creados por la función info_init()
/////////////////////////////////////////////////////////////////////////////////
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 );
      }
       }
 }

Ahora lo más interesante es la información en sí misma.

En consecuencia tenemos 10 objetos, cada uno de los cuales está en su lugar listo para "aceptar" el texto a mostrar. Analicemos cómo se puede utilizar la función de visualización de la manera más sencilla posible.

En primer lugar, lo más sencillo es especificar el número y no el nombre del objeto.

En segundo lugar, solo se utilizan dos parámetros: el número del objeto y el número del texto que se muestra. Pueden ser opcionales otros parámetros porque el color del texto o el tamaño de la fuente no suelen cambiarse a menudo. Aquí, tenemos que pensar qué hacemos si los parámetros se pasan por alto. Hay dos opciones:

Creo que la segunda opción es más conveniente; los parámetros predeterminados necesitarían cambiarse en el código de cada experto, y los últimos parámetros utilizados se salvarán automáticamente al utilizar la función.

Así pues, el código de nuestra función queda así:

void info( int LabelNumber, string Text, color Color = -1, 
              double FontSize = -1.0, string Font = "-1" )
 {
     //---- definimos el nombre del objeto
      string LabelName;
  if ( LabelNumber < 10 )
          LabelName = StringConcatenate( "InfoLabel_0", LabelNumber );
  else
         LabelName = StringConcatenate( "InfoLabel_" , LabelNumber );
 
  //---- si los parámetros adicionales no se especifican, 
    //---- establecemos los últimos valores utilizados
    if ( Color < 0 ) 
          Color = lastusedColor;
  if ( FontSize < 0 ) 
     FontSize = lastusedFontSize;
  if ( Font == "-1" ) 
     Font = lastusedFont;
 
  //---- guardamos los últimos valores utilizados
    lastusedColor = Color;
  lastusedFontSize = FontSize;
  lastusedFont = Font;

Para evitar situaciones donde las últimas variables utilizadas contienen el valor null, les asignamos inmediatamente los valores en la declaración:

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

Probablemente notará que las últimas variables utilizadas se declaran fuera de las funciones. Esto previene que sus valores se establezcan a cero en cada llamada a la función info().

Ahora vamos a mostrar el texto nuevo y vamos a redibujar los objetos. Esto es para que los cambios que hemos hecho al objeto se muestren inmediatamente:

 //---- mostrar el texto nuevo
    if( !ObjectSetText( LabelName, Text, FontSize, Font, Color ) )
      {
        int _GetLastError = GetLastError();
        Print( "ObjectSetText( \"", LabelName,"\", \"", Text, "\", ", FontSize, ", ", Font, 
                                             ", ", Color, " ) - Error #", _GetLastError );
      }
    //---- redibujar los objetos
    ObjectsRedraw();
  }

El código completo de las tres funciones se puede encontrar en el archivo adjunto info.mq4.

Ahora vamos a comprobar lo que tenemos:




Espero que el resultado sea bueno.

Finamente crearemos algo más: una función que reinicie la información por completo. La llamaremos info_clear(). Seguro que usted sabe cómo utilizarla.

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

4. Conclusión

El presente artículo ha descrito métodos alternativos para gestionar los archivos de log y mostrar la información. Hemos creado dos archivos que se tienen que guardar en la carpeta "experts/include": log.mq4 y info.mq4. Se pueden utilizar en cualquier asesor experto.