MQL5 Cookbook : Développement d’un Expert Advisor Multi-devises avec un nombre illimité de paramètres
Introduction
L'Expert Advisor multidevises considéré dans l'article précédent "MQL5 Cookbook : Expert Advisor multi-devises - Une approche simple, nette et rapide", peut être très utile si le nombre de symboles et de paramètres de stratégie de trading utilisés est faible. Cependant, il existe une restriction sur le nombre de paramètres d'entrée d'un Expert Advisor dans MQL5 : ils ne doivent pas dépasser 1024.
Et même si ce nombre sera très souvent suffisant, il est très gênant d'utiliser une liste de paramètres aussi énorme. Chaque fois qu'une modification ou une optimisation des paramètres d'un symbole donné est requise, vous devez rechercher les paramètres de ce symbole spécifique dans la longue liste de paramètres.
Dans cet article, nous allons créer un modèle qui utilise un seul ensemble de paramètres pour l'optimisation d'un système de trading, tout en permettant un nombre illimité de paramètres. La liste des symboles sera créée dans un fichier texte standard (*.txt). Les paramètres d'entrée pour chaque symbole seront également stockés dans des fichiers.
Il convient de mentionner ici que l'Expert Advisor travaillera sur un symbole en mode de fonctionnement normal, mais vous pourrez le tester dans le Strategy Tester sur une variété de symboles sélectionnés (sur chaque symbole séparément).
Il serait en fait encore plus pratique de créer la liste de symboles directement dans la fenêtre Market Watch, étant donné que cela permet même de sauvegarder des jeux de symboles prêts à l'emploi. Nous pourrions même demander à l'Expert Advisor d'obtenir la liste des symboles dans la fenêtre Market Watch directement à partir du Strategy Tester. Mais malheureusement, il n'est actuellement pas possible d'accéder à la fenêtre Market Watch depuis le Strategy Tester, nous devrons donc créer la liste de symboles manuellement au préalable ou à l'aide d'un script.
Développement d’Expert Advisor
L'Expert Advisor multidevises présenté dans l'article précédent "MQL5 Cookbook : Expert Advisor multi-devises - Une approche simple, nette et rapide" sera pris comme modèle. Décidons d'abord des paramètres d'entrée. Comme mentionné ci-dessus, nous ne laisserons qu'un seul ensemble de paramètres. Ci-dessous la liste des paramètres d'entrée de l'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
Seuls les paramètres avec le modificateur d'entrée seront écrits dans un fichier. De plus, nous devrions développer les trois nouveaux paramètres que nous n'avons jamais rencontrés auparavant.
- SymbolNumber - ce paramètre indique le numéro du symbole du fichier qui contient la liste des symboles. S'il est défini sur 0, tous les symboles de la liste seront testés. Si vous dépassez la liste, le test ne sera pas effectué.
- RewriteParameters - si ce paramètre a la valeur true, le fichier avec les paramètres du symbole spécifié (numéro dans le paramètre SymbolNumber) sera réécrit en utilisant les valeurs actuelles des paramètres d'entrée. Sinon, s'il est défini sur false, les paramètres seront lus à partir d'un fichier.
- ParametersReadingMode - ce paramètre indique le mode de lecture par rapport aux paramètres d'entrée. Le type de paramètre est l'énumération personnalisée ENUM_INPUTS_READING_MODE qui offre deux options : lire à partir d'un fichier et utiliser les paramètres actuels.
Le code d'énumération ENUM_INPUTS_READING_MODE :
//+------------------------------------------------------------------+ //| Input parameter reading modes | //+------------------------------------------------------------------+ enum ENUM_INPUTS_READING_MODE { FILE = 0, // File INPUT_PARAMETERS = 1 // Input parameters };
Le nombre de symboles était précédemment déterminé à l'aide de la constante NUMBER_OF_SYMBOLS. Cette valeur dépend maintenant de différents modes, nous allons donc la remplacer par la variable globale SYMBOLS_COUNT :
//--- 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;
Au début du code de l'Expert Advisor, nous déclarons une autre constante, TESTED_PARAMETERS_COUNT, qui détermine la taille des tableaux et le nombre d'itérations de boucle lors de l'itération sur les paramètres d'entrée :
//--- Number of tested/optimized parameters #define TESTED_PARAMETERS_COUNT 8
Le fichier avec la liste des symboles, ainsi que le dossier contenant les paramètres de chaque symbole doivent être placés dans le dossier commun du terminal car il est accessible au niveau du programme à la fois pendant le fonctionnement normal de l'Expert Advisor et lors de son test .
Maintenant, nous devons préparer les tableaux avec lesquels travailler plus tard. Tous les tableaux de l'Expert Advisor multi-devises de l'article précédent que nous allons modifier sont statiques, c'est-à-dire avec une taille d'éléments prédéfinie. Nous devons tous les rendre dynamiques car la taille dépendra désormais du nombre de symboles utilisés dans le fichier et du mode de lecture des paramètres d'entrée. Nous allons également ajouter de nouveaux tableaux (surlignés en jaune) qui sont nécessaires pour travailler avec des fichiers :
//--- 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];
Ensuite, nous devons déterminer les tailles des tableaux et les initialiser à des valeurs.
Le tableau de valeurs de paramètres d'entrée créé ci-dessus sera initialisé dans la fonction InitializeTestedParametersValues() que nous allons créer dans le fichier InitializeArrays.mqh. Ce tableau sera utilisé pour écrire les valeurs des paramètres d'entrée dans le fichier.
//+------------------------------------------------------------------+ //| 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; }
Considérons maintenant les opérations sur les fichiers. Tout d'abord, créez une autre bibliothèque de fonctions, FileFunctions.mqh, dans le dossier UnlimitedParametersEA\Include de votre Expert Advisor. Cette bibliothèque sera utilisée pour créer des fonctions liées à la lecture et à l'écriture de données dans un fichier. Incluez-le dans le fichier principal de l'Expert Advisor et d'autres fichiers du projet.
//---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"
Nous commençons par créer une fonction pour lire la liste des symboles à partir d'un fichier texte - ReadSymbolsFromFile(). Ce fichier (appelons-le TestedSymbols.txt) doit être placé dans le sous-dossier \Files du dossier commun du terminal client MetaTrader 5. Dans mon cas, ce sera C:\ProgramData\MetaQuotes\Terminal\Common mais vous devez vérifier soigneusement le chemin à l'aide de TERMINAL_COMMONDATA_PATH :
TerminalInfoString(TERMINAL_COMMONDATA_PATH)
Pour vérifier la fonctionnalité, ajoutez quelques symboles au fichier texte créé, en séparant chacun des symboles par un saut de ligne ("\r\n") :
Fig. 1. Liste des symboles dans le fichier du dossier commun du terminal.
De plus, examinons le code de la fonction ReadSymbolsFromFile() :
//+------------------------------------------------------------------+ //| 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); }
Ici, le fichier texte est lu ligne par ligne. Le nom de chaque instrument financier qui a été lu est écrit dans le tableau temporaire Temporary_symbols[] créé précédemment (l'accessibilité réelle du symbole sur le serveur de trading sera vérifiée ultérieurement). De plus, à la fin la fonction retourne le nombre de chaînes lues, c'est à dire le nombre de symboles sur lesquels notre Expert Advisor sera testé.
Revenons au fichier InitializeArrays.mqh où nous allons créer la fonction InitializeInputSymbols() pour remplir le tableau InputSymbols[] des noms de symboles déclarés précédemment. Les débutants le trouveront probablement assez complexe, j'ai donc fourni des commentaires détaillés sur le 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(); } //--- }
Une fois que la taille du tableau de paramètres d'entrée a été déterminée par le nombre de symboles utilisés, vous devez définir la taille de tous les autres tableaux de paramètres d'entrée. Implémentons-le en tant que fonction distincte - 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); }
Maintenant, nous devons créer la fonctionnalité qui nous permettra de lire les valeurs des paramètres d'entrée pour chaque symbole, ainsi que d'écrire ces valeurs de paramètres dans un fichier séparé (pour chaque symbole) en fonction du mode d'entrée sélectionné et des paramètres de l'Expert Advisor. Cela se fera de la même manière que nous lisons la liste des symboles à partir du fichier. C'est une tâche complexe, alors divisons-la en plusieurs procédures.
Tout d'abord, nous devons apprendre à lire les valeurs des paramètres d'entrée d'un fichier dans un tableau. Le tableau contiendra des valeurs de type double. Nous convertirons plus tard certains d'entre eux (période de l'indicateur et indicateur d'inversion de position) en types int et bool, respectivement. Le descripteur de fichier ouvert et le tableau dans lequel les valeurs des paramètres du fichier sont stockées sont transmis à la fonction ReadInputParametersValuesFromFile() :
//+----------------------------------------------------------------------+ //| 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); }
Quels descripteurs de fichiers et tableaux allons-nous passer à cette fonction ? Cela dépendra des symboles avec lesquels nous travaillerons après les avoir lus à partir du fichier "TestedSymbols.txt". Chaque symbole correspondra à un certain fichier texte contenant les valeurs des paramètres d'entrée. Il y a deux cas de figure à considérer :
- Le fichier existe et nous lisons les valeurs des paramètres d'entrée à partir de ce fichier à l'aide de la fonction ReadInputParametersValuesFromFile() décrite ci-dessus.
- Le fichier n'existe pas ou nous devons réécrire les valeurs des paramètres d'entrée existants.
Le format du fichier texte (un fichier .ini, bien que vous puissiez choisir toute autre extension que vous jugerez nécessaire) contenant les valeurs des paramètres d'entrée sera simple :
input_parameter_name1=value input_parameter_name2=value .... input_parameter_nameN=value
Combinons cette logique en une seule fonction, ReadWriteInputParameters(), dont le code est fourni ci-dessous :
//+------------------------------------------------------------------+ //| 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); } }
La dernière fonction de fichier CreateInputParametersFolder() va créer un dossier avec le nom de l'Expert Advisor dans le dossier commun du terminal client. Il s'agit du dossier à partir duquel les fichiers texte (dans notre cas, les fichiers .ini) avec les valeurs des paramètres d'entrée seront lus/écrits. Tout comme dans la fonction précédente, nous allons vérifier si le dossier existe. Si le dossier a été créé avec succès ou existe déjà, la fonction renverra le chemin ou une chaîne vide en cas d'erreur :
//+----------------------------------------------------------------------------------+ //| 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(""); }
Regroupons maintenant les appels de fonction ci-dessus dans une seule fonction - InitializeInputParametersArrays(). Cette fonction couvre 4 options d'initialisation des paramètres d'entrée lorsque vous travaillez avec l'Expert Advisor :
- Le mode de fonctionnement standard (ou optimisation des paramètres pour un symbole sélectionné) utilisant les valeurs des paramètres d'entrée actuels
- La réécriture des paramètres dans les fichiers lors du test ou de l'optimisation
- Le test d’un symbole sélectionné
- Le teste de tous les symboles de la liste à partir du fichier
Toutes les opérations sont expliquées dans les commentaires détaillés du 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; } }
Dans les modes #1 et #2, nous utilisons la fonction InitializeWithCurrentValues(). Il initialise zéro (unique) index aux valeurs actuelles des paramètres d'entrée. En d'autres termes, cette fonction est utilisée lorsqu'un seul symbole est requis :
//+------------------------------------------------------------------+ //| 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; }
Maintenant, nous devons faire la chose la plus simple, mais la plus importante : l’implémentation des appels consécutifs des fonctions ci-dessus depuis le point d'entrée, les OnInit() :
//+------------------------------------------------------------------+ //| 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(); }
Donc, nous en avons fini avec le code. Vous pouvez vous familiariser avec les fonctions décrites à l'aide des fichiers joints à l'article, ils n'ont rien de compliqué. Maintenant, allons plus loin pour voir ce que nous avons comme résultat et comment il peut être utilisé.
Optimisation des paramètres et test Expert Advisor
Comme déjà mentionné, vous devriez avoir le fichier TestedSymbols.txt avec la liste des symboles dans le dossier commun du terminal client. À titre d'exemple/à des fins de test, nous allons créer une liste de trois symboles : AUDUSD, EURUSD et NZDUSD. Nous allons maintenant optimiser consécutivement les paramètres d'entrée pour chaque symbole séparément. Le testeur de stratégie doit être configuré comme indiqué ci-dessous :
Fig. 2. Paramètres du testeur de stratégie.
Vous pouvez définir n'importe quel symbole (dans notre cas, EURUSD) dans l'onglet "Paramètres" car cela n'affecte pas l'Expert Advisor. Ensuite, nous sélectionnons les paramètres d'optimisation de l'Expert Advisor :
Fig. 3. Paramètres d'entrée de l'Expert Advisor.
La figure ci-dessus montre que le paramètre SymbolNumber (Numéro du symbole testé) est défini sur 1. Cela signifie que lors de l'exécution de l'optimisation, l'Expert Advisor utilisera le premier symbole de la liste du fichier TestedSymbols.txt. Dans notre cas, il s'agit de l'AUDUSD.
Remarque : en raison des particularités de cet Expert Advisor (la liste des symboles est définie par lecture du fichier texte), l'optimisation avec des agents distants ne sera pas possible.
Nous essaierons de contourner cette restriction dans l'un des articles suivants de cette série.
Une fois l'optimisation terminée, vous pouvez exécuter des tests, en étudiant les résultats de différentes passes d'optimisation. Si vous souhaitez que l'Expert Advisor lise les paramètres du fichier, vous devez sélectionner File dans la liste déroulante du paramètre ParametersReadingMode (mode de lecture des paramètres). Pour pouvoir utiliser les paramètres actuels de l'Expert Advisor (définis dans l'onglet "Paramètres"), vous devez sélectionner l'option Input parameters.
L'option Input parameters est certainement requise pour visualiser les résultats de l'optimisation. Lors de la première exécution du test, l'Expert Advisor créera un dossier portant le nom correspondant dans le dossier commun du terminal. Le dossier créé contiendra un fichier avec les paramètres actuels du symbole testé. Dans notre cas, il s'agit de AUDUSD.ini. Vous pouvez voir le contenu de ce fichier dans la figure ci-dessous :
Fig. 4. Liste des paramètres d'entrée dans le fichier du symbole.
Lorsque la combinaison de paramètres requise a été trouvée, vous devez définir true dans le paramètre RewriteParameters (Réécriture des paramètres) et relancer le test. Le fichier de paramètres sera mis à jour. Vous pouvez ensuite définir à nouveau false et vérifier les autres résultats des passes d'optimisation. Il est également pratique de comparer les résultats des valeurs écrites dans le fichier avec celles définies dans les paramètres d'entrée en basculant simplement entre les options du paramètre Parameter reading mode.
Ensuite, nous exécutons l'optimisation pour EURUSD, qui est le deuxième symbole de la liste du fichier de liste de symboles. Pour ce faire, nous devons définir la valeur du paramètre Numéro du symbole testé égale à 2. Suite à l'optimisation, et après avoir déterminé les paramètres et les avoir écrits dans le fichier, il faudra également faire la même chose pour le troisième symbole de la liste.
Une fois que les paramètres de tous les symboles ont été écrits dans le fichier, vous pouvez soit afficher les résultats pour chaque symbole séparément, en spécifiant le numéro de symbole, soit afficher le résultat cumulé pour tous les symboles, en définissant le Numéro du symbole testé sur 0. J'ai le résultat cumulé suivant pour tous les symboles :
Fig. 5. Le résultat cumulé de l'Expert Advisor multi-devises.
Conclusion
En conséquence, nous avons un modèle assez pratique pour les Expert Advisors multi-devises. Il peut être développé davantage, si vous le souhaitez. L'archive téléchargeable avec les fichiers de l'Expert Advisor est jointe à l'article pour votre considération. Après avoir décompressé, placez le dossier UnlimitedParametersEA sous <MetaTrader 5 terminal folder>\MQL5\Experts. L'indicateur EventsSpy.mq5 doit être placé dans <MetaTrader 5 terminal folder>\MQL5\Indicators. En plus de cela, n'oubliez pas de créer le fichier texte TestedSymbols.txt dans le dossier commun du terminal client.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/650
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation