English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
MQL5 Cookbook : Analyse des propriétés de position dans le testeur de stratégie MetaTrader 5

MQL5 Cookbook : Analyse des propriétés de position dans le testeur de stratégie MetaTrader 5

MetaTrader 5Exemples | 13 janvier 2022, 09:42
180 0
Anatoli Kazharski
Anatoli Kazharski

Introduction

Dans cet article, nous allons modifier l'Expert Advisor créé dans l'article précédent "MQL5 Cookbook : Propriétés de la position dans le panneau d'informations personnalisé" et résolvez les problèmes suivants :

  • La recherche de nouveaux événements de barre sur le symbole actuel ;
  • L’obtention des données à partir des barres ;
  • L’inclusion d’une classe de trade de la bibliothèque standard dans un fichier ;
  • La création d'une fonction pour rechercher des signaux de trading ;
  • La création d'une fonction d'exécution des opérations de trading ;
  • La détermination des événements de trade dans la fonction OnTrade().

En fait, chacune des questions ci-dessus peut mériter un article à part, mais à mon avis une telle approche ne ferait que compliquer l'étude de la langue.

Je vais utiliser des exemples très simples pour vous montrer comment ces fonctionnalités peuvent être implémentées. En d'autres termes, la mise en œuvre de chacune des tâches énumérées ci-dessus s'intégrera littéralement dans une fonction simple et directe. Lors du développement d'une certaine idée dans les futurs articles de la série, nous allons progressivement rendre ces fonctions plus complexes, selon les besoins et dans la mesure requise par la tâche à accomplir.

Tout d'abord, copions l'Expert Advisor du dans l'article précédent car nous aurons besoin de toutes ses fonctions.


Développer un Expert Advisor

Nous commençons par inclure la classe CTrade de la bibliothèque standard dans notre fichier. Cette classe possède toutes les fonctions nécessaires à l'exécution des opérations de trading. Pour commencer, nous pouvons facilement les utiliser, sans même regarder à l'intérieur, ce que nous allons faire.

Pour inclure la classe, nous devons écrire ce qui suit :

//--- Include a class of the Standard Library
#include <Trade/Trade.mqh>

Vous pouvez placer ce code au tout début du fichier pour pouvoir le retrouver facilement par la suite, par exemple après la directive #define. La commande #include indique que le fichier Trade.mqh doit être extrait du <MetaTrader 5 terminal directory>\MQL5\Include\Trade\. La même approche peut être utilisée pour inclure tout autre fichier contenant des fonctions. Ceci est particulièrement utile lorsque la quantité de code de projet augmente et qu'il devient difficile de s'y retrouver.

Maintenant, nous devons créer une instance de la classe afin d'avoir accès à toutes ses fonctions. Cela peut être fait en écrivant le nom de l'instance après le nom de la classe :

//--- Load the class
CTrade trade;

Dans cette version de l'Expert Advisor, nous allons utiliser une seule fonction de trade parmi toutes les fonctions disponibles dans la classe CTrade. C'est la fonction PositionOpen() qui permet d'ouvrir une position. Il peut également être utilisé pour l'inversion d'une position ouverte existante. La façon dont cette fonction peut être appelée depuis la classe sera expliquée plus loin dans cet article lors de la création d'une fonction responsable de l'exécution des opérations de trading.

De plus, nous ajoutons deux tableaux dynamiques à portée globale. Ces tableaux prendront des valeurs de barre.

//--- Price data arrays
double               close_price[]; // Close (closing prices of the bar)
double               open_price[];  // Open (opening prices of the bar)

Ensuite, créez une fonction CheckNewBar() à l'aide de laquelle le programme vérifiera les nouveaux événements de barre car les opérations de trading ne seront exécutées que sur les barres terminées.

Vous trouverez ci-dessous le code de la fonction CheckNewBar() avec des commentaires détaillés :

//+------------------------------------------------------------------+
//| CHECKING FOR THE NEW BAR                                         |
//+------------------------------------------------------------------+
bool CheckNewBar()
  {
//--- Variable for storing the opening time of the current bar
   static datetime new_bar=NULL;
//--- Array for getting the opening time of the current bar
   static datetime time_last_bar[1]={0};
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(_Symbol,Period(),0,1,time_last_bar)==-1)
     { Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError())+""); }
//--- If this is a first function call
   if(new_bar==NULL)
     {
      // Set the time
      new_bar=time_last_bar[0];
      Print(__FUNCTION__,": Initialization ["+_Symbol+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(time_last_bar[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false); // Return false and exit 
     }
//--- If the time is different
   if(new_bar!=time_last_bar[0])
     {
      new_bar=time_last_bar[0]; // Set the time and exit 
      return(true); // Store the time and return true
     }
//--- If we have reached this line, then the bar is not new, return false
   return(false);
  }

Comme vous pouvez le voir dans le code ci-dessus, la fonction CheckNewBar() renvoie true si la barre est nouvelle ou false s'il n'y a pas encore de nouvelle barre. De cette façon, vous pouvez contrôler la situation lors du trading/test, en n'exécutant que des opérations de trading sur les barres terminées.

Au tout début de la fonction, nous déclarons une variable statique et un tableau statique de type datetime. Les variables locales statiques conservent leurs valeurs même après la sortie de la fonction. À chaque appel de fonction suivant, ces variables locales contiendront les valeurs qu'elles ont prises lors de l'appel précédent de la fonction.

De plus, veuillez noter la fonction CopyTime(). Cela nous aide à obtenir l'heure de la dernière barre du tableau time_last_bar. Assurez-vous de vérifier la syntaxe de la fonction dans MQL5 Reference.

On peut également remarquer la fonction TimeframeToString() définie par l'utilisateur qui n'a jamais été mentionnée dans cette série d'articles auparavant. Il convertit les valeurs de période en une chaîne claire pour l'utilisateur :

string TimeframeToString(ENUM_TIMEFRAMES timeframe)
  {
   string str="";
   //--- If the passed value is incorrect, take the time frame of the current chart
   if(timeframe==WRONG_VALUE || timeframe == NULL)
      timeframe = Period();
   switch(timeframe)
     {
      case PERIOD_M1  : str="M1";  break;
      case PERIOD_M2  : str="M2";  break;
      case PERIOD_M3  : str="M3";  break;
      case PERIOD_M4  : str="M4";  break;
      case PERIOD_M5  : str="M5";  break;
      case PERIOD_M6  : str="M6";  break;
      case PERIOD_M10 : str="M10"; break;
      case PERIOD_M12 : str="M12"; break;
      case PERIOD_M15 : str="M15"; break;
      case PERIOD_M20 : str="M20"; break;
      case PERIOD_M30 : str="M30"; break;
      case PERIOD_H1  : str="H1";  break;
      case PERIOD_H2  : str="H2";  break;
      case PERIOD_H3  : str="H3";  break;
      case PERIOD_H4  : str="H4";  break;
      case PERIOD_H6  : str="H6";  break;
      case PERIOD_H8  : str="H8";  break;
      case PERIOD_H12 : str="H12"; break;
      case PERIOD_D1  : str="D1";  break;
      case PERIOD_W1  : str="W1";  break;
      case PERIOD_MN1 : str="MN1"; break;
     }
//---
   return(str);
  }

La façon dont la fonction CheckNewBar() est utilisée sera montrée plus loin dans l'article lorsque nous aurons toutes les autres fonctions nécessaires prêtes. Regardons maintenant la fonction GetBarsData() qui prend les valeurs du nombre de barres demandé.

//+------------------------------------------------------------------+
//| GETTING BAR VALUES                                               |
//+------------------------------------------------------------------+
void GetBarsData()
  {
//--- Number of bars for getting their data in an array
   int amount=2;
//--- Reverse the time series ... 3 2 1 0
   ArraySetAsSeries(close_price,true);
   ArraySetAsSeries(open_price,true);
//--- Get the closing price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyClose(_Symbol,Period(),0,amount,close_price)<amount)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the opening price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyOpen(_Symbol,Period(),0,amount,open_price)<amount)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
  }

Regardons de plus près le code ci-dessus. Tout d'abord, dans la variable amount, nous spécifions le nombre de barres dont nous devons obtenir les données. Ensuite, nous définissons l'ordre d'indexation du tableau de sorte que la valeur de la dernière barre (courante) se trouve dans l'index zéro du tableau, à l'aide de la fonction ArraySetAsSeries() . Par exemple, si vous souhaitez utiliser la valeur de la dernière barre dans vos calculs, elle peut être écrite comme suit, si cela est illustré par le prix d'ouverture : open_price[0]. La notation de l'avant-dernière barre sera également : open_price[1].

Le mécanisme d'obtention des cours de clôture et d'ouverture est similaire à celui de la fonction CheckNewBar() où nous devions obtenir l'heure de la dernière barre. C'est juste que dans ce cas nous utilisons les fonctions CopyClose() et CopyOpen(). De même, CopyHigh() et CopyLow() sont utilisés pour obtenir respectivement les prix des barres haut et bas.

Passons à autre chose et considérons un exemple très simple montrant comment déterminer les signaux d'ouverture/inversion d'une position. Les tableaux de prix stockent les données de deux barres (la barre actuelle et la précédente terminée). Nous utiliserons les données de la barre complétée.

  • Un signal d'achat se produit lorsque le cours de clôture est supérieur au cours d'ouverture (barre haussière) ;
  • Un signal de vente se produit lorsque le cours de clôture est inférieur au cours d'ouverture (barre baissière).

Le code pour la mise en œuvre de ces conditions simples est fourni ci-dessous :

//+------------------------------------------------------------------+
//| DETERMINING TRADING SIGNALS                                      |
//+------------------------------------------------------------------+
int GetTradingSignal()
  {
//--- A Buy signal (0) :
   if(close_price[1]>open_price[1])
      return(0);
//--- A Sell signal (1) :
   if(close_price[1]<open_price[1])
      return(1);
//--- No signal (3):
   return(3);
  }

Comme vous pouvez le voir, c'est très simple. On peut facilement comprendre comment gérer des conditions plus complexes de la même manière. La fonction renvoie zéro si une barre complétée est en haut ou un si une barre complétée est en bas. Si pour une raison quelconque il n'y a pas de signal, la fonction renverra 3.

Il ne nous reste plus qu'à créer une fonction TradingBlock() pour la mise en œuvre des activités de trading. Ci-dessous le code de la fonction avec des commentaires détaillés :

//+------------------------------------------------------------------+
//| TRADING BLOCK                                                    |
//+------------------------------------------------------------------+
void TradingBlock()
  {
   int               signal=-1;           // Variable for getting a signal
   string            comment="hello :)";  // Position comment
   double            start_lot=0.1;       // Initial volume of a position
   double            lot=0.0;             // Volume for position calculation in case of reverse position
   double            ask=0.0;             // Ask price
   double            bid=0.0;             // Bid price
//--- Get a signal
   signal=GetTradingSignal();
//--- Find out if there is a position
   pos_open=PositionSelect(_Symbol);
//--- If it is a Buy signal
   if(signal==0)
     {
      //--- Get the Ask price
      ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      //--- If there is no position
      if(!pos_open)
        {
         //--- Open a position. If the position failed to open, print the relevant message
         if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,start_lot,ask,0,0,comment))
           { Print("Error opening a BUY position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
        }
      //--- If there is a position
      else
        {
         //--- Get the position type
         pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         //--- If it is a SELL position
         if(pos_type==POSITION_TYPE_SELL)
           {
            //--- Get the position volume
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            //--- Adjust the volume
            lot=NormalizeDouble(pos_volume+start_lot,2);
            //--- Open a position. If the position failed to open, print the relevant message
            if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,ask,0,0,comment))
              { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
           }
        }
      //---
      return;
     }
//--- If there is a Sell signal
   if(signal==1)
     {
      //-- Get the Bid price
      bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      //--- If there is no position
      if(!pos_open)
        {
         //--- Open a position. If the position failed to open, print the relevant message
         if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,start_lot,bid,0,0,comment))
           { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
        }
      //--- If there is a position
      else
        {
         //--- Get the position type
         pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         //--- If it is a BUY position
         if(pos_type==POSITION_TYPE_BUY)
           {
            //--- Get the position volume
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            //--- Adjust the volume
            lot=NormalizeDouble(pos_volume+start_lot,2);
            //--- Open a position. If the position failed to open, print the relevant message
            if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,bid,0,0,comment))
              { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
           }
        }
      //---
      return;
     }
  }

Je pense que tout doit être clair jusqu'au moment où une position est ouverte. Comme vous pouvez le voir dans le code ci-dessus, le pointeur (trade) est suivi d'un point qui à son tour est suivi de la méthode PositionOpen. C'est ainsi que vous pouvez appeler une certaine méthode à partir d'une classe. Après avoir mis un point, vous verrez une liste contenant toutes les méthodes de classe. Il vous suffit de sélectionner la méthode requise dans la liste :

Fig. 1. Appel d'une méthode de classe.

Fig. 1. Appel d'une méthode de classe.

Il y a deux blocs principaux dans la fonction TradingBlock() - acheter et vendre. Juste après avoir déterminé la direction du signal, nous obtenons le prix de demande dans le cas d'un signal d'achat et le prix de l'offre dans le cas d'un signal de vente.

Tous les prix/niveaux utilisés dans les ordres de trade doivent être normalisés à l'aide de la fonction NormalizeDouble(), sinon une tentative d'ouverture ou de modification d'une position entraînera une erreur. L'utilisation de cette fonction est également conseillée lors du calcul du lot. De plus, veuillez noter que les paramètres Stop Loss et Take Profit ont des valeurs nulles. Plus d'informations sur la définition des niveaux de trading seront fournies dans le prochain article de la série.

Alors maintenant que toutes les fonctions définies par l'utilisateur sont prêtes, nous pouvons les organiser dans le bon ordre :

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialize the new bar
   CheckNewBar();
//--- Get position properties and update the values on the panel
   GetPositionProperties();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Print the deinitialization reason to the journal
   Print(GetDeinitReasonText(reason));
//--- When deleting from the chart
   if(reason==REASON_REMOVE)
      //--- Delete all objects relating to the info panel from the chart
      DeleteInfoPanel();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- If the bar is not new, exit
   if(!CheckNewBar())
      return;
//--- If there is a new bar
   else
     {
      GetBarsData();  // Get bar data
      TradingBlock(); // Check the conditions and trade
     }
//--- Get the properties and update the values on the panel
   GetPositionProperties();
  }

Il ne reste qu'une chose à considérer - déterminer les événements de trade à l'aide de la fonction OnTrade(). Ici, nous ne l'aborderons que brièvement pour vous donner une idée générale. Dans notre cas, nous devons mettre en œuvre le scénario suivant : lors de l'ouverture/fermeture/modification d'une position manuellement, les valeurs de la liste des propriétés de la position sur le panneau d'information doivent être mises à jour dès que l'opération est terminée, plutôt qu'à la réception une nouvelle tique. Pour cela, il suffit d'ajouter le code suivant :

//+------------------------------------------------------------------+
//| TRADE EVENT                                                      |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Get position properties and update the values on the panel
   GetPositionProperties();
  }

En gros, tout est prêt et nous pouvons procéder aux tests. Le Strategy Tester vous permet d'exécuter rapidement un test en mode visualisation et de rechercher les éventuelles erreurs. L'utilisation du Strategy Tester peut également être considérée comme bénéfique du fait que vous pouvez continuer à développer votre programme même le week-end lorsque les marchés sont fermés.

Configurez le testeur de stratégie, activez le mode de visualisation et cliquez sur Démarrer. L'Expert Advisor commencera à trader dans le Strategy Tester et vous verrez une image similaire à celle ci-dessous :

Fig. 2. Mode de visualisation dans le testeur de stratégie MetaTrader 5.

Fig. 2. Mode de visualisation dans le testeur de stratégie MetaTrader 5.

Vous pouvez suspendre les tests en mode visualisation à tout moment et continuer les tests étape par étape en appuyant sur F12. Le pas sera égal à une barre si vous réglez le testeur de stratégie sur le modePrix d'ouverture uniquement, ou à un tick si vous avez sélectionné le mode Chaque tick. Vous pouvez également contrôler la vitesse de test.

Pour s'assurer que les valeurs du panneau d'informations sont mises à jour juste après l'ouverture/fermeture d'une position manuellement ou l'ajout/la modification des niveaux de Stop Loss/Take Profit, l'Expert Advisor doit être testé en mode temps réel. Pour ne pas attendre trop longtemps, exécutez simplement l'Expert Advisor sur une période de 1 minute afin que les opérations de trading soient exécutées toutes les minutes.

En plus de cela, j'ai ajouté un autre tableau pour les noms des propriétés de position sur le panneau d'informations :

// Array of position property names
string pos_prop_texts[INFOPANEL_SIZE]=
  {
   "Symbol :",
   "Magic Number :",
   "Comment :",
   "Swap :",
   "Commission :",
   "Open Price :",
   "Current Price :",
   "Profit :",
   "Volume :",
   "Stop Loss :",
   "Take Profit :",
   "Time :",
   "Identifier :",
   "Type :"
  };

Dans l'article précédent, j'ai mentionné que nous aurions besoin de ce tableau pour réduire le code de la fonction SetInfoPanel(). Vous pouvez maintenant voir comment cela peut être fait si vous ne l'avez pas encore mis en œuvre ou compris par vous-même. La nouvelle implémentation de la liste de création des objets relatifs aux propriétés de position est la suivante :

//--- List of the names of position properties and their values
   for(int i=0; i<INFOPANEL_SIZE; i++)
     {
      //--- Property name
      CreateLabel(0,0,pos_prop_names[i],pos_prop_texts[i],anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[i],2);
      //--- Property value
      CreateLabel(0,0,pos_prop_values[i],GetPropertyValue(i),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[i],2);
     }

Au début de la fonction SetInfoPanel(), vous pouvez remarquer la ligne suivante :

//--- Testing in the visualization mode
   if(MQL5InfoInteger(MQL5_VISUAL_MODE))
     {
      y_bg=2;
      y_property=16;
     }

Il indique au programme que les coordonnées Y des objets sur le panneau d'informations doivent être ajustées si le programme est actuellement testé en mode visualisation. Cela est dû au fait que lors des tests dans le mode de visualisation du Strategy Tester, le nom de l'Expert Advisor n'est pas affiché dans le coin supérieur droit du graphique comme en temps réel. Par conséquent, l'indentation inutile peut ainsi être supprimée.


Conclusion

Nous avons terminé pour le moment. Dans le prochain article, nous nous concentrerons sur la définition et la modification des niveaux de trading. Ci-dessous, vous pouvez télécharger le code source de l'Expert Advisor, PositionPropertiesTesterEN.mq5.

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

MQL5 Cookbook : Comment éviter les erreurs lors de la définition/modification des niveaux de trade MQL5 Cookbook : Comment éviter les erreurs lors de la définition/modification des niveaux de trade
Dans la continuité de notre travail sur l'Expert Advisor de l'article précédent de la série intitulée "MQL5 Cookbook : Analyse des propriétés des positions dans le testeur de stratégie MetaTrader 5", nous l'améliorerons avec de nombreuses fonctions utiles, ainsi que d'améliorer et d'optimiser celles existantes. L'Expert Advisor aura cette fois des paramètres externes qui peuvent être optimisés dans le testeur de stratégie MetaTrader 5 et ressemblera à certains égards à un simple système de trading.
MQL5 Cookbook : Propriétés de position dans le panneau d'informations personnalisé MQL5 Cookbook : Propriétés de position dans le panneau d'informations personnalisé
Cette fois, nous allons créer un simple Expert Advisor qui obtiendra les propriétés de position sur le symbole actuel et les affichera sur le panneau d'informations personnalisé pendant le trading manuel. Le panneau d'informations sera créé à l'aide d'objets graphiques et les informations affichées seront actualisées à chaque tick. Cela va être beaucoup plus pratique que d'avoir tout le temps à exécuter manuellement le script décrit dans l'article précédent de la série intitulé "MQL5 Cookbook : Getting Position Properties".
MQL5 Cookbook : L'historique des transactions et la bibliothèque de fonctions pour obtenir les propriétés de position MQL5 Cookbook : L'historique des transactions et la bibliothèque de fonctions pour obtenir les propriétés de position
Il est temps de résumer brièvement les informations fournies dans les articles précédents sur les propriétés de position. Dans cet article, nous allons créer quelques fonctions supplémentaires pour obtenir les propriétés qui ne peuvent être obtenues qu'après avoir accédé à l'historique des transactions. Nous nous familiariserons également avec les structures de données qui nous permettront d'accéder aux propriétés de position et de symbole de manière plus pratique.
MQL5 Cookbook : Obtention des propriétés de la position MQL5 Cookbook : Obtention des propriétés de la position
Dans cet article, nous allons créer un script qui récupère toutes les propriétés de position et les affiche à l'utilisateur dans une boîte de dialogue. Lors de l'exécution du script, vous pourrez sélectionner l'un des deux modes disponibles dans la liste déroulante des paramètres externes : soit pour afficher les propriétés de position uniquement sur le symbole actuel, soit pour afficher les propriétés de position sur tous les symboles.