MQL5 Cookbook : Analyse des propriétés de position dans le testeur de stratégie MetaTrader 5
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.
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.
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
- 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