English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
La Méthode Optimale pour le calcul du volume total de la position par Nombre Magique Indiqué

La Méthode Optimale pour le calcul du volume total de la position par Nombre Magique Indiqué

MetaTrader 5Systèmes de trading | 17 novembre 2021, 15:49
240 0
Dmitry Fedoseev
Dmitry Fedoseev

Introduction

Le terminal client MetaTrader 5 permet le travail en parallèle de plusieurs Expert Advisors avec un seul symbole. C'est simple - il suffit d'ouvrir plusieurs graphiques et d'y associer des Expert Advisors. Ce serait bien, si chaque Expert Advisor travaillait indépendamment des autres Expert Advisors travaillant avec le même symbole (il n'y a pas un tel problème pour les Expert Advisors travaillant avec des symboles différents).

Tout d'abord, il permettra à un Expert Advisor de trader en toute conformité avec ses performances de test et d'optimisation dans le Testeur de Stratégie. Les conditions d'ouverture de la position peuvent dépendre de la taille ou de l'absence de position déjà ouverte. Si plusieurs Expert Advisors travaillent avec le même symbole, ils s'affecteront les uns les autres.

La deuxième chose et probablement la plus importante est de permettre aux Expert Advisors d'utiliser différents systèmes de gestion de l'argent, en fonction des stratégies de trading implémentées dans Expert Advisors. Et enfin - la possibilité de surveiller les résultats de chaque Expert Advisor et de le désactiver si nécessaire.

1. Le Principe Général du Calcul du Volume de Position

Lorsque vous ouvrez une commande, vous pouvez la marquer d'un nombre magique en indiquant la valeur de la variable magique dans la structure MqlTradeRequest, transmise à la fonction OrderSend(). Lorsque la commande est exécutée, le deal est également marqué avec le numéro magique de la commande. De plus, en analysant les deals de l'historique, nous pouvons voir les deals ouverts par différents Expert Advisors.

La méthode de calcul de la position totale est assez simple : par exemple, si vous exécutez un deal d'achat avec un volume de 0,1, puis un autre achat 0,1 et vente 0,1, le volume de la position totale sera égal à 0,1+0,1-0,1 = +0.1. Nous ajoutons les volumes de deals d'achat et soustrayons les volumes de deals de vente et nous obtenons le volume de la position totale.

Il est important de commencer les calculs lorsque le volume de la position totale est égal à 0. Le premier et le plus évident de ces points est le moment de l'ouverture du compte. En d'autres termes, vous pouvez demander tout l'historique des deals du compte à l'aide de la fonction HistorySelect() avec le premier paramètre, égal à 0 (le moins de temps possible) et la valeur du deuxième paramètre TimeCurrent() (l'heure récente connue d'un serveur):

HistorySelect(0,TimeCurrent()); // load all history

Ensuite, parcourez tout l'historique du début à la fin, en ajoutant des volumes de deals d'achat et en soustrayant les volumes d'opérations de vente pour chaque opération avec le nombre magique indiqué. C'est aussi une solution, mais en pratique, l'historique des deals peut être assez long. Cela peut considérablement affecter la vitesse d'un Expert Advisor, notamment lors des tests et de l'optimisation, jusqu'à l'impossibilité de l'utilisation pratique d'un tel Expert Advisor. Nous devons trouver le tout dernier moment dans l'historique des deals, lorsque le volume total net de la position était égal à zéro.

Pour ce faire, nous devons d'abord parcourir tout l'historique et trouver le dernier point, lorsque le volume net total de la position était de zéro. En trouvant ce point, nous le sauvegardons dans une certaine variable (temps de position fixe). Plus tard, l'Expert Advisor parcourra l'historique des deals à partir du moment du point enregistré. La meilleure solution est de sauvegarder ce point dans une variable globale du terminal client au lieu d'une variable d'un Expert Advisor, car dans un tel cas il sera détruit lors du détachement de l'Expert Advisor.

Dans un tel cas, même lorsque l'Expert Advisor est lancé, vous devez charger un minimum nécessaire de l'historique, au lieu de l'historique complet des deals. Il existe de nombreux Expert Advisors qui peuvent trader sur le même symbole, nous partagerons donc cette variable globale (avec l'heure stockée du point récent du volume zéro) avec tous les Expert Advisors.

Sortons du sujet principal et considérons l'utilisation des variables globales du terminal client, qui permettent à plusieurs Expert Advisors de travailler avec le même symbole (peut-être avec des paramètres différents), et évitent la coïncidence des noms, créés par différentes instances d'Expert Conseillers.

2. Utilisation des Variables Globales du Terminal Client

Le langage MQL5 possède la fonction MQLInfoString(), qui permet d'obtenir différentes informations sur un programme mql5.

Pour obtenir des informations sur le nom du fichier, appelez cette fonction avec l'identifiant MQL_PROGRAM_NAME :

MQL5InfoString(MQL_PROGRAM_NAME); // Expert Advisor name

Par conséquent, nous commençons les noms des variables globales par le nom d'un Expert Advisor. Un Expert Advisor peut travailler avec plusieurs symboles ; cela signifie que nous devons ajouter le nom d'un symbole (Symbole). Les Expert Advisors peuvent travailler avec le même symbole, mais des délais différents (avec des paramètres différents), pour ces cas, nous devons utiliser le nombre magique. Par conséquent, nous ajoutons également le nombre magique.

Par exemple, si l'Expert Advisor dispose d’ un nombre magique, stocké dans la variable Magic_N, nous l'ajoutons au nom de la variable globale.

Les noms de toutes les variables globales se présenteront comme suit :

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor and symbol name 
                                                                            // and its magic number

où gvp (Global Variable Prefix) - est une variable de chaîne, déclarée dans la section des variables communes.

Je voudrais clarifier la terminologie pour éviter la confusion des variables globales, telles qu'elles sont utilisées en programmation (les variables globales sont visibles à l'intérieur de toutes les fonctions, les variables locales des fonctions ne sont visibles qu'à l'intérieur de la fonction).

Mais ici nous avons un cas différent - le terme "variables globales" désigne les variables globales du terminal client (variables spéciales, stockées dans un fichier, elles sont disponibles par les fonctions GlobalVariable...()). Lorsque nous parlons de variables globales (telles qu'elles sont utilisées en programmation), nous utiliserons le terme de "variables communes". Le terme de variables locales désignera les variables locales.

Les variables globales sont utiles, car elles enregistrent leurs valeurs après désinitialisation d'un Expert Advisor (redémarrage d'Expert Advisor, terminal client, ordinateur), mais en mode test il faut effacer toutes les variables (ou une passe précédente lors de l'optimisation). Les variables globales, utilisées dans les opérations réelles doivent être séparées des variables globales, créées lors du test, il est nécessaire de les supprimer après le test. Mais vous ne devez pas modifier ou supprimer les variables globales créées par Expert Advisor.

En utilisant la fonction AccountInfoInteger() et en l'appelant avec l'identifiant ACCOUNT_TRADE_MODE, vous pouvez distinguer le mode actuel : testeur, démo ou compte réel.

Ajoutons un préfixe aux variables globales : "d" - lorsque vous travaillez sur des comptes de démonstration, "r" - lorsque vous travaillez sur des comptes réels, "t" - lorsque vous travaillez dans le Testeur de Stratégie :

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor, symbol name
                                                                                  // and the Expert Advisor magic number
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO))
  {
   gvp=gvp+"d_"; // demo account
  }
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
  {
   gvp=gvp+"r_"; // real
  }
if(MQL5InfoInteger(MQL_TESTER))
  {
   gvp=gvp+"t_"; // testing
  } 

La fonction doit être appelée à partir de la fonction OnInit() de l'Expert Advisor.

Comme mentionné ci-dessus, les variables globales doivent être supprimées lors du test, en d'autres termes, nous devons ajouter la fonction qui supprime les variables globales dans la fonction OnDeinit() d'un Expert Advisor :

void fDeleteGV()
  {
   if(MQL5InfoInteger(MQL_TESTER)) // Testing mode
     {
      for(int i=GlobalVariablesTotal()-1;i>=0;i--) // Check all global variables (from the end, not from the begin)
        {
         if(StringFind(GlobalVariableName(i),gvp,0)==0) // search for the specified prefix
           {
            GlobalVariableDel(GlobalVariableName(i)); // Delete variable
           }
        }
     }
  }

À l'heure actuelle, il est impossible d'interrompre les tests dans MetaTrader 5, en d'autres termes, l'exécution de la fonction OnDeinit() n'est pas garantie, mais cela peut s’afficher à l'avenir. Nous ne savons pas si la fonction OnDeinit() sera exécutée après l'interruption du Testeur de Stratégie, nous supprimons donc les variables globales au début de l'Expert Advisor en cours d'exécution - à l'intérieur de la fonction OnInit().

Nous obtiendrons le code suivant des fonctions OnInit() et OnDeinit() :

int OnInit()
  {
   fCreateGVP(); // Creating a prefix for the names of global variables of the client terminal
   fDeleteGV();  // Delete global variables when working in Tester
   return(0);
  }

void OnDeinit(const int reason)
  {
   fDeleteGV();  // Delete global variables when working in tester
  }

Nous pouvons également simplifier l'utilisation des variables globales, en créant les fonctions avec des noms courts pour la création des variables globales (au lieu de GlobalVariableSet(gvp+...),.

La fonction pour définir la valeur de la variable globale :

void fGVS(string aName,double aValue)
  {
   GlobalVariableSet(gvp+aName,aValue);
  }

La fonction pour obtenir la valeur de la variable globale :

double fGVG(string aName)
  {
   return(GlobalVariableGet(gvp+aName));
  }

La fonction pour supprimer la variable globale :

void fGVD(string aName)
  {
   GlobalVariableDel(gvp+aName);
  }

Nous avons discuté des variables globales, mais ce n'est pas tout.

Nous devons assurer la possibilité de créer des variables globales pour un symbole, et fournir leur fonctionnement différent sur le compte et dans le Testeur de Stratégie. Les noms de ces variables globales ne doivent pas dépendre du nom et du nombre magique d'un Expert Advisor.

Déclarons une autre variable pour un préfixe de variable globale, nommée "Commom_gvp". Ensuite, en travaillant avec un compte, il aura la valeur "COMMUNE", et il aura la même valeur, qu'une variable gvp lorsque vous travaillez avec Testeur de Stratégie (pour supprimer la variable après ou avant le processus de test inversé de stratégie).

Enfin, la fonction de préparation des préfixes des variables globales présente la forme suivante :

void fCreateGVP()
  {
   gvp=MQL5InfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_";
   Commom_gvp="COMMOM_"; // Prefix for common variables for all Expert Advisors
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO)
     {
      gvp=gvp+"d_";
     }
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
     {
      gvp=gvp+"r_";
     }
   if(MQLInfoInteger(MQL_TESTER))
     {
      gvp=gvp+"t_";
      Commom_gvp=gvp; // To be used in tester, the variables with such a prefix 
                      // will be deleted after the testing
     }
  }

Quelqu'un peut penser que les préfixes des variables globales incluent des informations supplémentaires - la séparation des comptes démo et réels, et le préfixe "t" lors du test, bien que cela puisse être fait simplement en ajoutant le caractère "t", qui indique que notre Expert Advisor travaille dans Testeur de Stratégie. Mais je l'ai fait de cette façon. Nous ne connaissons pas l'avenir et les choses qui peuvent être nécessaires pour analyser le travail des Expert Advisors.

Le magasin n'est pas douloureux, disent-ils.

Les fonctions présentées ci-dessus indiquent que le terminal client fonctionne avec un seul compte, il n'y a aucun changement de compte pendant son travail. Il est interdit de changer de compte pendant le travail d'un Expert Advisor. Bien entendu, si nécessaire, ce problème peut être résolu en ajoutant un numéro de compte aux noms des variables globales.

Une autre remarque très importante ! La longueur du nom de la variable globale est limitée à 63 symboles. Pour cette raison, n’attribuez pas de noms longs à vos conseillers experts.

Nous avons terminé avec les variables globales, il est maintenant temps de considérer le sujet principal de l'article - le calcul du volume de position par un nombre magique indiqué.

3. Calcul du Volume d'une Position

Tout d'abord, vérifions s'il existe une variable globale avec les informations sur la dernière heure de la position de volume zéro à l'aide de la fonction GlobalVariableCheck() (pour faire simple, s'il n'y a pas de position ouverte, nous l'appelons un cas "position zéro" ).

Si une telle variable existe- chargeons l'historique des deals à partir de l'heure stockée dans la variable, sinon nous chargerons tout l'historique :

if(GlobalVariableCheck(Commom_gvp+sSymbol+"_HistStTm")) // Saved time of a "zero" total position
  {
   pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm"); // initial date setting 
                                                                             // select only the history needed
  }
else
 {
   GlobalVariableSet(Commom_gvp+sSymbol+"_HistStTm",0);
 }
if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Load the necessary part of the deal history
  { 
   return(false);
  } 

Ensuite, nous définissons le volume total net de la position pour un symbole :

double CurrentVolume=fSymbolLots(pSymbol);

Le volume d'une position est déterminé à l'aide de la fonction fSymbolLots().

Il existe plusieurs manières d'obtenir le volume d'une position : par exemple, cela peut être fait à l'aide de la fonction PositionSelect(). Si la fonction renvoie faux, cela indique qu'il n'y a pas de position (son volume est égal à zéro). Si la fonction renvoie vrai, le volume peut être obtenu à l'aide de la fonction PositionGetDouble() avec l'identifiant POSITION_VOLUME. Le type de position (achat ou vente) est déterminé à l'aide de la fonction PositionGetInteger() avec l'identifiant POSITION_TYPE. La fonction renvoie une valeur positive pour les positions longues et négative pour les positions courtes.

La fonction complète se présente comme suit :

double fSymbolLots(string aSymbol)
  {
   if(PositionSelect(aSymbol,1000)) // the position has been selected successfully, so it exists
     {
      switch(PositionGetInteger(POSITION_TYPE)) // It returns the positive or negative value dependent on the direction
        {
         case POSITION_TYPE_BUY:
            return(NormalizeDouble(PositionGetDouble(POSITION_VOLUME),2));
            break;
         case POSITION_TYPE_SELL:
            return(NormalizeDouble(-PositionGetDouble(POSITION_VOLUME),2));
            break;
        }
     }
   else
     {
      return(0);
     }
  }

Alternativement, vous pouvez déterminer le volume de la position totale du symbole par boucle à travers toutes les positions, le nombre de positions est déterminé par la fonction PositionsTotal(). Ensuite, trouvez le symbole nécessaire à l'aide de la fonction PositionGetSymbol() et déterminez le volume et la direction de la position (le PositionGetDouble() avec l'identifiant POSITION_VOLUME et la fonction PositionGetInteger() avec l'identifiant POSITION_TYPE).

Dans ce cas, la fonction prêt se présentera sou la forme : 

double fSymbolLots(string aSymbol)
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++) // Go through all positions
     {
      if(PositionGetSymbol(i)==aSymbol) // we have found a position with specified symbol
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1; // the sign is dependent on the position type           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

Après détermination du volume actuel, nous allons parcourir l'historique des deals de la fin au début, jusqu'à ce que la somme des volumes devienne égale au volume.

La longueur de l'historique des deals sélectionnées est déterminée à l'aide de la fonction HistoryDealsTotal(), le ticket est déterminé pour chaque transaction à l'aide de la fonction HistoryDealGetTicket(), les données de transaction sont extraites à l'aide de la fonction HistoryDealGetInteger() (l'identifiant DEAL_TYPE pour le type de transaction) et HistoryDealGetDouble() (l'identifiant DEAL_VOLUME pour le volume de deals) :

double Sum=0; 
int FromI=0;
int FromTicket=0;
for(int i=HistoryDealsTotal()-1;i>=0;i--) // go through all the deals from the end to the beginning 
  {
   ulong ticket=HistoryDealGetTicket(i); // Get ticket of the deal
   if(ticket!=0)
     {
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // We add or subtract the volume depending on deal direction
        {
         case DEAL_TYPE_BUY:
            Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
         case DEAL_TYPE_SELL:
            Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
        }
      if(CurrentVolume==Sum) // all the deals has scanned
        {
         sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME); // Save the time of a "zero" position
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
         FromI=i; // Save the index
         break;
        }
     }
  }

Lorsque nous avons trouvé ce point, nous stockons l'heure dans la variable globale, qui sera utilisée ultérieurement lors du chargement de l'historique des deals (l'index des deals dans l'historique est stocké dans la variable FromI).

Avant le deal avec un indice FromI, la position totale sur le symbole était égale à zéro.

Maintenant, nous allons du FromI vers la fin de l'historique et comptons le volume des deals avec le nombre magique indiqué :

static double sVolume=0;
static ulong sLastTicket=0;
for(int i=FromI;i<HistoryDealsTotal();i++) // from the first deal until the end
  {
   ulong ticket=HistoryDealGetTicket(i);   // Get deal ticket
   if(ticket!=0)
     {
      if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol) // Specified symbol
        {
         long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
         if(PosMagic==aMagic || aMagic==-1) // Specified magic
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // add or subtract the deal volumes 
                                                       // depending on the deal type
              {
               case DEAL_TYPE_BUY:
                  sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
               case DEAL_TYPE_SELL:
                  sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
              }
           }
        }
     }
  } 

Après la fin d'une boucle, nous aurons le volume d'une position actuelle par le nombre magique indiqué, le ticket d'un dernier deal avec le nombre magique indiqué sera stocké dans la variable sLastTicket, après l'exécution du deal, le volume total d’une position avec le nombre magique indiqué sera égal à sVolume. Les travaux préliminaires de la fonction sont terminés.

Les variables sLoadHistoryFrom, sLastTicket et sVolume sont déclarées statiques (elles stockent leurs valeurs après l'achèvement de la fonction), ces valeurs seront utilisées ultérieurement pour chaque appel de la fonction.

Nous avons le temps (le point de départ de l'historique des deals), le ticket de transaction, après son exécution le volume de la position totale (avec le symbole indiqué) aura la valeur actuelle.

Parce que l'heure de la position de volume zéro, il suffit de parcourir l'historique de l'heure actuelle à l'heure enregistrée et d'effectuer la sommation des volumes de deal et d'enregistrer le volume et le ticket du dernier deal.

Ainsi, le calcul de la position totale de l'Expert Advisor est le traitement de quelques derniers deals :

if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Request for the deals history up to the current time
  {
   return(false);
  }
for(int i=HistoryDealsTotal()-1;i>=0;i--) // Loop from the end
  {
   ulong ticket=HistoryDealGetTicket(i); // Get ticke
   if(ticket!=0)
     {
      if(ticket==sLastTicket) // We have found the already calculated deal, save the ticket and break
        {
         sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
         break;
        }
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // Add or subtract deal volume depending on deal type      
        {
         case DEAL_TYPE_BUY:
            sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
         case DEAL_TYPE_SELL:
            sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
        }
     }
  }

L'algorithme de la fonction peut être présenté comme suit :


La fonction complète :

bool fGetPositionVolume(string aSymbol,int aMagic,double aVolume)
  {
   static bool FirstStart=false;
   static double sVolume=0;
   static ulong sLastTicket=0;
   static datetime sLoadHistoryFrom=0;
   // First execution of function when Expert Advisor has started
   if(!FirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+aSymbol+"_HistStTm"))
        {
         sLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+aSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Return if unsuccessful, 
                                                      // we will repeat on the next tick
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(aSymbol); // Total volume
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      // Search the last time when position volume was equal to zero
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      // Calculate the volume of position with specified magic number and symbol
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==aMagic || aMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      FirstStart=true;
     }

   // Recalculate the volume of a position (with specified symbol and magic)
   // for the deals, after the zero position time
   if(!HistorySelect(sLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==sLastTicket)
           {
            sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
            break;
           }
         switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
           {
            case DEAL_TYPE_BUY:
               sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
            case DEAL_TYPE_SELL:
               sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
           }
        }
     }
   aVolume=NormalizeDouble(sVolume,2);;
   return(true);
  }

Le symbole et le nombre magique sont passés à la fonction, qui renvoie le volume de position. Il renvoie vrai en cas de succès et faux dans le cas contraire.

En cas de succès, il renvoie le volume demandé à la variable aVolume, passé à la fonction par référence. Les variables statiques, déclarées dans la fonction, ne permettent pas d'utiliser cette fonction avec des paramètres différents (symbole et nombre magique).

Dans le cas de MQL4, ce problème pourrait être résolu en créant une copie de cette fonction avec un nom différent et en l'appelant pour l'autre paire "symbole-magie" ou en déclarant les variables FirstStart, sVolume, sLastTicket, sLoadHistoryFrom comme variables communes - pour chaque paire "symbole-magie" et les passer dans la fonction.

Il peut également être implémenté dans MQL5 de la même manière, mais MQL5 dispose d’ une fonctionnalité beaucoup plus pratique - les classes, c'est le cas où l'utilisation des classes est raisonnable. Lors de l'utilisation de classes, il est nécessaire de créer une instance de classe pour chaque paire de nombre symbole-magique, les données seront stockées dans chaque instance de classe.

Déclarons une classe PositionVolume. Toutes les variables, déclarées comme statiques à l'intérieur de la fonction, seront déclarées comme privées, nous ne les utiliserons pas directement depuis l'Expert Advisor, à l'exception de la variable Volume. Mais nous n'en aurons besoin qu'après l'exécution de la fonction de calcul de volume. Nous déclarons également les variables Symbole et Magie - il n'est pas pratique de les transmettre à la fonction, il suffit de le faire une fois lors de l'initialisation de l'instance de classe.

La classe assurera deux fonctions publiques : la fonction d'initialisation et la fonction de calcul du volume de la position, et une fonction privée pour déterminer le volume total de la position :

class PositionVolume
  {
private:
   string            pSymbol;
   int               pMagic;
   bool              pFirstStart;
   ulong             pLastTicket;
   double            pVolume;
   datetime         pLoadHistoryFrom;
   double            SymbolLots();
public:
   void Init(string aSymbol,int aMagic)
     {
      pSymbol=aSymbol;
      pMagic=aMagic;
      pFirstStart=false;
      pLastTicket=0;
      pVolume=0;
     }
   bool              GetVolume(double  &aVolume);
  }; 
bool PositionVolume::GetVolume(double  &aVolume)
  {
   if(!pFirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+pSymbol+"_HistStTm"))
        {
         pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(pSymbol);
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               pLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",pLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==pMagic || pMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      pFirstStart=true;
     }
   if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==pLastTicket)
           {
            break;
           }
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
           {
            long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
            if(PosMagic==pMagic || pMagic==-1)
              {
               switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                 {
                  case DEAL_TYPE_BUY:
                     pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                  case DEAL_TYPE_SELL:
                     pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                 }
              }
           }
        }
     }
   if(HistoryDealsTotal()>0)
     {
      pLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
     }
   pVolume=NormalizeDouble(pVolume,2);
   aVolume=pVolume;
   return(true);
  }

double PositionVolume::SymbolLots()
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++)
     {
      if(PositionGetSymbol(i)==pSymbol)
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1;
           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

Lorsque vous utilisez cette classe pour chaque paire symbole-nombre magique, il est nécessaire de créer une instance de classe :

PositionVolume PosVol11;
PositionVolume PosVol12;
PositionVolume PosVol21;
PositionVolume PosVol22;

Il doit être initialisé dans la fonction OnInit() d'un Expert Advisor, par exemple :

PosVol11.Init(Symbol_1,Magic_1); 
PosVol12.Init(Symbol_1,Magic_2);
PosVol21.Init(Symbol_2,Magic_1); 
PosVol22.Init(Symbol_2,Magic_2);   

Après cela, il est possible d'obtenir le volume d'une position par le symbole et le nombre magique indiqués. Appelons la fonction GetVolume de l'instance de classe correspondante.

En cas de succès, il renvoie vrai et met la valeur dans la variable, passée par référence en paramètre de la fonction :

double Vol11;
double Vol12;
double Vol21;
double Vol22;
PosVol11.GetVolume(Vol11);
PosVol12.GetVolume(Vol12);
PosVol21.GetVolume(Vol21);
PosVol22.GetVolume(Vol22);

Ici, on peut dire, vous avez terminé, mais il reste le test de contrôle.

4. Test de Contrôle

Pour tester le travail de la fonction nous avons utilisé un Expert Advisor travaillant simultanément avec quatre Positions :

  1. en utilisant l'indicateur RSI avec la période 14 sur l'EURUSD avec le numéro magique 1 ;
  2. en utilisant l'indicateur RSI avec la période 21 sur l'EURUSD avec le chiffre magique 2 ;
  3. en utilisant l'indicateur RSI avec la période 14 sur GBPUSD avec le numéro magique 1 ;
  4. en utilisant l'indicateur RSI avec la période 21 sur GBPUSD avec le numéro magique 2 ;

L'Expert Advisor avec le numéro magique 1 a tradé 0,1 lot du volume, l'Expert Advisor avec le numéro magique 2 a tradé le volume égal à 0,2 lot.

Le volume du deal est ajouté aux variables de l'Expert Advisor lors de l'exécution d'un deal, avant et après le deal, le volume de chaque position a été déterminé à l'aide de la fonction présentée ci-dessus. 

La fonction génère un message s'il y a eu une erreur dans le calcul des volumes.

Le code de l'Expert Advisor se trouve dans la pièce jointe à l'article (nom de fichier : ePosVolTest.mq5).

Conclusion

De nombreuses fonctions sont nécessaires pour un Expert Advisor, et elles doivent être implémentées de manière facile à utiliser à toutes les étapes. Ces fonctions doivent être écrites en termes d'utilisation optimale des ressources informatiques.

La méthode de calcul du volume de position proposée dans cet article répond à ces conditions - elle ne charge que le minimum requis de l'historique des deals lors de son lancement. Lorsqu'il travaille, il recalcule le volume actuel de la position en utilisant les dernières deals.

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

Fichiers joints |
eposvoltest.mq5 (17.98 KB)
Utilisation de la fonction TesterWithdrawal() pour modéliser les retraits de bénéfice. Utilisation de la fonction TesterWithdrawal() pour modéliser les retraits de bénéfice.
Cet article décrit l’utilisation de la fonction TesterWithDrawal() pour estimer les risques dans les systèmes de trade qui impliquent le retrait d’une certaine partie des actifs pendant leur fonctionnement. En outre, il décrit l’effet de cette fonction sur l’algorithme de calcul du prélèvement d’actions dans le testeur de stratégie. Cette fonction est utile lors de l’optimisation des paramètres de vos Expert Advisors.
Rédaction d'un Expert Advisor à l'aide de l'approche de programmation  orientée-objet MQL5 Rédaction d'un Expert Advisor à l'aide de l'approche de programmation orientée-objet MQL5
Cet article est axé sur l'approche orientée-objet pour faire ce que nous avons fait dans l'article « Guide étape par étape pour écrire un Expert Advisor en MQL5 pour les débutants » : créer un Expert Advisor simple. La plupart des gens pensent que c'est difficile, mais je tiens à vous rassurer qu'au moment où vous aurez fini de lire cet article, vous serez en mesure d'écrire votre propre Expert Advisor qui est orienté-objet.
L'utilisation des bibliothèques MQL5 Standard de Cours de Trade  dans la rédaction d'un Expert Advisor L'utilisation des bibliothèques MQL5 Standard de Cours de Trade dans la rédaction d'un Expert Advisor
Cet article explique comment utiliser les principales fonctionnalités de la bibliothèque standard MQL5 de classes de trade dans la rédaction des conseillers experts qui implémente la fermeture de la position et la modification , la passation et la suppression de commande en attente et la vérification de la marge avant de passer un trade.. Nous avons également démontré comment les classes de trade peuvent être utilisés pour obtenir les détails des commandes et des deals.
Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Canaux Nommés Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Canaux Nommés
L'article décrit comment implémenter la communication inter-processus entre les terminaux clients MetaTrader 5 à l'aide de Canaux Nommés. Pour l'utilisation des Canaux Nommés, la classe CNamedPipes est élaborée. Pour tester son utilisation et mesurer le débit de la connexion, l'indicateur de coche, les scripts serveur et client sont présentés. L'utilisation de Canaux Nommés est suffisante pour les offres en temps réel.