MQL5 Cookbook : Expert Advisor multi-devises - Approche simple, nette et rapide
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 :
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.
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.
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 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.
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.
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.
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
- 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