English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
MQL5 Cookbook : Expert Advisor multi-devises - Approche simple, nette et rapide

MQL5 Cookbook : Expert Advisor multi-devises - Approche simple, nette et rapide

MetaTrader 5Exemples | 13 janvier 2022, 10:43
267 0
Anatoli Kazharski
Anatoli Kazharski

Introduction

Cet article décrira une mise en œuvre d'une approche simple adaptée à un Expert Advisor multi-devises. Cela signifie que vous pourrez configurer l'Expert Advisor pour les tests/trading dans des conditions identiques mais avec des paramètres différents pour chaque symbole. A titre d'exemple, nous allons créer un motif pour deux symboles mais de manière à pouvoir ajouter des symboles supplémentaires, si nécessaire, en apportant de petites modifications au code.

Un modèle multi-devises peut être implémenté dans MQL5 de plusieurs manières :

  • Nous pouvons utiliser un modèle où un Expert Advisor est guidé par le temps, étant capable d'effectuer des contrôles plus précis aux intervalles de temps spécifiés dans les OnTimer().

  • Alternativement, comme dans tous les Expert Advisors présentés dans les articles précédents de la série, la vérification peut être effectuée dans les OnTick() auquel cas l'Expert Advisor dépendra des ticks pour le symbole actuel qu'il travaille sur. Ainsi, s'il y a une barre complétée sur un autre symbole, alors qu'il n'y a pas encore de tick pour le symbole courant, l'Expert Advisor n'effectuera une vérification qu'une fois qu'il y aura une nouveau tick pour le symbole courant.

  • Il y a encore une autre option intéressante suggérée par son auteur Konstantin Gruzdev (Lizar). Il utilise un modèle d'événement : à l'aide des OnChartEvent(), un Expert Advisor obtient des événements qui sont reproduits par des agents indicateurs situés sur les graphiques de symboles impliqués dans les tests/trading. Les agents indicateurs peuvent reproduire de nouveaux événements de barre et de tick des symboles auxquels ils sont attachés. Ce type d'indicateur (EventsSpy.mq5) peut être téléchargé à la fin de l'article. Nous en aurons besoin pour le fonctionnement de l'Expert Advisor.


Développement d’Expert Advisor

L'Expert Advisor présenté dans l'article "MQL5 Cookbook : Utilisation d'indicateurs pour définir les conditions de trading dans les Expert Advisors" servira de modèle. J'en ai déjà supprimé tout ce qui avait à voir avec le panneau d'information et j'ai également simplifié la condition d'ouverture de position telle qu'implémentée dans l'article précédent intitulé "MQL5 Cookbook : Développement d’un cadre pour un système de trading basé sur la stratégie du triple écran". Puisque nous avons l'intention de créer un Expert Advisor pour deux symboles, chacun d'eux aura besoin de son propre ensemble de paramètres externes :

//--- External parameters of the Expert Advisor
sinput long   MagicNumber           = 777;      // Magic number
sinput int    Deviation             = 10;       // Slippage
//---
sinput string delimeter_00=""; // --------------------------------
sinput string Symbol_01             = "EURUSD"; // Symbol 1
input  int    IndicatorPeriod_01    = 5;        // |     Indicator period
input  double TakeProfit_01         = 100;      // |     Take Profit
input  double StopLoss_01           = 50;       // |     Stop Loss
input  double TrailingStop_01       = 10;       // |     Trailing Stop
input  bool   Reverse_01            = true;     // |     Position reversal
input  double Lot_01                = 0.1;      // |     Lot
input  double VolumeIncrease_01     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_01 = 10;       // |     Volume increase step
//---
sinput string delimeter_01=""; // --------------------------------
sinput string Symbol_02             = "NZDUSD"; // Symbol 2
input  int    IndicatorPeriod_02    = 5;        // |     Indicator period
input  double TakeProfit_02         = 100;      // |     Take Profit
input  double StopLoss_02           = 50;       // |     Stop Loss
input  double TrailingStop_02       = 10;       // |     Trailing Stop
input  bool   Reverse_02            = true;     // |     Position reversal
input  double Lot_02                = 0.1;      // |     Lot
input  double VolumeIncrease_02     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_02 = 10;       // |     Volume increase step

Les paramètres externes seront placés dans des tableaux dont les tailles dépendront du nombre de symboles utilisés. Le nombre de symboles utilisés dans l'Expert Advisor sera déterminé par la valeur de la constante NUMBER_OF_SYMBOLS que nous devons créer au début du fichier :

//--- Number of traded symbols
#define NUMBER_OF_SYMBOLS 2
//--- Name of the Expert Advisor
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)

Créons les tableaux qui seront nécessaires pour stocker les paramètres externes :

//--- Arrays for storing external parameters
string Symbols[NUMBER_OF_SYMBOLS];            // Symbol
int    IndicatorPeriod[NUMBER_OF_SYMBOLS];    // Indicator period
double TakeProfit[NUMBER_OF_SYMBOLS];         // Take Profit
double StopLoss[NUMBER_OF_SYMBOLS];           // Stop Loss
double TrailingStop[NUMBER_OF_SYMBOLS];       // Trailing Stop
bool   Reverse[NUMBER_OF_SYMBOLS];            // Position reversal
double Lot[NUMBER_OF_SYMBOLS];                // Lot
double VolumeIncrease[NUMBER_OF_SYMBOLS];     // Position volume increase
double VolumeIncreaseStep[NUMBER_OF_SYMBOLS]; // Volume increase step

Les fonctions d'initialisation du tableau seront placées dans le fichier include InitArrays.mqh. Pour initialiser le tableau Symbols[], nous allons créer la fonction GetSymbol(). Il obtiendra le nom du symbole à partir des paramètres externes et si un tel symbole est disponible dans la liste des symboles sur le serveur, il sera sélectionné dans la fenêtre Market Watch. Ou bien, si le symbole requis est introuvable sur le serveur, la fonction renverra une chaîne vide et le Journal of Expert Advisors sera mis à jour en conséquence.

Vous trouverez ci-dessous le code de la fonction GetSymbol() :

//+------------------------------------------------------------------+
//| Adding the specified symbol to the Market Watch window           |
//+------------------------------------------------------------------+
string GetSymbolByName(string symbol)
  {
   string symbol_name="";   // Symbol name on the server
//--- If an empty string is passed, return the empty string
   if(symbol=="")
      return("");
//--- Iterate over the list of all symbols on the server
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      //--- Get the symbol name
      symbol_name=SymbolName(s,false);
      //--- If the required symbol is available on the server
      if(symbol==symbol_name)
        {
         //--- Select it in the Market Watch window
         SymbolSelect(symbol,true);
         //--- Return the symbol name
         return(symbol);
        }
     }
//--- If the required symbol cannot be found, return the empty string
   Print("The "+symbol+" symbol could not be found on the server!");
   return("");
  }

Le tableauSymbols[] sera initialisé dans la fonction GetSymbols() :

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void GetSymbols()
  {
   Symbols[0]=GetSymbolByName(Symbol_01);
   Symbols[1]=GetSymbolByName(Symbol_02);
  }

De plus, nous l'implémenterons de telle sorte qu'une valeur vide dans les paramètres externes d'un certain symbole indiquera que le bloc correspondant ne sera pas impliqué dans le test/trading. Ceci est nécessaire pour pouvoir optimiser les paramètres de chaque symbole séparément, tout en excluant complètement le reste.

Tous les autres tableaux de paramètres externes sont initialisés de la même manière. En d'autres termes, nous devons créer une fonction distincte pour chaque tableau. Les codes de toutes ces fonctions sont fournis ci-dessous :

//+------------------------------------------------------------------+
//| Filling the indicator period array                               |
//+------------------------------------------------------------------+
void GetIndicatorPeriod()
  {
   IndicatorPeriod[0]=IndicatorPeriod_01;
   IndicatorPeriod[1]=IndicatorPeriod_02;
  }
//+------------------------------------------------------------------+
//| Filling the Take Profit array                                    |
//+------------------------------------------------------------------+
void GetTakeProfit()
  {
   TakeProfit[0]=TakeProfit_01;
   TakeProfit[1]=TakeProfit_02;
  }
//+------------------------------------------------------------------+
//| Filling the Stop Loss array                                      |
//+------------------------------------------------------------------+
void GetStopLoss()
  {
   StopLoss[0]=StopLoss_01;
   StopLoss[1]=StopLoss_02;
  }
//+------------------------------------------------------------------+
//| Filling the Trailing Stop array                                  |
//+------------------------------------------------------------------+
void GetTrailingStop()
  {
   TrailingStop[0]=TrailingStop_01;
   TrailingStop[1]=TrailingStop_02;
  }
//+------------------------------------------------------------------+
//| Filling the Reverse array                                        |
//+------------------------------------------------------------------+
void GetReverse()
  {
   Reverse[0]=Reverse_01;
   Reverse[1]=Reverse_02;
  }
//+------------------------------------------------------------------+
//| Filling the Lot array                                            |
//+------------------------------------------------------------------+
void GetLot()
  {
   Lot[0]=Lot_01;
   Lot[1]=Lot_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncrease array                                 |
//+------------------------------------------------------------------+
void GetVolumeIncrease()
  {
   VolumeIncrease[0]=VolumeIncrease_01;
   VolumeIncrease[1]=VolumeIncrease_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncreaseStep array                             |
//+------------------------------------------------------------------+
void GetVolumeIncreaseStep()
  {
   VolumeIncreaseStep[0]=VolumeIncreaseStep_01;
   VolumeIncreaseStep[1]=VolumeIncreaseStep_02;
  }

Créons maintenant une fonction qui nous aidera à initialiser facilement tous les tableaux de paramètres externes à la fois - la fonction InitializeInputParameters() :

//+------------------------------------------------------------------+
//| Initializing external parameter arrays                           |
//+------------------------------------------------------------------+
void InitializeInputParameters()
  {
   GetSymbols();
   GetIndicatorPeriod();
   GetTakeProfit();
   GetStopLoss();
   GetTrailingStop();
   GetReverse();
   GetLot();
   GetVolumeIncrease();
   GetVolumeIncreaseStep();
  }

Suite à l'initialisation des tableaux de paramètres externes, nous pouvons passer à la partie principale. Certaines procédures telles que l'obtention des poignées d'indicateur, leurs valeurs et les informations sur les prix, ainsi que la vérification de la nouvelle barre, etc. seront effectuées en boucles consécutivement pour chaque symbole. C'est pourquoi les valeurs des paramètres externes ont été organisées en tableaux. Donc tout se fera dans les boucles comme suit :

//--- Iterate over all symbols
for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
  {
//--- If trading for this symbol is allowed
   if(Symbols[s]!="")
     {
      //--- The rest of the code
     }
  }

Mais avant de commencer à modifier les fonctions existantes et à en créer de nouvelles, créons également des tableaux qui seront nécessaires dans ce modèle.

Nous aurons besoin de deux tableaux pour les poignées d'indicateur :

//--- Array of indicator agent handles
int spy_indicator_handles[NUMBER_OF_SYMBOLS];
//--- Array of signal indicator handles
int signal_indicator_handles[NUMBER_OF_SYMBOLS];

Ces deux tableaux seront d'abord initialisés à des valeurs invalides :

//+------------------------------------------------------------------+
//| Initializing arrays of indicator handles                         |
//+------------------------------------------------------------------+
void InitializeArrayHandles()
  {
   ArrayInitialize(spy_indicator_handles,INVALID_HANDLE);
   ArrayInitialize(signal_indicator_handles,INVALID_HANDLE);
  }

Les tableaux de données de prix et de valeurs d'indicateurs seront désormais accessibles à l'aide de structures :

//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[NUMBER_OF_SYMBOLS];      // Opening price of the bar
PriceData high[NUMBER_OF_SYMBOLS];      // High price of the bar
PriceData low[NUMBER_OF_SYMBOLS];       // Low price of the bar
PriceData close[NUMBER_OF_SYMBOLS];     // Closing price of the bar
PriceData indicator[NUMBER_OF_SYMBOLS]; // Array of indicator values

Maintenant, si vous avez besoin d'obtenir la valeur de l'indicateur sur la dernière barre complétée du premier symbole de la liste, vous devez écrire quelque chose comme ça :

double indicator_value=indicator[0].value[1];

Nous devons également créer des tableaux au lieu des variables précédemment utilisées dans la fonction CheckNewBar() :

//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[NUMBER_OF_SYMBOLS];
//--- Array for checking the new bar for each symbol
datetime new_bar[NUMBER_OF_SYMBOLS];

Nous avons donc arrangé les tableaux. Nous devons maintenant modifier un certain nombre de fonctions en fonction des modifications apportées ci-dessus. Commençons par la fonction GetIndicatorHandles() :

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(signal_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            signal_indicator_handles[s]=iMA(Symbols[s],_Period,IndicatorPeriod[s],0,MODE_SMA,PRICE_CLOSE);
            //--- If the indicator handle could not be obtained
            if(signal_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to get the indicator handle for the symbol "+Symbols[s]+"!");
           }
        }
     }
  }

Désormais, quel que soit le nombre de symboles utilisés dans les tests/trading, le code de la fonction restera le même.

De même, nous allons créer une autre fonction, GetSpyHandles(), pour obtenir des poignées d'agents indicateurs qui transmettront les ticks d'autres symboles. Mais avant cela, nous allons ajouter une autre énumération de tous les événements par symbole, ENUM_CHART_EVENT_SYMBOL, organisé sous forme d'indicateurs dans le fichier Enums.mqh :

//+------------------------------------------------------------------+
//| New bar and tick events from all symbols and time frames         |
//+------------------------------------------------------------------+
enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_NO         = 0,          // Events are disabled - 0
   CHARTEVENT_INIT       = 0,          // Initialization event - 0
   //---
   CHARTEVENT_NEWBAR_M1  = 0x00000001, // New bar event on a minute chart (1)
   CHARTEVENT_NEWBAR_M2  = 0x00000002, // New bar event on a 2-minute chart (2)
   CHARTEVENT_NEWBAR_M3  = 0x00000004, // New bar event on a 3-minute chart (4)
   CHARTEVENT_NEWBAR_M4  = 0x00000008, // New bar event on a 4-minute chart (8)
   //---
   CHARTEVENT_NEWBAR_M5  = 0x00000010, // New bar event on a 5-minute chart (16)
   CHARTEVENT_NEWBAR_M6  = 0x00000020, // New bar event on a 6-minute chart (32)
   CHARTEVENT_NEWBAR_M10 = 0x00000040, // New bar event on a 10-minute chart (64)
   CHARTEVENT_NEWBAR_M12 = 0x00000080, // New bar event on a 12-minute chart (128)
   //---
   CHARTEVENT_NEWBAR_M15 = 0x00000100, // New bar event on a 15-minute chart (256)
   CHARTEVENT_NEWBAR_M20 = 0x00000200, // New bar event on a 20-minute chart (512)
   CHARTEVENT_NEWBAR_M30 = 0x00000400, // New bar event on a 30-minute chart (1024)
   CHARTEVENT_NEWBAR_H1  = 0x00000800, // New bar event on an hour chart (2048)
   //---
   CHARTEVENT_NEWBAR_H2  = 0x00001000, // New bar event on a 2-hour chart (4096)
   CHARTEVENT_NEWBAR_H3  = 0x00002000, // New bar event on a 3-hour chart (8192)
   CHARTEVENT_NEWBAR_H4  = 0x00004000, // New bar event on a 4-hour chart (16384)
   CHARTEVENT_NEWBAR_H6  = 0x00008000, // New bar event on a 6-hour chart (32768)
   //---
   CHARTEVENT_NEWBAR_H8  = 0x00010000, // New bar event on a 8-hour chart (65536)
   CHARTEVENT_NEWBAR_H12 = 0x00020000, // New bar event on a 12-hour chart (131072)
   CHARTEVENT_NEWBAR_D1  = 0x00040000, // New bar event on a daily chart (262144)
   CHARTEVENT_NEWBAR_W1  = 0x00080000, // New bar event on a weekly chart (524288)
   //---
   CHARTEVENT_NEWBAR_MN1 = 0x00100000, // New bar event on a monthly chart (1048576)
   CHARTEVENT_TICK       = 0x00200000, // New tick event (2097152)
   //---
   CHARTEVENT_ALL        = 0xFFFFFFFF  // All events are enabled (-1)
  };

Cette énumération est nécessaire pour travailler avec l'indicateur personnalisé EventsSpy.mq5 (le fichier est joint à l'article) dans la fonction GetSpyHandles() dont le code est fourni ci-dessous :

//+------------------------------------------------------------------+
//| Getting agent handles by the specified symbols                   |
//+------------------------------------------------------------------+
void GetSpyHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(spy_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            spy_indicator_handles[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_TICK);
            //--- If the indicator handle could not be obtained
            if(spy_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to install the agent on "+Symbols[s]+"");
           }
        }
     }
  }

Veuillez noter le dernier paramètre de la fonction iCustom() : dans ce cas, l'identifiant CHARTEVENT_TICK a été utilisé pour obtenir les événements de tick. Mais si cela est nécessaire, il peut être modifié pour obtenir les nouveaux événements de barre. Par exemple, si vous utilisez la ligne comme indiqué ci-dessous, l'Expert Advisor obtiendra de nouveaux événements de barre sur des périodes d'une minute (M1) et d'une heure (H1) :

handle_event_indicator[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_H1);

Pour obtenir tous les événements (événements de tick et de barre sur toutes les périodes), vous devez spécifier l'identifiant CHARTEVENT_ALL.

Tous les tableaux sont initialisés dans les OnInit() :

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialization of arrays of external parameters
   InitializeInputParameters();
//--- Initialization of arrays of indicator handles
   InitializeArrayHandles();
//--- Get agent handles
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeArrayNewBar();
  }

Comme déjà mentionné au début de l'article, les événements des agents indicateurs sont reçus dans les OnChartEvent(). Ci-dessous le code qui sera utilisé dans cette fonction :

//+------------------------------------------------------------------+
//| Chart events handler                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Event identifier
                  const long &lparam,   // Long type event parameter
                  const double &dparam, // Double type event parameter
                  const string &sparam) // String type event parameter
  {
//--- If this is a custom event
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Exit if trading is not allowed
      if(CheckTradingPermission()>0)
         return;
      //--- If there was a tick event
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Check signals and trade on them
         CheckSignalsAndTrade();
         return;
        }
     }
  }

Dans la fonction CheckSignalAndTrade() (la ligne en évidence dans le code ci-dessus), nous aurons une boucle où tous les symboles seront alternativement vérifiés pour l'événement de nouvelle barre et les signaux de trading comme implémenté précédemment dans les OnTick() :

//+------------------------------------------------------------------+
//| Checking signals and trading based on the new bar event          |
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
  {
//--- Iterate over all specified symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the bar is not new, proceed to the next symbol
         if(!CheckNewBar(s))
            continue;
         //--- If there is a new bar
         else
           {
            //--- Get indicator data. If there is no data, proceed to the next symbol
            if(!GetIndicatorsData(s))
               continue;
            //--- Get bar data               
            GetBarsData(s);
            //--- Check the conditions and trade
            TradingBlock(s);
            //--- Trailing Stop
            ModifyTrailingStop(s);
           }
        }
     }
  }

Toutes les fonctions qui utilisaient les paramètres externes, ainsi que les données de symboles et d'indicateurs, doivent être modifiées conformément à tous les changements ci-dessus. À cette fin, nous devons ajouter le numéro de symbole comme premier paramètre et remplacer toutes les variables et les tableaux à l'intérieur de la fonction par les nouveaux tableaux décrits ci-dessus.

A titre d'illustration, les codes révisés des fonctions CheckNewBar(), TradingBlock() et OpenPosition() sont fournis ci-dessous.

Le code de la fonction CheckNewBar() :

//+------------------------------------------------------------------+
//| Checking for the new bar                                         |
//+------------------------------------------------------------------+
bool CheckNewBar(int number_symbol)
  {
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(Symbols[number_symbol],Period(),0,1,lastbar_time[number_symbol].time)==-1)
      Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError()));
//--- If this is a first function call
   if(new_bar[number_symbol]==NULL)
     {
      //--- Set the time
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      Print(__FUNCTION__,": Initialization ["+Symbols[number_symbol]+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(lastbar_time[number_symbol].time[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false);
     }
//--- If the time is different
   if(new_bar[number_symbol]!=lastbar_time[number_symbol].time[0])
     {
      //--- Set the time and exit
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      return(true);
     }
//--- If we have reached this line, then the bar is not new, so return false
   return(false);
  }

Le code de la fonction TradingBlock() :

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Variable for getting a signal
   string               comment="hello :)";                 // Position comment
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Volume for position calculation in case of position reversal
   double               position_open_price=0.0;            // Position opening price
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Order type for opening a position
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Opposite position type
//--- Find out if there is a position
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Get the signal
   signal=GetTradingSignal(symbol_number);
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Get symbol properties
   GetSymbolProperties(symbol_number,S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=symb.ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Assign values to variables for a SELL
      case ORDER_TYPE_SELL :
         position_open_price=symb.bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Get the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(symbol_number,order_type);
   tp=CalculateTakeProfit(symbol_number,order_type);
//--- If there is no position
   if(!pos.exists)
     {
      //--- Adjust the volume
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Open a position
      OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
     }
//--- If the position exists
   else
     {
      //--- Get the position type
      GetPositionProperties(symbol_number,P_TYPE);
      //--- If the position is opposite to the signal and the position reversal is enabled
      if(pos.type==opposite_position_type && Reverse[symbol_number])
        {
         //--- Get the position volume
         GetPositionProperties(symbol_number,P_VOLUME);
         //--- Adjust the volume
         lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]);
         //--- Reverse the position
         OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
         return;
        }
      //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume
      if(!(pos.type==opposite_position_type) && VolumeIncrease[symbol_number]>0)
        {
         //--- Get the Stop Loss of the current position
         GetPositionProperties(symbol_number,P_SL);
         //--- Get the Take Profit of the current position
         GetPositionProperties(symbol_number,P_TP);
         //--- Adjust the volume
         lot=CalculateLot(symbol_number,VolumeIncrease[symbol_number]);
         //--- Increase the position volume
         OpenPosition(symbol_number,lot,order_type,position_open_price,pos.sl,pos.tp,comment);
         return;
        }
     }
  }

Le code de la fonction OpenPosition() :

//+------------------------------------------------------------------+
//| Opening a position                                               |
//+------------------------------------------------------------------+
void OpenPosition(int symbol_number,
                  double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- Set the magic number in the trading structure
   trade.SetExpertMagicNumber(MagicNumber);
//--- Set the slippage in points
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Instant Execution and Market Execution mode
//    *** Starting with build 803, Stop Loss and Take Profit ***
//    *** can be set upon opening a position in the SYMBOL_TRADE_EXECUTION_MARKET mode ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT ||
      symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- If the position failed to open, print the relevant message
      if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment))
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
  }

Ainsi, chaque fonction reçoit maintenant le numéro de symbole (symbol_number). Veuillez également noter le changement introduit dans la build 803 :

À partir de la version 803, Stop Loss et Take Profit peuvent être définis lors de l'ouverture d'une position en mode SYMBOL_TRADE_EXECUTION_MARKET.

Les codes révisés des autres fonctions se trouvent dans les fichiers joints. Il ne nous reste plus qu'à optimiser les paramètres et à effectuer des tests.


Optimisation des paramètres et test d’Expert Advisor

Nous allons d'abord optimiser les paramètres du premier symbole puis du second. Commençons par l'EURUSD.

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

Fig. 1. Paramètres du testeur de stratégie

Fig. 1. Paramètres du testeur de stratégie.

Les paramètres de l'Expert Advisor doivent être effectués comme indiqué ci-dessous (pour plus de commodité, les fichiers .set contenant les paramètres de chaque symbole sont joints à l'article). Pour exclure un certain symbole de l'optimisation, vous devez simplement laisser le champ de paramètre de nom de symbole vide. L'optimisation des paramètres effectuée pour chaque symbole séparément accélérera également le processus d'optimisation.

Fig. 2. Paramètres de l'Expert Advisor pour l'optimisation des paramètres : EURUSD

Fig. 2. Paramètres de l'Expert Advisor pour l'optimisation des paramètres : EURUSD.

L'optimisation prendra environ une heure sur un processeur dual-core. Les résultats du test de facteur de récupération maximal sont indiqués ci-dessous :

Fig. 3. Résultats du test du facteur de récupération maximum pour EURUSD

Fig. 3. Résultats du test du facteur de récupération maximum pour l'EURUSD.

Définissez maintenant NZDUSD comme deuxième symbole. Pour l'optimisation, laissez la ligne avec le nom du symbole pour le premier bloc de paramètres vide.

Alternativement, vous pouvez simplement ajouter un tiret à la fin du nom du symbole. L'Expert Advisor ne trouvera pas le symbole portant ce nom dans la liste des symboles et initialisera l'index du tableau sur une chaîne vide.

Les résultats pour le NZDUSD semblent être les suivants :

Fig. 4. Résultats du test de facteur de récupération maximum pour le NZDUSD

Fig. 4. Résultats du test de facteur de récupération maximum pour le NZDUSD.

Nous pouvons maintenant tester deux symboles ensemble. Dans les paramètres de Strategy Tester, vous pouvez définir n'importe quel symbole sur lequel l'Expert Advisor est lancé puisque les résultats seront identiques. Il peut même s'agir d'un symbole qui n'est pas impliqué dans le trading/test.

Voici les résultats pour deux symboles testés ensemble :

Fig. 5. Résultats du test pour deux symboles : EURUSD et NZDUSD

Fig. 5. Résultats du test pour deux symboles : EURUSD et NZDUSD.


Conclusion

C'est à peu près ça. Les codes sources sont joints ci-dessous et peuvent être téléchargés pour une étude plus détaillée de ce qui précède. Pour vous entraîner, essayez de sélectionner un ou plusieurs symboles ou de modifier les conditions d'ouverture de position à l'aide d'autres indicateurs.

Après avoir extrait les fichiers de l'archive, placez le dossier MultiSymbolExpert dans le répertoire MetaTrader 5\MQL5\Experts. De plus, l'indicateur EventsSpy.mq5 doit être placé dans le répertoire MetaTrader 5\MQL5\Indicators.

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/648

MQL5 Cookbook : Développement d’un Expert Advisor Multi-devises avec un nombre illimité de paramètres MQL5 Cookbook : Développement d’un Expert Advisor Multi-devises avec un nombre illimité 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. De cette façon, nous pourrons contourner la restriction du terminal sur le nombre de paramètres d'entrée d'un Expert Advisor.
MQL5 Cookbook : Développement d'un cadre pour un système de trading basé sur la stratégie du triple écran MQL5 Cookbook : Développement d'un cadre pour un système de trading basé sur la stratégie du triple écran
Dans cet article, nous allons développer un cadre pour un système de trading basé sur la stratégie Triple Screen dans MQL5. L’Expert Advisor ne sera pas développé à partir de zéro. Au lieu de cela, nous allons simplement modifier le programme de l’article précédent « MQL5 Cookbook : Utilisation des indicateurs pour définir les conditions de trading dans l’Expert Advisors" qui répond déjà largement à notre objectif. Ainsi, l’article montrera également comment vous pouvez facilement modifier des modèles de programmes prêts à l’emploi.
MQL5 Cookbook : Écriture de l'historique des transactions dans un fichier et création des graphiques d’équilibre pour chaque symbole dans Excel MQL5 Cookbook : Écriture de l'historique des transactions dans un fichier et création des graphiques d’équilibre pour chaque symbole dans Excel
Lorsque je communiquais dans divers forums, j'utilisais souvent des exemples de mes résultats de test affichés sous forme de captures d'écran de graphiques Microsoft Excel. On m'a souvent demandé d'expliquer comment de tels graphiques peuvent être créés. Enfin, j'ai maintenant un peu de temps pour tout expliquer dans cet article.
L'indicateur ZigZag : Approche novatrice et nouvelles solutions L'indicateur ZigZag : Approche novatrice et nouvelles solutions
L'article examine la possibilité de créer un indicateur ZigZag avancé. L'idée d'identifier les nœuds est basée sur l'utilisation de l'indicateur Enveloppes. Nous supposons que nous pouvons trouver une certaine combinaison de paramètres d'entrée pour une série d'enveloppes, où tous les nœuds ZigZag se trouvent dans les limites des bandes d'enveloppes. Par conséquent, nous pouvons essayer de prédire les coordonnées du nouveau nœud.