English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Traitement des événements de trade dans Expert Advisor à l'aide de la fonction OnTrade()

Traitement des événements de trade dans Expert Advisor à l'aide de la fonction OnTrade()

MetaTrader 5Exemples | 22 décembre 2021, 16:19
188 0
KlimMalgin
KlimMalgin

Introduction

Tout trader qui écrit Experts sur MQL est tôt ou tard confronté à la nécessité de rendre compte de la façon dont son Expert travaille. Ou il peut avoir besoin de mettre en place un système de notification par SMS ou par e-mail sur les actions de l'Expert. Dans tous les cas, nous devons « saisir » certains événements, survenus sur le marché ou des actions effectuées par un expert, et informer les utilisateurs.

Dans cet article, je souhaite vous expliquer comment vous pouvez appliquer le traitement des événements de trade et vous proposer mon application.

Dans cet article, nous examinerons le traitement des événements suivants :

  • Positions
    1. Ouvert
    2. Ajout
    3. Modifier (changer Stop Loss et Take Profit)
    4. inversé
    5. Fermer toute la position
    6. Fermer une partie de la position
  • Ordres en Attente
    1. Emplacement
    2. Modifier

1. Comment ça marche ? 

Avant de commencer, en termes généraux, je vais décrire le fonctionnement des événements de trade et tous les détails nécessaires seront expliqués à la volée.

Il existe des événements prédéfinis et personnalisés dans MQL5. Nous nous intéressons aux prédéfinis, en particulier à l'événement Trade.

L'événement Trade est généré à chaque fois, lorsque l'opération Trade est terminée. Chaque fois après la génération de l'événement Trade, la fonction OnTrade() est appelée. Le traitement des ordres et des positions se fera exactement à l'intérieur de la fonction OnTrade().

2. Modèle d'expert

Créons donc un nouvel Expert Advisor. Dans MetaEditor, cliquez sur File -> New pour lancer l'assistant MQL5. Sélectionnez Expert Advisor et cliquez sur Suivant. Dans la boîte de dialogue « Propriétés générales de l'Expert Advisor », saisissez le Nom de l'Expert Advisor et vos propres données, si nécessaire. J'ai nommé mon Expert Advisor comme « TradeControl ». Vous pouvez prendre ce nom ou choisir le vôtre, ce n'est pas important. Nous ne spécifierons aucun paramètre, car ils seront créés à la volée lors de l'écriture d'un expert.

C’est fait ! Le modèle Expert Advisor est créé, nous devons y ajouter la fonction OnTrade().

En conséquence, vous devriez obtenir le code suivant :

//+------------------------------------------------------------------+
//|                                              TradeControl_en.mq5 |
//|                                             Copyright KlimMalgin |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "KlimMalgin"
#property link      ""
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

3. Gestion des positions

Commençons par l'événement Trade le plus simple - les positions d'ouverture et de clôture. Tout d'abord, vous devez comprendre quels processus se produisent après avoir appuyé sur les boutons « Vendre » et « Acheter ».

Si nous demandons la fonction OnTrade() :

Alert("The Trade event occurred");

Nous verrons alors qu'après l'ouverture par la fonction Market OnTrade() et avec elle, notre Alerte a été exécutée quatre fois :

Figure 1. Alertes

Figure 1. Alertes

Pourquoi OnTrade() est demandé quatre fois, et comment pouvons-nous répondre à ces alertes ? Pour comprendre cela, examinons la documentation :

 OnTrade

La fonction est demandée lorsque l'événement Trade se produit. Cela se produit lorsque la liste des ordres passés, des positions ouvertes, de l'historique des ordres et de l'historique des transactions est modifiée.

Je me dois de mentionner une chose, ici :

Lors de la rédaction de cet article et de la communication avec les développeurs, j'ai constaté que les changements dans l’historique n’aboutissent pas à l’appel OnTrade() ! Le fait est que la fonction OnTrade() n'est demandée que lorsque la liste des ordres passés et des positions ouvertes est modifiée ! Lors du développement d'un gestionnaire d'événements de trading, vous pouvez être confronté au fait que les ordres et les transactions exécutés peuvent apparaître dans l'historique avec un retard, et vous ne pourrez pas les traiter lorsque la fonction OnTrade () est en cours d'exécution.  

Revenons maintenant aux événements. Comme nous l'avons vu, lorsque vous ouvrez par Market, l'événement Trade se produit 4 fois :

  1. Création d'un ordre d'ouverture par Market.
  2. Exécution de la transaction
  3. Passer un ordre complet, de l’ordre à l'historique.
  4. Ouverture de Position

  Pour suivre ce processus dans le terminal, faites attention à la liste des ordres dans l'onglet « Trade » de la fenêtre MetaTrader : 

Figure 2. Liste des ordres dans l’onglet « Trade »

Figure 2. Liste des ordres dans l'onglet «Trade »

Une fois que vous avez ouvert une position (down, par exemple), dans la liste des commandes apparaît une commande qui a le statut mis en marche (Fig. 2). Cela modifie la liste des ordres passés et l'événement Trade est appelé. C'est la première fois que la fonction OnTrade() est activée. Ensuite, une transaction est exécutée par ordre créé. A ce stade, la fonction OnTrade() est exécutée une deuxième fois. Dès que la transaction est exécutée, l'ordre terminé et sa transaction exécutée seront envoyés à l'historique, et la fonction OnTrade() est appelée une troisième fois. Au dernier stade, une position est ouverte par transaction exécutée et la fonction OnTrade() est appelée la quatrième fois.

Pour « saisir » le moment de l'ouverture de la position, chaque fois que vous demandez OnTrade(), vous devez analyser la liste des ordres, l'historique des ordres et l'historique des transactions. C'est ce que nous allons faire maintenant !

D’ac, la fonction OnTrade() est demandée, et nous avons besoin de savoir si le nombre des ordres a changé dans l'onglet « Trade ». Pour ce faire, nous devons comparer le nombre des ordres dans la liste au moment de l’appel précédent OnTrade() et maintenant. Pour savoir combien d’ordres se trouvent actuellement dans la liste, nous utiliserons la fonction OrdersTotal(). Et pour savoir combien d’ordres ont été répertoriés lors de l'appel précédent, nous devrons conserver la valeur des OrdersTotal() dans chaque appel OnTrade(). Pour ce faire, nous allons créer une variable spéciale :

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call

À la fin de la fonction OnTrade(), la variable OrdersPrev sera assignée avec la valeur OrdersTotal().

Vous devez également tenir compte de la situation lorsque vous exécutez Expert Advisor et qu'il y a déjà des ordres en attente dans la liste. L'Expert doit être capable de les repérer, donc dans la fonction OnInit(), la variable OrdersPrev doit également être assignée avec la valeur OrdersTotal(). Les modifications que nous venons d'effectuer dans l'Expert ressembleront à ce qui suit : 

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   OrdersPrev = OrdersTotal();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

OrdersPrev = OrdersTotal();
//---
  }

Maintenant que nous connaissons le nombre des ordres pour les appels en cours et antérieurs, nous pouvons savoir quand l’ordre est apparu dans la liste et quand, pour une raison ou une autre, il a disparu. Pour ce faire, nous utiliserons la condition suivante :

if (OrdersPrev < OrdersTotal())
{
  // Order appeared
}
else if(OrdersPrev > OrdersTotal())
{
  // Order disappeared
}

Donc, il s'avère que si pour l'appel précédent, nous avons moins d’ordres que maintenant, l’ordre apparaît dans la liste (plusieurs ordres ne peuvent pas apparaître simultanément), mais si c'est le contraire, c'est-à-dire maintenant nous avons moins d’ordres que lors d’un précédent appel OnTrade( ), alors l’ordre est soit exécuté soit annulé pour une certaine raison quelconque. Presque tout le travail avec les positions commence par ces deux conditions.

Seuls le Stop Loss et le Take Profit nécessitent un travail séparé avec eux. À la fonction OnTrade(), j'ajouterai le code, qui fonctionne avec les positions. Examinons-le :

void OnTrade()
  {
//---
Alert("Trade event occurred");

HistorySelect(start_date,TimeCurrent());

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
}
else if(OrdersPrev > OrdersTotal())
{
   state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE);

   // If order is not found, generate an error
   _GetLastError=GetLastError();
   if (_GetLastError != 0){Alert("Error #",_GetLastError," Order is not found!");LastOrderTicket = 0;}
   Print("Error #",_GetLastError," state: ",state);ResetLastError();


   // If order is fully executed
   if (state == ORDER_STATE_FILLED)
   {
      // Then analyze the last deal
      // --
      Alert(LastOrderTicket, "Order executed, going to deal");
      switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
      {
         
         // Entering the market
         case DEAL_ENTRY_IN:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
               break;
               
               case 1:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Exiting the market
         case DEAL_ENTRY_OUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Sell position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Sell position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
               break;
               
               case 1:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Buy position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Buy position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Reverse
         case DEAL_ENTRY_INOUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
                  Alert("Sell is reversed to Buy on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               case 1:
                  Alert("Buy is reversed to Sell on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Indicates the state record
         case DEAL_ENTRY_STATE:
            Alert("Indicates the state record. Unprocessed code of type: ", 
            HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
         break;
      }
      // --
   }
}

OrdersPrev = OrdersTotal();

//---
  }

Assurez-vous également qu'au début du programme vous avez déclaré les variables suivantes :

datetime start_date = 0;   // Date, from which we begin to read history

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call
int PositionsPrev = 0;     // Number of positions at the time of previous OnTrade() call
ulong LastOrderTicket = 0; // Ticket of the last processed order

int _GetLastError=0;       // Error code
long state=0;              // Order state

Revenons sur le contenu OnTrade().

Vous pouvez commenter l'alerte au début, mais je vais laisser cela pour aborder la fonction HistorySelect(). Elle génère une liste des transactions et de l'historique des commandes pour la période spécifiée, qui est définie par deux paramètres de la fonction. Si cette fonction n'est pas appelée avant d'accéder à l'historique des transactions et des ordres, nous n'obtiendrons aucune information car les listes d'historique seront vides. Après avoir appelé HistorySelect(), les conditions sont évaluées, comme cela a été écrit juste auparavant.

Lorsqu'un nouvel ordre arrive, nous la sélectionnons d'abord et vérifions les erreurs :

OrderGetTicket(OrdersTotal()-1);// Select the last order for work
_GetLastError=GetLastError();
Print("Error #",_GetLastError);ResetLastError();

Après avoir sélectionné l’ordre, nous obtenons le code d'erreur à l'aide de la fonction GetLastError(). Ensuite, en utilisant la fonction Print(), nous imprimons le code dans le journal et en utilisant la fonction ResetLastError(), nous réinitialisons le code d'erreur à zéro, donc lors du prochain appel GetLastError() pour d'autres situations, nous n'obtiendrons pas le même code d'erreur.

Après avoir vérifié les erreurs, si l’ordre a été sélectionné avec succès, vérifiez son statut :

if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
{
   Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
   LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
}

Si l’ordre au statut de mis en marche, c'est-à-dire qu'il est vérifié pour son exactitude, mais pas encore accepté, alors il devrait être exécuté dans un proche avenir, et nous donnons simplement une Alerte() notifiant que l’ordre est en cours de traitement et enregistrons son ticket sur le prochain appel OnTrade(). Au lieu d'Alert(), vous pouvez utiliser n'importe quel autre type de notifications.

Le code ci-dessus

OrderGetTicket(OrdersTotal()-1)

renverra le dernier ticket des ordres de la liste complète des ordres.

OrdersTotal()-1 indique que nous devons obtenir le dernier ordre. Étant donné que la fonction OrdersTotal() renvoie le nombre total d’ordres (par exemple, s'il y a 1 ordre dans la liste, alors OrdersTotal() renvoie 1), et le numéro d'indice de l’ordre est compté à partir de 0, puis pour obtenir le numéro d'indice du dernier ordre, nous devons soustraire 1 du nombre total des ordres (si OrdersTotal() renvoie 1, alors le numéro d'indice de cet ordre sera égal à 0). Et la fonction OrderGetTicket() renverra à son tour le ticket d’ordre, dont le numéro lui sera transmis.

C'était la première condition, elle est généralement déclenchée lors du premier appel OnTrade(). Vient ensuite la deuxième condition, qui est remplie lors du deuxième appel OnTrade(), lorsque l'ordre est exécuté, est transmis à l'historique et cela devrait suffire pour ouvrir la position.

Si l’ordre manque dans la liste, c'est qu'il est entré dans l'historique, car il doit bien y être ! Par conséquent, nous faisons appel à l'historique des ordres en utilisant la fonction HistoryOrderGetInteger() pour obtenir le statut de l’ordre. Et pour lire les données de l'historique d'un ordre particulier, nous avons besoin de son ticket. Pour cela, si pour la première condition, le ticket de l’ordre entrant a été stocké dans la variable LastOrderTicket,

alors, nous obtenons le statut de l’ordre, indiquant le ticket d’ordre comme premier paramètre pour HistoryOrderGetInteger(), et le type de propriété nécessaire - comme second. Après avoir essayé d'obtenir le statut de l’ordre, nous obtenons le code d'erreur et l'inscrivons dans le journal. C'est nécessaire au cas où votre ordre, avec lequel nous devons travailler, n'a pas encore réussi à entrer dans l'historique, et nous y faisons appel (l'expérience montre que cela est possible et dans la plupart des cas ainsi. J'ai écrit à ce sujet au début de cet article).

Si une erreur se produit, le traitement s'arrête car il n'y a pas de données avec lesquelles travailler et aucune des conditions suivantes n'est remplie. Et si l'appel HistoryOrderGetInteger() a réussi et que l’ordre a le statut d’ « Ordre entièrement exécuté » :

// If order is fully executed
if (state == ORDER_STATE_FILLED)

Alors, donnez une autre notification :

// Then analyze the last deal
// --
  Alert(LastOrderTicket, "Order executed, going to deal");

Et passons au traitement de la transaction, qui a été invoquée par cet ordre. Tout d'abord, recherchez la direction de la transaction (DEAL_ENTRY). La direction n’est pas Buy ou Sell, mais Entrée dans Market, Sortie du Market, Inversion ou Indication d’enregistrement du statut. Ainsi, en utilisant la propriété DEAL_ENTRY, nous pouvons savoir si l'ordre a été défini en position ouverte, en position fermée ou en position inversée. 

Pour analyser la transaction et ses résultats, faisons également appel à l'historique en utilisant la construction suivante :

switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
{
  ...
}

Cela fonctionne de la même manière qu'avec les ordres :

HistoryDealsTotal() renvoie le nombre total de transactions. Pour obtenir le nombre de dernières transactions, nous soustrayons 1 de la valeur de HistoryDealsTotal(). Le nombre de transactions en résultant est transmis à la fonction HistoryDealGetTicket(), qui à son tour transmet le ticket de la transaction sélectionnée à la fonction HistoryDealGetInteger(). Et l’HistoryDealGetInteger() par ticket et type de propriété spécifiés renverra la direction de la transaction..

Examinons en détail la direction de l'entrée en Market. Les autres directions seront abordées brièvement, car elles sont traitées presque de la même manière :

La valeur d’expression, obtenue à partir de HistoryDealGetInteger(), est comparée aux valeurs des blocs de cas, jusqu'à ce qu'une correspondance soit trouvée. Supposons que nous entrons sur le marché, c'est-à-dire que nous ouvrons l'ordre de vente. Alors, le premier bloc sera exécuté :

// Entering the market
case DEAL_ENTRY_IN:

Au début du bloc, vous recevez notification de la création de la transaction. Dans le même temps, cette notification garantit que tout allait bien et que la transaction est en cours de traitement.

Après la notification vient un autre bloc de commutation, qui analyse le type de transaction :

   switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
   {
      case 0:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
      break;
      
      case 1:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         
      break;
      
      default:
         Alert("Unprocessed code of type: ",
               HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
      break;
   }

Obtenez des informations sur la transaction à partir de l'historique - de la même manière que précédemment, à l'exception de la propriété spécifiée. Cette fois, vous devez spécifier le DEAL_TYPE pour savoir si une transaction d'achat ou de vente est effectuée. J'analyse uniquement les types d'achat et de vente, mais à part eux, il y en a quatre autres. Mais ces quatre types de transactions restantes sont moins courantes, donc au lieu de quatre blocs de cas, un seul bloc par défaut est utilisé pour eux. Cela donnera une Alert() avec le code de type.

Comme vous l'avez probablement remarqué dans le code, non seulement les ouvertures de positions Achat et Vente sont traitées, mais aussi leur incrémentation. Pour déterminer quand la position a été incrémentée et quand elle a été ouverte, vous devez comparer le volume de transaction exécutée et la position qui est devenue le résultat de cette transaction. Si le volume de position est égal au volume de transaction exécutée - cette position a été ouverte, et si les volumes de position et de transaction sont différents - cette position a été incrémentée. Ceci s'applique à la fois aux positions d'achat (dans le cas du bloc '0') et aux positions de vente (dans le cas du bloc '1'). Le dernier bloc est le bloc par défaut, qui gère toutes les situations autres que l'achat et la vente. L'ensemble du traitement consiste en une notification sur le code de type, renvoyée par la fonction HistoryDealGetInteger().

Et enfin, la dernière préoccupation concerne le travail avec les positions. Il s'agit du traitement des modifications des valeurs Stop Loss et Take Profit. Pour savoir lequel des paramètres de position a changé, nous devons comparer le statut actuel et antérieur de ses paramètres. Les valeurs actuelles des paramètres de position peuvent toujours être obtenues à l'aide des fonctions de service, mais les valeurs précédentes doivent être enregistrées.

Pour cela, nous écrirons une fonction spéciale, qui enregistrera les paramètres de position dans le tableau des structures :

void GetPosition(_position &Array[])
  {
   int _GetLastError=0,_PositionsTotal=PositionsTotal();

   int temp_value=(int)MathMax(_PositionsTotal,1);
   ArrayResize(Array, temp_value);

   _ExpertPositionsTotal=0;
   for(int z=_PositionsTotal-1; z>=0; z--)
     {
      if(!PositionSelect(PositionGetSymbol(z)))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect() - Error #",_GetLastError);
         continue;
        }
      else
        {
            // If the position is found, then put its info to the array
            Array[z].type         = PositionGetInteger(POSITION_TYPE);
            Array[z].time         = PositionGetInteger(POSITION_TIME);
            Array[z].magic        = PositionGetInteger(POSITION_MAGIC);
            Array[z].volume       = PositionGetDouble(POSITION_VOLUME);
            Array[z].priceopen    = PositionGetDouble(POSITION_PRICE_OPEN);
            Array[z].sl           = PositionGetDouble(POSITION_SL);
            Array[z].tp           = PositionGetDouble(POSITION_TP);
            Array[z].pricecurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
            Array[z].comission    = PositionGetDouble(POSITION_COMMISSION);
            Array[z].swap         = PositionGetDouble(POSITION_SWAP);
            Array[z].profit       = PositionGetDouble(POSITION_PROFIT);
            Array[z].symbol       = PositionGetString(POSITION_SYMBOL);
            Array[z].comment      = PositionGetString(POSITION_COMMENT);
        _ExpertPositionsTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertPositionsTotal,1);
   ArrayResize(Array,temp_value);
  }

Pour utiliser cette fonction, nous devons ajouter le code suivant dans le bloc de déclaration des variables globales :

/*
 *
 * Structure that stores information about positions
 *
 */
struct _position
{

long     type,          // Position type
         magic;         // Magic number for position
datetime time;          // Time of position opening

double   volume,        // Position volume
         priceopen,     // Position price
         sl,            // Stop Loss level for opened position
         tp,            // Take Profit level for opened position
         pricecurrent,  // Symbol current price
         comission,     // Commission
         swap,          // Accumulated swap
         profit;        // Current profit

string   symbol,        // Symbol, by which the position has been opened
         comment;       // Comment to position
};

int _ExpertPositionsTotal = 0;

_position PositionList[],     // Array that stores info about position
          PrevPositionList[];

Le prototype de la fonction GetPosition() a été trouvé il y a longtemps dans les articles de www.mql4.com, mais je n'ai pas pu le trouver maintenant et je ne peux pas spécifier la source. Je ne vais pas détailler le travail de cette fonction. Le fait est que, en tant que paramètre par référence, un tableau de type _position (structure avec des champs correspondant aux champs de position) a été transmis, auquel toutes les informations sur les positions actuellement ouvertes et les valeurs de leurs paramètres sont transmises.

Pour suivre facilement les changements dans les paramètres de position, créons deux tableaux de type _position. Ce sont PositionList[] (le statut actuel des positions) et PrevPositionList[] (le statut antérieur des positions).

Pour commencer à travailler avec les positions, nous devons ajouter le prochain appel dans OnInit() et à la fin de OnTrade() :

GetPosition(PrevPositionList);

Également au début d'Ontrade(), nous devons ajouter l'appel :

GetPosition(PositionList);

Désormais, dans les tableaux PositionList[] et PrevPositionList[] à notre disposition, vous trouverez respectivement des informations sur les positions sur l'appel OnTrade() actuel et précédent.

Considérons maintenant le code réel de suivi des modifications dans sl et tp :

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{
   string _alerts = "";
   bool modify = false;
     
   for (int i=0;i<_ExpertPositionsTotal;i++)
   {
      if (PrevPositionList[i].sl != PositionList[i].sl)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Stop Loss changed from "+ PrevPositionList[i].sl +" to "+ PositionList[i].sl +"\n";
         modify = true;
      }
      if (PrevPositionList[i].tp != PositionList[i].tp)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Take Profit changed from "+ PrevPositionList[i].tp +" to "+ PositionList[i].tp +"\n";
         modify = true;
      }
      
   }
   if (modify == true)
   {
      Alert(_alerts);
      modify = false;
   }
}

Comme on le voit, le code n'est pas trop gros, mais c'est uniquement à cause du travail préparatoire énorme. Approfondissons cela.

Tout commence par la condition :

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))

Ici, nous voyons que ni les ordres ni les positions n'ont été placés ou supprimés. Si la condition est remplie, il est fort probable que les paramètres de certaines positions ou ordres ont changé.

Au début de la fonction, deux variables sont déclarées :

  • _alerts - stocke toutes les notifications sur les changements.
  • modifier - vous permet d'afficher des messages sur les changements uniquement s'ils l'étaient vraiment.

Ensuite dans la boucle, nous vérifions la correspondance des valeurs des Take Profits et des Stop Loss sur les appels OnTrade() précédents et actuels pour chaque position. Les informations sur toutes les incompatibilités seront stockées dans la variable _alerts et plus tard, elles seront affichées par la fonction Alert(). Par ailleurs, le traitement des modifications des ordres en attente sera effectué de la même manière.

Pour l'instant finissons avec les positions et procédons au placement des ordres en attente.

4. Gestion des ordres

Commençons par l'événement de placement des ordres en attente.

Lorsqu'un nouvel ordre en attente apparaît, l'événement Trade n'est généré qu'une seule fois, mais il suffit de le traiter ! Mettez le code, qui fonctionne avec les ordres en attente, dans le corps de l'opérateur :

if (OrdersPrev < OrdersTotal())

Et obtenez les éléments suivants :

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {
      switch(OrderGetInteger(ORDER_TYPE))
      {
         case 2:
            Alert("Pending order Buy Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 3:
            Alert("Pending order Sell Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 4:
            Alert("Pending order Buy Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 5:
            Alert("Pending order Sell Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 6:
            Alert("Pending order Buy Stop Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
                 
         case 7:
            Alert("Pending order Sell Stop Limit  #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;         
      }
   }
}

Ici, le code, qui fonctionne avec les ordres en attente, commence par ce qui suit :

   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {

Tout d'abord, le statut de l’ordre est vérifié. L’ordre doit avoir le statut ORDER_STATE_PLACED, c'est-à-dire qu'il doit être accepté. Et si cette condition est remplie, vient alors l'opérateur de commutation, qui imprime un message en fonction du type d’ordre.

Ensuite, nous travaillerons avec des événements qui se produisent lorsque les ordres sont modifiés. La modification des ordres s'apparente à la modification des positions. De même, la structure qui stocke les propriétés d’ordres est créée : 

/*
 *
 * Structure that stores information about orders
 *
 */
struct _orders
{

datetime time_setup,       // Time of order placement
         time_expiration,  // Time of order expiration
         time_done;        // Time of order execution or cancellation
         
long     type,             // Order type
         state,            // Order state
         type_filling,     // Type of execution by remainder
         type_time,        // Order lifetime
         ticket;           // Order ticket
         
long     magic,            // Id of Expert Advisor, that placed an order
                           // (intended to ensure that each Expert 
                           // must place it's own unique number)
                           
         position_id;      // Position id, that is placed on order,
                           // when it is executed. Each executed order invokes a
                           // deal, that opens new or changes existing 
                           // position. Id of that position is placed on 
                           // executed order in this moment.
                           
double volume_initial,     // Initial volume on order placement
       volume_current,     // Unfilled volume
       price_open,         // Price, specified in the order
       sl,                 // Stop Loss level
       tp,                 // Take Profit level
       price_current,      // Current price by order symbol
       price_stoplimit;    // Price of placing Limit order when StopLimit order is triggered
       
string symbol,             // Symbol, by which the order has been placed
       comment;            // Comment
                           
};

int _ExpertOrdersTotal = 0;

_orders OrderList[],       // Arrays that store info about orders
        PrevOrderList[];

Chaque champ de la structure correspond à l'une des propriétés de l'ordre. Après avoir déclaré la structure, la variable de type int et deux tableaux de type _orders sont déclarés. La variable _ExpertOrdersTotal stockera le nombre total des ordres, et les tableaux OrderList[] et PrevOrderList[] stockeront des informations sur les ordres dans l'appel OnTrade() actuel et précédent respectivement.

La fonction elle-même ressemblera à ceci :

void GetOrders(_orders &OrdersList[])
  {
   
   int _GetLastError=0,_OrdersTotal=OrdersTotal();

   int temp_value=(int)MathMax(_OrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

   _ExpertOrdersTotal=0;
   for(int z=_OrdersTotal-1; z>=0; z--)
     {
      if(!OrderGetTicket(z))
        {
         _GetLastError=GetLastError();
         Print("GetOrders() - Error #",_GetLastError);
         continue;
        }
      else
        {
        OrdersList[z].ticket          = OrderGetTicket(z);
        OrdersList[z].time_setup      = OrderGetInteger(ORDER_TIME_SETUP);
        OrdersList[z].time_expiration = OrderGetInteger(ORDER_TIME_EXPIRATION);
        OrdersList[z].time_done       = OrderGetInteger(ORDER_TIME_DONE);
        OrdersList[z].type            = OrderGetInteger(ORDER_TYPE);
        
        OrdersList[z].state           = OrderGetInteger(ORDER_STATE);
        OrdersList[z].type_filling    = OrderGetInteger(ORDER_TYPE_FILLING);
        OrdersList[z].type_time       = OrderGetInteger(ORDER_TYPE_TIME);
        OrdersList[z].magic           = OrderGetInteger(ORDER_MAGIC);
        OrdersList[z].position_id     = OrderGetInteger(ORDER_POSITION_ID);
        
        OrdersList[z].volume_initial  = OrderGetDouble(ORDER_VOLUME_INITIAL);
        OrdersList[z].volume_current  = OrderGetDouble(ORDER_VOLUME_CURRENT);
        OrdersList[z].price_open      = OrderGetDouble(ORDER_PRICE_OPEN);
        OrdersList[z].sl              = OrderGetDouble(ORDER_SL);
        OrdersList[z].tp              = OrderGetDouble(ORDER_TP);
        OrdersList[z].price_current   = OrderGetDouble(ORDER_PRICE_CURRENT);
        OrdersList[z].price_stoplimit = OrderGetDouble(ORDER_PRICE_STOPLIMIT);
        
        OrdersList[z].symbol          = OrderGetString(ORDER_SYMBOL);
        OrdersList[z].comment         = OrderGetString(ORDER_COMMENT);
        
        _ExpertOrdersTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertOrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

  }

À l’instar de la fonction GetPosition(), elle lit les informations sur les propriétés de chaque ordre passé et les place dans un tableau, qui lui est transmis en tant que paramètre d'entrée. Le code de la fonction doit être placé à la fin de votre expert, et ses appels - comme suit :

GetOrders(PrevOrderList);

Placé dans OnInit() et à la fin de OnTrade().

GetOrders(OrderList);

Placé au début de OnTrade().

Considérons maintenant le code, qui traitera la modification des ordres. Il s’agit d’une boucle qui complète le code de modification des positions :

   for (int i = 0;i<_ExpertOrdersTotal;i++)
   {
      if (PrevOrderList[i].sl != OrderList[i].sl)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Stop Loss from "+ PrevOrderList[i].sl +" to "+ OrderList[i].sl +"\n";
         modify = true;
      }
      if (PrevOrderList[i].tp != OrderList[i].tp)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Take Profit from "+ PrevOrderList[i].tp +" to "+ OrderList[i].tp +"\n";
         modify = true;
      }
   }

La boucle traite tous les ordres et compare les valeurs des Stop Losses et des Take Profits sur les appels OnTrade() actuels et précédents. S'il y a des différences, elles sont enregistrées dans la variable _alerts, et lorsque la boucle est terminée, elles seront affichées par la fonction Alert().

Ce code est placé dans le corps de l'opérateur :

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{

Immédiatement après la boucle, cela fonctionne avec les positions.

Pour l'instant, le travail avec les événements Trade ne s'arrête pas. Cet article ne couvre que les grands principes de travail avec l'événement Trade. En général, les possibilités offertes par cette méthode sont assez vastes et dépassent le cadre de cet article.

Conclusion

La capacité à travailler avec des événements Trade (dans le cadre du langage MQL5) est potentiellement un outil puissant, qui permet non seulement d'implémenter assez rapidement des algorithmes de vérification des ordres et de générer des rapports Trade, mais aussi de réduire le coût des ressources système et le volume de code source, ce qui profitera sans aucun doute aux développeurs.

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

Fichiers joints |
tradecontrol_en.mq5 (20.25 KB)
Comment faire appel aux indicateurs dans MQL5 Comment faire appel aux indicateurs dans MQL5
Avec la nouvelle version du langage de programmation MQL disponible, non seulement l'approche du traitement des indicateurs a changé, mais il existe également de nouvelles façons de créer des indicateurs. De plus, vous disposez d'une flexibilité supplémentaire en travaillant avec les tampons d'indicateurs - vous pouvez désormais spécifier la direction d'indexation souhaitée et obtenir exactement autant de valeurs d'indicateurs que vous le souhaitez. Cet article explique les méthodes de base pour faire appel aux indicateurs et récupérer des données à partir des tampons de l'indicateur.
Indicateurs personnalisés dans MQL5 pour débutants Indicateurs personnalisés dans MQL5 pour débutants
Tout nouveau sujet semble compliqué et difficile à apprendre pour un débutant. Les sujets que nous connaissons nous semblent très simples et clairs. Mais nous oublions simplement que nous avons tous dû étudier quelque chose à partir de zéro, et même notre langue maternelle. Il en va de même avec le langage de programmation MQL5 qui offre de larges possibilités de développer ses propres stratégies de trading - vous pouvez commencer à l'apprendre à partir de notions de base et d'exemples les plus simples. L'interaction d'un indicateur technique avec le terminal client MetaTrader 5 est envisagée dans cet article sur l'exemple de l'indicateur personnalisé simple SMA.
Les styles de dessin dans MQL5 Les styles de dessin dans MQL5
Il existe 6 styles de dessin dans MQL4 et 18 styles de dessin dans MQL5. Par conséquent, il peut être intéressant d'écrire un article pour présenter les styles de dessin de MQL5. Dans cet article, nous examinerons les détails des styles de dessin dans MQL5. De plus, nous allons créer un indicateur pour montrer comment utiliser ces styles de dessin et affiner le tracé.
Utilisation des pointeurs d'objet dans MQL5 Utilisation des pointeurs d'objet dans MQL5
Par défaut, tous les objets de MQL5 sont transmis par référence, mais il est possible d'utiliser les pointeurs d'objet. Cependant, il est nécessaire d'effectuer la vérification du pointeur, car l'objet peut ne pas être initialisé. Dans ce cas, le programme MQL5 sera terminé avec une erreur critique et déchargé. Les objets, créés automatiquement, ne provoquent pas une telle erreur, donc dans ce sens, ils sont tout à fait sûrs. Dans cet article, nous allons essayer de comprendre la différence entre la référence d'objet et le pointeur d'objet, et d'examiner comment écrire du code sécurisé qui utilise les pointeurs.