Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
MQL5 Cookbook: Developing a Multi-Currency Expert Advisor with Unlimited Number of Parameters

MQL5 Cookbook: Developing a Multi-Currency Expert Advisor with Unlimited Number of Parameters

MetaTrader 5Examples | 15 July 2013, 11:24
10 965 3
Anatoli Kazharski
Anatoli Kazharski

Introduction

The multi-currency Expert Advisor considered in the previous article "MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach", can be very useful if the number of symbols and trading strategy parameters used is small. However, there is a restriction on the number of input parameters of an Expert Advisor in MQL5: they should be no more than 1024.

And even though this number will very often be sufficient, it is very inconvenient to use such a huge list of parameters. Every time a change or optimization of parameters for a given symbol is required, you have to search parameters for that specific symbol in the long parameter list.

You can familiarize yourself with further restrictions in the Inputs section of the client terminal Help.

In this article, we will create a pattern that uses a single set of parameters for optimization of a trading system, while allowing for unlimited number of parameters. The list of symbols will be created in a standard text file (*.txt). Input parameters for each symbol will also be stored in files.

It needs to be mentioned here that the Expert Advisor will work on one symbol in the normal operation mode but you will be able to test it in the Strategy Tester on a variety of selected symbols (on each symbol separately).

It would, in fact, be even more convenient to create the symbol list directly in the Market Watch window, considering that it even allows for saving ready made symbol sets. We could even make the Expert Advisor to get the symbol list in the Market Watch window directly from the Strategy Tester. But unfortunately, it is currently not possible to access the Market Watch window from the Strategy Tester, so we will have to create the symbol list manually in advance or using a script.


Expert Advisor Development

The multi-currency Expert Advisor featured in the previous article "MQL5 Cookbook: Multi-Currency Expert Advisor - Simple, Neat and Quick Approach" will be taken as a template. Let's first decide on the input parameters. As mentioned above, we will only leave one set of parameters. Below is the list of the input parameters of the Expert Advisor:

//--- Input parameters of the Expert Advisor
sinput long                      MagicNumber          =777;      // Magic number
sinput int                       Deviation            =10;       // Slippage
sinput string                    delimeter_00=""; // --------------------------------
sinput int                       SymbolNumber         =1;        // Number of the tested symbol
sinput bool                      RewriteParameters    =false;    // Rewriting parameters
sinput ENUM_INPUTS_READING_MODE  ParametersReadingMode=FILE;     // Parameter reading mode
sinput string                    delimeter_01=""; // --------------------------------
input  int                       IndicatorPeriod      =5;        // Indicator period
input  double                    TakeProfit           =100;      // Take Profit
input  double                    StopLoss             =50;       // Stop Loss
input  double                    TrailingStop         =10;       // Trailing Stop
input  bool                      Reverse              =true;     // Position reversal
input  double                    Lot                  =0.1;      // Lot
input  double                    VolumeIncrease       =0.1;      // Position volume increase
input  double                    VolumeIncreaseStep   =10;       // Volume increase step

Only parameters with the input modifier will be written to a file. Further, we should expand on the three new parameters that we have never come across before.

  • SymbolNumber - this parameter indicates the number of the symbol from the file that contains the symbol list. If it is set to 0, all symbols from the list will be tested. If you go beyond the list, the test will not be performed.
  • RewriteParameters - if this parameter value is set to true, the file with parameters of the specified symbol (number in the SymbolNumber parameter) will be rewritten using current input parameter values. Alternatively, if it is set to false, parameters will be read from a file.
  • ParametersReadingMode - this parameter indicates the reading mode with respect to input parameters. The parameter type is the ENUM_INPUTS_READING_MODE custom enumeration that offers two options: to read from a file and to use current parameters.

The ENUM_INPUTS_READING_MODE enumeration code:

//+------------------------------------------------------------------+
//| Input parameter reading modes                                    |
//+------------------------------------------------------------------+
enum ENUM_INPUTS_READING_MODE
  {
   FILE             = 0, // File
   INPUT_PARAMETERS = 1  // Input parameters
  };

The number of symbols was previously determined using the NUMBER_OF_SYMBOLS constant. This value now depends on different modes so we will change it to the SYMBOLS_COUNT global variable:

//--- Number of traded symbols. It is calculated and depends on the testing mode and the number of symbols in the file
int SYMBOLS_COUNT=0;

At the beginning of the Expert Advisor's code, we declare another constant, TESTED_PARAMETERS_COUNT, that determines array sizes and the number of loop iterations when iterating over input parameters:

//--- Number of tested/optimized parameters
#define TESTED_PARAMETERS_COUNT 8

The file with the symbol list, as well as the file folder containing parameters for each symbol must be placed in the common folder of the terminal as it can be accessed on a program level both during the normal operation of the Expert Advisor and when testing it.

Now we need to prepare arrays to work with later. All arrays in the multi-currency Expert Advisor from the previous article that we are going to modify are static, i.e. with a preset size of elements. We should make all of them dynamic as the size will now depend on the number of symbols used in the file and the input parameter reading mode. We are also going to add new arrays (highlighted in yellow) which are required for working with files:

//--- Arrays of input parameters
string InputSymbols[];            // Symbol names
//---
int    InputIndicatorPeriod[];    // Indicator periods
double InputTakeProfit[];         // Take Profit values
double InputStopLoss[];           // Stop Loss values
double InputTrailingStop[];       // Trailing Stop values
bool   InputReverse[];            // Values of position reversal flags
double InputLot[];                // Lot values
double InputVolumeIncrease[];     // Position volume increases
double InputVolumeIncreaseStep[]; // Volume increase steps
//--- Array of handles for indicator agents
int spy_indicator_handles[];
//--- Array of signal indicator handles
int signal_indicator_handles[];
//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[];      // Opening price of the bar
PriceData high[];      // High price of the bar
PriceData low[];       // Low price of the bar
PriceData close[];     // Closing price of the bar
PriceData indicator[]; // Array of indicator values
//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[];
//--- Array for checking the new bar for each symbol
datetime new_bar[];
//--- Array of input parameter names for writing to the file
string input_parameters[TESTED_PARAMETERS_COUNT]=
  {
   "IndicatorPeriod",   // Indicator period
   "TakeProfit",        // Take Profit
   "StopLoss",          // Stop Loss
   "TrailingStop",      // Trailing Stop
   "Reverse",           // Position reversal
   "Lot",               // Lot
   "VolumeIncrease",    // Position volume increase
   "VolumeIncreaseStep" // Volume increase step
  };
//--- Array for untested symbols
string temporary_symbols[];
//--- Array for storing input parameters from the file of the symbol selected for testing or trading
double tested_parameters_from_file[];
//--- Array of input parameter values for writing to the file
double tested_parameters_values[TESTED_PARAMETERS_COUNT];

Then we need to determine the array sizes and initialize them to values.

The array of input parameter values created above will be initialized in the InitializeTestedParametersValues() function that we are going to create in the InitializeArrays.mqh file. That array will be used in writing input parameter values to the file.

//+------------------------------------------------------------------+
//| Initializing the array of tested input parameters                |
//+------------------------------------------------------------------+
void InitializeTestedParametersValues()
  {
   tested_parameters_values[0]=IndicatorPeriod;
   tested_parameters_values[1]=TakeProfit;
   tested_parameters_values[2]=StopLoss;
   tested_parameters_values[3]=TrailingStop;
   tested_parameters_values[4]=Reverse;
   tested_parameters_values[5]=Lot;
   tested_parameters_values[6]=VolumeIncrease;
   tested_parameters_values[7]=VolumeIncreaseStep;
  }

Now let's consider file operations. First, create another function library, FileFunctions.mqh, in UnlimitedParametersEA\Include folder of your Expert Advisor. This library will be used for creating functions related to reading and writing data to a file. Include it in the main file of the Expert Advisor and other files of the project.

//---Include custom libraries
#include "Include\Enums.mqh"
#include "Include\InitializeArrays.mqh"
#include "Include\Errors.mqh"
#include "Include\FileFunctions.mqh"
#include "Include\TradeSignals.mqh"
#include "Include\TradeFunctions.mqh"
#include "Include\ToString.mqh"
#include "Include\Auxiliary.mqh"

We start with creating a function for reading the symbol list from a text file - ReadSymbolsFromFile(). This file (let's name it TestedSymbols.txt) should be placed in the \Files sub-folder of the common folder of the MetaTrader 5 client terminal. In my case it will be C:\ProgramData\MetaQuotes\Terminal\Common but you should carefully check the path using the standard TERMINAL_COMMONDATA_PATH constant:

TerminalInfoString(TERMINAL_COMMONDATA_PATH)
All custom files should be placed in the \Files sub-folder of the client terminal's common folder or in <Terminal Data Folder>\MQL5\Files\. File functions can only access these folders.

To check the functionality, add a few symbols to the created text file, separating each of the symbols with a line break ("\r\n"):

Fig. 1. Symbol list in the file from the common folder of the terminal.

Fig. 1. Symbol list in the file from the common folder of the terminal.

Further, let's have a look at the ReadSymbolsFromFile() function code:

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) in the file and        |
//| filling the temporary array of symbols temporary_symbols[]       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   int strings_count=0; // String counter
//--- Open the file for reading from the common folder of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      ulong  offset =0; // Offset for determining the position of the file pointer
      string text ="";  // The read string will be written to this variable
      //--- Read until the current position of the file pointer reaches the end of the file or until the program is deleted
      while(!FileIsEnding(file_handle) || !IsStopped())
        {
         //--- Read until the end of the string or until the program is deleted
         while(!FileIsLineEnding(file_handle) || !IsStopped())
           {
            //--- Read the whole string
            text=FileReadString(file_handle);
            //--- Get the position of the pointer
            offset=FileTell(file_handle);
            //--- Go to the next string if this is not the end of the file
            //    For this purpose, increase the offset of the file pointer
            if(!FileIsEnding(file_handle))
               offset++;
            //--- Move it to the next string
            FileSeek(file_handle,offset,SEEK_SET);
            //--- If the string is not empty
            if(text!="")
              {
               //--- Increase the string counter
               strings_count++;
               //--- Increase the size of the array of strings,
               ArrayResize(temporary_symbols,strings_count);
               //--- Write the read string to the current index
               temporary_symbols[strings_count-1]=text;
              }
            //--- Exit the nested loop
            break;
           }
         //--- If this is the end of the file, terminate the main loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- Return the number of strings in the file
   return(strings_count);
  }

Here, the text file is read line by line. The name of each financial instrument that has been read is written to the temporary temporary_symbols[] array created earlier (the actual accessibility of the symbol on the trade server will be checked later). Moreover, at the end the function returns the number of read strings, i.e. the number of symbols on which our Expert Advisor will be tested.

Always refer to MQL5 Reference for detailed information on functions and identifiers you never came across before. The comments provided in the code will simplify the learning process.

Let's get back to the InitializeArrays.mqh file where we are going to create the InitializeInputSymbols() function for filling the InputSymbols[] array of symbol names declared earlier. Beginners will probably find it quite complex so I have provided detailed comments to the code:

//+------------------------------------------------------------------+
//| Filling the InputSymbol[] array of symbols                       |
//+------------------------------------------------------------------+
void InitializeInputSymbols()
  {
   int    strings_count=0;    // Number of strings in the symbol file
   string checked_symbol="";  // To check the accessibility of the symbol on the trade server
//--- Get the number of symbols from the "TestedSymbols.txt" file
   strings_count=ReadSymbolsFromFile("TestedSymbols.txt");
//--- In optimization mode or in one of the two modes (testing or visualization), provided that the symbol NUMBER IS SPECIFIED
   if(IsOptimization() || ((IsTester() || IsVisualMode()) && SymbolNumber>0))
     {
      //--- Determine the symbol to be involved in parameter optimization
      for(int s=0; s<strings_count; s++)
        {
         //--- If the number specified in the parameters and the current loop index match
         if(s==SymbolNumber-1)
           {
            //--- Check whether the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Set the number of symbols
               SYMBOLS_COUNT=1;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[0]=checked_symbol;
              }
            //--- Exit
            return;
           }
        }
     }
//--- In testing or visualization mode, if you need to test ALL symbols from the list in the file
   if((IsTester() || IsVisualMode()) && SymbolNumber==0)
     {
      //--- Parameter reading mode: from the file
      if(ParametersReadingMode==FILE)
        {
         //--- Iterate over all symbols in the file
         for(int s=0; s<strings_count; s++)
           {
            //--- Check if the symbol is on the trade server
            if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
              {
               //--- Increase the symbol counter
               SYMBOLS_COUNT++;
               //--- Set the size of the array of symbols
               ArrayResize(InputSymbols,SYMBOLS_COUNT);
               //--- Write the symbol name
               InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
              }
           }
         //--- Exit
         return;
        }
      //--- Parameter reading mode: from input parameters of the Expert Advisor
      if(ParametersReadingMode==INPUT_PARAMETERS)
        {
         //--- Set the number of symbols
         SYMBOLS_COUNT=1;
         //--- Set the size of the array of symbols
         ArrayResize(InputSymbols,SYMBOLS_COUNT);
         //--- Write the current symbol name
         InputSymbols[0]=Symbol();
         //--- Exit
         return;
        }
     }
//--- In normal operation mode of the Expert Advisor, use the current chart symbol
   if(IsRealtime())
     {
      //--- Set the number of symbols
      SYMBOLS_COUNT=1;
      //--- Set the size of the array of symbols
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- Write the symbol name
      InputSymbols[0]=Symbol();
     }
//---
  }

Once the size of the array of input parameters has been determined by the number of symbols used, you should set the size of all other arrays of input parameters. Let's implement it as a separate function - ResizeInputParametersArrays():

//+------------------------------------------------------------------+
//| Setting the new size for arrays of input parameters              |
//+------------------------------------------------------------------+
void ResizeInputParametersArrays()
  {
   ArrayResize(InputIndicatorPeriod,SYMBOLS_COUNT);
   ArrayResize(InputTakeProfit,SYMBOLS_COUNT);
   ArrayResize(InputStopLoss,SYMBOLS_COUNT);
   ArrayResize(InputTrailingStop,SYMBOLS_COUNT);
   ArrayResize(InputReverse,SYMBOLS_COUNT);
   ArrayResize(InputLot,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncrease,SYMBOLS_COUNT);
   ArrayResize(InputVolumeIncreaseStep,SYMBOLS_COUNT);
  }

Now, we need to create the functionality that will allow us to read input parameter values for each symbol, as well as to write those parameter values to a separate file (for each symbol) depending on the input mode selected and the Expert Advisor settings. This will be done similarly to how we read the symbol list from the file. It is a complex task so let's break it into several procedures.

First, we should learn to read input parameter values from a file to an array. The array will contain double type values. We will later convert some of them (indicator period and position reversal flag) to int and bool types, respectively. The open file handle and the array where the parameter values from the file are stored are passed to the ReadInputParametersValuesFromFile() function:

//+----------------------------------------------------------------------+
//| Reading parameters from the file and storing them in the passed array|
//| Text file format: key=value
//+----------------------------------------------------------------------+
bool ReadInputParametersValuesFromFile(int handle,double &array[])
  {
   int    delimiter_position  =0;  // Number of the symbol position "=" in the string
   int    strings_count       =0;  // String counter
   string read_string         =""; // The read string
   ulong  offset              =0;  // Position of the pointer (offset in bytes)
//--- Move the file pointer to the beginning of the file
   FileSeek(handle,0,SEEK_SET);
//--- Read until the current position of the file pointer reaches the end of the file
   while(!FileIsEnding(handle))
     {
      //--- If the user deleted the program
      if(IsStopped())
         return(false);
      //--- Read until the end of the string
      while(!FileIsLineEnding(handle))
        {
         //--- If the user deleted the program
         if(IsStopped())
            return(false);
         //--- Read the string
         read_string=FileReadString(handle);
         //--- Get the index of the separator ("=") in the read string
         delimiter_position=StringFind(read_string,"=",0);
         //--- Get everything that follows the separator ("=") until the end of the string
         read_string=StringSubstr(read_string,delimiter_position+1);
         //--- Place the obtained value converted to the double type in the output array
         array[strings_count]=StringToDouble(read_string);
         //--- Get the current position of the file pointer
         offset=FileTell(handle);
         //--- If it's the end of the string
         if(FileIsLineEnding(handle))
           {
            //--- Go to the next string if it's not the end of the file
            if(!FileIsEnding(handle))
               //--- Increase the offset of the file pointer by 1 to go to the next string
               offset++;
            //--- Move the file pointer relative to the beginning of the file
            FileSeek(handle,offset,SEEK_SET);
            //--- Increase the string counter
            strings_count++;
            //--- Exit the nested loop for reading the string
            break;
           }
        }
      //--- If it's the end of the file, exit the main loop
      if(FileIsEnding(handle))
         break;
     }
//--- Return the fact of successful completion
   return(true);
  }

What file handles and arrays are we going to pass to this function? It will depend on what symbols we will work with after reading them from the "TestedSymbols.txt" file. Each symbol will correspond to a certain text file containing input parameter values. There are two case scenarios to consider:

  1. The file exists and we read input parameter values from that file using the ReadInputParametersValuesFromFile() function described above.
  2. The file does not exist or we need to rewrite the existing input parameter values.

The format of the text file (an .ini file, although you can choose any other extension you may consider necessary) containing input parameter values will be simple:

input_parameter_name1=value
input_parameter_name2=value
....
input_parameter_nameN=value

Let's combine this logic in one single function, ReadWriteInputParameters(), whose code is provided below:

//+------------------------------------------------------------------+
//| Reading/writing input parameters from/to a file for a symbol     |
//+------------------------------------------------------------------+
void ReadWriteInputParameters(int symbol_number,string path)
  {
   string file_name=path+InputSymbols[symbol_number]+".ini"; // File name
//---
   Print("Find the file '"+file_name+"' ...");
//--- Open the file with input parameters of the symbol
   int file_handle_read=FileOpen(file_name,FILE_READ|FILE_ANSI|FILE_COMMON);
   
//--- Scenario #1: the file exists and parameter values do not need to be rewritten
   if(file_handle_read!=INVALID_HANDLE && !RewriteParameters)
     {
      Print("The file '"+InputSymbols[symbol_number]+".ini' exists, reading...");
      //--- Set the array size
      ArrayResize(tested_parameters_from_file,TESTED_PARAMETERS_COUNT);
      //--- Fill the array with file values
      ReadInputParametersValuesFromFile(file_handle_read,tested_parameters_from_file);
      //--- If the array size is correct
      if(ArraySize(tested_parameters_from_file)==TESTED_PARAMETERS_COUNT)
        {
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_from_file[0];
         Print("InputIndicatorPeriod[symbol_number] = "+(string)InputIndicatorPeriod[symbol_number]);
         InputTakeProfit[symbol_number]         =tested_parameters_from_file[1];
         InputStopLoss[symbol_number]           =tested_parameters_from_file[2];
         InputTrailingStop[symbol_number]       =tested_parameters_from_file[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_from_file[4];
         InputLot[symbol_number]                =tested_parameters_from_file[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_from_file[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_from_file[7];
        }
      //--- Close the file and exit
      FileClose(file_handle_read);
      return;
     }
//--- Scenario #2: If the file does not exist or the parameters need to be rewritten
   if(file_handle_read==INVALID_HANDLE || RewriteParameters)
     {
      //--- Close the handle of the file for reading
      FileClose(file_handle_read);
      //--- Get the handle of the file for writing
      int file_handle_write=FileOpen(file_name,FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON,"");
      //--- If the handle has been obtained
      if(file_handle_write!=INVALID_HANDLE)
        {
         string delimiter="="; // Separator
         //--- Write the parameters
         for(int i=0; i<TESTED_PARAMETERS_COUNT; i++)
           {
            FileWrite(file_handle_write,input_parameters_names[i],delimiter,tested_parameters_values[i]);
            Print(input_parameters_names[i],delimiter,tested_parameters_values[i]);
           }
         //--- Write parameter values to arrays
         InputIndicatorPeriod[symbol_number]    =(int)tested_parameters_values[0];
         InputTakeProfit[symbol_number]         =tested_parameters_values[1];
         InputStopLoss[symbol_number]           =tested_parameters_values[2];
         InputTrailingStop[symbol_number]       =tested_parameters_values[3];
         InputReverse[symbol_number]            =(bool)tested_parameters_values[4];
         InputLot[symbol_number]                =tested_parameters_values[5];
         InputVolumeIncrease[symbol_number]     =tested_parameters_values[6];
         InputVolumeIncreaseStep[symbol_number] =tested_parameters_values[7];
         //--- Depending on the indication, print the relevant message
         if(RewriteParameters)
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been rewritten'");
         else
            Print("The file '"+InputSymbols[symbol_number]+".ini' with parameters of the '"+EXPERT_NAME+".ex5 Expert Advisor has been created'");
        }
      //--- Close the handle of the file for writing
      FileClose(file_handle_write);
     }
  }

The last file function CreateInputParametersFolder() will create a folder with the name of the Expert Advisor in the common folder of the client terminal. This is the folder from which text files (in our case, .ini files) with input parameter values will be read/written. Just like in the previous function, we will check whether the folder exists. If the folder has been successfully created or already exists, the function will return the path or an empty string in case of an error:

//+----------------------------------------------------------------------------------+
//| Creating a folder for files of input parameters in case the folder does not exist|
//| and returns the path in case of success                                          |
//+----------------------------------------------------------------------------------+
string CreateInputParametersFolder()
  {
   long   search_handle       =INVALID_HANDLE;   // Folder/file search handle
   string EA_root_folder      =EXPERT_NAME+"\\"; // Root folder of the Expert Advisor
   string returned_filename   ="";               // Name of the found object (file/folder)
   string search_path         ="";               // Search path
   string folder_filter       ="*";              // Search filter (* - check all files/folders)
   bool   is_root_folder      =false;            // Flag of existence/absence of the root folder of the Expert Advisor

//--- Find the root folder of the Expert Advisor
   search_path=folder_filter;
//--- Set the search handle in the common folder of the terminal
   search_handle=FileFindFirst(search_path,returned_filename,FILE_COMMON);
//--- If the first folder is the root folder, flag it
   if(returned_filename==EA_root_folder)
      is_root_folder=true;
//--- If the search handle has been obtained
   if(search_handle!=INVALID_HANDLE)
     {
      //--- If the first folder is not the root folder
      if(!is_root_folder)
        {
         //--- Iterate over all files to find the root folder
         while(FileFindNext(search_handle,returned_filename))
           {
            //--- Process terminated by the user
            if(IsStopped())
               return("");
            //--- If it is found, flag it
            if(returned_filename==EA_root_folder)
              {
               is_root_folder=true;
               break;
              }
           }
        }
      //--- Close the root folder search handle
      FileFindClose(search_handle);
      //search_handle=INVALID_HANDLE;
     }
//--- Otherwise print an error message
   else
      Print("Error when getting the search handle or "
            "the folder '"+TerminalInfoString(TERMINAL_COMMONDATA_PATH)+"' is empty: ",ErrorDescription(GetLastError()));

//--- Based on the check results, create the necessary folder
   search_path=EXPERT_NAME+"\\";
//--- If the root folder of the Expert Advisor does not exist
   if(!is_root_folder)
     {
      //--- Create it. 
      if(FolderCreate(EXPERT_NAME,FILE_COMMON))
        {
         //--- If the folder has been created, flag it
         is_root_folder=true;
         Print("The root folder of the '..\\"+EXPERT_NAME+"\\ Expert Advisor has been created'");
        }
      else
        {
         Print("Error when creating "
               "the root folder of the Expert Advisor: ",ErrorDescription(GetLastError()));
         return("");
        }
     }
//--- If the required folder exists
   if(is_root_folder)
      //--- Return the path to create a file for writing parameters of the Expert Advisor
      return(search_path+"\\");
//--- In case of errors, return an empty string
   return("");
  }

Let's now put the above function calls together in a single function - InitializeInputParametersArrays(). This function covers 4 input parameter initialization options when working with the Expert Advisor:

  1. Standard operation mode (or parameter optimization for a selected symbol) using current input parameter values
  2. Rewriting parameters in files when testing or optimizing
  3. Testing a selected symbol
  4. Testing all symbols on the list from the file

All operations are explained in detailed comments to the code:

//+-------------------------------------------------------------------+
//| Initializing arrays of input parameters depending on the mode     |
//+-------------------------------------------------------------------+
void InitializeInputParametersArrays()
  {
   string path=""; // To determine the folder that contains files with input parameters
//--- Mode #1 :
//    - standard operation mode of the Expert Advisor OR
//    - optimization mode OR
//    - reading from input parameters of the Expert Advisor without rewriting the file
   if(IsRealtime() || IsOptimization() || (ParametersReadingMode==INPUT_PARAMETERS && !RewriteParameters))
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      return;
     }
//--- Mode #2 :
//    - rewriting parameters in the file for the specified symbol
   if(RewriteParameters)
     {
      //--- Initialize parameter arrays to current values
      InitializeWithCurrentValues();
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
         //--- Write/read the file of symbol parameters
         ReadWriteInputParameters(0,path);
      //---
      return;
     }
//--- Mode #3 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on a SELECTED symbol
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber>0)
     {
      //--- If the folder of the Expert Advisor exists or in case no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols (in this case, the number of symbols = 1)
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
//--- Mode #4 :
//    - testing (it may be in visualization mode, without optimization) the Expert Advisor on ALL symbols
   if((IsTester() || IsVisualMode()) && !IsOptimization() && SymbolNumber==0)
     {
      //--- If the folder of the Expert Advisor exists and
      //    no errors occurred when it was being created
      if((path=CreateInputParametersFolder())!="")
        {
         //--- Iterate over all symbols
         for(int s=0; s<SYMBOLS_COUNT; s++)
            //--- Write or read the file of symbol parameters
            ReadWriteInputParameters(s,path);
        }
      return;
     }
  }

In modes #1 and #2 we use the InitializeWithCurrentValues() function. It initializes zero (sole) index to current input parameter values. In other words, this function is used when only one symbol is required:

//+------------------------------------------------------------------+
//| Initializing arrays of input parameters to current values        |
//+------------------------------------------------------------------+
void InitializeWithCurrentValues()
  {
   InputIndicatorPeriod[0]=IndicatorPeriod;
   InputTakeProfit[0]=TakeProfit;
   InputStopLoss[0]=StopLoss;
   InputTrailingStop[0]=TrailingStop;
   InputReverse[0]=Reverse;
   InputLot[0]=Lot;
   InputVolumeIncrease[0]=VolumeIncrease;
   InputVolumeIncreaseStep[0]=VolumeIncreaseStep;
  }

Now we need to do the simplest, yet the most important thing: to implement consecutive calls of the above functions from the entry point, the OnInit() function:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialize the array of tested input parameters for writing to the file
   InitializeTestedParametersValues();
//--- Fill the array of symbol names
   InitializeInputSymbols();
//--- Set the size of arrays of input parameters
   ResizeInputParametersArrays();
//--- Initialize arrays of indicator handles
   InitializeIndicatorHandlesArrays();
//--- Initialize arrays of input parameters depending on the operation mode of the Expert Advisor
   InitializeInputParametersArrays();
//--- Get agent handles from the "EventsSpy.ex5" indicator
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeNewBarArray();
//--- Initialize price arrays and indicator buffers
   ResizeDataArrays();
  }

So, we are done with the code. You can familiarize yourself with the described functions using the files attached to the article, there is nothing complicated in them. Now, let's move further to see what we have got as a result and how it can be used.


Optimizing Parameters and Testing Expert Advisor

As already mentioned, you should have the TestedSymbols.txt file with the symbol list in the common folder of the client terminal. As an example/for testing purposes, we will create a list of three symbols: AUDUSD, EURUSD and NZDUSD. We will now consecutively optimize input parameters for each symbol separately. The Strategy Tester needs to be set as shown below:

Fig. 2. Strategy Tester settings.

Fig. 2. Strategy Tester settings.

You can set any symbol (in our case, EURUSD) in the "Settings" tab since it does not affect the Expert Advisor. Then we select parameters for optimization of the Expert Advisor:

Fig. 3. Input parameters of the Expert Advisor.

Fig. 3. Input parameters of the Expert Advisor.

The above figure shows that the SymbolNumber parameter (Number of the tested symbol) is set to 1. This means that when running the optimization the Expert Advisor will use the first symbol on the list from the TestedSymbols.txt file. In our case, it is AUDUSD.

Note: due to peculiarities of this Expert Advisor (the symbol list is set by reading from the text file), optimization with remote agents will not be possible.

We will try to circumvent this restriction in one of the following articles of this series.

After completing the optimization, you can run tests, studying the results of different optimization passes. If you want the Expert Advisor to read parameters from the file, you should select File in the drop-down list of the ParametersReadingMode parameter (Parameter reading mode). To be able to use current parameters of the Expert Advisor (set in the "Settings" tab), you should select the Input parameters option.

The Input parameters option is certainly required in viewing the optimization results. When running the test for the first time, the Expert Advisor will create a folder with the corresponding name in the common folder of the terminal. The created folder will contain a file with current parameters of the tested symbol. In our case, this is AUDUSD.ini. You can see the content of this file in the below figure:

Fig. 4. List of input parameters in the symbol's file.

Fig. 4. List of input parameters in the symbol's file.

When the required combination of parameters has been found, you should set true in the RewriteParameters parameter (Rewriting parameters) and run the test again. The parameter file will be updated. You can afterwards set false again and check other results of optimization passes. It is also convenient to compare results by values written to the file against those that are set in the input parameters by simply switching between the options of the Parameter reading mode parameter.

Then, we run the optimization for EURUSD, which is the second symbol on the list from the symbol list file. To do this, we need to set the value of the Number of the tested symbol parameter equal to 2. Following the optimization, and after determining the parameters and writing them to the file, the same will also need to be done for the third symbol on the list.

Once parameters for all the symbols have been written to the file, you can either view results for each symbol separately, by specifying the symbol number, or view the cumulative result for all the symbols, by setting the Number of the tested symbol to 0. I have got the following cumulative result for all the symbols:

Fig. 5. The cumulative result of the multi-currency Expert Advisor.

Fig. 5. The cumulative result of the multi-currency Expert Advisor.


Conclusion

As a result, we have got a fairly convenient pattern for multi-currency Expert Advisors. It can be developed further, if desired. Attached to the article is the downloadable archive with the files of the Expert Advisor for your consideration. After unzipping, place the UnlimitedParametersEA folder under <MetaTrader 5 terminal folder>\MQL5\Experts. The EventsSpy.mq5 indicator should be placed in <MetaTrader 5 terminal folder>\MQL5\Indicators. Besides that, do not forget to create the TestedSymbols.txt text file in the common folder of the client terminal.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/650

Attached files |
eventsspy.mq5 (7.61 KB)
Last comments | Go to discussion (3)
zeno
zeno | 25 Jul 2013 at 17:17

Hello Anatoli,

Thank you for sharing this great EA.

I am testing the EA and there is no 'AUDUSD.ini' file that is being created in the 'common' folder, nor anywhere else. You mention that 'To be able to use current parameters of the Expert Advisor (set in the "Settings" tab), you should select the Input parameters option.' However, I do not see where to make that selection, neither on the "settings" tab nor anywhere else. I would suppose that may be the reason that the '.ini' file is not created? Can you please clarify?

I read in 'FileFunctions.mqh' file under line 87 the 2 scenarios. With 'print' I probably should receive a message in pop-up screen or in journal about this '.ini' file, but that is not the case.

The journal gives a few similar to this line: 'tested with error "critical runtime error 502 in OnInit function (array out of range, module Experts\UnlimitedParametersEA.ex5, file InitializeArrays.mqh, line 168, col 24)" in 16 ms'. This is referring to 'InputIndicatorPeriod'. With H8 specified, why is that and is this a cause for failure of '.ini' file? 

Thanks so much. 

david mackay
david mackay | 7 Aug 2013 at 19:01

Thank you for this excellently documented and easy to understand presentation.


daveM

Simon Gniadkowski
Simon Gniadkowski | 7 Aug 2013 at 21:42
zeno:

Hello Anatoli,

Thank you for sharing this great EA.

Don't be disappointed if you don't get a reply,  Anatoli is Russian and he didn't make this thread,  he may not even be aware of it. Try a PM instead.
Alert and Comment for External Indicators. Multi-Currency Analysis Through External Scanning Alert and Comment for External Indicators. Multi-Currency Analysis Through External Scanning
Alert for multi-currency and multiple time frame analysis of external indicators. The article deals with a method of getting event information in respect of events in external indicators, without having to attach indicators to a chart or open charts themselves. We will call it external scanning.
Graphical Control of External Parameters of Indicators Graphical Control of External Parameters of Indicators
External variables of indicators are controlled using a special window where parameters can be changed and the indicator is then run again. The obvious inconvenience of these manipulations has given rise to the need for displaying the required parameters on the screen and controlling the indicator graphically.
Alert and Comment for External Indicators (Part Two) Alert and Comment for External Indicators (Part Two)
Since I published the article "Alert and Comment for External Indicators", I have been receiving requests and questions regarding the possibility of developing an external informer operating based on indicator lines. Having analyzed the questions, I have decided to continue with the subject. Getting data stored in indicator buffers turned out to be another area of interest to users.
Social Trading with the MetaTrader 4 and MetaTrader 5 Trading Platforms Social Trading with the MetaTrader 4 and MetaTrader 5 Trading Platforms
What is social trading? It is a mutually beneficial cooperation of traders and investors whereby successful traders allow monitoring of their trading and potential investors take the opportunity to monitor their performance and copy trades of those who look more promising.