Création d'un Expert Advisor multi-devises multi-systèmes

Maxim Khrolenko | 13 janvier, 2022

Introduction

Je crois qu'il y a pas mal de traders qui tradent plus d'un symbole de trading et utilisent plusieurs stratégies. Cette approche vous permet non seulement d'augmenter potentiellement votre bénéfice, mais également de minimiser le risque de prélèvement substantiel sur une gestion efficace de l'argent. Lors de la création d'un Expert Advisor, la première étape naturelle pour vérifier l'efficacité de la stratégie du programme est l'optimisation afin de déterminer les meilleurs paramètres d'entrée.

Une fois les valeurs des paramètres identifiées, les Expert Advisors seraient techniquement prêts pour le trading. Cependant, cela laisserait une question importante sans réponse. À quoi ressembleraient les résultats des tests si un trader pouvait regrouper toutes ses stratégies dans un seul Expert Advisor ? La prise de conscience que le retrait sur plusieurs symboles ou stratégies peut à un moment donné se chevaucher et donner lieu à un retrait total épouvantable, voire à un appel de marge, peut parfois constituer une mauvaise surprise.

Cet article présente un concept de création d'un Expert Advisor multi-devises multi-systèmes qui nous permettra de trouver une réponse à cette question importante.


1. Structure du conseiller expert

De manière générale, la structure de l'Expert Advisor est la suivante :

Fig. 1. Structure de l'Expert Advisor multi-devises multi-système

Fig. 1. Structure de l'Expert Advisor multi-devises multi-systèmes

Comme vous pouvez le voir, le programme est basé sur une boucle for. Chaque stratégie est organisée en boucle où chaque itération est responsable du trading de chaque symbole séparément. Ici, vous pouvez organiser en boucles un nombre illimité de stratégies. Il est important que votre ordinateur dispose de suffisamment de ressources pour "traiter" un tel programme.

Vous devez garder à l'esprit qu'il ne peut y avoir qu'une seule position pour chaque symbole tradé dans MetaTrader 5. Cette position représente la somme des lots d'achats et de ventes précédemment exécutés. Par conséquent, le résultat des tests multi-stratégies pour un symbole ne sera pas identique à la somme des résultats de tests séparés des mêmes stratégies pour le même symbole.

Pour examiner de plus près la structure de l'Expert Advisor, nous allons prendre 2 stratégies, chacune tradant deux symboles :

Stratégie A :

Stratégie B :

Pour être indépendant des nouveaux ticks d'un symbole sur lequel l'Expert Advisor sera testé ou sur lequel il va trader, il est conseillé d'utiliser la fonction OnTimer() pour trader en mode multi-devises.

À cette fin, lors de l'initialisation de l'Expert Advisor, nous spécifions la fréquence de génération d'un événement pour l'appel de calcul de programme à l'aide de la fonction EventSetTimer(), et lors de la désinitialisation, nous utilisons la fonction EventKillTimer() pour dire au terminal d'arrêter la génération d'événements :

// Include standard libraries
// Create external parameters
// Create arrays, variables, indicator handles, etc.

//--- Initialization of the Expert Advisor
int OnInit()
  {
   //--- Set event generation frequency
   EventSetTimer(1); // 1 second
   // ...
   return(0);
  }
void OnTimer()
  {
   // ...
  }
//--- Deinitialization of the Expert Advisor
void OnDeinit(const int reason)
  {
   //--- Stop event generation
   EventKillTimer();
   // ...
  }

Au lieu de EventSetTimer(), vous pouvez également utiliser EventSetMillisecondTimer(), où la fréquence est définie avec précision à la milliseconde, mais vous ne devez pas en abuser par des appels de calcul de programme trop fréquents.

Pour accéder aux paramètres de compte, de position et de symbole, ainsi qu'aux fonctions de trading, nous utiliserons respectivement les classes CAccountInfo, CPositionInfo, CSymbolInfo et CTrade. Incluons-les dans l'Expert Advisor :

//--- Include standard libraries
#include <Trade\AccountInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\Trade.mqh>

Étant donné que l'Expert Advisor est basé sur des boucles for, nous devrons créer des tableaux pour ses paramètres externes. Créons d'abord des constantes égales au nombre de symboles pour chaque stratégie :

//--- Number of traded symbols for each strategy
#define Strategy_A 2
#define Strategy_B 2

Nous créons ensuite des paramètres externes. À l'aide de constantes, nous déterminons les tailles des tableaux dans lesquels ils seront copiés. De plus, nous créons des poignées d'indicateur et d'autres variables globales.

Un exemple pour un symbole de stratégie est fourni ci-dessous :

//------------------- External parameters of strategy A
input string          Data_for_Strategy_A="Strategy A -----------------------";
//--- Symbol 0
input string          Symbol_A0      = "EURUSD";   // Symbol
input bool            IsTrade_A0     = true;       // Permission for trading
//--- Bollinger Bands (BB) parameters
input ENUM_TIMEFRAMES Period_A0      = PERIOD_H1;  // ВВ period
input uint            BBPeriod_A0    = 20;         // Period for calculation of the moving average of BB
input int             BBShift_A0     = 0;          // Horizontal shift of ВВ
input double          BBDeviation_A0 = 2.0;        // Number of standard deviations of BB
//...
//--- General parameters of strategy A
input double          DealOfFreeMargin_A = 1.0;    // Percent of free margin for a deal
input uint            MagicNumber_A      = 555;    // Magic number
input uint            Slippage_A         = 100;    // Permissible slippage for a deal
//...
//------------- Set variables of strategy A -----
//--- Arrays for external parameters
string          Symbol_A[Strategy_A];
bool            IsTrade_A[Strategy_A];
ENUM_TIMEFRAMES Period_A[Strategy_A];
int             BBPeriod_A[Strategy_A];
int             BBShift_A[Strategy_A];
double          BBDeviation_A[Strategy_A];
//--- Arrays for global variables
double          MinLot_A[Strategy_A],MaxLot_A[Strategy_A];
double          Point_A[Strategy_A],ContractSize_A[Strategy_A];
uint            DealNumber_A[Strategy_A];
datetime        Locked_bar_time_A[Strategy_A],time_arr_A[];
//--- Indicator handles
int             BB_handle_high_A[Strategy_A];
int             BB_handle_low_A[Strategy_A];
//--- Arrays for indicator values
double          BB_upper_band_high[],BB_lower_band_high[];
double          BB_upper_band_low[],BB_lower_band_low[];
//--- Class
CTrade          Trade_A;
//...
//--- Set global variables for all strategies
long            Leverage;
//--- Classes
CAccountInfo    AccountInfo;
CPositionInfo   PositionInfo;
CSymbolInfo     SymbolInfo;

Pour avoir la possibilité de désactiver le trading pour un certain symbole, nous avons créé une variable booléenne IsTrade_A0 qui sera placée au tout début des boucles for.


2. Initialisation de l'Expert Advisor

Tout d'abord, obtenons les valeurs requises pour toutes les stratégies, par exemple l'effet de levier. Étant donné que l'effet de levier est appliqué au compte de trading et n'a rien à voir avec une stratégie ou un symbole, il n'est pas nécessaire de copier sa valeur dans les tableaux :

//--- Get the leverage for the account
   Leverage=AccountInfo.Leverage();

Nous copions ensuite les variables externes dans des tableaux.

//--- Copy external variables to arrays
   Symbol_A[0]     =Symbol_A0;
   IsTrade_A[0]    =IsTrade_A0;
   Period_A[0]     =Period_A0;
   BBPeriod_A[0]   =(int)BBPeriod_A0;
   BBShift_A[0]    =BBShift_A0;
   BBDeviation_A[0]=BBDeviation_A0;

Si un paramètre externe est défini par le type qui nécessitera une conversion en un autre, cela peut être fait de manière plus pratique lors de la copie dans des tableaux.

Dans ce cas, nous pouvons voir que BBPeriod_A0 a été créé en tant que uint pour empêcher l'utilisateur de définir une valeur négative. Ici, nous le convertissons en int et le copions dans le tableau qui a également été créé en tant que int. Sinon, le compilateur donnera un avertissement si vous essayez d'insérer un paramètre de type uint dans la poignée de l'indicateur.

Voyons ensuite si le symbole tradé est disponible dans le Market Watch et s'il a été utilisé plus d'une fois au sein d'une même stratégie :

//--- Check for the symbol in the Market Watch
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      if(IsSymbolInMarketWatch(Symbol_A[i])==false)
        {
         Print(Symbol_A[i]," could not be found on the server!");
         ExpertRemove();
        }
     }

//--- Check whether the symbol is used more than once
   if(Strategy_A>1)
     {
      for(int i=0; i<Strategy_A-1; i++)
        {
         if(IsTrade_A[i]==false) continue;
         for(int j=i+1; j<Strategy_A; j++)
           {
            if(IsTrade_A[j]==false) continue;
            if(Symbol_A[i]==Symbol_A[j])
              {
               Print(Symbol_A[i]," is used more than once!");
               ExpertRemove();
              }
           }
        }
     }
//--- The IsSymbolInMarketWatch() function
bool IsSymbolInMarketWatch(string f_Symbol)
  {
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      if(f_Symbol==SymbolName(s,false))
         return(true);
     }
   return(false);
  }

Si les symboles ont été sélectionnés correctement, vérifiez les erreurs dans les paramètres d'entrée pour chacun d'eux, créez des poignées d'indicateur, obtenez les données nécessaires au calcul du lot et, si nécessaire, faites d'autres choses telles que définies par la stratégie donnée.

Nous allons implémenter les actions mentionnées ci-dessus dans une boucle for.

//--- General actions
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      //--- Check for errors in input parameters
      //...
      //--- Set indicator handles
      BB_handle_high_A[i]=iBands(Symbol_A[i],Period_A[i],BBPeriod_A[i],BBShift_A[i],BBDeviation_A[i],
                                 PRICE_HIGH);
      if(BB_handle_high_A[i]<0)
        {
         Print("Failed to create a handle for Bollinger Bands based on High prices for ",Symbol_A[i]," . Handle=",INVALID_HANDLE,
               "\n Error=",GetLastError());
         ExpertRemove();
        }
      //...
      //--- Calculate data for the Lot
      //--- set the name of the symbol for which the information will be obtained
      SymbolInfo.Name(Symbol_A[i]);
      //--- minimum and maximum volume size in trading operations
      MinLot_A[i]=SymbolInfo.LotsMin();
      MaxLot_A[i]=SymbolInfo.LotsMax();
      //--- point value
      Point_A[i]=SymbolInfo.Point();
      //--- contract size
      ContractSize_A[i]=SymbolInfo.ContractSize();

      //--- Set some additional parameters
     }

Ensuite, nous définissons les paramètres des opérations de trading de la stratégie A à l'aide de l'objet Trade_A de la classe CTrade.

//--- Set parameters for trading operations
//--- set the magic number
   Trade_A.SetExpertMagicNumber(MagicNumber_A);
//--- set the permissible slippage in points upon deal execution
   Trade_A.SetDeviationInPoints(Slippage_A);
//--- order filling mode, use the mode that is allowed by the server
   Trade_A.SetTypeFilling(ORDER_FILLING_RETURN);
//--- logging mode, it is advisable not to call this method as the class will set the optimal mode by itself
   Trade_A.LogLevel(1);
//--- the function to be used for trading: true - OrderSendAsync(), false - OrderSend().
   Trade_A.SetAsyncMode(true);

La même procédure est répétée pour chaque stratégie, c'est-à-dire

  1. Copier les variables externes dans les tableaux ;
  2. Vérifier si les symboles sont correctement sélectionnés ;
  3. Vérifier les erreurs, définir les poignées d'indicateur, calculer les données pour le lot et pour tout ce qui est nécessaire pour une stratégie donnée ;
  4. Définir des paramètres pour les opérations de trading.

Enfin, il serait bon de vérifier si un même symbole est utilisé dans plusieurs stratégies (un exemple pour deux stratégies est fourni ci-dessous) :

//--- Check whether one and the same symbol is used in several strategies
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      for(int j=0; j<Strategy_B; j++)
        {
         if(IsTrade_B[j]==false) continue;
         if(Symbol_A[i]==Symbol_B[j])
           {
            Print(Symbol_A[i]," is used in several strategies!");
            ExpertRemove();
           }
        }
     }

3. Échanger des boucles « For »

Le cadre des boucles for à l'intérieur de la fonction OnTimer() est le suivant :

void OnTimer()
  {
//--- Check if the terminal is connected to the trade server
   if(TerminalInfoInteger(TERMINAL_CONNECTED)==false) return;

//--- Section A: Main loop of the FOR operator for strategy A -----------
   for(int A=0; A<Strategy_A; A++)
     {
      //--- A.1: Check whether the symbol is allowed to be traded
      if(IsTrade_A[A]==false)
         continue; // terminate the current FOR iteration

     }

//--- Section В: Main loop of the FOR operator for strategy В -----------
   for(int B=0; B<Strategy_B; B++)
     {
      //--- B.1: Check whether the symbol is allowed to be traded
      if(IsTrade_B[B]==false)
         continue; // terminate the current FOR iteration

     }
  }

Si un Expert Advisor à symbole unique basé sur une stratégie unique a une condition selon laquelle tous les calculs ultérieurs doivent être arrêtés, nous utilisons l'opérateur return. Dans notre cas, il suffit de terminer l'itération en cours et de passer à l'itération de symbole suivante. Pour cela, il est préférable d'utiliser l'opérateur continue.

Si vous souhaitez améliorer votre Expert Advisor multi-stratégies en ajoutant une stratégie avec une boucle for qui contient une condition d'arrêt de tous les calculs suivants, vous pouvez utiliser le modèle suivant :

//--- Section N: Main loop of the FOR operator for strategy N -----------
for(int N=0; N<Strategy_N; N++)
  {

   //...
   bool IsInterrupt=false;
   for(int i=0; i<Number; i++)
     {
      if(...) // terminate all calculations
        {
         IsInterrupt=true;
         break;
        }
     }
   if(IsInterrupt=true)
      continue; // terminate the current FOR iteration
   //...

  }

Après avoir créé le cadre des boucles for, nous y insérons simplement les codes d'autres EA, puis nous remplaçons certaines variables par des éléments de tableau.

Par exemple, nous changeons la variable prédéfinie _Symbol en Symbol_A[i] ou _Point en Point_A[i]. Les valeurs de ces variables sont typiques du symbole donné et ont donc été copiées dans des tableaux lors de l'initialisation.

Par exemple, trouvons la valeur de l'indicateur :

 //--- A.3: Lower band of BB calculated based on High prices
 if(CopyBuffer(BB_handle_high_A[A],LOWER_BAND,BBShift_A[A],1,BB_lower_band_high)<=0)
    continue; // terminate the current FOR iteration
 ArraySetAsSeries(BB_lower_band_high,true);

Pour implémenter la clôture d'une position d'achat, nous écrirons le code suivant :

 //--- A.7.1: Calculate the current Ask and Bid prices
 SymbolInfo.Name(Symbol_A[A]);
 SymbolInfo.RefreshRates();
 double Ask_price=SymbolInfo.Ask();
 double Bid_price=SymbolInfo.Bid();

 if(PositionSelect(Symbol_A[A]))
   {
    //--- A.7.2: Closing a BUY position
    if(PositionInfo.PositionType()==POSITION_TYPE_BUY)
      {
       if(Bid_price>=BB_lower_band_high[0] || DealNumber_A[A]==0)
         {
          if(!Trade_A.PositionClose(Symbol_A[A]))
            {
             Print("Failed to close the Buy ",Symbol_A[A]," position. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
          else
            {
             Print("The Buy ",Symbol_A[A]," position closed successfully. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
         }
      }

    //...
   }

Ouvrir une position d'achat :

 //--- A.9.1: for a Buy
 if(Ask_price<=BB_lower_band_low[0])
   {
    //...

    //--- A.9.1.3: Execute a deal
    if(!Trade_A.Buy(OrderLot,Symbol_A[A]))
      {
       Print("The Buy ",Symbol_A[A]," has been unsuccessful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
    else
      {
       Print("The Buy ",Symbol_A[A]," has been successful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
   }

N'oubliez pas de mettre fin à la génération d'événements de minuterie et de supprimer les poignées d'indicateur lors de la désinitialisation.


4. Résultats de test

Lorsque l'Expert Advisor est prêt, nous testons chaque stratégie et chaque symbole séparément et comparons les résultats du test avec ceux obtenus en mode test lors du trading simultané de toutes les stratégies et symboles.

On suppose que l'utilisateur a déjà identifié les valeurs optimales des paramètres d'entrée.


Vous trouverez ci-dessous les paramètres du Strategy Tester :

Fig. 2. Paramètres du Strategy Tester

Fig. 2. Paramètres du Strategy Tester

Résultats pour la stratégie A, EURUSD :

Fig. 3. Résultats du test pour la stratégie A, EURUSD

Fig. 3. Résultats du test pour la stratégie A, EURUSD

Résultats pour la stratégie A, GBPUSD :

Fig. 4. Résultats du test pour la stratégie A, GBPUSD

Fig. 4. Résultats du test pour la stratégie A, GBPUSD

Résultats pour la stratégie B, AUDUSD :

Fig. 5. Résultats du test pour la stratégie В, AUDUSD

Fig. 5. Résultats du test pour la stratégie В, AUDUSD

Résultats pour la stratégie B, EURJPY :

Fig. 6. Résultats du test pour la stratégie , EURJPY

Fig. 6. Résultats du test pour la stratégie В, EURJPY

Résultats du test pour toutes les stratégies et tous les symboles :

Fig. 7. Résultats du test pour toutes les stratégies et tous les symboles

Fig. 7. Résultats du test pour toutes les stratégies et tous les symboles


Conclusion

En conséquence, nous avons une structure pratique et simple de l'Expert Advisor multi-devises et multi-systèmes dans lequel vous pouvez placer pratiquement n'importe laquelle de vos stratégies.

Un tel Expert Advisor vous permet de mieux évaluer l'efficacité du trading en utilisant toutes vos stratégies. Cela peut également s'avérer utile dans le cas où un seul Expert Advisor est autorisé à travailler sur un compte donné. Le code source de l'Expert Advisor est joint à l'article pour faciliter l'étude des informations ci-dessus.