
MQL5 Cookbook : L'historique des transactions et la bibliothèque de fonctions pour obtenir les propriétés de position
Introduction
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.
Les systèmes de trading où les volumes de positions restent les mêmes tout au long de leur existence ne nécessitent pas vraiment l'utilisation des fonctions qui seront fournies dans cet article. Mais si vous envisagez de mettre en œuvre un système de gestion de l'argent et de contrôler la taille d'un lot de position dans votre stratégie de trading à un stade ultérieur, ces fonctions seront indispensables.
Avant de commencer, je voudrais faire une suggestion aux lecteurs qui ont suivi un lien vers cet article, tout en étant les premiers visiteurs de ce site Web, ou qui viennent de commencer à apprendre la langue MQL5, pour commencer par les articles précédents du " Série "MQL5 Cookbook".
Développement d’Expert Advisor
Pour pouvoir voir le fonctionnement des nouvelles fonctions dans l'Expert Advisor modifié dans l'article précédent intitulé "MQL5 Cookbook : Comment éviter les erreurs lors de la définition/modification des niveaux de trading", nous ajouterons la possibilité d'augmenter le volume de la position si un signal d'ouverture se produit à nouveau, alors que la position est déjà là.
Il peut y avoir plusieurs transactions dans l'historique des positions, et s'il y a eu des changements dans le volume de la position au cours du trading, il doit également y avoir eu des changements dans le prix de la position actuelle. Pour connaître le prix du premier point d'entrée, nous devons accéder à l'historique des transactions par rapport à cette position spécifique. La figure ci-dessous est une démonstration du cas où une position n'a qu'un seul deal (point d'entrée) :
Fig. 1. Première transaction dans la position.
La figure suivante montre un changement dans le prix de la position après la deuxième transaction :
Fig. 2. Deuxième affaire dans la position.
Comme démontré dans les articles précédents, les identifiants standards permettent d'obtenir uniquement le prix de la position actuelle (POSITION_PRICE_OPEN) et le prix actuel d'un symbole (POSITION_PRICE_CURRENT) pour lequel une position est ouverte.
Cependant, dans certains systèmes de trading, nous avons besoin de connaître la distance parcourue par le prix depuis le premier point d'entrée, ainsi que le prix de la dernière transaction. Toutes ces informations sont disponibles dans l'historique des transactions/ordres du compte. Ci-dessous la liste des deals associés à la figure précédente :
Fig. 3. L'historique des transactions dans le compte.
Je crois que la situation est maintenant claire et tous les objectifs sont fixés. Continuons à modifier l'Expert Advisor présenté dans les articles précédents. Tout d'abord, nous allons ajouter de nouveaux identifiants numérotés 0, 6, 9, 12 et 16 à l'énumération des propriétés de position :
//--- Enumeration of position properties enum ENUM_POSITION_PROPERTIES { P_TOTAL_DEALS = 0, P_SYMBOL = 1, P_MAGIC = 2, P_COMMENT = 3, P_SWAP = 4, P_COMMISSION = 5, P_PRICE_FIRST_DEAL= 6, P_PRICE_OPEN = 7, P_PRICE_CURRENT = 8, P_PRICE_LAST_DEAL = 9, P_PROFIT = 10, P_VOLUME = 11, P_INITIAL_VOLUME = 12, P_SL = 13, P_TP = 14, P_TIME = 15, P_DURATION = 16, P_ID = 17, P_TYPE = 18, P_ALL = 19 };
Les commentaires pour chacune des propriétés seront donnés dans une structure qui sera revue un peu ci-dessous.
Augmentons le nombre des paramètres externes. Maintenant, nous allons pouvoir préciser :
- MagicNumber - un identifiant unique de l'Expert Advisor (numéro magique) ;
- Déviation - dérapage ;
- VolumeIncrease - une valeur par laquelle le volume de position sera augmenté ;
- InfoPanel - un paramètre qui vous permet d'activer/désactiver l'affichage du panneau d'informations.
Voici comment il est mis en œuvre :
//--- External parameters of the Expert Advisor sinput long MagicNumber=777; // Magic number sinput int Deviation=10; // Slippage input int NumberOfBars=2; // Number of Bullish/Bearish bars for a Buy/Sell input double Lot=0.1; // Lot input double VolumeIncrease=0.1; // Position volume increase input double StopLoss=50; // Stop Loss input double TakeProfit=100; // Take Profit input double TrailingStop=10; // Trailing Stop input bool Reverse=true; // Position reversal sinput bool ShowInfoPanel=true; // Display of the info panel
Veuillez noter les paramètres dont les sinput modifier sont définies. Ce modificateur vous permet de désactiver l'optimisation dans le Strategy Tester. En fait, lorsque vous développez un programme pour votre propre usage, vous avez une parfaite compréhension des paramètres qui affecteront le résultat final, il vous suffit donc de les décocher de l'optimisation. Mais lorsqu'il s'agit d'un très grand nombre de paramètres, cette méthode permet de les séparer visuellement des autres au fur et à mesure qu'ils sont grisés :
Fig. 4. Les paramètres désactivés pour l'optimisation sont grisés.
Remplaçons maintenant les variables globales qui stockaient les valeurs de propriété de position et de symbole par des structures de données (struct) :
//--- Position properties struct position_properties { uint total_deals; // Number of deals bool exists; // Flag of presence/absence of an open position string symbol; // Symbol long magic; // Magic number string comment; // Comment double swap; // Swap double commission; // Commission double first_deal_price; // Price of the first deal in the position double price; // Current position price double current_price; // Current price of the position symbol double last_deal_price; // Price of the last deal in the position double profit; // Profit/Loss of the position double volume; // Current position volume double initial_volume; // Initial position volume double sl; // Stop Loss of the position double tp; // Take Profit of the position datetime time; // Position opening time ulong duration; // Position duration in seconds long id; // Position identifier ENUM_POSITION_TYPE type; // Position type };
//--- Symbol properties struct symbol_properties { int digits; // Number of decimal places in the price int spread; // Spread in points int stops_level; // Stops level double point; // Point value double ask; // Ask price double bid; // Bid price double volume_min; // Minimum volume for a deal double volume_max; // Maximum volume for a deal double volume_limit; // Maximum permissible volume for a position and orders in one direction double volume_step; // Minimum volume change step for a deal double offset; // Offset from the maximum possible price for a transaction double up_level; // Upper Stop level price double down_level; // Lower Stop level price }
Maintenant, pour accéder à un certain élément de la structure, nous devons créer une variable de ce type de structure. La procédure est similaire à la création d'un objet pour une classe de commerce qui a été pris en compte dans l'article intitulé "MQL5 Cookbook : Analyse des propriétés de position dans le testeur de stratégie MetaTrader 5".
//--- variables for position and symbol properties
position_properties pos;
symbol_properties symb;
Vous pouvez accéder aux éléments de la même manière que pour les méthodes de classe. En d'autres termes, il suffit de mettre un point après le nom d'une variable de structure pour afficher la liste des éléments contenus dans cette structure spécifique. C'est très pratique. Dans le cas où des commentaires sur une seule ligne sont fournis pour les champs de la structure (comme dans notre exemple), ils seront affichés dans une info-bulle à droite.
Fig. 5. Liste des champs de structure.
Un autre point important. En modifiant l'Expert Advisor, nous avons changé pratiquement toutes ses variables globales utilisées dans de nombreuses fonctions, nous devons donc maintenant les remplacer par les champs de structure correspondants pour les propriétés de symbole et de position. Par exemple, la variable globale pos_open qui servait à stocker le drapeau de présence/absence d'un poste ouvert a été remplacée par le champ existe du type de structure position_properties. Par conséquent, partout où la variable pos_open a été utilisée, elle doit être remplacée par pos.exists.
Ce sera un processus long et épuisant si vous le faites manuellement. Il serait donc préférable d'automatiser la solution à cette tâche en utilisant les fonctionnalités de MetaEditor : Rechercher et remplacer -> Remplacer dans le menu Edit ou la combinaison de touches Ctrl+H :
Fig. 6. Recherche et remplacement du texte.
Nous devons rechercher et remplacer toutes les variables globales pour les propriétés de position et de symbole pour poursuivre un test, après avoir compilé le fichier. Si aucune erreur n'est détectée, cela signifie que nous avons tout fait correctement. Je ne fournirai pas le code ici pour ne pas rendre l'article inutilement long. En outre, un code source prêt à l'emploi est disponible à la fin de l'article en téléchargement.
Maintenant que nous avons réglé le problème avec les variables, passons à la modification des fonctions existantes et à la création de nouvelles.
Dans les paramètres externes, vous pouvez maintenant définir le nombre magique et le glissement en points. Nous devons donc également apporter les modifications pertinentes dans le code de l'Expert Advisor. Nous allons créer une fonction auxiliaire définie par l'utilisateur OpenPosition(), où ces propriétés seront définies à l'aide des fonctions de la classe CTrade avant d'envoyer un ordre d'ouverture de position.
//+------------------------------------------------------------------+ //| Opening a position | //+------------------------------------------------------------------+ void OpenPosition(double lot, ENUM_ORDER_TYPE order_type, double price, double sl, double tp, string comment) { trade.SetExpertMagicNumber(MagicNumber); // Set the magic number in the trading structure trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); // Set the slippage in points //--- If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment)) { Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } }
Nous n'avons qu'à apporter quelques petites modifications au code de la fonction de trading principale de l'Expert Advisor - TradingBlock(). Voici la partie du code de fonction qui a subi des modifications :
//--- If there is no position if(!pos.exists) { //--- Adjust the volume lot=CalculateLot(Lot); //--- Open a position OpenPosition(lot,order_type,position_open_price,sl,tp,comment); } //--- If there is a position else { //--- Get the position type GetPositionProperties(P_TYPE); //--- If the position is opposite to the signal and the position reversal is enabled if(pos.type==opposite_position_type && Reverse) { //--- Get the position volume GetPositionProperties(P_VOLUME); //--- Adjust the volume lot=pos.volume+CalculateLot(Lot); //--- Reverse the position OpenPosition(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>0) { //--- Get the Stop Loss of the current position GetPositionProperties(P_SL); //--- Get the Take Profit of the current position GetPositionProperties(P_TP); //--- Adjust the volume lot=CalculateLot(Increase); //--- Increase the position volume OpenPosition(lot,order_type,position_open_price,pos.sl,pos.tp,comment); return; }
Le code ci-dessus a été amélioré avec le bloc où la direction de la position actuelle est vérifiée par rapport à la direction du signal. Si leurs directions coïncident et que l'augmentation du volume de la position est activée dans les paramètres externes (la valeur du paramètre VolumeIncrease est supérieure à zéro), nous vérifions/ajustons un lot donné et envoyons l'ordre correspondant. Maintenant, tout ce que vous avez à faire pour envoyer un ordre pour ouvrir ou inverser une position ou pour augmenter le volume de la position est d'écrire une ligne de code.
Créons des fonctions pour obtenir des propriétés de position à partir de l'historique des transactions. Nous allons commencer par une fonction CurrentPositionTotalDeals() qui renvoie le nombre de transactions dans la position actuelle :
//+------------------------------------------------------------------+ //| Returning the number of deals in the current position | //+------------------------------------------------------------------+ uint CurrentPositionTotalDeals() { int total =0; // Total deals in the selected history list int count =0; // Counter of deals by the position symbol string deal_symbol =""; // symbol of the deal //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list for(int i=0; i<total; i++) { //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- If the symbol of the deal and the current symbol are the same, increase the counter if(deal_symbol==_Symbol) count++; } } //--- return(count); }
Le code ci-dessus est fourni avec des commentaires assez détaillés. Mais nous devrions dire quelques mots sur la façon dont l'historique est sélectionné. Dans notre cas, nous avons obtenu la liste à partir du point d'ouverture de la position actuelle déterminée par l'heure d'ouverture jusqu'au moment actuel à l'aide de la fonction HistorySelect(). Une fois l'historique sélectionné, nous pouvons connaître le nombre de transactions dans la liste à l'aide de la fonction HistoryDealsTotal(). Le reste devrait être clair dans les commentaires.
L'historique d'une position particulière peut également être sélectionné par son identifiant à l'aide de la fonction HistorySelectByPosition(). Ici, vous devez considérer que l'identifiant de position reste le même lorsque la position est inversée, comme cela arrive parfois dans notre Expert Advisor. Cependant, le temps d'ouverture de la position change lors de l'inversion, donc cette variante est plus facile à mettre en œuvre. Mais si vous devez gérer l'historique des transactions qui ne s'applique pas uniquement à la position actuellement ouverte, vous devez utiliser des identifiants. Nous reviendrons sur l'historique des transactions dans les prochains articles.
Continuons en créant une fonction CurrentPositionFirstDealPrice() qui renvoie le prix de la première transaction de la position, c'est-à-dire le prix de la transaction à laquelle la position a été ouverte.
//+------------------------------------------------------------------+ //| Returning the price of the first deal in the current position | //+------------------------------------------------------------------+ double CurrentPositionFirstDealPrice() { int total =0; // Total deals in the selected history list string deal_symbol =""; // symbol of the deal double deal_price =0.0; // Price of the deal datetime deal_time =NULL; // Time of the deal //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list for(int i=0; i<total; i++) { //--- Get the price of the deal deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- Get the time of the deal deal_time=(datetime)HistoryDealGetInteger(HistoryDealGetTicket(i),DEAL_TIME); //--- If the time of the deal equals the position opening time, // and if the symbol of the deal and the current symbol are the same, exit the loop if(deal_time==pos.time && deal_symbol==_Symbol) break; } } //--- return(deal_price); }
Le principe est ici le même que dans la fonction précédente. Nous obtenons l'historique à partir du point d'ouverture de la position, puis vérifions l'heure de la transaction et l'heure d'ouverture de la position à chaque itération. En plus du prix de la transaction, nous obtenons le nom du symbole et l'heure de la transaction. La toute première transaction est identifiée lorsque l'heure de la transaction coïncide avec l'heure d'ouverture de la position. Comme son prix a déjà été affecté à la variable pertinente, nous n'avons qu'à retourner la valeur.
Continuons. Parfois, vous devrez peut-être obtenir le prix de la dernière transaction dans la position actuelle. Pour cela, nous allons créer une fonction CurrentPositionLastDealPrice() :
//+------------------------------------------------------------------+ //| Returning the price of the last deal in the current position | //+------------------------------------------------------------------+ double CurrentPositionLastDealPrice() { int total =0; // Total deals in the selected history list string deal_symbol =""; // Symbol of the deal double deal_price =0.0; // Price //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal for(int i=total-1; i>=0; i--) { //--- Get the price of the deal deal_price=HistoryDealGetDouble(HistoryDealGetTicket(i),DEAL_PRICE); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- If the symbol of the deal and the current symbol are the same, exit the loop if(deal_symbol==_Symbol) break; } } //--- return(deal_price); }
Cette fois, la boucle a commencé avec la dernière transaction de la liste et il arrive souvent que la transaction requise soit identifiée lors de la première itération de la boucle. Mais si vous tradez sur plusieurs symboles, la boucle se poursuivra jusqu'à ce que le symbole de la transaction corresponde au symbole actuel.
Le volume de la position actuelle peut être obtenu à l'aide des POSITION_VOLUME identificateur standard. Pour connaître le volume de la position initiale (le volume de la première donne), nous allons créer une fonction CurrentPositionInitialVolume() :
//+------------------------------------------------------------------+ //| Returning the initial volume of the current position | //+------------------------------------------------------------------+ double CurrentPositionInitialVolume() { int total =0; // Total deals in the selected history list ulong ticket =0; // Ticket of the deal ENUM_DEAL_ENTRY deal_entry =WRONG_VALUE; // Position modification method bool inout =false; // Flag of position reversal double sum_volume =0.0; // Counter of the aggregate volume of all deals, except for the first one double deal_volume =0.0; // Volume of the deal string deal_symbol =""; // Symbol of the deal datetime deal_time =NULL; // Deal execution time //--- If the position history is obtained if(HistorySelect(pos.time,TimeCurrent())) { //--- Get the number of deals in the obtained list total=HistoryDealsTotal(); //--- Iterate over all the deals in the obtained list from the last deal in the list to the first deal for(int i=total-1; i>=0; i--) { //--- If the order ticket by its position is obtained, then... if((ticket=HistoryDealGetTicket(i))>0) { //--- Get the volume of the deal deal_volume=HistoryDealGetDouble(ticket,DEAL_VOLUME); //--- Get the position modification method deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); //--- Get the deal execution time deal_time=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); //--- Get the symbol of the deal deal_symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); //--- When the deal execution time is less than or equal to the position opening time, exit the loop if(deal_time<=pos.time) break; //--- otherwise calculate the aggregate volume of deals by the position symbol, except for the first one if(deal_symbol==_Symbol) sum_volume+=deal_volume; } } } //--- If the position modification method is a reversal if(deal_entry==DEAL_ENTRY_INOUT) { //--- If the position volume has been increased/decreased // I.e. the number of deals is more than one if(fabs(sum_volume)>0) { //--- Current volume minus the volume of all deals except for the first one double result=pos.volume-sum_volume; //--- If the resulting value is greater than zero, return the result, otherwise return the current position volume deal_volume=result>0 ? result : pos.volume; } //--- If there are no more deals, other than the entry, if(sum_volume==0) deal_volume=pos.volume; // return the current position volume } //--- Return the initial position volume return(NormalizeDouble(deal_volume,2)); }
Cette fonction est sortie plus complexe que les précédentes. J'ai essayé de prendre en considération toutes les situations possibles pouvant entraîner une mauvaise valeur. Un test minutieux n'a révélé aucun problème. Les commentaires détaillés fournis dans le code devraient vous aider à comprendre.
Il sera également utile d'avoir une fonction qui retourne la durée de la position. Nous l'organiserons de manière à permettre à l'utilisateur de sélectionner le format approprié de la valeur renvoyée : secondes, minutes, heures ou jours. Pour cela, créons une autre énumération :
//--- Position duration enum ENUM_POSITION_DURATION { DAYS = 0, // Days HOURS = 1, // Hours MINUTES = 2, // Minutes SECONDS = 3 // Seconds };
Vous trouverez ci-dessous le code de la fonction CurrentPositionDuration() responsable de tous les calculs pertinents :
//+------------------------------------------------------------------+ //| Returning the duration of the current position | //+------------------------------------------------------------------+ ulong CurrentPositionDuration(ENUM_POSITION_DURATION mode) { ulong result=0; // End result ulong seconds=0; // Number of seconds //--- Calculate the position duration in seconds seconds=TimeCurrent()-pos.time; //--- switch(mode) { case DAYS : result=seconds/(60*60*24); break; // Calculate the number of days case HOURS : result=seconds/(60*60); break; // Calculate the number of hours case MINUTES : result=seconds/60; break; // Calculate the number of minutes case SECONDS : result=seconds; break; // No calculations (number of seconds) //--- default : Print(__FUNCTION__,"(): Unknown duration mode passed!"); return(0); } //--- Return result return(result); }
Créons une fonction CurrentPositionDurationToString() pour le panneau d'informations où les propriétés de position sont affichées. La fonction convertira la durée de la position en secondes dans un format facilement compréhensible par l'utilisateur. Le nombre de secondes sera passé à la fonction, et la fonction à son tour renverra une chaîne de caractères contenant la durée de la position en jours, heures, minutes et secondes :
//+------------------------------------------------------------------+ //| Converting the position duration to a string | //+------------------------------------------------------------------+ string CurrentPositionDurationToString(ulong time) { //--- A dash if there is no position string result="-"; //--- If the position exists if(pos.exists) { //--- Variables for calculation results ulong days=0; ulong hours=0; ulong minutes=0; ulong seconds=0; //--- seconds=time%60; time/=60; //--- minutes=time%60; time/=60; //--- hours=time%24; time/=24; //--- days=time; //--- Generate a string in the specified format DD:HH:MM:SS result=StringFormat("%02u d: %02u h : %02u m : %02u s",days,hours,minutes,seconds); } //--- Return result return(result); }
Tout est réglé et prêt maintenant. Je ne vais pas fournir les codes de fonction GetPositionProperties() et GetPropertyValue() qui doivent être modifiés conformément à tous les changements ci-dessus. Si vous avez lu tous les articles précédents de la série, vous ne devriez pas avoir de difficulté à le faire vous-même. Quoi qu'il en soit, le fichier de code source est joint à l'article.
Par conséquent, le panneau d'informations devrait apparaître comme indiqué ci-dessous :
Fig. 7. Démonstration de toutes les propriétés de position sur le panneau d'information.
Ainsi, nous avons maintenant la bibliothèque de fonctions pour obtenir les propriétés de position et nous continuerons probablement à travailler dessus dans les futurs articles, au fur et à mesure des besoins.
Optimisation des paramètres et test Expert Advisor
A titre expérimental, essayons d'optimiser les paramètres de l'Expert Advisor. Bien que ce que nous avons actuellement ne puisse pas encore être qualifié de système de trading complet, le résultat que nous obtiendrons nous ouvrira les yeux sur certaines choses et améliorera notre expérience en tant que développeurs de systèmes de trading.
Nous allons définir les paramètres du testeur de stratégie comme indiqué ci-dessous :
Fig. 8. Paramètres du testeur de stratégie pour l'optimisation des paramètres.
Les réglages des paramètres externes de l'Expert Advisor doivent être les suivants :
Fig. 9. Paramètres de l'Expert Advisor pour l'optimisation.
Suite à l'optimisation, nous trions les résultats obtenus par le facteur de récupération maximum :
Fig. 10. Résultats triés par facteur de récupération maximum.
Testons maintenant le tout premier ensemble de paramètres, la valeur du facteur de récupération étant égale à 4,07. Même compte tenu du fait que l'optimisation a été effectuée pour l'EURUSD, nous pouvons voir les résultats positifs pour de nombreux symboles :
Résultats pour l’EURUSD :
Fig. 11. Résultats pour l’EURUSD.
Résultats pour l’AUDUSD:
Fig. 12. Résultats pour l’AUDUSD.
Résultats pour le NZDUSD :
Fig. 13. Résultats pour le NZDUSD.
Conclusion
Pratiquement n'importe quelle idée peut être développée et améliorée. Chaque système commercial doit être soigneusement testé avant d'être rejeté comme défectueux. Dans les prochains articles, nous examinerons divers mécanismes et schémas qui peuvent jouer un rôle très positif dans la personnalisation et l'adaptation de presque tous les systèmes de trading.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/644





- 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