MQL5 Cookbook: Developing a Multi-Currency Expert Advisor with Unlimited Number of Parameters
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.
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)
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.
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.
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:
- The file exists and we read input parameter values from that file using the ReadInputParametersValuesFromFile() function described above.
- 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:
- Standard operation mode (or parameter optimization for a selected symbol) using current input parameter values
- Rewriting parameters in files when testing or optimizing
- Testing a selected symbol
- 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.
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.
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.
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.
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
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
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.
Thank you for this excellently documented and easy to understand presentation.
daveM
Hello Anatoli,
Thank you for sharing this great EA.