MQL5 Cookbook : Réduction de l'effet du surajustement et traitement de l'absence de cotations

Anatoli Kazharski | 13 janvier, 2022

Introduction

Je pense que de nombreux traders se sont interrogés à plusieurs reprises sur les paramètres optimaux pour leurs systèmes de trading. En effet, un algorithme de trading seul ne suffit pas. Il faut voir comment il peut encore être utilisé. Quelle que soit la stratégie de trading que vous utilisez, qu'elle soit simple ou complexe, avec un ou plusieurs instruments, vous ne pouvez pas éviter la question des paramètres à choisir pour assurer des profits futurs.

Nous avons tendance à vérifier les systèmes de trading avec des paramètres qui ont montré de bons résultats sur la période d'optimisation (backtesting) et sur la période suivante (forward testing ou tests avancés). Les tests avancés ne sont en fait pas si nécessaires. Les résultats pertinents peuvent être obtenus en utilisant des données historiques.

Cette méthode soulève une énorme question à laquelle on ne peut pas répondre avec certitude : quelle quantité de données historiques faut-il utiliser pour optimiser un système de trading ? Le truc, c'est qu'il y a beaucoup d'options. Tout dépend de la gamme de fluctuations de prix sur laquelle vous vous attendez à capitaliser.

Pour en revenir à la quantité d'historique requise pour l'optimisation, nous concluons que les données disponibles pourraient bien être suffisantes pour le trading intra-horaire. Ce n'est pas toujours vrai pour les périodes plus longues. Plus le nombre de répétitions d'un modèle cohérent est élevé, c'est-à-dire plus nous avons des trades, plus les performances du système de trading testé sont véridiques que nous pouvons nous attendre à voir à l'avenir.

Que se passe-t-il si les données de prix d'un certain instrument ne suffisent pas pour obtenir un nombre suffisant de répétitions et se sentir plus sûr ? Réponse : utilisez les données de tous les instruments disponibles.


Exemple dans NeuroShell DayTrader Professional

Avant de passer à la programmation dans MetaTrader 5, examinons un exemple dans NeuroShell DayTrader Professional. Il offre d'excellentes fonctionnalités pour optimiser les paramètres d'un système de trading (compilé avec un constructeur) pour plusieurs symboles. Vous pouvez définir les paramètres requis dans les paramètres du module de trading, optimiser les paramètres pour chaque symbole séparément ou trouver un ensemble de paramètres optimal pour tous les symboles à la fois. Cette option se trouve dans l'onglet Optimisation :

L'onglet Optimisation dans le module de trading de NeuroShell DayTrader Professional

Fig. 1. L'onglet Optimisation dans le module de trading de NeuroShell DayTrader Professional

Dans notre cas, n'importe quel système de trading simple fera l'affaire car nous n'avons qu'à comparer les résultats de deux méthodes d'optimisation, donc le choix du système est actuellement de peu d'importance.

Vous pouvez trouver l'information sur la façon de compiler les stratégies de trading dans NeuroShell DayTrader Professional dans d'autres articles de mon blog (vous pouvez chercher ou utiliser les tags pour trouver l'information pertinente). Je vous recommande également de lire un article intitulé "Comment préparer des cotations MetaTrader 5 pour d'autres applications" qui décrit et montre comment, à l'aide d'un script, vous pouvez télécharger des cotations de MetaTrader 5 dans le format compatible avec NeuroShell DayTrader Professional.

Pour faire ce test, j'ai préparé des données obtenues à partir de barres quotidiennes pour huit symboles de l'année 2000 à janvier 2013 :

Liste des symboles pour un test dans NeuroShell DayTrader Professional

Fig. 2. Liste des symboles pour un test dans NeuroShell DayTrader Professional

La figure ci-dessous montre deux résultats d'optimisation. La partie supérieure affiche le résultat de l'optimisation où chaque symbole obtient ses propres paramètres, tandis que la partie inférieure affiche le résultat où les paramètres sont communs à tous les symboles.

Comparaison des résultats de deux modes d'optimisation de paramètres

Fig. 3. Comparaison des résultats de deux modes d'optimisation des paramètres

Le résultat montrant les paramètres communs n'est pas aussi bon que celui où les paramètres sont différents pour chaque symbole. Pourtant, cela inspire plus de confiance car le système de trading passe par un certain nombre de modèles de comportement de prix différents (volatilité, nombre de tendances/plats) avec les mêmes paramètres pour tous les symboles.

En continuant sur le même sujet, nous pouvons logiquement trouver un autre argument en faveur de l'optimisation utilisant une plus grande quantité de données. Il se peut très bien que le comportement des prix d'une certaine paire de devises, par exemple EURUSD, soit assez différent par la suite (dans deux, cinq ou dix ans). Par exemple, les tendances des prix GBPUSD seront similaires au comportement des prix passés de l'EURUSD et vice versa. Vous devriez être prêt pour cela car cela est vrai pour n'importe quel instrument.


Un exemple dans MetaTrader 5

Voyons maintenant quels modes d'optimisation des paramètres sont proposés dans MetaTrader 5. Ci-dessous, vous pouvez voir tous les symboles sélectionnés dans le mode d'optimisation Market Watch marqués d'une flèche dans la liste déroulante des modes d'optimisation.

Fig. 4. Modes d'optimisation dans le testeur de stratégie MetaTrader 5

Fig. 4. Modes d'optimisation dans le testeur de stratégie MetaTrader 5

Ce mode vous permet de tester uniquement un EA avec les paramètres actuels sur chaque symbole un par un. Les symboles utilisés dans les tests sont ceux qui sont actuellement sélectionnés dans la fenêtre Market Watch. En d'autres termes, l'optimisation des paramètres n'est dans ce cas pas effectuée. Cependant, MetaTrader 5 et MQL5 vous permettent de mettre en œuvre cette idée par vous-même.

Maintenant, nous devons voir comment mettre en œuvre une telle évaluation environnementale. La liste des symboles sera fournie dans un fichier texte (*.txt). De plus, nous mettrons en œuvre la possibilité de stocker plusieurs ensembles de listes de symboles. Chaque ensemble sera dans une section séparée avec son propre en-tête comportant un numéro de section. Les chiffres sont nécessaires pour faciliter la vérification visuelle.

Notez qu'il est important d'avoir # devant le nombre afin de permettre à l'Expert Advisor d'obtenir le bon jeu de données lors du remplissage du tableau de symboles. Généralement, l'en-tête peut contenir n'importe quel symbole, mais il doit toujours avoir #. Le signe dièse peut être remplacé par tout autre symbole selon lequel l'Expert Advisor déterminera/comptera les sections. Dans ce cas, le remplacement devra être reflété dans le code.

Ci-dessous, vous pouvez voir le fichier SymbolsList.txt qui contient trois jeux de symboles à tester. Ce fichier, tel qu'illustré, sera ensuite utilisé lors du test de la méthode.

Fig. 5. Plusieurs jeux de symboles fournis dans un fichier texte pour le test

Fig. 5. Plusieurs jeux de symboles fournis dans un fichier texte pour les tests

Dans les paramètres externes, nous ajouterons un autre paramètre, SectionOfSymbolList, pour indiquer l'ensemble de symboles que l'Expert Advisor doit utiliser dans le test en cours. Ce paramètre prend la valeur (de zéro vers le haut) qui définit le jeu de symboles. Si la valeur dépasse le nombre d'ensembles disponibles, l'Expert Advisor écrira une entrée correspondante dans le journal et le test ne sera effectué que sur le symbole actuel.

SymbolsList.txt doit être situé dans le répertoire du terminal local sous Metatrader 5\MQL5\Files. Il peut également être placé dans le dossier commun mais dans ce cas, il ne sera pas disponible pour l'optimisation des paramètres dans le MQL5 Cloud Network. De plus, pour permettre l'accès au fichier et aux indicateurs personnalisés pertinents pour les tests, nous devons écrire les lignes suivantes au début du fichier :

//--- Allow access to the external file and indicator for optimization in the cloud
#property tester_file      "SymbolsList.txt"
#property tester_indicator "EventsSpy.ex5"

Notre Expert Advisor sera basé sur l'Expert Advisor multi-devises prêt à l'emploi présenté dans l'article "MQL5 Cookbook : Développement d'un Expert Advisor multi-devises avec un nombre illimité de paramètres". Sa stratégie de trading sous-jacente est assez simple mais il suffira de tester l'efficacité de la méthode. Nous allons simplement supprimer les parties inutiles, ajouter ce dont nous avons besoin et corriger le code pertinent existant. Nous allons certainement améliorer notre Expert Advisor avec la fonction d'enregistrement de rapport largement décrite dans l'article précédent de la série "MQL5 Cookbook : Écriture de l'historique des transactions dans un fichier et création de graphiques d'équilibre pour chaque symbole dans Excel". Des tableaux d'équilibre pour tous les symboles seront également nécessaires pour évaluer l'efficacité de la méthode considérée.

Les paramètres externes de l'Expert Advisor doivent être modifiés comme suit :

//--- External parameters of the Expert Advisor
sinput int    SectionOfSymbolList = 1;     // Section number in the symbol lists
sinput bool   UpdateReport        = false; // Report update
sinput string delimeter_00="";   // --------------------------------
sinput long   MagicNumber         = 777;   // Magic number
sinput int    Deviation           = 10;    // Slippage
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

Tous les tableaux associés aux paramètres externes doivent être supprimés car ils ne seront pas nécessaires et doivent en outre être remplacés par les variables externes dans tout le code. Nous ne devons laisser que le tableau dynamique de symboles, InputSymbols[], dont la taille dépendra du nombre de symboles utilisés dans l'un des ensembles du fichier SymbolsList.txt. Si l'Expert Advisor est utilisé en dehors du Strategy Tester, la taille de ce tableau sera égale à 1 car en mode temps réel, l'Expert Advisor fonctionnera avec un seul symbole.

Les modifications correspondantes doivent également être effectuées dans le fichier d'initialisation du tableau - InitializeArrays.mqh. C'est-à-dire que toutes les fonctions responsables de l'initialisation des tableaux de variables externes doivent être supprimées. La fonction InitializeArraySymbols() ressemble maintenant à ce qui suit :

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void InitializeArraySymbols()
  {
   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
//--- Test mode message
   string message_01="<--- All symbol names in the <- SymbolsList.txt -> file are incorrect ... --->\n"
                     "<--- ... or the value of the \"Section of List Symbols\" parameter is greater, "
                     "than the number of file sections! --->\n"
                     "<--- Therefore we will test only the current symbol. --->";
//--- Real-time mode message
   string message_02="<--- In real-time mode, we only work with the current symbol. --->";
//--- If in real-time mode
   if(!IsRealtime())
     {
      //--- Get the number of strings from the specified symbol set in the file and fill the temporary array of symbols
      strings_count=ReadSymbolsFromFile("SymbolsList.txt");
      //--- Iterate over all symbols from the specified set
      for(int s=0; s<strings_count; s++)
        {
         //--- If the correct string is returned following the symbol check
         if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
           {
            //--- increase the counter
            SYMBOLS_COUNT++;
            //--- set/increase the array size
            ArrayResize(InputSymbols,SYMBOLS_COUNT);
            //--- index with the symbol name
            InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
           }
        }
     }
//--- If all symbol names were not input correctly or if currently working in real-time mode
   if(SYMBOLS_COUNT==0)
     {
      //--- Real-time mode message
      if(IsRealtime())
         Print(message_02);
      //--- Test mode message
      if(!IsRealtime())
         Print(message_01);
      //--- We will work with the current symbol only
      SYMBOLS_COUNT=1;
      //--- set the array size and
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- index with the current symbol name
      InputSymbols[0]=_Symbol;
     }
  }

Le code de la fonction ReadSymbolsFromFile() doit également être modifié. Auparavant, il lisait toute la liste des symboles alors que maintenant nous voulons qu'il ne lise que le jeu de symboles spécifié. Ci-dessous le code de fonction modifié :

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) from the specified     |
//| set in the file and filling the temporary array of symbols       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   ulong  offset         =0;   // Offset for determining the position of the file pointer
   string delimeter      ="#"; // Identifier of the section start
   string read_line      ="";  // For the check of the read string
   int    limit_count    =0;   // Counter limiting the number of the possibly open charts
   int    strings_count  =0;   // String counter
   int    sections_count =-1;  // Section counter
   
//--- Message 01
   string message_01="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- The first string does not contain the section number identifier ("+delimeter+")! --->";
//--- Message 02
   string message_02="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- There is no line break identifier in the last string, --->\n"
                     "<--- so only the current symbol will be involved in testing. --->";
//--- Message 03
   string message_03="<--- The <- "+file_name+" -> file could not be found! --->"
                     "<--- Only the current symbol will be involved in testing. --->";
                     
//--- Open the file (get the handle) for reading in the local directory of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI,'\n');
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      //--- 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
            read_line=FileReadString(file_handle);
            //--- If the section number identifier has been found
            if(StringFind(read_line,delimeter,0)>-1)
               //--- Increase the section counter
               sections_count++;
            //--- If the section has been read, exit the function
            if(sections_count>SectionOfSymbolList)
              {
               FileClose(file_handle); // Close the file
               return(strings_count);  // Return the number of strings in the file
              }
            //--- If this is the first iteration and the first string does not contain the section number identifier
            if(limit_count==0 && sections_count==-1)
              {
               PrepareArrayForOneSymbol(strings_count,message_01);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Increase the counter limiting the number of the possibly open charts
            limit_count++;
            //--- If the limit has been reached
            if(limit_count>=CHARTS_MAX)
              {
               PrepareArrayForOneSymbol(strings_count,message_02);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Get the position of the pointer
            offset=FileTell(file_handle);
            //--- If this is the end of the string
            if(FileIsLineEnding(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 we are not in the specified section of the file, exit the loop
               if(sections_count!=SectionOfSymbolList)
                  break;
               //--- Otherwise,
               else
                 {
                  //--- if the string is not empty
                  if(read_line!="")
                    {
                     //--- increase the string counter
                     strings_count++;
                     //--- increase the size of the array of strings,
                     ArrayResize(temporary_symbols,strings_count);
                     //--- write the string to the current index
                     temporary_symbols[strings_count-1]=read_line;
                    }
                 }
               //--- Exit the loop
               break;
              }
           }
         //--- If this is the end of the file, terminate the entire loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
   else
      PrepareArrayForOneSymbol(strings_count,message_03);
//--- Return the number of strings in the file
   return(strings_count);
  }

Vous pouvez voir que certaines chaînes du code ci-dessus sont mises en évidence. Ces chaînes contiennent la fonction PrepareArrayForOneSymbol() qui prépare simplement un tableau pour un symbole (actuel) en cas d'erreur.

//+------------------------------------------------------------------+
//| Preparing an array for one symbol                                |
//+------------------------------------------------------------------+
void PrepareArrayForOneSymbol(int &strings_count,string message)
  {
//--- Print the message to the log
   Print(message);
//--- Array size
   strings_count=1;
//--- Set the size of the array of symbols
   ArrayResize(temporary_symbols,strings_count);
//--- Write the string with the current symbol name to the current index
   temporary_symbols[0]=_Symbol;
  }

Tout est maintenant prêt pour tester la méthode d'optimisation des paramètres. Mais avant de procéder aux tests, ajoutons une autre série de données au rapport. Auparavant, en plus des soldes de tous les symboles, le fichier de rapport contenait tous les retraits des maxima locaux exprimés en pourcentage. Désormais, le rapport couvrira également tous les retraits en termes monétaires. Dans le même temps, nous allons modifier la fonction CreateSymbolBalanceReport() où le rapport est généré.

Le code de la fonction CreateSymbolBalanceReport() est fourni ci-dessous :

//+------------------------------------------------------------------+
//| Creating test report on deals in .csv format                     |
//+------------------------------------------------------------------+
void CreateSymbolBalanceReport()
  {
   int    file_handle =INVALID_HANDLE; // File handle
   string path        ="";             // File path

//--- If an error occurred when creating/getting the folder, exit
   if((path=CreateInputParametersFolder())=="")
      return;
//--- Create a file to write data in the common folder of the terminal
   file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON);
//--- If the handle is valid (file created/opened)
   if(file_handle>0)
     {
      int           digits           =0;   // Number of decimal places in the price
      int           deals_total      =0;   // Number of deals in the specified history
      ulong         ticket           =0;   // Deal ticket
      double        drawdown_max     =0.0; // Drawdown
      double        balance          =0.0; // Balance
      string        delimeter        =","; // Delimiter
      string        string_to_write  ="";  // To generate the string for writing
      static double percent_drawdown =0.0; // Drawdown expressed as percentage
      static double money_drawdown   =0.0; // Drawdown in monetary terms

      //--- Generate the header string
      string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,"
                     "PRICE,SWAP($),PROFIT($),DRAWDOWN(%),DRAWDOWN($),BALANCE";
      //--- If more than one symbol is involved, modify the header string
      if(SYMBOLS_COUNT>1)
        {
         for(int s=0; s<SYMBOLS_COUNT; s++)
            StringAdd(headers,","+InputSymbols[s]);
        }
      //--- Write the report headers
      FileWrite(file_handle,headers);
      //--- Get the complete history
      HistorySelect(0,TimeCurrent());
      //--- Get the number of deals
      deals_total=HistoryDealsTotal();
      //--- Resize the array of balances according to the number of symbols
      ArrayResize(symbol_balance,SYMBOLS_COUNT);
      //--- Resize the array of deals for each symbol
      for(int s=0; s<SYMBOLS_COUNT; s++)
         ArrayResize(symbol_balance[s].balance,deals_total);
      //--- Iterate in a loop and write the data
      for(int i=0; i<deals_total; i++)
        {
         //--- Get the deal ticket
         ticket=HistoryDealGetTicket(i);
         //--- Get all the deal properties
         GetHistoryDealProperties(ticket,D_ALL);
         //--- Get the number of digits in the price
         digits=(int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS);
         //--- Calculate the overall balance
         balance+=deal.profit+deal.swap+deal.commission;
         //--- Calculate the max drawdown from the local maximum
         TesterDrawdownMaximum(i,balance,percent_drawdown,money_drawdown);
         //--- Generate a string for writing using concatenation
         StringConcatenate(string_to_write,
                           deal.time,delimeter,
                           DealSymbolToString(deal.symbol),delimeter,
                           DealTypeToString(deal.type),delimeter,
                           DealEntryToString(deal.entry),delimeter,
                           DealVolumeToString(deal.volume),delimeter,
                           DealPriceToString(deal.price,digits),delimeter,
                           DealSwapToString(deal.swap),delimeter,
                           DealProfitToString(deal.symbol,deal.profit),delimeter,
                           DrawdownToString(percent_drawdown),delimeter,
                           DrawdownToString(money_drawdown),delimeter,
                           DoubleToString(balance,2));
         //--- If more than one symbol is involved, write their balance values
         if(SYMBOLS_COUNT>1)
           {
            //--- Iterate over all symbols
            for(int s=0; s<SYMBOLS_COUNT; s++)
              {
               //--- If the symbols are equal and the deal result is non-zero
               if(deal.symbol==InputSymbols[s] && deal.profit!=0)
                 {
                  //--- Display the deal in the balance for the corresponding symbol
                  //    Take into consideration swap and commission
                  symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]+
                                               deal.profit+
                                               deal.swap+
                                               deal.commission;
                  //--- Add to the string
                  StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                 }
               //--- Otherwise write the previous value
               else
                 {
                  //--- If the deal type is "Balance" (the first deal)
                  if(deal.type==DEAL_TYPE_BALANCE)
                    {
                     //--- the balance is the same for all symbols
                     symbol_balance[s].balance[i]=balance;
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                  //--- Otherwise write the previous value to the current index
                  else
                    {
                     symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1];
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                 }
              }
           }
         //--- Write the generated string
         FileWrite(file_handle,string_to_write);
         //--- Mandatory zeroing out of the variable for the next string
         string_to_write="";
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- If the file could not be created/opened, print the appropriate message
   else
      Print("Error creating the file! Error: "+IntegerToString(GetLastError())+"");
  }

Nous avions l'habitude de calculer les retraits dans la fonction DrawdownMaximumToString(). Ceci est maintenant effectué par la fonction TesterDrawdownMaximum(), tandis que la valeur de retrait est convertie en une chaîne de caractères à l'aide de la fonction de base DrawdownToString().

Le code de la fonction TesterDrawdownMaximum() est le suivant :

//+------------------------------------------------------------------+
//| Returning the max drawdown from the local maximum                |
//+------------------------------------------------------------------+
void TesterDrawdownMaximum(int deal_number,
                           double balance,
                           double &percent_drawdown,
                           double &money_drawdown)
  {
   ulong         ticket =0;   // Deal ticket
   string        str    ="";  // The string to be displayed in the report
//--- To calculate the local maximum and drawdown
   static double max    =0.0;
   static double min    =0.0;
   
//--- If this is the first deal
   if(deal_number==0)
     {
      //--- There is no drawdown yet
      percent_drawdown =0.0;
      money_drawdown   =0.0;
      //--- Set the initial point as the local maximum
      max=balance;
      min=balance;
     }
   else
     {
      //--- If the current balance is greater than in the memory, then...
      if(balance>max)
        {
         //--- Calculate the drawdown using the previous values:
         //    in monetary terms
         money_drawdown=max-min;
         //    expressed as percentage
         percent_drawdown=100-((min/max)*100);
         //--- Update the local maximum
         max=balance;
         min=balance;
        }
      //--- Otherwise
      else
        {
         //--- Return zero value of the drawdown
         money_drawdown=0.0;
         percent_drawdown=0.0;
         //--- Update the minimum
         min=fmin(min,balance);
         //--- If the deal ticket by its position in the list has been obtained, then...
         if((ticket=HistoryDealGetTicket(deal_number))>0)
           {
            //--- ...get the deal comment
            GetHistoryDealProperties(ticket,D_COMMENT);
            //--- Flag of the last deal
            static bool last_deal=false;
            //--- The last deal in the test can be identified by the "end of test" comment
            if(deal.comment=="end of test" && !last_deal)
              {
               //--- Set the flag
               last_deal=true;
               //--- Update the drawdown values:
               //    in monetary terms
               money_drawdown=max-min;
               //    expressed as percentage
               percent_drawdown+=100-((min/max)*100);
              }
           }
        }
     }
  }

Le code de la fonction DrawdownToString() est fourni ci-dessous :

//+------------------------------------------------------------------+
//| Converting drawdown to a string                                  |
//+------------------------------------------------------------------+
string DrawdownToString(double drawdown)
  {
   return((drawdown<=0) ? "" : DoubleToString(drawdown,2));
  }

Maintenant, tout est prêt pour le test de l'Expert Advisor et l'analyse des résultats. Au début de l'article, nous avons vu un exemple de fichier prêt à l'emploi. Procédons comme suit : optimisons les paramètres des symboles du deuxième ensemble (il y a trois symboles : EURUSD, AUDUSD et USDCHF) et après l'optimisation, exécutez le test en utilisant tous les symboles du troisième ensemble (sept symboles au total) pour voir les résultats des symboles dont les données n'étaient pas impliquées dans l'optimisation des paramètres.


Optimisation des paramètres et test Expert Advisor

Le testeur de stratégie doit être configuré comme indiqué ci-dessous :

Fig. 6. Les paramètres du testeur de stratégie pour l'optimisation

Fig. 6. Les paramètres de testeur de stratégie pour l'optimisation

Les paramètres de l'Expert Advisor pour l'optimisation des paramètres sont fournis ci-dessous :

Fig. 7. Les paramètres de l'Expert Advisor pour l'optimisation des paramètres

Fig. 7. Les paramètres de l'Expert Advisor pour l'optimisation des paramètres

Étant donné que l'optimisation implique trois symboles et que l'augmentation du volume de la position est activée pour chacun d'eux, nous définissons le lot minimum aux fins d'ouvrir une position et d'augmenter le volume de la position. Dans notre cas, la valeur est de 0,01.

Après l'optimisation, nous sélectionnons le meilleur résultat par le facteur de récupération maximal et définissons le paramètre VolumeIncrease à 0,1 pour le lot. Le résultat est présenté ci-dessous :

Fig. 8. Le résultat du test dans MetaTrader 5

Fig. 8. Le résultat du test dans MetaTrader 5

Ci-dessous, vous pouvez voir le résultat tel qu'il apparaît dans Excel 2010 :

Le résultat du test pour trois symboles comme indiqué dans Excel 2010

Fig. 9. Le résultat du test pour trois symboles comme indiqué dans Excel 2010

Le tirage en termes monétaires est affiché sous forme de marques vertes dans le graphique inférieur en termes de deuxième échelle (auxiliaire).

Vous devez également connaître les limites des graphiques dans Excel 2010 (la liste complète des spécifications et des limites est disponible sur page des spécifications et limites Excel du site Web de Microsoft Office).

Fig. 10. Spécifications et limites des graphiques dans Excel 2010

Fig. 10. Spécifications et limites des graphiques dans Excel 2010

Le tableau montre que nous pouvons exécuter le test pour 255 symboles en même temps et afficher tous les résultats dans le graphique ! Nous ne sommes limités que par les ressources informatiques.

Exécutons maintenant le test pour sept symboles du troisième ensemble avec les paramètres actuels et vérifions le résultat :

Le résultat du test pour sept symboles comme indiqué dans Excel 2010

Fig. 11. Le résultat du test pour sept symboles comme indiqué dans Excel 2010

Avec sept symboles à l'étude, nous avons 6901 transactions. Les données du graphique sont mises à jour assez rapidement dans Excel 2010.


Conclusion

Je pense que la méthode introduite est remarquable car même une stratégie de trading simple comme celle que nous avons utilisée a donné de bons résultats. Ici, il faut garder à l'esprit que l'optimisation n'a été effectuée que pour trois symboles sur sept. Nous pouvons essayer d'améliorer le résultat en optimisant les paramètres pour tous les symboles à la fois. Cependant, nous devrions avant tout viser à améliorer le système commercial, ou mieux encore, à avoir un portefeuille de différents systèmes de trading. Nous reviendrons sur cette idée plus tard.

C'est à peu près ça. Nous avons un outil assez utile pour étudier les résultats des stratégies de trading multi-devises. Vous trouverez ci-dessous le fichier zip téléchargeable avec les fichiers de l'Expert Advisor pour votre considération.

Après avoir extrait les fichiers, placez le dossier ReduceOverfittingEA dans le répertoire MetaTrader 5\MQL5\Experts. De plus, l'indicateur EventsSpy.mq5 doit être placé dans MetaTrader 5\MQL5\Indicators. SymbolsList.txt doit être situé sous MetaTrader 5\MQL5\Files.