Information Storage and View

Andrey Khatimlianskii | 7 July, 2006

1. Introduction

Have you ever spent hours of time trying to find some important information previously providently written by your expert into a log file? Or, perhaps, you are fully tired of peering at the small one-color letters displayed by the Comment() function? And yet, they can be of great importance for trading. If you know what I mean, this article is intended for you.

Below are the main problems I posed for myself writing this article:


2. The Expert's Native Log File

As I said before, the Print() function is not always convenient for writing of information generated by the expert during its operation. This is especially evident when several experts trade in the same terminal simultaneously. Each expert writes its own information into the log file, and then it is rather difficult to find something there. In this case, the constructive analysis of such information is absolutely out of the question: it is very laborious and cumbersome.

This problem can be solved in a rather simple way. Every expert must have its own log file. And the information will be written in this separate log file of the expert, not in the general one. To be able to use this opportunity as easily as possible, let us write the code as a function.

Well, it is described below what the function must do:

Everything is clear. The only arguable thing is the necessity to close the file after every record. On the one hand, this would allow opening of the file in another appplication during the expert operation. But, on the other hand, it could be harmful if the expert would not be able to open the file to make the next record, since the file could be used by another application. In this case, information can be simply lost. This may not be allowed, especially since there are programs that can open files for reading only, without disturbing MetaTrader to work with them.

As opening and closing of the file is performed only once, the responsible code will be placed into the init() and deinit() functions, respectively. To make it occupy the minimum space, let us represent it as functions, as well:

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 );
 }
Now we have an open file, information can be written into it. To do so:

The following function must result from the above:

//+------------------------------------------------------------------+
// 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 );
 }

Now, the word Print can be easily replaced with the word log in all experts, calls of the log_open and log_close functions not to be forgotten.

This is a simple exemplary expert using the included file log.mq4:

#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. Information Displaying

Now, when we have solved the problem of the log file, we can start "decorating" the displayed information.

First of all, let us consider all possible ways of how to implement this task. In MQL4, the Comment() function is responsible for displaying of the information, but it does not suit for the reasons described above. So we have to find another solution. A good example of this can be objects containing texts. There are only two of them: "Text" and "Text Label". The fundamental difference between them is that the "Text" is anchored to the chart coordinates (price and time), and the "Text Label" is to the windows coordinates. Since we need information to remain on the spot when the chart moves or its scale is changed, this will be the "Text Label" that we will use.

There are some functions in MQL4 that create and control objects, all names starting with the word Object. Let us see what of them can be useful for our purposes:

Well, this is what we have to do:

We will get 3 functions again, each completing its own task.

Before writing the code, I would like to mention an unpleasant limitation concerning usage of the "Text Label". It can be only of one line, i.e., it may not contain any line feed characters. But information is acquired much better if it is displayed in several lines. This is why we will create several objects and then distribute the data among them. I made five "lines", but you can use any other amounts of lines.

Besides, there is a limitation concerning the length of the displayed text. So I have added the second "column", i.e., five more lines on the right-hand side.

This is how the function info_init() that creates objects looks:

/////////////////////////////////////////////////////////////////////////////////
// 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 );
      }
  }

As you can see, objects will be named as "InfoLabel_" + object number (from 00 to 04 for the left "column" and from 10 to 14 for the right one). The objects are anchored to the upper left corner. A space is intentionally made before, since many users got used to see OHLC (the current bar information) there. I made the vertical space between lines equal to 15, this is enough for a text of a normal size. So, initialization is over, let us make the deinitialization. They will be rather similar:

/////////////////////////////////////////////////////////////////////////////////
// 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 );
      }
       }
 }

Now, the most interesting thing is the information displaying itself.

Thus, we have 10 objects, which are each on their places and ready to "accept" the text to be displayed. Let us think of how to make the use of the displaying function as simple as possible.

First, it is simpler to specify the number, not the name of the object.

Second, only two parameters must be used: the object number and the displayed text number. Other parameters can be optional since the text color or the font size do not often need to be changed. Here, we have to think over what to do if the parameters are skipped. There are two choices:

I think the second choice to be more useful: The default parameters would need being changed in the code for every expert, and the last used parameters will be saved automatically at every use of the function.

Thus, this is how our function looks:

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;

To avoid situations where the last used variables contain the null as a value, let us assign them their values immediately at declaration:

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

You could probably note that the last used variables are declared out of functions. This will prevent their values from zeroizing at every info() function call.

And now, let us display the new text and redraw the objects. This must be done for the changes that we have made to the object to be displayed immediately:

 //---- 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();
  }

The full code of all three functions can be found in the attached file named info.mq4.

Now, let us check what we have got:




I suppose this to be very good.

Finally, we can create one more convenience – a function that would clear the entire information. It will be named info_clear(). I am sure you know how to use it.

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

4. Summary

The article described alternative methods of how to keep log files and display information. Two included files were created to be kept in the "experts/include" folder: the log.mq4 and the info.mq4. They can be used from any expert.