English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Trading bidirectionnel et couverture des positions dans MetaTrader 5 à l’aide du HedgeTerminalAPI, Partie 2

Trading bidirectionnel et couverture des positions dans MetaTrader 5 à l’aide du HedgeTerminalAPI, Partie 2

MetaTrader 5Systèmes de trading | 13 janvier 2022, 16:09
316 0
Vasiliy Sokolov
Vasiliy Sokolov

Table des matières


Introduction

Cet article est la suite de la première partie «Trading bidirectionnel et couverture des positions dans MetaTrader 5 à l’aide du panneau HedgeTerminal, Partie 1» Dans la deuxième partie, nous aborderons l'intégration d'Expert Advisors et d'autres programmes MQL5 avec la bibliothèque HedgeTerminalAPI. Lisez cet article pour apprendre à travailler avec la bibliothèque. Il vous aidera à créer des Expert Advisors en trading bidirectionnel tout en travaillant dans un environnement de trading simple et confortable.

En plus de la description de la bibliothèque, l'article aborde les principes fondamentaux du trading asynchrone et de la programmation multithread. Ces descriptions sont données dans les troisième et quatrième sections de cet article. Par conséquent, ce matériel sera utile pour les traders qui ne sont pas intéressés par le trading bidirectionnel, mais qui souhaitent découvrir quelque chose de nouveau sur la programmation asynchrone et multithread.

Le matériel présenté ci-dessous est destiné aux traders algorithmiques expérimentés qui connaissent le langage de programmation MQL5. Si vous ne connaissez pas MQL5, veuillez lire la première partie de l'article, qui contient des schémas et des dessins simples expliquant le principe général de la bibliothèque et du panneau HedgeTerminal.


Chapitre 1. Interaction des Expert Advisors avec HedgeTerminal et son panneau

1.1. Installation de HedgeTermianlAPI. Le premier démarrage de la bibliothèque

Le processus d'installation de HedgeTerminalAPI diffère de l'installation du panneau visuel HT, car la bibliothèque ne peut pas s'exécuter seule dans MetaTrader 5. Au lieu de cela, vous devrez développer un Expert Advisor spécial pour appeler la fonction HedgeTerminalInstall() à partir de la bibliothèque. Cette fonction définira un fichier d'en-tête spécial Prototypes.mqh décrivant les fonctions disponibles dans HedgeTerminalAPI.

Une bibliothèque s'installe sur un ordinateur en trois étapes :

Étape 1. Téléchargez la bibliothèque HedgeTerminalAPI sur votre ordinateur. Emplacement de la bibliothèque par rapport à votre terminal : \MQL5\Experts\Market\HedgeTerminalApi.ex5.

Étape 2. Créez un nouvel Expert Advisor dans l'assistant MQL5 à l'aide d'un modèle standard. L'assistant MQL5 génère le code source suivant :

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }

Étape 3. Vous n'aurez besoin que d'une seule fonction de l'Expert Advisor résultant - OnInit(), et de la directive d'exportation décrivant la fonction d'installation spéciale HedgeTerminalInstall() exportée par la bibliothèque HedgeTerminalApi. Exécutez cette fonction directement dans la fonction OnInit(). Le code source marqué en jaune effectue ces opérations :

//+------------------------------------------------------------------+
//|                                   InstallHedgeTerminalExpert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#import "HedgeTerminalAPI.ex5"
   void HedgeTerminalInstall(void);
#import

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   HedgeTerminalInstall();
   ExpertRemove();   
//---
   return(INIT_SUCCEEDED);
  }

Étape 4. Vos actions ultérieures dépendent de l'achat ou non de la bibliothèque. Si vous l'avez acheté, vous pouvez exécuter l'Expert Advisor en temps réel directement sur le graphique. Cela lancera le programme d'installation standard de toute la gamme de produits de HedgeTerminal. Vous pouvez facilement effectuer cette étape en suivant les instructions décrites dans les sections 2.1 et 2.2 de l'article «Trading bidirectionnelle et couverture de positions dans MetaTrader 5 à l'aide du panneau HedgeTerminal, Partie 1». L'assistant d'installation installe tous les fichiers requis, y compris le fichier d'en-tête et l'Expert Advisor de test sur votre ordinateur.

Si vous n'avez pas acheté la bibliothèque et que vous souhaitez uniquement la tester, le fonctionnement de l’EA en temps réel ne sera pas disponible pour vous, mais vous pouvez tester l'API en exécutant l'EA dans le testeur de stratégie. Le programme d'installation ne s'exécutera pas dans ce cas. En mode test, HedgeTermianalAPI fonctionne en mode mono-utilisateur, il n'a donc pas besoin des fichiers installés en mode normal. Cela signifie que vous n'avez pas besoin de configurer quoi que ce soit d'autre.

Dès que le test de l’EA est terminé, le dossier \HedgeTerminal est créé dans le dossier commun du terminal. Le chemin normal vers le répertoire commun des terminaux MetaTrader est c:\Users\<Username>\AppData\Roaming\MetaQuotes\Terminal\Common\Files\HedgeTerminal\, où <Username> est le nom de votre compte d'ordinateur actuel. Le dossier \HedgeTerminal contient déjà les fichiers \MQL5\Include\Prototypes.mqh et \MQL5\Experts\Chaos2.mq5. Copiez ces fichiers dans les mêmes répertoires de votre terminal : fichier Prototypes.mqh dans \MetaTrader5\MQL5\Include, et fichier Chaos2.mq5 dans \MetaTrader5\MQL5\Experts.

Le fichier Prototypes.mqh est un fichier d'en-tête contenant la description des fonctions exportées depuis la bibliothèque HedgeTerminalAPI. Leur objectif et leurs descriptions sont contenus dans les commentaires qui leur sont adressés.

Le fichier Chaos2.mq5 contient un exemple d'EA décrit dans la section « La fonction SendTradeRequest et la structure HedgeTradeRequest à travers l'exemple de Chaos II EA». De cette façon, vous pouvez comprendre visuellement comment fonctionne HedgeTerminalAPI et comment développer un Expert Advisor en utilisant la technologie de virtualisation de HedgeTerminal.

Les fichiers copiés sont disponibles pour vos EA. Il vous suffit donc d'inclure le fichier d'en-tête dans le code source de l'Expert Advisor pour commencer à utiliser la bibliothèque. Voici un exemple :

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int transTotal = TransactionsTotal();
   printf((string)transTotal);
  }

Par exemple, le code ci-dessus obtient le nombre total de positions actives et affiche le nombre dans l'onglet « Experts » du terminal MetaTrader 5.

Il est important de comprendre que HedgeTerminal est en fait initialisé au premier appel d'une de ses fonctions. Cette initialisation s'appelle lazy. Par conséquent, le premier appel d'une de ses fonctions peut prendre beaucoup de temps. Si vous souhaitez une réponse rapide lors de la première exécution, vous devez initialiser HT au préalable, par exemple, vous pouvez appeler la fonction TransactionTotal() dans le bloc de OnInit().

Avec l'initialisation lente, vous pouvez omettre l'initialisation explicite de l'Expert Advisor. Cela simplifie grandement le travail avec HedgeTerminal et rend inutile sa préconfiguration.


1.2. Intégration des Expert Advisors dans le panneau de HedgeTerminal

Si vous disposez du panneau visuel HedgeTerminal et de la version complète de la bibliothèque, qui peut être exécutée en temps réel, vous pouvez intégrer vos Expert Advisors au panneau afin que toutes les opérations de trade qu'ils effectuent apparaissent également dans le panneau. En général, l'intégration n'est pas visible. Si vous utilisez les fonctions HedgeTermianalAPI, les actions effectuées par les robots sont automatiquement affichées sur le panneau. Cependant, vous pouvez étendre la visualisation en indiquant le nom de l’EA dans chaque transaction validée. Vous pouvez le faire en supprimant les commentaires de la ligne ci-dessous dans le fichier Settings.xml:

<Column ID="Magic" Name="Magic" Width="100"/>

Cette balise se trouve dans les sections <Active-Position> et <History-Position>.

Désormais, les commentaires sont supprimés et les balises sont incluses dans le traitement. Après le redémarrage du panneau, une nouvelle colonne de « Magic » apparaîtra dans les tables des positions actives et historiques. La colonne contient le nombre magique de l'Expert Advisor auquel appartient la position.

Si vous souhaitez afficher le nom de l’EA au lieu de son numéro magique, ajoutez le nom au fichier d'alias ExpertAliases.xml. Par exemple, le numéro magique d'un EA est 123847, et vous souhaitez afficher son nom, comme « ExPro 1.1 », ajoutez la balise suivante au fichier :

<Expert Magic="123847" Name="ExPro 1.1"></Expert>

Si c'est fait correctement, le nom de l’EA sera affiché à la place de son numéro magique dans la colonne appropriée :

Fig. 1.  Afficher le nom de l’EA au lieu de son Magic

Fig. 1. Afficher le nom de l’EA au lieu de son Magic

Notez que le panneau et les Expert Advisors communiquent en temps réel. Cela signifie que si vous fermez la position d'un EA directement sur le panneau, l'EA le saura lors du prochain appel de la fonction TransactionsTotal(). Et vice versa : une fois que l'Expert Advisor a fermé sa position, il disparaît immédiatement de l'onglet des positions actives.


1.3. Principes généraux du fonctionnement de HedgeTerminalAPI

En plus des positions bidirectionnelles, HedgeTerminal fonctionne également avec d'autres types de trading, tels que les ordres en attente, les transactions et les opérations de broker. HedgeTerminal traite tous ces types comme un seul groupe de transactions. Une transaction, un ordre en attente, une position bidirectionnelle - tous sont des transactions. Cependant, une transaction ne peut pas exister seule. En termes de programmation orientée objet, une transaction peut être présentée comme une classe de base abstraite, à partir de laquelle toutes les entités possibles du trading, telles que les transactions et les positions bidirectionnelles sont héritées. À cet égard, toutes les fonctions de HedgeTerminalAPI peuvent être divisées en plusieurs groupes :

  1. Fonctions de recherche et de sélection de transactions. La signature commune des fonctions et leur mode de fonctionnement coïncident presque entièrement avec les fonctions OrderSend() et OrderSelect() dans MetaTrader 4 ;
  2. Fonctions d'obtention des propriétés d'une transaction sélectionnée Chaque transaction a un ensemble spécifique de propriétés et de fonctions spécifiques pour la sélection des propriétés. La signature commune des fonctions et la façon dont elles fonctionnent ressemblent aux fonctions du système MetaTrader 5 dans la façon dont elles accèdent aux positions, aux transactions et aux ordres (comme OrderGetDouble() ou HistoryDealGetInteger()) ;
  3. HedgeTerminalAPI n'utilise qu'une seule fonction de trading : SendTradeRequest(). Cette fonction permet de fermer une position bidirectionnelle ou une partie de celle-ci. La même fonction est utilisée pour modifier le stop loss, le take profit ou le commentaire sortant. Travailler avec la fonction est semblable à OrderSend() dans MetaTrader 5 ;
  4. La fonction d’obtention des erreurs courantes GetHedgeError(), les fonctions pour l'analyse détaillée des actions de trading HedgeTerminal : TotalActionsTask() et GetActionResult(). Également utilisées pour la détection d'erreurs. Il n'y a pas d'analogues dans MetaTrader 4 ou MetaTrader 5.

Travailler avec presque toutes les fonctions est similaire à l'utilisation des fonctions système de MetaTrader 4 et MetaTrader 5. En règle générale, l'entrée de la fonction est un identifiant (valeur d'énumération) et la fonction renvoie une valeur qui lui correspond.

Des énumérations spécifiques sont disponibles pour chaque fonction. La signature d'appel commune est la suivante :

<value> = Function(<identifier>);

Considérons un exemple d'obtention d'un identifiant de position unique. Voici à quoi cela ressemble dans MetaTrader 5 :

ulong id = PositionGetInteger(POSITION_IDENTIFIER);

Dans HedgeTerminal, la réception d'un identifiant unique d'une position bidirectionnelle est la suivante :

ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)

Les principes généraux des fonctions sont les mêmes. Seuls les types d'énumérations diffèrent.


1.4. Sélection des transactions

La sélection d'une transaction passe par la liste des transactions, ce qui est similaire à la recherche d'ordres dans MetaTrader 4. Cependant, dans MetaTrader 4, seuls les ordres sont recherchés, tandis que dans HedgeTerminal, tout peut être trouvé en tant que transaction - comme un ordre en attente ou une position de couverture. Par conséquent, chaque transaction doit d'abord être sélectionnée à l'aide de la fonction TransactionSelect(), puis son type doit être identifié via TransactionType().

Deux listes de transactions sont disponibles à ce jour : les transactions actives et historiques. La liste à appliquer est définie en fonction du modificateur ENUM_MODE_TRADES. Il est similaire au modificateur MODE_TRADES de MetaTrader 4.

L'algorithme de recherche et de sélection des transactions est le suivant :

1: for(int i=TransactionsTotal(MODE_TRADES)-1; i>=0; i--)
2:     {
3:      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))continue;
4:      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
5:      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
6:      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
7:      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN)continue;
8:      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID)
9:     }

Le code parcourt la liste des transactions actives dans le cycle pour (ligne 1). Avant de procéder à la transaction, sélectionnez-la à l'aide de TransactionSelect() (ligne 3). Seules les positions bidirectionnelles sont sélectionnées parmi ces transactions (ligne 4). Si le nombre magique de la position et son symbole ne correspondent pas au nombre magique de l'EA actuel et du symbole sur lequel il tourne, HT passe à la position suivante (lignes 5 et 6). Ensuite, il définit l'identifiant de position unique (ligne 8).

Une attention particulière doit être portée à la ligne 7. Les positions sélectionnées doivent être vérifiées en termes de possibilité de modification. Si la position est déjà en cours de modification, elle ne peut pas être modifiée dans le thread actuel, bien que vous puissiez obtenir l'une de ses propriétés. Si la position est verrouillée, mieux vaut attendre qu'elle soit libérée pour accéder à ses propriétés ou réessayer de la modifier. La propriété HEDGE_POSITION_STATE permet de savoir si une modification de position est possible.

Le modificateur POSITION_STATE_FROZEN indique que la position est « gelée » et ne peut pas être modifiée. Le modificateur POSITION_STATE_ACTIVE indique qu'une position est active et peut être modifiée. Ces modificateurs sont répertoriés dans l'énumération ENUM_HEDGE_POSITION_STATE, qui est documentée dans la section appropriée.

Si une recherche dans les transactions historiques est nécessaire, le modificateur MODE_TRADES dans les fonctions TransactionTotal() et TransactionSelect() doit être remplacé par MODE_HISTORY.

Une transaction dans HedgeTerminal peut être imbriquée dans une autre. C'est très différent du concept de MetaTrader 5 où il n'y a pas d'imbrication. Par exemple, la position bidirectionnelle historique dans HedgeTerminal se compose de deux ordres, dont chacun comprend un ensemble arbitraire de transactions. L'imbrication peut être représentée comme suit :

Fig. 2. Transactions imbriquées

Fig. 2. Transactions imbriquées

L'imbrication des transactions est clairement visible dans le panneau visuel HedgeTerminal.

La capture d'écran ci-dessous montre les détails d'une position de MagicEx 1.3 :

Fig. 3. Transactions imbriquées dans le panneau HedgeTerminal

Fig. 3. Transactions imbriquées dans le panneau HedgeTerminal

Vous pouvez accéder aux propriétés d'un ordre particulier ou d'une transaction dans la position bidirectionnelle.

Pour faire cela :

  1. Sélectionnez une transaction historique et assurez-vous qu'il s'agit d'une position bidirectionnelle ;
  2. Sélectionnez l'un des ordres de cette position en utilisant HedgeOrderSelect();
  3. Obtenez une des propriétés de l’ordre sélectionné : le nombre de transactions qu'il contient ;
  4. Sélectionnez l'une des transactions appartenant à l’ordre en recherchant toutes les transactions ;
  5. Obtenez la propriété de transaction requise.

Notez qu'une fois la transaction sélectionnée, ses propriétés spécifiques lui sont accessibles. Par exemple, si la transaction est un ordre, après sélection via HedgeOrderSelect(), vous pouvez trouver le nombre de transactions lui appartenant (HedgeOrderGetInter(HEDGE_ORDER_DEALS_TOTAL)) ou le prix d'entrée moyen pondéré (HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED)).

Découvrons le prix de la transaction #1197610, qui est marqué en rouge sur la capture d'écran. Cette transaction fait partie de la position bidirectionnelle de l’EA MagicEx 1.3.

Grâce au code ci-dessous, l'EA peut accéder à sa position et à cette transaction :

#include <Prototypes.mqh>

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
    {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // Select transaction #i;
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;                 // If transaction is not position - continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;  // If position is not main - continue;
      ulong id = HedgePositionGetInteger(HEDGE_POSITION_ENTRY_ORDER_ID);   // Get id for closed order;
      if(id!=5917888)continue;                                             // If id of position != 5917888 - continue;
      printf("1: -> Select position #"+(string)id);                        // Print position id;
      if(!HedgeOrderSelect(ORDER_SELECTED_CLOSED))continue;                // Select closed order or continue;    
      ulong order_id = HedgeOrderGetInteger(HEDGE_ORDER_ID);               // Get id closed order;
      printf("2: ----> Select order #" + (string)order_id);                // Print id closed order;
      int deals_total = (int)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);// Get deals total in selected order;
      for(int deal_index = deals_total-1; deal_index >= 0; deal_index--)   // Search deal #1197610...
        {
         if(!HedgeDealSelect(deal_index))continue;                         // Select deal by index or continue;
         ulong deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);               // Get id for current deal;
         if(deal_id != 1197610)continue;                                   // Select deal #1197610;
         double price = HedgeDealGetDouble(HEDGE_DEAL_PRICE_EXECUTED);     // Get price executed;
         printf("3: --------> Select deal #"+(string)deal_id+              // Print price excecuted;
              ". Executed price = "+DoubleToString(price,0));
        }
     }
  }

Après l'exécution du code, l'entrée suivante sera créée dans l'onglet Experts du terminal MetaTrader 5 :

2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      3: --------> Select deal #1197610. Executed price = 4735
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      2: ----> Select order #6389111
2014.10.21 14:46:37.545 MagicEx1.3 (VTBR-12.14,D1)      1: -> Select position #5917888

L'EA sélectionne d'abord la position #5917888, puis sélectionne l’ordre #6389111 à l'intérieur de la position. Une fois l’ordre sélectionné, l'EA commence à rechercher le numéro de transaction 1197610. Lorsque la transaction est trouvée, l'EA obtient son prix d'exécution et ajoute le prix dans le journal.


1.5. Comment obtenir des codes d'erreur à l'aide de GetHedgeError()

Des erreurs et des situations imprévues peuvent survenir lors de l'utilisation de l'environnement HedgeTerminal. Des fonctions d'obtention et d'analyse d'erreurs sont utilisées dans ces cas.

Le cas le plus simple où vous obtenez une erreur est lorsque vous oubliez de sélectionner une transaction à l'aide de la fonction TransactionSelect(). La fonction TransactionType() renverra le modificateur TRANS_NOT_DEFINED dans ce cas.

Pour comprendre où se situe le problème, nous devons obtenir le modificateur de la dernière erreur. Le modificateur nous dira que maintenant la transaction a été sélectionnée. Le code suivant fait cela :

for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
  {
   //if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;        // forgot to select;
   ENUM_TRANS_TYPE type = TransactionType();
   if(type == TRANS_NOT_DEFINED)
   {
      ENUM_HEDGE_ERR error = GetHedgeError();
      printf("Error, transaction type not defined. Reason: " + EnumToString(error));
   }
  }

Voici le message résultant :

Error, transaction type not defined. Reason: HEDGE_ERR_TRANS_NOTSELECTED

L'identifiant d'erreur suggère que nous avons oublié de sélectionner une transaction avant d'essayer d'obtenir son type.

Toutes les erreurs possibles sont répertoriées dans la structure ENUM_HEDGE_ERR.


1.6. Analyse détaillée du trading et identification des erreurs à l'aide de TotalActionsTask() et GetActionResult()

En plus des erreurs survenant lors du travail avec l'environnement HedgeTerminal, des erreurs de trade peuvent survenir à la suite de l'appel SendTradeRequest(). Ces types d'erreurs sont plus difficiles à traiter. Une tâche effectuée par SendTradeRequest() peut contenir plusieurs activités de trading (sous-tâches). Par exemple, pour modifier le commentaire sortant dans une position active protégée par un niveau stop loss, vous devez effectuer deux actions de trading :

  1. Annuler l'ordre stop en attente représentant le niveau stop ;
  2. Passez un nouvel ordre stop en attente avec un nouveau commentaire à la place de l'ordre précédent.

Si le nouvel ordre stop se déclenche, alors son commentaire sera affiché comme un commentaire fermant la position, ce qui est une manière correcte.

Cependant, la tâche peut être exécutée en partie. Supposons que l’ordre en attente soit annulé avec succès, mais que le nouvel ordre échoue pour une raison quelconque. Dans ce cas, la position sera laissée sans le niveau de stop loss. Afin de pouvoir gérer cette erreur, l'EA devra appeler un journal des tâches spécial et y rechercher la sous-tâche qui a échoué.

Cela se fait à l'aide de deux fonctions : TotalActionsTask() renvoie le nombre total d'actions de trading (sous-tâches) dans cette tâche ; et GetActionResult() accepte l'index des sous-tâches et renvoie son type et son résultat d'exécution. Étant donné que toutes les opérations de trading sont effectuées à l'aide des outils standards de MetaTrader 5, le résultat de leur performance correspond au code de retour du serveur de trading.

En général, l'algorithme de recherche de la cause de l’échec est le suivant :

  1. Obtenir le nombre total de sous-tâches dans la tâche à l'aide de TotalActionsTask() ;
  2. Recherche dans toutes les sous-tâches de la boucle pour. Déterminer le type de chaque sous-tâche et son résultat.

Supposons que l'ordre stop avec un nouveau commentaire ne puisse pas être placé car le prix d'exécution de l'ordre était trop proche du niveau du prix actuel.

L'exemple de code ci-dessous montre comment l'EA pourrait trouver la cause de cet échec :

#include <Prototypes.mqh> 

ulong Magic=5760655; // MagicEx 1.3.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnTick()
  {
//detect active position
   for(int i=TransactionsTotal(MODE_HISTORY)-1; i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_HISTORY))continue;
      ENUM_TRANS_TYPE type=TransactionType();
      if(type==TRANS_NOT_DEFINED)
        {
         ENUM_HEDGE_ERR error=GetHedgeError();
         printf("Error, transaction not defined. Reason: "+EnumToString(error));
        }
      if(TransactionType()!=TRANS_HEDGE_POSITION)continue;
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)continue;
      if(HedgePositionGetString(HEDGE_POSITION_SYMBOL) != Symbol())continue;
      HedgeTradeRequest request;
      request.action=REQUEST_MODIFY_COMMENT;
      request.exit_comment="My new comment";
      if(!SendTradeRequest(request)) // Is error?
        {
         for(uint action=0; action < TotalActionsTask(); action++)
           {
            ENUM_TARGET_TYPE typeAction;
            int retcode=0;
            GetActionResult(action, typeAction, retcode);
            printf("Action#" + (string)action + ": " + EnumToString(type) +(string)retcode);
           }
        }
     }
  }

Le message suivant apparaîtra après l'exécution du code :

Action #0 TARGET_DELETE_PENDING_ORDER 10009 (TRADE_RETCODE_PLACED)
Action #1 TARGET_SET_PENDING_ORDER 10015 (TRADE_RETCODE_INVALID_PRICE)

En comparant les chiffres avec les modificateurs standard des codes de retour du serveur de trade, nous découvrons que l’offre en attente a été supprimé avec succès, mais que la passation d'un nouvel ordre a échoué. Le serveur de trading a renvoyé une erreur 10015 (prix incorrect), ce qui peut signifier que le prix actuel est trop proche du niveau d'arrêt.

Sachant cela, l'EA peut prendre le contrôle des niveaux d'arrêt. Pour ce faire, l'EA n'aura qu'à fermer cette position en utilisant la même fonction SendTradeRequest().


1.7. Suivi de l'état de l'exécution des tâches du trade

Chaque tâche de trading peut consister en un nombre quelconque de sous-tâches qui doivent être effectuées de manière séquentielle.

En mode asynchrone, une tâche peut être exécutée en plusieurs passages du code. Il peut également y avoir des cas où la tâche peut « se figer ». Par conséquent, le contrôle de l'EA sur l'exécution des tâches est requis. Lors de l'appel de la fonction HedgePositionGetInteger() avec le modificateur HEDGE_POSITION_TASK_STATUS, cela renvoie l'énumération de type ENUM_TASK_STATUS contenant l'état de la tâche de la position actuelle.

Par exemple, si quelque chose ne va pas après l'envoi d'un ordre de fermeture d'une position, en raison duquel la position n'est pas fermée, vous devez alors obtenir le statut de la tâche.

L'exemple suivant montre le code qu'un Expert Advisor asynchrone peut exécuter pour analyser le statut de la tâche pour la position :

ENUM_TASK_STATUS status=HedgePositionGetInteger(HEDGE_POSITION_TASK_STATUS);
switch(status)
  {
   case TASK_STATUS_COMPLETE:
      printf("Task complete!");
      break;
   case TASK_STATUS_EXECUTING:
      printf("Task executing. Waiting...");
      Sleep(200);
      break;
   case TASK_STATUS_FAILED:
      printf("Filed executing task. Print logs...");
      for(int i=0; i<TotalActionsTask(); i++)
        {
         ENUM_TARGET_TYPE type;
         uint retcode;
         GetActionResult(i,type,retcode);
         printf("#"+i+" "+EnumToString(type)+" "+retcode);
        }
      break;
   case TASK_STATUS_WAITING:
      printf("task will soon start.");
      break;
  }

Notez que l'exécution de certaines tâches complexes nécessite plusieurs itérations.

En mode asynchrone, les événements à venir qui signalent des changements dans l'environnement du trading commencent une nouvelle itération. Ainsi, toutes les itérations sont effectuées sans délai, les unes après les autres, suite aux réponses reçues du serveur de trading. L'exécution de la tâche diffère en mode synchrone.

La méthode synchrone utilise l'émulateur d'opération synchrone, grâce auquel les utilisateurs peuvent effectuer même des tâches composites en un seul passage. L'émulateur utilise des décalages temporels. Par exemple, après le début de l'exécution d'une sous-tâche, l'émulateur ne renvoie pas le thread d'exécution à l'EA. Au lieu de cela, il attend un certain temps pour s'attendre à ce que l'environnement du trading change. Après cela, il relit à nouveau l'environnement du trading. S'il comprend que la sous-tâche a été effectuée avec succès, il démarre les sous-tâches suivantes.

Ce processus réduit quelque peu les performances globales, car il faut un certain temps d'attente. Mais il transforme l'exécution de tâches même complexes en une opération séquentielle assez simple effectuée dans un seul appel de fonction. Par conséquent, vous n'avez presque jamais besoin d'analyser le journal d'exécution des tâches dans la méthode synchrone.


1.8. Comment modifier et fermer des positions bidirectionnelles

Les positions bidirectionnelles sont modifiées et fermées à l'aide de la fonction SendTradeRequest(). Seules trois options peuvent être appliquées à une position active :

  1. Une position peut être entièrement ou partiellement fermée ;
  2. Les positions stop loss et take profit peuvent être modifiées ;
  3. Le commentaire sortant d'une position peut être modifié.

La position historique ne peut pas être modifiée. Semblable à la fonction OrderSend() de MetaTrader 5, SendTradeRequest() utilise une requête pré-compilée sous la forme d'une structure de trading HedgeTraderRequest. Lisez la documentation pour plus de détails sur la fonction SendTradeRequest() et la structure HedgeTraderRequest. L'exemple indiquant la modification de position et la fermeture est disponible dans la section sur Chaos II EA.


1.9. Comment définir les propriétés HedgeTerminal à partir d'un Expert Advisor

HedgeTerminal possède un ensemble de propriétés, telles que la fréquence d’actualisation, le nombre de secondes d'attente d'une réponse du serveur et autres.

Toutes ces propriétés sont définies dans Settings.xml. Lorsqu'un EA s'exécute en temps réel, la bibliothèque lit les propriétés du fichier et définit les paramètres internes appropriés. Lorsque l'EA est testé sur un graphique, le fichier Settings.xml n'est pas utilisé. Cependant, dans certaines situations, vous devrez peut-être modifier individuellement les propriétés EA, qu'elles soient exécutées sur un graphique ou dans le testeur de stratégie.

Cela se fait via l'ensemble spécial de fonctions HedgePropertySet… La version actuelle ne comporte qu'un seul prototype de cet ensemble :

enum ENUM_HEDGE_PROP_INTEGER
{
   HEDGE_PROP_TIMEOUT,
};

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, int value)

Par exemple, pour définir le délai d'attente de la bibliothèque dans l’attente d’une réponse du serveur, écrivez ce qui suit :

bool res = HedgePropertySetInteger(HEDGE_PROP_TIMEOUT, 30);

Si la réponse du serveur n'est pas reçue dans les 30 secondes suivant l'envoi d'une demande asynchrone, la position verrouillée sera libérée.


1.10. Modes de fonctionnement synchrone et asynchrone

HedgeTerminal et son API effectuent des activités de trading de manière totalement asynchrone.

Cependant, ce mode nécessite une logique plus complexe des EA. Pour masquer cette complexité, HedgeTerminalAPI comprend un émulateur spécial de fonctionnement synchrone, permettant aux EA développés selon une méthode synchrone conventionnelle de communiquer avec les algorithmes asynchrones de HedgeTerminalAPI. Cette interaction est révélée au moment de la modification et de la fermeture de position bidirectionnelle via SendTradeRequest(). Cette fonction permet d'exécuter une tâche de trade en mode synchrone ou asynchrone. Par défaut, toutes les actions de trading sont exécutées de manière synchrone via l'émulateur d'opération synchrone. Cependant, si une demande de trade (structure de HedgeTradeRequest) contient un indicateur explicitement spécifié asynch_mode = true, la tâche de trade sera exécutée en mode asynchrone.

En mode asynchrone, les tâches sont effectuées indépendamment du thread principal. La mise en œuvre de l'interaction entre un EA asynchrone et les algorithmes asynchrones de HedgeTerminal n'est pas encore terminée.

L'émulateur synchrone est très simple. Il démarre les sous-tâches de manière séquentielle, puis attend un certain temps jusqu'à ce que l'environnement du trading dans MetaTrader 5 change. L'émulateur analyse ces changements et détermine l'état de la tâche en cours. Si l'exécution de la tâche est réussie, l'émulateur passe à la suivante.

L'émulateur synchrone provoque des retards mineurs dans l'exécution des ordres de trading. Cela est dû au fait que l'environnement de trading dans MetaTrader 5 prend un certain temps pour refléter les activités de trading exécutées. La nécessité d'accéder à l'environnement est principalement liée au fait que HedgeTermianlAPI ne peut pas accéder aux événements allant vers le gestionnaire OnTradeTransaction() en mode d'émulation de thread synchrone.

Les problèmes d'interaction entre les threads asynchrones, ainsi qu'entre les threads asynchrones et synchrones par émulation sont trop compliqués et n'ont pas de solutions évidentes.


1.11. Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script

Dans le script ci-dessous, la fonction TransactionSelect() recherche toutes les transactions disponibles dans la liste des transactions actives.

Chaque transaction est sélectionnée dans la liste. Si la transaction est une position, certaines de ses propriétés sont accessibles puis imprimées. En plus des propriétés des positions, les propriétés des ordres et des transactions à l'intérieur de la position sont également imprimées. Un ordre et une transaction sont d'abord sélectionnés à l'aide de HedgeOrderSelect() et HedgeDealSelect() respectivement.

Toutes les propriétés de la position, ses ordres et ses transactions sont combinées et imprimées sur une seule ligne à l'aide de la fonction système printf.

//+------------------------------------------------------------------+
//|                                           sample_using_htapi.mq5 |
//|         Copyright 2014, Vasiliy Sokolov, Russia, St.-Petersburg. |
//|                              https://login.mql5.com/ru/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

// Include prototypes function of HedgeTerminalAPI library.
#include <Prototypes.mqh> 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+ 
void OnStart()
  {
   // Search all transaction in list transaction...
   for(int i=TransactionsTotal(); i>=0; i--)
     {
      if(!TransactionSelect(i,SELECT_BY_POS,MODE_TRADES))                           // Selecting from active transactions
        {
         ENUM_HEDGE_ERR error=GetHedgeError();                                      // Get reason if selecting has failed
         printf("Error selecting transaction # "+(string)i+". Reason: "+            // Print reason
                EnumToString(error));
         ResetHedgeError();                                                         // Reset error
         continue;                                                                  // Go to next transaction
        }
      // Only for hedge positions
      if(TransactionType()==TRANS_HEDGE_POSITION) 
        {
         // --- Position captions --- //
         ENUM_TRANS_DIRECTION direction=(ENUM_TRANS_DIRECTION)                      // Get direction caption
                              HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
         double price_entry = HedgeOrderGetDouble(HEDGE_ORDER_PRICE_EXECUTED);      // Get volume of positions
         string symbol = HedgePositionGetString(HEDGE_POSITION_SYMBOL);             // Get symbol of position
         // --- Order captions --- //
         if(!HedgeOrderSelect(ORDER_SELECTED_INIT))continue;                        // Selecting init order in position
         double slippage = HedgeOrderGetDouble(HEDGE_ORDER_SLIPPAGE);               // Get some slippage was
         uint deals_total = (uint)HedgeOrderGetInteger(HEDGE_ORDER_DEALS_TOTAL);    // Get deals total
         // --- Deals captions --- //
         double commissions=0.0;
         ulong deal_id=0;
         //Search all deals in list deals...
         for(uint d_index=0; d_index<deals_total; d_index++)                        
           {
            if(!HedgeDealSelect(d_index))continue;                                  // Selecting deal by its index
            deal_id = HedgeDealGetInteger(HEDGE_DEAL_ID);                           // Get deal id
            commissions += HedgeDealGetDouble(HEDGE_DEAL_COMMISSION);               // Count commissions
           }
         int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
         printf("Position #" + (string)i + ": DIR " + EnumToString(direction) +     // Print result line
         "; PRICE ENTRY " + DoubleToString(price_entry, digits) + 
         "; INIT SLIPPAGE " + DoubleToString(slippage, 2) + "; LAST DEAL ID " +
         (string)deal_id + "; COMMISSIONS SUM " + DoubleToString(commissions, 2));
        }
     }
  }

1.12. La fonction SendTradeRequest() et la structure HedgeTradeRequest à travers l'exemple de Chaos II EA

À titre d'exemple, développons un robot de trading basé sur les tactiques de trading proposées par Bill Williams dans son livre Trading Chaos. Deuxième édition.

Nous ne suivrons pas toutes ses recommandations, mais simplifierons le schéma en omettant l'indicateur Alligator et quelques autres conditions. Le choix de cette stratégie découle de plusieurs considérations. La principale est que cette stratégie inclut des tactiques de gestion de position composites. Parfois, vous devez fermer une partie d'une position et déplacer le stop loss vers le seuil de rentabilité.

Lorsqu'il est déplacé vers le seuil de rentabilité, le niveau d'arrêt doit être suivi après le prix. La deuxième considération est que cette tactique est suffisamment connue et que les indicateurs développés pour elle sont inclus dans le pack de livraison standard de MetaTrader 5. Modifions et simplifions légèrement les règles, pour éviter que la logique complexe de l'Expert Advisor n'entrave ses objectifs premiers : montrer un exemple d'interaction de l’EA avec la bibliothèque HedgeTerminalAPI. La logique de l'EA utilise la plupart des fonctions de trading de HedgeTerminalAPI. C'est un bon test pour la bibliothèque.

Commençons par la barre de retournement. Une barre de retournement haussière est une barre dont le cours de fermeture se situe dans son tiers supérieur, dont le plus bas est le plus bas des N dernières barres. Une barre de retournement baissière est une barre dont le cours de fermeture se situe dans son tiers inférieur, dont le plus haut est le plus haut des N dernières barres. N est un paramètre choisi au hasard, il peut être défini au démarrage de l'Expert Advisor. Cela diffère de la stratégie classique « Chaos 2 ».

Une fois la barre d'inversion définie, deux ordres en attente sont passés. Pour une barre haussière, les ordres sont passés au-dessus de son plus haut, une barre baissière - juste en dessous de son plus bas. Si ces deux ordres ne se déclenchent pas pendant les barres OldPending, le signal est considéré comme obsolète, et les ordres sont annulés. Les valeurs de OldPending et N sont définies par l'utilisateur avant de lancer l'EA sur le graphique.

Les ordres se déclenchent et se transforment en deux positions bidirectionnelles. L'EA les distingue par des numéros dans les commentaires, « # 1 » et « # 2 », respectivement. Ce n'est pas une solution très élégante, mais c'est bien à des fins de démonstration. Une fois les ordres déclenchés, un stop loss est placé au plus haut (pour une barre baissière) ou au plus bas (si la barre est haussière) de la barre de retournement.

La première position a des objectifs serrés. Son take profit est fixé de telle sorte qu'en cas de déclenchement, le profit de la position soit égal à la perte absolue d'un stop loss déclenché. Par exemple, si une position longue est ouverte à un prix de 1.0000 et que son stop loss est au niveau de 0.9000, le niveau de take profi serait 1.0000 + (1.0000 - 0.9000) = 1.1000. L'EA sort de la position en stop loss ou en take profit.

La deuxième position est une position à long terme. Son stop loss est suivi suivant le prix. L'arrêt se déplace après la fractale nouvellement formée de Bill Williams. Pour une position longue, l’arrêt est déplacé en fonction des fractales inférieures, et les fractales supérieures sont utilisées pour une position courte. L'EA sort de la position uniquement au stop loss.

Le graphique suivant illustre cette stratégie :

Fig. 4. La représentation des positions bidirectionnelles du Chaos 2 EA sur le graphique des prix

Fig. 4. La représentation des positions bidirectionnelles du Chaos 2 EA sur le graphique des prix

Les barres de retournement sont marquées d'un cadre rouge. La période N sur ce graphique est égale à 2. Le moment le plus opportun est choisi pour cette stratégie. Les positions courtes sont représentées par une ligne pointillée bleue, les positions longues sont représentées par la couleur verte. Comme on peut le voir, des positions longues et courtes peuvent exister simultanément même dans une stratégie relativement simple. Attention à la période du 5 au 8 janvier 2014.

C'est un tournant pour la tendance baissière de l'AUDCAD. Le 4 janvier, un signal a été reçu de la barre de retournement haussière et le 5 janvier, deux positions longues ont été ouvertes. Dans le même temps, il y avait encore trois positions courtes dont les arrêts étaient suivis suivant la tendance (ligne rouge pointillée). Puis, le 7 janvier, l’arrêt s'est déclenché pour les positions courtes, il ne restait donc que des positions longues sur le marché.

L'évolution serait difficilement contrôlable sur une position nette, puisque le volume net ne tiendrait pas compte du nombre de positions effectivement gérées par l'EA. HedgeTerminal permet aux EA de surveiller leurs positions individuelles quelle que soit la position nette actuelle, ce qui permet d'obtenir ces graphiques et de développer des stratégies similaires.

Vous trouverez ci-dessous le code mettant en œuvre cette stratégie.

Je n'ai volontairement pas utilisé la programmation orientée objet, en adaptant le code pour les débutants :

//+------------------------------------------------------------------+
//|                                                       Chaos2.mq5 |
//|     Copyright 2014, Vasiliy Sokolov specially for HedgeTerminal. |
//|                                          St.-Petersburg, Russia. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Vasiliy Sokolov."
#property link      "https://login.mql5.com/ru/users/c-4"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Prototypes.mqh>           // Include prototypes function of HedgeTerminalAPI library

//+------------------------------------------------------------------+
//| Input parameters.                                                |
//+------------------------------------------------------------------+
input uint N=2;                     // Period of extermum/minimum
input uint OldPending=3;            // Old pending

//+------------------------------------------------------------------+
//| Private variables of expert advisor.                             |
//+------------------------------------------------------------------+
ulong Magic = 2314;                 // Magic number of expert
datetime lastTime = 0;              // Remembered last time for function DetectNewBar
int hFractals = INVALID_HANDLE;     // Handle of indicator 'Fractals'. See: 'https://www.mql5.com/fr/docs/indicators/ifractals'
//+------------------------------------------------------------------+
//| Type of bar by Bill Wiallams strategy.                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_TYPE
  {
   BAR_TYPE_ORDINARY,               // Ordinary bar. 
   BAR_TYPE_BEARISH,                // This bar close in the upper third and it's minimum is lowest at N period
   BAR_TYPE_BULLISH,                // This bar close in the lower third and it's maximum is highest at N period
  };
//+------------------------------------------------------------------+
//| Type of Extremum.                                                |
//+------------------------------------------------------------------+
enum ENUM_TYPE_EXTREMUM
  {
   TYPE_EXTREMUM_HIGHEST,           // Extremum from highest prices
   TYPE_EXTREMUM_LOWEST             // Extremum from lowest prices
  };
//+------------------------------------------------------------------+
//| Type of position.                                                |
//+------------------------------------------------------------------+
enum ENUM_ENTRY_TYPE
  {
   ENTRY_BUY1,                      // Buy position with short target
   ENTRY_BUY2,                      // Buy position with long target
   ENTRY_SELL1,                     // Sell position with short target
   ENTRY_SELL2,                     // Sell position with long target
   ENTRY_BAD_COMMENT                // My position, but wrong comment
  };
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create indicator 'Fractals' ---//
   hFractals=iFractals(Symbol(),NULL);
   if(hFractals==INVALID_HANDLE)
      printf("Warning! Indicator 'Fractals' not does not create. Reason: "+
             (string)GetLastError());
//--- Corection magic by timeframe ---//
   int minPeriod=PeriodSeconds()/60;
   string strMagic=(string)Magic+(string)minPeriod;
   Magic=StringToInteger(strMagic);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete indicator 'Fractals' ---//
   if(hFractals!=INVALID_HANDLE)
      IndicatorRelease(hFractals);
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Run logic only open new bar. ---//
   int totals=SupportPositions();
   if(NewBarDetect()==true)
     {
      MqlRates rates[];
      CopyRates(Symbol(),NULL,1,1,rates);
      MqlRates prevBar=rates[0];
      //--- Set new pendings order ---//
      double closeRate=GetCloseRate(prevBar);
      if(closeRate<=30 && BarIsExtremum(1,N,TYPE_EXTREMUM_HIGHEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BEARISH);
        }
      else if(closeRate>=70 && BarIsExtremum(1,N,TYPE_EXTREMUM_LOWEST))
        {
         DeleteOldPendingOrders(0);
         SetNewPendingOrder(1,BAR_TYPE_BULLISH);
        }
      DeleteOldPendingOrders(OldPending);
     }
//---
  }
//+------------------------------------------------------------------+
//| Analyze open positions and modify it if needed.                  |
//+------------------------------------------------------------------+
int SupportPositions()
  {
//---
   int count=0;
   //--- Analize active positions... ---//
   for(int i=0; i<TransactionsTotal(); i++) // Get total positions.
     {
      //--- Select main active positions ---//
      if(!TransactionSelect(i, SELECT_BY_POS, MODE_TRADES))continue;             // Select active transactions
      if(TransactionType() != TRANS_HEDGE_POSITION)continue;                     // Select hedge positions only
      if(HedgePositionGetInteger(HEDGE_POSITION_MAGIC) != Magic)                 // Select main positions by magic
      if(HedgePositionGetInteger(HEDGE_POSITION_STATE) == POSITION_STATE_FROZEN) // If position is frozen - continue
         continue;                                                               // Let's try to get access to positions later
      count++;
      //--- What position do we choose?... ---//
      ENUM_ENTRY_TYPE type=IdentifySelectPosition();
      bool modify=false;
      double sl = 0.0;
      double tp = 0.0;
      switch(type)
        {
         case ENTRY_BUY1:
         case ENTRY_SELL1:
           {
            //--- Check sl, tp levels and modify it if need. ---//
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            sl=GetStopLossLevel();
            if(!DoubleEquals(sl,currentStop))
               modify=true;
            tp=GetTakeProfitLevel();
            double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
            //--- Close by take-profit if price more tp level
            bool isBuyTp=tp<bid && !DoubleEquals(tp,0.0) && type==ENTRY_BUY1;
            bool isSellTp=tp>ask && type==ENTRY_SELL1;
            if(isBuyTp || isSellTp)
              {
               HedgeTradeRequest request;
               request.action=REQUEST_CLOSE_POSITION;
               request.exit_comment="Close by TP from expert";
               request.close_type=CLOSE_AS_TAKE_PROFIT;
               if(!SendTradeRequest(request))
                 {
                  ENUM_HEDGE_ERR error=GetHedgeError();
                  string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
                  printf("Close position by tp failed. Reason: "+EnumToString(error)+" "+logs);
                  if(error==HEDGE_ERR_TASK_FAILED)
                     PrintTaskLog();
                  ResetHedgeError();
                 }
               else break;
              }
            double currentTakeProfit=HedgePositionGetDouble(HEDGE_POSITION_TP);
            if(!DoubleEquals(tp,currentTakeProfit))
               modify=true;
            break;
           }
         case ENTRY_BUY2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            if(sl>currentStop)
               modify=true;
            break;
           }
         case ENTRY_SELL2:
           {
            //--- Check sl level and set modify flag. ---//
            sl=GetStopLossLevel();
            double currentStop=HedgePositionGetDouble(HEDGE_POSITION_SL);
            bool usingSL=HedgePositionGetInteger(HEDGE_POSITION_USING_SL);
            if(sl<currentStop || !usingSL)
               modify=true;
            break;
           }
        }
      //--- if  need modify sl, tp levels - modify it. ---//
      if(modify)
        {
         HedgeTradeRequest request;
         request.action=REQUEST_MODIFY_SLTP;
         request.sl = sl;
         request.tp = tp;
         if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
            request.exit_comment="Exit by T/P level";
         else
            request.exit_comment="Exit by trailing S/L";
         if(!SendTradeRequest(request))
           {
            ENUM_HEDGE_ERR error=GetHedgeError();
            string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
            printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
            if(error==HEDGE_ERR_TASK_FAILED)
               PrintTaskLog();
            ResetHedgeError();
           }
         else break;
        }
     }
   return count;
//---
  }
//+------------------------------------------------------------------+
//| Return stop-loss level for selected position.                    |
//| RESULT                                                           |
//|   Stop-loss level                                                |
//+------------------------------------------------------------------+
double GetStopLossLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   double fractals[];
   double sl=0.0;
   MqlRates ReversalBar;

   if(!LoadReversalBar(ReversalBar))
     {
      printf("Reversal bar load failed.");
      return sl;
     }
   //--- What position do we choose?... ---//
   switch(IdentifySelectPosition())
     {
      case ENTRY_SELL2:
        {
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,UPPER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]<sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_ASK);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]>price+freeze)
                     sl=NormalizeDouble(fractals[i]+point,Digits());
                 }
              }
            break;
           }
        }
      case ENTRY_SELL1:
         sl=ReversalBar.high+point;
         break;
      case ENTRY_BUY2:
         if(HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
           {
            sl=NormalizeDouble(HedgePositionGetDouble(HEDGE_POSITION_SL),Digits());
            CopyBuffer(hFractals,LOWER_LINE,ReversalBar.time,TimeCurrent(),fractals);
            for(int i=ArraySize(fractals)-4; i>=0; i--)
              {
               if(DoubleEquals(fractals[i],DBL_MAX))continue;
               if(DoubleEquals(fractals[i],sl))continue;
               if(fractals[i]>sl)
                 {
                  double price= SymbolInfoDouble(Symbol(),SYMBOL_BID);
                  int ifreeze =(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL);
                  double freeze=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*ifreeze;
                  if(fractals[i]<price-freeze)
                     sl=NormalizeDouble(fractals[i]-point,Digits());
                 }
              }
            break;
           }
      case ENTRY_BUY1:
         sl=ReversalBar.low-point;
     }
   sl=NormalizeDouble(sl,Digits());
   return sl;
//---
  }
//+------------------------------------------------------------------+
//| Return Take-Profit level for selected position.                  |
//| RESULT                                                           |
//|   Take-profit level                                              |
//+------------------------------------------------------------------+
double GetTakeProfitLevel()
  {
//---
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   ENUM_ENTRY_TYPE type=IdentifySelectPosition();
   double tp=0.0;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
     {
      if(!HedgePositionGetInteger(HEDGE_POSITION_USING_SL))
         return tp;
      double sl=HedgePositionGetDouble(HEDGE_POSITION_SL);
      double openPrice=HedgePositionGetDouble(HEDGE_POSITION_PRICE_OPEN);
      double deltaStopLoss=MathAbs(NormalizeDouble(openPrice-sl,Digits()));
      if(type==ENTRY_BUY1)
         tp=openPrice+deltaStopLoss;
      if(type==ENTRY_SELL1)
         tp=openPrice-deltaStopLoss;
      return tp;
     }
   else
      return 0.0;
//---
  }
//+------------------------------------------------------------------+
//| Identify what position type is select.                           |
//| RESULT                                                           |
//|   Return type position. See ENUM_ENTRY_TYPE                      |
//+------------------------------------------------------------------+
ENUM_ENTRY_TYPE IdentifySelectPosition()
  {
//---   
   string comment=HedgePositionGetString(HEDGE_POSITION_ENTRY_COMMENT);
   int pos=StringLen(comment)-2;
   string subStr=StringSubstr(comment,pos);
   ENUM_TRANS_DIRECTION posDir=(ENUM_TRANS_DIRECTION)HedgePositionGetInteger(HEDGE_POSITION_DIRECTION);
   if(subStr=="#0")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY1;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL1;
     }
   else if(subStr=="#1")
     {
      if(posDir==TRANS_LONG)
         return ENTRY_BUY2;
      if(posDir==TRANS_SHORT)
         return ENTRY_SELL2;
     }
   return ENTRY_BAD_COMMENT;
//---
  }
//+------------------------------------------------------------------+
//| Set pending orders under or over bar by index_bar.               |
//| INPUT PARAMETERS                                                 |
//|   index_bar - index of bar.                                      |
//|   barType - type of bar. See enum ENUM_BAR_TYPE.                 |
//| RESULT                                                           |
//|   True if new order successfully set, othewise false.            | 
//+------------------------------------------------------------------+
bool SetNewPendingOrder(int index_bar,ENUM_BAR_TYPE barType)
  {
//---
   MqlRates rates[1];
   CopyRates(Symbol(),NULL,index_bar,1,rates);
   MqlTradeRequest request={0};
   request.volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   double vol=request.volume;
   request.symbol = Symbol();
   request.action = TRADE_ACTION_PENDING;
   request.type_filling=ORDER_FILLING_FOK;
   request.type_time=ORDER_TIME_GTC;
   request.magic=Magic;
   double point=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE)*3;
   string comment="";
   if(barType==BAR_TYPE_BEARISH)
     {
      request.price=rates[0].low-point;
      comment="Entry sell by bearish bar";
      request.type=ORDER_TYPE_SELL_STOP;
     }
   else if(barType==BAR_TYPE_BULLISH)
     {
      request.price=rates[0].high+point;
      comment="Entry buy by bullish bar";
      request.type=ORDER_TYPE_BUY_STOP;
     }
   MqlTradeResult result={0};
//--- Send pending order twice...
   for(int i=0; i<2; i++)
     {
      request.comment=comment+" #"+(string)i;       // Detect order by comment;
      if(!OrderSend(request,result))
        {
         printf("Trade error #"+(string)result.retcode+" "+
                result.comment);
         return false;
        }
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Delete old pending orders. If pending order set older that       |
//| n_bars ago pending orders will be removed.                       |
//| INPUT PARAMETERS                                                 |
//|   period - count bar.                                            |
//+------------------------------------------------------------------+
void DeleteOldPendingOrders(int n_bars)
  {
//---
   for(int i=0; i<OrdersTotal(); i++)
     {
      ulong ticket = OrderGetTicket(i);            // Get ticket of order by index.
      if(!OrderSelect(ticket))                     // Continue if not selected.
         continue;
      if(Magic!=OrderGetInteger(ORDER_MAGIC))      // Continue if magic is not main.
         continue;
      if(OrderGetString(ORDER_SYMBOL)!=Symbol())   // Continue if symbol is not main.
         continue;
      //--- Count time elipsed ---//
      datetime timeSetup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);
      int secElapsed=(int)(TimeCurrent()-timeSetup);
      //--- delete old pending order ---//
      if(secElapsed>=PeriodSeconds() *n_bars)
        {
         MqlTradeRequest request={0};
         MqlTradeResult result={0};
         request.action= TRADE_ACTION_REMOVE;
         request.order = ticket;
         if(!OrderSend(request,result))
            printf("Delete pending order failed. Reason #"+(string)result.retcode+" "+result.comment);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Detect new bar.                                                  |
//+------------------------------------------------------------------+
bool NewBarDetect(void)
  {
//---
   datetime timeArray[1];
   CopyTime(Symbol(),NULL,0,1,timeArray);
   if(lastTime!=timeArray[0])
     {
      lastTime=timeArray[0];
      return true;
     }
   return false;
//---
  }
//+------------------------------------------------------------------+
//| Get close rate. Type bar defined in trade chaos strategy         |
//| and equal enum 'ENUM_TYPE_BAR'.                                  |
//| INPUT PARAMETERS                                                 |
//|   index - index of bars series. for example:                     |
//|   '0' - is current bar. 1 - previous bar.                        |
//| RESULT                                                           |
//|   Type of ENUM_TYPE_BAR.                                         | 
//+------------------------------------------------------------------+
double GetCloseRate(const MqlRates &bar)
  {
//---
   double highLowDelta = bar.high-bar.low;      // Calculate diaposon bar.
   double lowCloseDelta = bar.close - bar.low;  // Calculate Close - Low delta.
   double percentClose=0.0;
   if(!DoubleEquals(lowCloseDelta, 0.0))                    // Division by zero protected.   
      percentClose = lowCloseDelta/highLowDelta*100.0;      // Calculate percent 'lowCloseDelta' of 'highLowDelta'.
   return percentClose;
//---
  }
//+------------------------------------------------------------------+
//| If bar by index is extremum - return true, otherwise             |
//| return false.                                                    |
//| INPUT PARAMETERS                                                 |
//|   index - index of bar.                                          |
//|   period - Number of bars prior to the extremum.                 |
//|   type - Type of extremum. See ENUM_TYPE_EXTREMUM TYPE enum.     |
//| RESULT                                                           |
//|   True - if bar is extremum, otherwise false.                    | 
//+------------------------------------------------------------------+
bool BarIsExtremum(const int index,const int period,ENUM_TYPE_EXTREMUM type)
  {
//--- Copy rates --- //
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,index,N+1,rates);
//--- Search extremum --- //
   for(int i=1; i<ArraySize(rates); i++)
     {
      //--- Reset comment if you want include volume analize. ---//
      //if(rates[0].tick_volume<rates[i].tick_volume)
      //   return false;
      if(type==TYPE_EXTREMUM_HIGHEST && 
         rates[0].high<rates[i].high)
         return false;
      if(type==TYPE_EXTREMUM_LOWEST && 
         rates[0].low>rates[i].low)
         return false;
     }
   return true;
//---
  }
//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }
//+------------------------------------------------------------------+
//| Load reversal bar. The current position must be selected.        |
//| OUTPUT PARAMETERS                                                |
//|   bar - MqlRates bar.
//+------------------------------------------------------------------+  
bool LoadReversalBar(MqlRates &bar)
  {
//---
   datetime time=(datetime)(HedgePositionGetInteger(HEDGE_POSITION_ENTRY_TIME_SETUP_MSC)/1000+1);
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   CopyRates(Symbol(),NULL,time,2,rates);
   int size=ArraySize(rates);
   if(size==0)return false;
   bar=rates[size-1];
   return true;
//---   
  }
//+------------------------------------------------------------------+
//| Compares two double numbers.                                     |
//| RESULT                                                           |
//|   True if two double numbers equal, otherwise false.             |
//+------------------------------------------------------------------+
bool DoubleEquals(const double a,const double b)
  {
//---
   return(fabs(a-b)<=16*DBL_EPSILON*fmax(fabs(a),fabs(b)));
//---
  }

Vous trouverez ci-dessous une brève description du fonctionnement de ce code. L'EA est appelé à chaque tick. Il analyse la barre précédente à l'aide de la fonction BarIsExtremum() : si elle est baissière ou haussière, il passe deux ordres en attente (fonction SetNewPendingOrder()). Une fois activés, les ordres en attente sont convertis en positions. L'EA définit alors un stop loss et un take profit pour les positions.

Malheureusement, ces niveaux ne peuvent pas être placés avec des ordres en attente, car il n'y a pas encore de position réelle. Les niveaux sont définis via la fonction SupportPositions(). Pour fonctionner correctement, nous devons connaître la position pour laquelle le take profit doit être passé, et la position qui doit être suivie après les fractales. Cette définition des positions est effectuée par la fonction IdentifierSelectPosition(). Elle analyse le commentaire de position initial, et si elle contient la chaîne « #1 », une cible étroite est définie pour elle ; si elle contient la « # 2 », le stop suiveur est appliqué.

Pour modifier une position bidirectionnelle ouverte, ou pour la fermer, une demande de transaction spéciale est créée, qui est ensuite envoyée à la fonction SendTradeRequest() pour exécution :

...
if(modify)
  {
   HedgeTradeRequest request;
   request.action=REQUEST_MODIFY_SLTP;
   request.sl = sl;
   request.tp = tp;
   if(type==ENTRY_BUY1 || type==ENTRY_SELL1)
      request.exit_comment="Exit by T/P level";
   else
      request.exit_comment="Exit by trailing S/L";
   if(!SendTradeRequest(request))
     {
      ENUM_HEDGE_ERR error=GetHedgeError();
      string logs=error==HEDGE_ERR_TASK_FAILED ? ". Print logs..." : "";
      printf("Modify stop-loss or take-profit failed. Reason: "+EnumToString(error)+" "+logs);
      if(error==HEDGE_ERR_TASK_FAILED)
         PrintTaskLog();
      ResetHedgeError();
     }
   else break;
  }
...

Faites attention à la gestion des erreurs.

Si l'envoi échoue et que la fonction renvoie faux, nous devons obtenir le dernier code d'erreur à l'aide de la fonction GetHedgeError(). Dans certains cas, l'exécution d'un ordre de trading ne commencera même pas. Si la position n'a pas été présélectionnée, alors la requête sera mal faite et son exécution sera impossible.

Si un ordre n'est pas exécuté, il est inutile d'analyser le journal de son exécution, obtenir un code d'erreur suffit.

Cependant, si la requête est correcte, mais que l’ordre n'a pas été exécuté pour une raison quelconque, l'erreur HEDGE_ERR_TASK_FAILED sera renvoyée. Dans ce cas, il est nécessaire d'analyser le journal d'exécution des ordres en effectuant une recherche dans le journal. Cela se fait via la fonction spécialePrintTaskLog() :

//+------------------------------------------------------------------+
//| Print current error and reset it.                                |
//+------------------------------------------------------------------+  
void PrintTaskLog()
  {
//---
   uint totals=(uint)HedgePositionGetInteger(HEDGE_POSITION_ACTIONS_TOTAL);
   for(uint i = 0; i<totals; i++)
     {
      uint retcode=0;
      ENUM_TARGET_TYPE type;
      GetActionResult(i,type,retcode);
      printf("---> Action #"+(string)i+"; "+EnumToString(type)+"; RETCODE: "+(string)retcode);
     }
//---
  }

Ces messages permettent d'identifier la raison de l’échec et de le corriger.

Illustrons maintenant l'affichage du Chaos2 EA et de ses positions dans HedgeTerminal en temps réel. L'EA fonctionne sur le graphique M1 :

Fig. 5. La représentation des positions bidirectionnelles du Chaos 2 EA dans le panneau HedgeTerminal

Fig. 5. La représentation des positions bidirectionnelles du Chaos 2 EA dans le panneau HedgeTerminal

Comme on peut le voir, même les positions bidirectionnelles d'un EA peuvent parfaitement coexister.


1.13. À propos des « symboles en double » et de la virtualisation par le broker

Lorsque MetaTrader 5 a été publié, certains brokers ont commencé à fournir les soi-disant symboles en double. Leurs cotations sont égales aux instruments originaux, mais elles ont en règle générale un suffixe, comme «_m» ou «_1». Elles ont été introduites pour permettre aux traders d'avoir des positions bidirectionnelles sur pratiquement le même symbole.

Cependant, de tels symboles sont presque inutiles pour les traders algorithmiques utilisant des robots. Et voici pourquoi. Supposons que nous ayons besoin d'écrire l'EA ( « Chaos II » sans la bibliothèque de HedgeTerminalAPI. Au lieu de cela, nous aurions des symboles en double. Comment ferions-nous cela ? Supposons que toutes les opérations de vente aient été ouvertes sur un seul instrument, tel que EURUSD, et toutes les opérations d'achat sur l'autre, par exemple EURUSD_m1.

Mais que se passerait-il si, au moment de l'ouverture de la position, l'un des symboles était déjà tradé par un autre robot ou par le trader ? Même si de tels symboles étaient toujours libres, le problème ne serait pas résolu pour ce robot, qui pourrait avoir simultanément plusieurs positions dans la même direction.

La capture d'écran ci-dessus montre trois positions de vente, et il peut y en avoir encore plus. Les positions ont différents niveaux d'arrêt de protection, c'est pourquoi elles ne peuvent pas être combinées en une seule position nette. La solution consiste à ouvrir une nouvelle position pour un nouveau symbole en double. Mais il peut ne pas y avoir assez de tels symboles, car un robot a besoin de six instruments en double (trois dans chaque direction de trade). Si deux robots fonctionnent sur des périodes différentes, 12 symboles sont requis.

Aucun des brokers ne fournit autant de symboles en double. Mais même s'il y avait une quantité illimitée de tels symboles, et ils étaient toujours libres, une décomposition complexe de l'algorithme serait nécessaire. Le robot devrait parcourir tous les symboles disponibles à la recherche de doublons et de ses propres positions. Cela créerait plus de problèmes qu'il n'en résoudrait. 

Il y a encore plus de difficultés avec les symboles en double. Voici une brève liste des problèmes supplémentaires découlant de leur utilisation :

  • Vous payez pour chaque symbole en double sous forme de swaps négatifs, car les swaps de verrouillage ou de verrouillage partiel sont toujours négatifs, et c'est le cas lorsque vous conservez deux positions bidirectionnelles sur deux instruments différents.
  • Tous les brokers ne fournissent pas de symboles en double. Une stratégie développée pour un broker qui fournit des symboles en double ne fonctionnera pas avec un broker ne fournissant qu'un seul instrument. La différence dans les noms des symboles est une autre source potentielle de problèmes.
  • La création d'un symbole en double n'est pas toujours possible. Sur des marchés transparents soumis à une réglementation stricte, toute transaction est un document financier. La position nette est la norme de facto sur ces marchés et donc la création de symboles individuels y est loin d'être possible. Par exemple, aucun broker fournissant des symboles en double ne peut jamais apparaître sur Moscow Exchange MOEX. Sur les marchés réglementés moins stricts, les brokers peuvent créer n'importe quel symbole pour leurs clients.
  • Les instruments en double sont inefficaces lors du trading à l'aide de robots. Les raisons de leur inefficacité ont été divulguées dans l'exemple ci-dessus de Chaos 2 EA.

Un symbole en double est essentiellement une virtualisation du côté du broker. HedgeTerminal utilise la virtualisation côté client.

Dans les deux cas, nous utilisons la virtualisation en tant que telle. Elle modifie la représentation réelle des obligations du trader. Avec la virtualisation, une position peut se transformer en deux positions. Il n'y a pas de problème quand cela se produit du côté client, car les clients peuvent représenter ce qu'ils veulent. Mais si la virtualisation est effectuée par le broker, les organismes de réglementation et de licence peuvent avoir des questions sur la relation entre les informations fournies et les informations réelles. La deuxième difficulté est que cela nécessite d'avoir deux API en une : un ensemble de fonctions et de modificateurs à utiliser en mode net, et un autre pour le mode bidirectionnel.

De nombreux traders algorithmiques ont trouvé leur propre moyen de lier les transactions en une seule position. Beaucoup de ces méthodes fonctionnent bien, et il existe des articles décrivant ces méthodes. Cependant, la virtualisation des positions est une procédure plus compliquée qu'il ne paraît. Dans HedgeTerminal, les algorithmes associés à la virtualisation des positions reprennent environ 20 000 lignes du code source. De plus, HedgeTerminal n'implémente que des fonctions de base. Créer une quantité similaire de code dans votre EA uniquement pour accompagner les positions bidirectionnelles consommerait trop de ressources.


Chapitre 2. Manuel de HedgeTerminal API

2.1. Fonctions de sélection des transactions

Fonction TransactionsTotal()

La fonction renvoie le nombre total de transactions dans la liste des transactions. C'est la fonction de base pour effectuer des recherches dans les transactions disponibles (voir l'exemple dans les sections 1.4 et 1.11 de cet article).

int TransactionsTotal(ENUM_MODE_TRADES pool = MODE_TRADES);

Paramètres

  • [in] pool=MODE_TRADES - Spécifie l'identifiant de la source de données pour la sélection. Il peut s'agir de l'une des valeurs de l'énumération ENUM_MODE_TRADES.

Valeur de renvoi

La fonction renvoie le nombre total de transactions dans la liste des transactions.


Fonction TransactionType()

La fonction renvoie le type d'une transaction sélectionnée.

ENUM_TRANS_TYPE TransactionType(void);

Valeur de renvoi

Type de renvoi. La valeur peut être l'une des valeurs ENUM_TRANS_TYPE.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction TransactionSelect()

Cette fonction sélectionne une transaction pour d'autres manipulations. La fonction sélectionne une transaction par son index ou identifiant unique dans la liste des transactions.

bool TransactionSelect(int index,
     ENUM_MODE_SELECT select = SELECT_BY_POS,
     ENUM_MODE_TRADES pool=MODE_TRADES
     );

Paramètres

  • [in] index - L'index de l’ordre dans la liste des ordres ou un identifiant unique de la transaction en fonction du paramètre 'sélectionner'.
  • [in] select=SELECT_BY_POS - Identifiant du type 'index' du paramètre. La valeur peut être l'une des valeurs ENUM_MODE_SELECT.
  • [in] pool=MODE_TRADES - Spécifie l'identifiant de la source de données pour la sélection. Il peut s'agir de l'une des valeurs de l'énumération ENUM_MODE_TRADES.

Valeur de renvoi

Renvoie vrai si une transaction a été sélectionnée avec succès ou faux dans le cas contraire. Pour obtenir les détails de l'erreur, appelez GetHedgeError().

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».

Remarque

Si une transaction est sélectionnée en fonction de son index, la complexité de l'opération correspond à O(1). Si une transaction est sélectionnée sur la base de son identifiant unique, la complexité de l'opération tend asymptotiquement vers O(log2(n)).


Fonction HedgeOrderSelect()

La fonction sélectionne l'un des ordres inclus dans la position bidirectionnelle. La position bidirectionnelle, qui inclut l'ordre requis, doit être présélectionnée à l'aide de TransactionSelect ().

bool HedgeOrderSelect(ENUM_HEDGE_ORDER_SELECTED_TYPE type);

Paramètres

Valeur de renvoi

Renvoie vraie si un ordre a été sélectionné avec succès ou faux dans le cas contraire. Pour obtenir les détails de l'erreur, appelez GetHedgeError().

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction HedgeDealSelect()

La fonction sélectionne l'une des transactions qui ont exécuté l'ordre. L'ordre dont la partie est la transaction sélectionnée doit être présélectionné à l'aide de la fonction HedgeOrderSelect().

bool HedgeDealSelect(int index);

Paramètres

  • [in] index - L'indice de la transaction à sélectionner dans la liste des transactions qui ont exécuté l'ordre. Pour connaître le nombre total de transactions dans un ordre, appelez la propriété de l’ordre approprié à l'aide de la fonction HedgeOrderGetInteger(). Pour le paramètre, utilisez le modificateur ENUM_HEDGE_ORDER_PROP_INTEGER égal à la valeur HEDGE_ORDER_DEALS_TOTAL.

Valeur de renvoi

Renvoie vraie si une transaction a été sélectionnée avec succès ou faux dans le cas contraire. Pour obtenir les détails de l'erreur, appelez GetHedgeError().

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


2.2. Fonctions d'obtention des propriétés d'une transaction sélectionnée

Fonction HedgePositionGetInteger()

La fonction renvoie la propriété d'une position bidirectionnelle sélectionnée. La propriété peut être de type int, long, datetime ou bool selon le type de la propriété demandée. La position bidirectionnelle doit être présélectionnée à l'aide de la fonction TransactionSelect().

ulong HedgePositionGetInteger(ENUM_HEDGE_POSITION_PROP_INTEGER property);

Paramètres

  • [in] propriété - Identifiant de la propriété de la position bidirectionnelle. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_DEAL_PROP_INTEGER.

Valeur de renvoi

Valeur du type ulong. Pour une utilisation ultérieure de la valeur, son type devrait être explicitement converti en type de la propriété demandée.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction HedgePositionGetDouble()

La fonction renvoie la propriété d'une position bidirectionnelle sélectionnée. Le type de la propriété de renvoi est double. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_POSITION_PROP_DOUBLE. La position bidirectionnelle doit être présélectionnée à l'aide de TransactionSelect().

ulong HedgePositionGetDouble(ENUM_HEDGE_POSITION_PROP_DOUBLE property);

Paramètres

  • [in] propriété- Identifiant de la propriété de la position bidirectionnelle. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_DEAL_PROP_DOUBLE.

Valeur de renvoi

Une valeur de type double.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction HedgePositionGetString()

La fonction renvoie la propriété d'une position bidirectionnelle sélectionnée. La propriété est de type chaîne. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_POSITION_PROP_STRING. La position bidirectionnelle doit être présélectionnée à l'aide de TransactionSelect().

ulong HedgePositionGetString(ENUM_HEDGE_POSITION_PROP_STRING property);

Paramètres

  • [in] propriété - Identifiant de la propriété de la position bidirectionnelle. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_POSITION_PROP_STRING.

Valeur de renvoi

Une valeur du type chaîne.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction HedgeOrderGetInteger()

La fonction renvoie la propriété de l'ordre sélectionné, qui fait partie de la position bidirectionnelle. La propriété peut être de type int, long, datetime ou bool. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_ORDER_PROP_INTEGER. L’ordre doit être présélectionné à l'aide de la fonction HedgeOrderSelect().

ulong HedgeOrderGetInteger(ENUM_HEDGE_ORDER_PROP_INTEGER property);

Paramètres

  • [in] propriété - Identifiant de la propriété de l’ordre, qui fait partie de la position bidirectionnelle. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_ORDER_PROP_INTEGER.

Valeur de renvoi

Valeur du type ulong. Pour une utilisation ultérieure de la valeur, son type doit être explicitement converti en type de la propriété demandée.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction HedgeOrderGetDouble()

La fonction renvoie la propriété de l'ordre sélectionné, qui fait partie de la position bidirectionnelle. La propriété demandée est de type double. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_ORDER_PROP_DOUBLE. L’ordre doit être présélectionné à l'aide de la fonction HedgeOrderSelect().

double HedgeOrderGetDouble(ENUM_HEDGE_ORDER_PROP_DOUBLE property);

Paramètres

  • [in] propriété - Identifiant de la propriété de l’ordre, qui fait partie de la position bidirectionnelle. La valeur peut être n'importe laquelle des valeurs d'énumération ENUM_HEDGE_ORDER_PROP_DOUBLE.

Valeur de renvoi

Une valeur de type double.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction HedgeDealGetInteger()

La fonction renvoie la propriété de la transaction sélectionnée, qui fait partie de l'ordre exécuté. La propriété peut être de type int, long, datetime ou bool. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_DEAL_PROP_INTEGER. La transaction doit être présélectionnée à l'aide de la fonction HedgeDealSelect().

ulong HedgeOrderGetInteger(ENUM_HEDGE_DEAL_PROP_INTEGER property);

Paramètres

  • [in] propriété - Identifiant de la propriété de l'opération sélectionnée incluse dans l'ordre exécuté. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_DEAL_PROP_INTEGER.

Valeur de renvoi

Valeur du type ulong. Pour une utilisation ultérieure de la valeur, son type devrait être explicitement converti en type de la propriété demandée.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction HedgeDealGetDouble()

La fonction renvoie la propriété de la transaction sélectionnée, qui fait partie de l'ordre exécuté. La propriété peut être de type double. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_DEAL_PROP_DOUBLE. La transaction doit être présélectionnée à l'aide de la fonction HedgeDealSelect().

ulong HedgeOrderGetDouble(ENUM_HEDGE_DEAL_PROP_DOUBLE property);

Paramètres

  • [in] propriété - Identifiant de la propriété de l'opération sélectionnée incluse dans l'ordre exécuté. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_DEAL_PROP_DOUBLE.

Valeur de renvoi

Une valeur de type double.

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


2.3. Fonctions de définition et d'obtention des propriétés de HedgeTerminal à partir des Expert Advisors

Fonction HedgePropertySetInteger()

La fonction définit l'une des propriétés de HedgeTerminal. La propriété peut être de type int, long, datetime ou bool. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_PROP_INTEGER.

bool HedgePropertySetInteger(ENUM_HEDGE_PROP_INTEGER property, long value);

Paramètres

  • [in] propriété - Identifiant de la propriété qui doit être définie pour HedgeTerminal. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_PROP_INTEGER.

Valeur de renvoi

Une valeur de type bool. Si la propriété a été définie avec succès, la fonction renvoie vrai, sinon elle renvoie faux.

Exemple d'utilisation

Dans l'exemple, la fonction permet de régler le temps de blocage de position lors de l'envoi d'une demande asynchrone. Si la réponse du serveur n'est pas reçue dans les 30 secondes suivant l'envoi d'une demande asynchrone, la position verrouillée sera débloquée.

void SetTimeOut()
  {
   bool res=HedgePropertySetInteger(HEDGE_PROP_TIMEOUT,30);
   if(res)
      printf("The property is set successfully");
   else
      printf("Property is not set");
  }

Fonction HedgePropertyGetInteger()

La fonction obtient l'une des propriétés de HedgeTerminal. La propriété peut être de type int, long, datetime ou bool. Le type de propriété est spécifié via l'énumération ENUM_HEDGE_PROP_INTEGER.

long HedgePropertyGetInteger(ENUM_HEDGE_PROP_INTEGER property);

Paramètres

  • [in] propriété - Identifiant de la propriété qui doit être reçue de HedgeTerminal. La valeur peut être l'une des valeurs d'énumération ENUM_HEDGE_PROP_INTEGER.

Valeur de renvoi

Une valeur de type long.

Exemple d'utilisation

La fonction reçoit le temps de blocage de position lors de l'envoi d'une demande asynchrone et l'affiche dans le terminal.

void GetTimeOut()
  {
   int seconds=HedgePropertyGetInteger(HEDGE_PROP_TIMEOUT);
   printf("Timeout is "+(string) seconds);
  }

2.4. Fonctions d'obtention et de gestion des codes d'erreur

Fonction GetHedgeError()

La fonction renvoie l'identifiant de l'erreur, qui a été obtenu à partir de la dernière action. L'identifiant d'erreur correspond à l'énumération ENUM_HEDGE_ERR.

ENUM_HEDGE_ERR GetHedgeError(void);

Valeur de renvoi

Identifiant de la position La valeur peut être n'importe quel type d'énumération ENUM_HEDGE_ERR.

Remarque

Après l'appel, la fonction GetHedgeError() ne réinitialise pas l'identifiant d'erreur. Pour réinitialiser l'identifiant d'erreur, utilisez la fonction ResetHedgeError().

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction ResetHedgeError()

La fonction réinitialise l'identifiant de la dernière erreur reçue. Après son appel, l'identifiant ENUM_HEDGE_ERR renvoyé par GetHedgeError() sera égal à HEDGE_ERR_NOT_ERROR.

void ResetHedgeError(void);

Exemple d'utilisation

Voir l'exemple d'utilisation de la fonction dans la section 1.11 de cet article : «Gestion des propriétés de position bidirectionnelle à travers l'exemple d'un script».


Fonction TotalActionsTask()

Une fois la position sélectionnée à l'aide de la fonction HedgePositionSelect(), elle peut être modifiée à l'aide de la fonction SendTradeRequest(). Par exemple, elle peut être fermée ou son commentaire sortant peut être modifié. Cette modification est effectuée par une tâche de trading spéciale. Chaque tâche peut comprendre plusieurs activités de trading (sous-tâches). Une tâche peut échouer. Dans ce cas, vous devrez peut-être analyser le résultat de toutes les sous-tâches incluses dans la tâche pour voir quel type de sous-tâches a échoué.

La fonction TotalActionTask() renvoie le nombre de sous-tâches contenues dans la dernière tâche de trading en cours d'exécution pour la position sélectionnée. Connaissant le nombre total de sous-tâches, vous pouvez rechercher parmi toutes les sous-tâches par leur index et analyser leurs résultats d'exécution à l'aide de la fonction GetActionResult() et ainsi découvrir les circonstances de l'échec.

uint TotalActionsTask(void);

Valeur de renvoi

Renvoie le nombre total de sous-tâches dans la tâche.

Exemple d'utilisation

Voir l'exemple d'utilisation dans la section 1.6 de cet article : «Analyse détaillée du trading et identification des erreurs à l'aide de TotalActionsTask() et GetActionResult()».


Fonction GetActionResult()

La fonction prend l'index de la sous-tâche dans la tâche (voir TotalActionTask()). Renvoie le type de la sous-tâche et ses résultats d'exécution via des paramètres de référence. Le type de la sous-tâche est défini par l'énumération ENUM_TARGET_TYPE. Le résultat de l'exécution de la sous-tâche correspond aux codes de renvoi du serveur de trading MetaTrader 5.

void GetActionResult(uint index, ENUM_TARGET_TYPE& target_type, uint& retcode);

Paramètres

  • [in] index - L'index de la sous-tâche dans la liste des sous-tâches.
  • [out] target_type – Type de la sous-tâche. La valeur peut être l'une des valeur de l'énumération ENUM_TARGET_TYPE
  • [out] retcode – Code de renvoi du serveur de trade reçu lors de l'exécution de la sous-tâche.

Exemple d'utilisation

Voir l'exemple d'utilisation dans la section 1.6 de cet article : «Analyse détaillée du trading et identification des erreurs à l'aide de TotalActionsTask() et GetActionResult()».


2.5. Trading

Fonction SendTradeRequest()

La fonction envoie une demande pour modifier la position bidirectionnelle sélectionnée dans HedgeTerminalAPI. Le résultat de l'exécution de la fonction est l'une des trois actions :

  1. Fermeture d'une position ou d'une partie de son volume ;
  2. Modification des niveaux des Stop Loss et Take Profit ;
  3. Modification du commentaire sortant.

Le type d'action et ses paramètres sont spécifiés dans la structure HedgeTradeRequest, qui est transmise par référence en tant que paramètre. Avant l'appel de la fonction, la position bidirectionnelle doit être présélectionnée à l'aide de la fonction TransactionSelect().

bool SendTradeRequest(HedgeTradeRequest& request);

Paramètres

[in] demande - La structure de la demande de modification de la position bidirectionnelle. Veuillez consulter la description de la structure et l'explication de ses champs dans la description de la structure HedgeTradeRequest.

Valeur de renvoi

Renvoie vrai, si la demande de modification de position a été exécutée avec succès. Renvoie faux dans le cas contraire. En cas d'échec de l'exécution de la demande, utilisez les fonctions TotalActionsTask() et GetActionResult() pour trouver l'échec et ses causes.

Remarque

Dans le mode asynchrone d'envoi de demande, l'indicateur de renvoi contient vrai si une tâche a été passée et démarrée avec succès. Cependant, nous devons nous rappeler que même en cas de démarrage réussi d'une tâche, son exécution ne peut être garantie. Par conséquent, cet indicateur ne peut pas être utilisé pour contrôler l'achèvement de la tâche en mode asynchrone. En mode synchrone, une tâche est démarrée et exécutée dans un seul thread, donc en mode synchrone, vous pouvez contrôler le résultat de l'exécution de la demande de trade à l'aide de cet indicateur.


Trade Request Structure HedgeTradeRequest()

Les positions bidirectionnelles dans HedgeTerminal sont fermées et modifiées via un appel de la fonction SendTradeRequest(), dans laquelle la demande de trade est utilisée comme argument. La demande est représentée par une structure prédéfinie spéciale HedgeTradeRequest, qui contient tous les champs nécessaires pour fermer ou modifier la position sélectionnée :

struct HedgeTradeRequest
  {
   ENUM_REQUEST_TYPE action;             // type of action
   double            volume;             // volume of position
   ENUM_CLOSE_TYPE   close_type;         // Marker of closing order
   double            sl;                 // stop-loss level
   double            tp;                 // take-profit level
   string            exit_comment;       // outgoing comment
   uint              retcode;            // last retcode in executed operation
   bool              asynch_mode;        // true if the closure is performed asynchronously, otherwise false
   ulong             deviation;          // deviation in step price
                     HedgeTradeRequest() // default params
     {
      action=REQUEST_CLOSE_POSITION;
      asynch_mode=false;
      volume=0.0;
      sl = 0.0;
      tp = 0.0;
      retcode=0;
      deviation=3;
     }
  };

Description des Champs

ChampDescription
 action Le type de l'action requise avec la position. La valeur peut être n'importe laquelle des valeurs d'énumération ENUM_REQUEST_TYPE
 volume Le volume à fermer. Peut être inférieur au volume de la position actuellement active. Si le volume est nul, la position active sera complètement fermée.
 sl Le niveau de stop loss à placer pour la position active.
 tp Le niveau de take profit à placer pour la position active.
 exit_comment  Le commentaire sortant pour la postion active.
 retcode Code du résultat de la dernière opération exécutée.
 asynch_mode Vrai si le mode asynchrone d'envoi des demandes est utilisé, faux dans le cas contraire.
 Écart Écart maximum par rapport au prix utilisé.


2.6. Énumérations pour l'utilisation des fonctions de sélection des transactions

ENUM_TRANS_TYPE

Toutes les transactions disponibles pour l'analyse, y compris les ordres en attente et les positions bidirectionnelles, figurent sur la liste des transactions actives et historiques.

L'énumération ENUM_TRANS_TYPE contient le type de chaque transaction sélectionnée. Cette énumération est renvoyée par la fonction TransactionType(). Voici les champs d'énumération et leurs descriptions :

ChampDescription
 TRANS_NOT_DEFINED La transaction n'est pas sélectionnée par la fonction TransactionSelect() ou son type n'est pas défini.
 TRANS_HEDGE_POSITION La transaction est une position bidirectionnelle.
 TRANS_BROKERAGE_DEAL  La transaction est une transaction de broker (opération de compte). Par exemple, ajouter de l'argent sur le compte ou corriger.
 TRANS_PENDING_ORDER La transaction est un ordre en attente.
 TRANS_SWAP_POS La transaction est un swap facturé pour une position nette.


ENUM_MODE_SELECT

L'énumération définit le type de paramètre de l'index défini dans la fonction TransactionSelect().

ChampDescription
 SELECT_BY_POS Le paramètre index permet de passer l'index de la transaction dans la liste.
 SELECT_BY_TICKET Le numéro du ticket est transmis dans le paramètre d’index.


ENUM_MODE_TRADES

L'énumération définit la source de données, à partir de laquelle une transaction est sélectionnée à l'aide de TransactionSelect().

ChampDescription
 MODE_TRADES La transaction est sélectionnée parmi les transactions actives.
 MODE_HISTORY La transaction est sélectionnée parmi les transactions historiques.

2.7. Énumérations pour l'utilisation des fonctions qui obtiennent les propriétés des transactions

Énumération ENUM_TRANS_DIRECTION

Chaque transaction, qu'il s'agisse d'une transaction ou d'une position bidirectionnelle, a une direction de marché.

Cette direction de marché est définie par l'énumération ENUM_TRANS_DIRECTION. Voici ses champs et leurs descriptions :

ChampDescription
 TRANS_NDEF Le sens d'une transaction n'est pas défini. Par exemple, les transactions du broker sur le compte n'ont pas de direction du marché et sont accompagnées de ce modificateur.
 TRANS_LONG Indique que la transaction (ordre ou position bidirectionnelle) est une transaction d'achat.
 TRANS_SHORT  Indique que la transaction (ordre ou position bidirectionnelle) est une transaction de vente.


Énumération ENUM_HEDGE_POSITION_STATUS

L'énumération contient l'état d'une position bidirectionnelle.

ChampDescription
 HEDGE_POSITION_ACTIVE  Une position active. Les positions actives apparaissent dans l'onglet Actif du panneau HedgeTerminal.
 HEDGE_POSITION_HISTORY  Une position historique. Les positions historiques apparaissent dans l'onglet Historique du panneau HedgeTerminal.


Énumération ENUM_HEDGE_POSITION_STATE

L'énumération contient l'état d'une position bidirectionnelle.

ChampDescription
 POSITION_STATE_ACTIVE La position sélectionnée est active et peut être modifiée à l'aide de HedgeTradeRequest.
 POSITION_STATE_FROZEN  La position sélectionnée est verrouillée et ne peut pas être modifiée. Si ce modificateur est reçu, il faut attendre que la position soit déverrouillée.


Énumération ENUM_HEDGE_POSITION_PROP_INTEGER

L'énumération définit le type de la propriété renvoyée par HedgePositionGetInteger().

ChampDescription
 HEDGE_POSITION_ENTRY_TIME_SETUP_MSC Le temps en millisecondes depuis le 01.01.1970, lorsque l’ordre initiant la position bidirectionnelle a été passé.
 HEDGE_POSITION_ENTRY_TIME_EXECUTED_MSC  Le temps en millisecondes depuis le 01.01.1970, lorsque l'ordre initiant la position bidirectionnelle a été exécuté (heure d'ouverture de la position).
 HEDGE_POSITION_EXIT_TIME_SETUP_MSC Le temps en millisecondes depuis le 01.01.1970, lorsque l’ordre de fermeture de la position bidirectionnelle a été passé.
 HEDGE_POSITION_EXIT_TIME_EXECUTED_MSC Le temps en millisecondes depuis le 01.01.1970, lorsque l'ordre de fermeture de la position bidirectionnelle a été exécuté (heure de fermeture de la position).
 HEDGE_POSITION_TYPE Le type de la position bidirectionnelle. Égal au type de l'ordre d’initiation. Contient l'une des valeurs de l'énumération système ENUM_ORDER_TYPE.
 HEDGE_POSITION_DIRECTION Sens de la position. Défini par l'énumération ENUM_TRANS_DIRECTION.
 HEDGE_POSITION_MAGIC Le nombre magique de l'Expert Advisor auquel appartient la position sélectionnée. Une valeur de zéro indique que la position a été ouverte manuellement.
 HEDGE_POSITION_CLOSE_TYPE Le marqueur de l'ordre fermant la position. Défini par ENUM_CLOSE_TYPE.
 HEDGE_POSITION_ID Identifiant de la position Égal à l'identifiant de l'ordre d’initiation.
 HEDGE_POSITION_ENTRY_ORDER_ID L'identifiant de l'ordre d’initiation.
 HEDGE_POSITION_EXIT_ORDER_ID L'identifiant d'un ordre de fermeture pour une position historique.
 HEDGE_POSITION_STATUS Statut de la position. Défini par ENUM_HEDGE_POSITION_STATUS.
 HEDGE_POSITION_STATE État de la position. Défini par ENUM_HEDGE_POSITION_STATE
 HEDGE_POSITION_USING_SL L’indicateur de l'utilisation du stop loss. Si un stop loss est utilisé, la fonction HedgePositionGetInteger() renvoie vrai, dans le cas contraire elle renvoie faux.
 HEDGE_POSITION_USING_TP L’indicateur d'un niveau de take profit utilisé. Si un take profit est utilisé, HedgePositionGetInteger() renvoie vrai, dans le cas contraire elle renvoie faux.
 HEDGE_POSITION_TASK_STATUS Statut de la tâche en cours d'exécution pour la position sélectionnée. La position peut être en cours de modification. Ce modificateur est utilisé pour suivre les modifications dans cette position. L'état de la position est défini par ENUM_TASK_STATUS.
 HEDGE_POSITION_ACTIONS_TOTAL Renvoie le nombre total de sous-tâches commencées pour modifier cette position.

 

Énumération ENUM_HEDGE_POSITION_PROP_DOUBLE

L'énumération définit le type de la propriété renvoyée par la fonction HedgePositionGetDouble().

ChampDescription
 HEDGE_POSITION_VOLUME Le volume de la position bidirectionnelle.
 HEDGE_POSITION_PRICE_OPEN Le prix d'ouverture moyen pondéré d'une position.
 HEDGE_POSITION_PRICE_CLOSED Le prix de fermeture moyen pondéré d'une position.
 HEDGE_POSITION_PRICE_CURRENT Le prix actuel d'une position active. Pour une position historique, ce modificateur renvoie le prix de fermeture de la position.
 HEDGE_POSITION_SL Le niveau de stop loss. Zéro si le stop loss n'est pas utilisé.
 HEDGE_POSITION_TP Le niveau de take profit. Zéro si le take profit n'est pas utilisé.
 HEDGE_POSITION_COMMISSION Le montant de la commission payée pour la position.
 HEDGE_POSITION_SLIPPAGE Glissement de points.
 HEDGE_POSITION_PROFIT_CURRENCY  Profit ou perte de la position. La valeur est spécifiée dans la devise de dépôt.
 HEDGE_POSITION_PROFIT_POINTS Profit ou perte de la position. La valeur est précisée dans les points du symbole financier de la position.

Remarque

Le glissement HEDGE_POSITION_SLIPPAGE est calculé comme la différence en points entre la meilleure transaction d'entrée de position et le prix d'entrée moyen pondéré.

 

Énumération ENUM_HEDGE_POSITION_PROP_STRING

L'énumération définit le type de la propriété renvoyée par la fonction HedgePositionGetString().

ChampDescription
 HEDGE_POSITION_SYMBOL Le symbole de la position actuelle.
 HEDGE_POSITION_ENTRY_COMMENT Le commentaire entrant d'une position.
 HEDGE_POSITION_EXIT_COMMENT Le commentaire sortant d'une position.

 

Énumération ENUM_HEDGE_ORDER_STATUS

L'énumération contient le type d’ordre.

ChampDescription
 HEDGE_ORDER_PENDING  L'ordre est en attente et est disponible dans l'onglet Trade de MetaTrader 5.
 HEDGE_ORDER_HISTORY L'ordre est historique et est disponible dans l'historique des ordres dans MetaTrader 5.

Énumération ENUM_HEDGE_ORDER_SELECTED_TYPE

L'énumération définit le type de l'ordre sélectionné par la fonction HedgeOrderSelect().

ChampValeur
 ORDER_SELECTED_INIT L'ordre initie une position bidirectionnelle.
 ORDER_SELECTED_CLOSED  L'ordre ferme une position bidirectionnelle.
 ORDER_SELECTED_SL L'ordre agit comme un niveau de stop loss.


Énumération ENUM_HEDGE_ORDER_PROP_INTEGER

L'énumération définit le type de la propriété renvoyée par la fonction HedgePositionGetInteger().

ChampDescription
 HEDGE_ORDER_ID Un identifiant d’ordre unique.
 HEDGE_ORDER_STATUS État de l'ordre La valeur peut être l'une des valeurs de l'énumération ENUM_HEDGE_ORDER_STATUS.
 HEDGE_ORDER_DEALS_TOTAL Le nombre total de transactions ayant rempli l’ordre. La valeur est zéro pour les ordres en attente.
 HEDGE_ORDER_TIME_SETUP_MSC Délai de passation de l’ordre en attente en millisecondes depuis le 01.01.1970.
 HEDGE_ORDER_TIME_EXECUTED_MSC Le temps d'exécution d'un ordre exécuté en millisecondes depuis le 01.01.1970.
 HEDGE_ORDER_TIME_CANCELED_MSC L'heure d'annulation d'un ordre exécuté en millisecondes depuis le 01.01.1970.

Remarque

L'heure d'exécution de l'ordre HEDGE_ORDER_TIME_EXECUTED_MSC est égale à l'heure de sa dernière transaction. 


Énumération ENUM_HEDGE_ORDER_PROP_DOUBLE

L'énumération définit le type de la propriété renvoyée par la fonction HedgeOrderGetDouble().

ChampDescription
 HEDGE_ORDER_VOLUME_SETUP Le volume de l’ordre spécifié dans l’ordre.
 HEDGE_ORDER_VOLUME_EXECUTED Volume exécuté de l’ordre. Si un ordre est en attente, le volume exécuté est nul.
 HEDGE_ORDER_VOLUME_REJECTED Le volume de l'ordre qui n'a pas pu être exécuté. Égal à la différence entre le volume initial et le volume exécuté.
 HEDGE_ORDER_PRICE_SETUP Prix de passation de l’ordre.
 HEDGE_ORDER_PRICE_EXECUTED Le prix d'exécution moyen pondéré d'un ordre.
 HEDGE_ORDER_COMMISSION Le montant de la commission versée au broker pour l'exécution de l'ordre. Spécifié dans la devise de dépôt.
 HEDGE_ORDER_SLIPPAGE Glissement de l’ordre.

Remarque

Le glissement HEDGE_ORDER_SLIPPAGE est calculé comme la différence en points entre la transaction la mieux exécutée et le prix d'entrée moyen pondéré de l'ordre.


Énumération ENUM_HEDGE_DEAL_PROP_INTEGER

L'énumération définit le type de la propriété renvoyée par HedgeDealGetInteger().

ChampDescription
 HEDGE_DEAL_ID Un identifiant de transaction unique.
 HEDGE_DEAL_TIME_EXECUTED_MSC Temps d'exécution de la transaction en millisecondes depuis le 01.01.1970


Énumération ENUM_HEDGE_DEAL_PROP_DOUBLE

L'énumération définit le type de la propriété renvoyée par HedgeDealGetDouble().

ChampDescription
 HEDGE_DEAL_VOLUME_EXECUTED Volume d'une transaction.
 HEDGE_DEAL_PRICE_EXECUTED Prix d’exécution de la transaction
 HEDGE_DEAL_COMMISSION Le montant de la commission versée au broker pour l'exécution de la transaction. Spécifié dans la devise de dépôt.

 

2.8. Énumérations pour la définition et l'obtention des propriétés de HedgeTerminal

Énumération ENUM_HEDGE_PROP_INTEGER

L'énumération définit le type de propriété que vous souhaitez obtenir ou définir dans HedgeTerminal.

ChampDescription
 HEDGE_PROP_TIMEOUT Temps, en secondes, pendant lequel HedgeTerminal attendra une réponse du serveur avant de déverrouiller une position en cours de modification.


2.9. Énumérations pour l'utilisation des fonctions de gestion des codes d'erreur

Énumération ENUM_TASK_STATUS

Chaque position bidirectionnelle peut être en cours de modification. Une position est modifiée via une tâche de trade.

Chaque tâche de trading en cours a son statut d'exécution défini dans ENUM_TASK_STATUS. Voici ses champs et leurs descriptions :

ChampDescription
 TASK_STATUS_WAITING Aucune tâche en cours ou la tâche est en attente.
 TASK_STATUS_EXECUTING La tâche de trading est en cours d'exécution.
 TASK_STATUS_COMPLETE La tâche de trading pour la position s'est terminée avec succès.
 TASK_STATUS_FAILED La tâche de trading pour la position a échoué.


Énumération ENUM_HEDGE_ERR

L'énumération contient l'identifiant de l'erreur qui peut être renvoyé par GetHedgeError().

ChampDescription
 HEDGE_ERR_NOT_ERROR Pas d'erreur.
 HEDGE_ERR_TASK_FAILED La tâche pour la position sélectionnée a échoué.
 HEDGE_ERR_TRANS_NOTFIND Transaction introuvable.
 HEDGE_ERR_WRONG_INDEX Indice incorrect.
 HEDGE_ERR_WRONG_VOLUME Volume incorrect.
 HEDGE_ERR_TRANS_NOTSELECTED  La transaction n'a pas été présélectionnée à l'aide de TransactionSelect().
 HEDGE_ERR_WRONG_PARAMETER L'un des paramètres transmis est incorrect.
 HEDGE_ERR_POS_FROZEN La position bidirectionnelle est actuellement en cours de modification et n'est pas disponible pour de nouvelles modifications. Attendez que la position soit libérée.
 HEDGE_ERR_POS_NO_CHANGES La demande de trade n'a pas changé.

 

Énumération ENUM_TARGET_TYPE

L'énumération définit le type de la tâche sélectionnée par la fonction GetActionResult().

ChampDescription
 TARGET_NDEF La sous-tâche n'est pas définie.
 TARGET_CREATE_TASK La sous-tâche est en cours de création. Ce type est utilisé dans les logiques internes de HedgeTerminalAPI.
 TARGET_DELETE_PENDING_ORDER Supprimer un ordre en attente
 TARGET_SET_PENDING_ORDER Passation d'un ordre en attente.
 TARGET_MODIFY_PENDING_ORDER  Modification du prix de l’ordre en attente.
 TARGET_TRADE_BY_MARKET Faire des opérations de trading.


2.10. Énumérations pour l'utilisation des fonctions de gestion des codes d'erreur

Énumération ENUM_REQUEST_TYPE

L'énumération décrit l'action de HedgeTerminal appliquée à la position bidirectionnelle.

ChampDescription
 REQUEST_CLOSE_POSITION Ferme la position. Si le champ de volume de la structure HedgeTradeRequest contient un volume inférieur au volume actuel, seule une partie de la position sera fermée. Dans ce cas, la partie de la position fermée correspond à la valeur du champ de volume.
 REQUEST_MODIFY_SLTP Définit ou modifie les niveaux existants de stop loss et take profit.
 REQUEST_MODIFY_COMMENT Modifie le commentaire sortant d'une position active.


Énumération ENUM_CLOSE_TYPE

L'énumération définit un marqueur spécial pour l'ordre fermant la position bidirectionnelle. Le marqueur indique la cause de la fermeture de la position. Cela peut être l'une des causes suivantes :

  • La position a atteint le niveau de perte maximum ou le stop loss ;
  • La position a atteint un certain niveau de profit ou le take profit ;
  • Position fermée par le marché. Les niveaux stop loss et take profit n'ont pas été passés ou atteints.
ChampDescription
 CLOSE_AS_MARKET Indique que la position est fermée par marché. Les niveaux stop loss et take profit n'ont pas été passés ou atteints.
 CLOSE_AS_STOP_LOSS Indique que la position est fermée en raison de l'atteinte du niveau stop loss.
 CLOSE_AS_TAKE_PROFIT  Indique que la position est fermée en raison de l'atteinte du niveau take profit.

 

Chapitre 3. Les fondamentaux du trading asynchrone

Le sujet des opérations asynchrones est complexe et nécessite un article détaillé séparé. Cependant, du fait que HedgeTerminal utilise activement des opérations asynchrones, il convient de décrire brièvement les principes d'organisation des Expert Advisors utilisant ce type de dépôt de demande . De plus, il n'y a presque pas de documents sur le sujet.

3.1. Organisation et schéma d'envoi d'un ordre de trading synchrone

MetaTrader 5 fournit deux fonctions pour envoyer des demandes de trade au serveur :

La fonction OrderSend() accepte une demande en tant que structure MqlTradeRequest remplie et effectue une vérification de base de l'exactitude de la structure. Si la vérification de base réussit, il envoie la demande à un serveur, attend son résultat, puis renvoie le résultat au thread personnalisé via la structure MqlTradeResult et l'indicateur de renvoi. Si la vérification de base échoue, la fonction renvoie une valeur négative.

La raison pour laquelle la demande n'a pas pu être vérifiée est également incluse dans MqlTradeResult.

Le schéma ci-dessous présente l'exécution du thread d'un programme MQL5 personnalisé avec la fonction OrderSend() :

Fig. 6. Le schéma d'organisation et d'envoi d'une demande de trade synchrone

Fig. 6. Le schéma d'organisation et d'envoi d'une demande de trade synchrone.

Comme le montre le schéma, le thread du programme MQL5 ne peut pas être séparé du thread système commun envoyant une demande au serveur et exécutant des opérations de trade sur la bourse.

C'est pourquoi, après l'achèvement de OrderSend(), nous pouvons analyser le résultat réel de la demande de trade. Le thread personnalisé est marqué par des flèches rouges. Il est exécuté presque instantanément. La plupart du temps est pris pour effectuer des opérations de trading sur la bourse. Étant donné que les deux threads sont connectés, un temps considérable s'écoule entre le début et la fin de la fonction OrderSend(). Du fait que les opérations de trading sont exécutées dans un seul thread, la logique des programmes MQL5 peut être séquentielle.


3.2. Organisation et schéma d'envoi d'un ordre de trading asynchrone

La fonction OrderSendAsync() est différente. Comme OrderSend(), elle accepte la demande de trade MqlTradeRequest et renvoie un indicateur indiquant son résultat.

Cependant, contrairement au premier exemple, elle n'attend pas que la demande de trade soit exécutée par le serveur, mais renvoie les valeurs ​​obtenu uniquement à partir du module de vérification de base des valeurs de demande de trade (Vérification de base à l'intérieur du terminal). Le schéma ci-dessous montre la procédure d'exécution du thread personnalisé lors de l'utilisation de la fonction OrderSendAsync() :

Fig. 7. Le schéma d'organisation et d'envoi d'une demande de trade asynchrone.

Fig. 7. Le schéma d'organisation et d'envoi d'une demande de trade asynchrone.

Une fois qu'une demande de trade est vérifiée avec succès, elle est envoyée au serveur de trading parallèlement au thread principal. La transmission d'une demande de trade sur le réseau ainsi que son exécution sur la bourse prend un certain temps, comme dans le premier cas. Mais le thread personnalisé obtiendra presque un résultat instantané de la fonction OrderSendAsync().

Le schéma ci-dessus montre que OrderSendAsync() forme en fait un nouveau thread parallèle qui est exécuté par un serveur de trading, et son résultat d'exécution entre dans la fonction OnTradeTransaction() ou OnTrade(). Ces fonctions commencent un nouveau thread personnalisé. Le résultat de l'envoi d'une demande d'échange doit être traité dans ce nouveau thread. Cela complique fortement la logique de l'Expert Advisor, car avec l'envoi d’ordre asynchrone, il est impossible d'organiser l'envoi de la demande et sa vérification dans un seul thread. Par exemple, vous ne pouvez pas placer séquentiellement le code d'envoi et de vérification d'une demande dans OnTick().

Écrivons un test simple de l’EA pour illustrer ce qui précède :

//+------------------------------------------------------------------+
//|                                                    AsynchExp.mq5 |
//|                           Copyright 2014, Vasiliy Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
input bool UsingAsynchMode=true;
bool sendFlag=false;
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(sendFlag)return;
   printf("Formation of order and send to the server...");
   MqlTradeRequest request={0};
   request.magic=12345;
   request.symbol = Symbol();
   request.volume = 0.1;
   request.type=ORDER_TYPE_BUY;
   request.comment= "asynch test";
   request.action = TRADE_ACTION_DEAL;
   request.type_filling=ORDER_FILLING_FOK;
   MqlTradeResult result;
   uint tiks= GetTickCount();
   bool res = false;
   if(UsingAsynchMode)
      res=OrderSendAsync(request,result);
   else
      res=OrderSend(request,result);
   uint delta=GetTickCount()-tiks;
   if(OrderSendAsync(request,result))
     {
      printf("The order has been successfully"+
             "sent to the server.");
     }
  else
     {
     printf("The order is not shipped."+
             " Reason: "+(string)result.retcode);
     }
   printf("Time to send a trade request: "+(string)delta);
   sendFlag=true;
//---
  }

Assurons-nous que l'EA fonctionne en le démarrant avec UsingAsynchMode = false.

L'EA ouvre une position longue de 0,1 lot. La demande de trade est effectuée de manière synchrone à l'aide de la fonction OrderSend(). Voici son exemple de journal :

2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   Time to send a trade request: 94
2014.11.06 17:49:28.442 AsynchExp (AUDCAD,H1)   The order has been successfullysent to the server.
2014.11.06 17:49:28.345 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

La demande d'échange a été traitée en 94 millisecondes. Ce temps nous indique que la demande a transmis la vérification de base, a été envoyée au serveur, puis a été remplie.

Maintenant, nous modifions le code de l’EA en changeant le volume de transaction à la valeur maximale possible DBL_MAX:

request.volume = DBL_MAX;

Évidemment, cette valeur est en dehors de la plage réelle. Essayons d'exécuter cette demande en mode synchrone :

2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10014
2014.11.06 17:54:15.373 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

L'envoi d'une demande a échoué. La raison de l'échec est l'erreur 10014 (volume demandé non valide). La demande a échoué lors de la vérification de base et n'a même pas été envoyée au serveur, comme le montre clairement le temps d'exécution de la demande de 0 milliseconde.

Encore une fois, modifions la demande. Cette fois, nous avons spécifié un volume suffisamment important, mais pas une valeur extrême - 15 lots. Pour le compte de 1 000 $ où l'EA est testé, cela est trop. Une telle position ne peut être ouverte sur ce compte.

Voyons ce que renvoie OrderSend() :

2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   Time to send a trade request: 78
2014.11.06 17:59:22.643 AsynchExp (AUDCAD,H1)   The order is not shipped. Reason: 10019
2014.11.06 17:59:22.550 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

L'erreur est différente cette fois : 10019 (Fonds insuffisants pour l'exécution de la demande, ce qui est vrai). Notez que le temps d'exécution de la demande est désormais de 79 millisecondes. Il indique que la demande a été envoyée au serveur et que le serveur a renvoyé une erreur.

Envoyons maintenant la même demande avec le volume de 15 lots en utilisant la fonction OrderSendAsync(). Comme avec OrderSend(), aucune position n'est ouverte. Mais analysons le journal :

2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:03:58.106 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:03:58.104 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

Le journal indique qu'il n'y a pas d'erreur ! Étant donné que l'erreur 10019 est détectée par le serveur de trading, elle n'est pas disponible pour le thread actuel en mode d'envoi d'ordre asynchrone. La valeur du renvoi indique uniquement que la demande a réussi la vérification de base. Afin d'obtenir l'erreur réelle 10019, nous devons analyser les résultats dans un nouveau thread personnalisé, dans la fonction système OnTradeTransaction(), qui devrait être ajouté à notre EA :

void  OnTradeTransaction(const MqlTradeTransaction    &trans,
                         const MqlTradeRequest        &request,
                         const MqlTradeResult         &result)
  {
   uint delta = GetTickCount() - tiks;
   printf("Server answer: " + (string)result.retcode + "; Time: " + (string)delta);
  }

Exécutons à nouveau l'EA et voyons les journaux :

2014.11.06 18:17:00.943 AsynchExp (AUDCAD,H1)   Server answer: 10019; Time: 94
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   Time to send a trade request: 0
2014.11.06 18:17:00.854 AsynchExp (AUDCAD,H1)   The order has been successfully sent to the server.
2014.11.06 18:17:00.851 AsynchExp (AUDCAD,H1)   Formation of order and send to the server...

L'erreur 10019 a été reçue, mais pas immédiatement après l'envoi. Elle a été reçue dans le nouveau thread personnalisé exécuté dans OnTradeTransaction().


3.3. Vitesse d'exécution des ordres asynchrones

Les traders pensent à tort que la vitesse d'exécution d'une demande asynchrone est proche de zéro.

Elle découle de l'observation de OrderSendAsync(), qui se termine en règle générale en moins d'une milliseconde. En réalité, comme cela a été montré ci-dessus, le temps réel d'exécution de la transaction de trade doit être mesuré lorsqu'une réponse du service est reçue à l'intérieur des fonctions OnTradeTransaction() ou OnTrade(). Cette mesure montre la vitesse réelle, qui est égale à la vitesse d'une exécution synchrone pour un seul ordre. De réels avantages en temps d'exécution sont perceptibles lors de l'envoi d'un groupe de transactions. Il existe au moins trois situations dans lesquelles vous devez envoyer plusieurs demandes :

  • Le temps nécessaire entre deux demandes successives est si petit qu'il n'y a aucune possibilité de vérifier le résultat de la demande avant d'envoyer la suivante. Lorsque la prochaine demande est envoyée, on espère que la précédente a été exécutée. Des tactiques similaires sont utilisées dans le trading à haute fréquence ;
  • Vous devez ouvrir plusieurs positions pour plusieurs symboles à la fois. Par exemple, les stratégies d'arbitrage et les positions synthétiques composites nécessitent l'ouverture simultanée de positions pour divers instruments aux prix courants. La formation progressive de positions n'est pas souhaitable dans de telles tactiques ;
  • Il est nécessaire de terminer le thread dès que possible et d'attendre d'autres événements et ordres utilisateurs. Cette exigence est importante pour les solutions multithread et d'infrastructure. C'est la principale raison pour laquelle HedgeTerminal utilise des demande asynchrones. Si HT utilisait l'envoi synchrone de demandes, il se figerait constamment pendant 1 à 2 secondes à chaque fois que l'utilisateur ferme ou modifie la position, ce qui est inacceptable.

N'oubliez pas de prendre en compte la limite d'envoi de demandes lors de la passation de plusieurs ordres.

Dans MetaTrader 5 build 1010 et supérieur, la limite est de 64 transactions, dont 4 sont réservées aux utilisateurs, et d'autres sont disponibles pour les Expert Advisors. La limite vise à protéger les traders débutants contre les erreurs graves dans leurs programmes, ainsi qu'à réduire la charge de spam sur un serveur de trading.

Cela signifie qu'en même temps, par exemple dans la boucle pour, vous pouvez envoyer jusqu'à 60 ordres de trade en appelant SendOrderAsync() avec une demande de trade appropriée. Une fois les 60 transactions envoyées, le tampon de transaction sera plein. Nous devons attendre la confirmation du serveur qu'une des transactions a été traitée par le serveur.

Après avoir été traitée, la place d'une transaction dans le tampon des transactions est libérée, et une nouvelle demande de trade peut la prendre. Une fois que le tampon est plein, l'espace pour les nouvelles transactions est libéré lentement, car un serveur de trade a besoin de temps pour traiter chaque transaction, et l'événement TradeTransaction() notifiant le début du traitement est transmis sur le réseau, ce qui entraîne des retards supplémentaires.

Ainsi, le temps nécessaire pour envoyer des demandes augmentera de manière non linéaire par rapport à la croissance du nombre de demandes. Le tableau ci-dessous présente les taux estimés d'envoi d'ordres en mode asynchrone. Les tests ont été effectués plusieurs fois, et le taux affiché est la valeur moyenne :

Nombre de demandesmillisecondes
5050
100180
2002100
5009000
100023000

Dans le cas où le nombre de demandes est inférieur à 60, le script n'attend pas la réponse du serveur, c'est pourquoi le temps est si court. Il est approximativement égal au temps qu'il faut pour envoyer une seule requête. En fait, pour obtenir un temps d'exécution réel approximatif, ajoutez le temps d'exécution moyen de la demande au temps de placement de la demande spécifié dans le tableau.

 

Chapitre 4. Les bases de la programmation multithread dans l'IDE MetaTrader 5

Les programmeurs MQL5 savent que les threads ne peuvent pas être contrôlés directement à partir des programmes MQL. Cette restriction est pour le bien des programmeurs novices, car l'utilisation de threads complique grandement les algorithmes du programme. Cependant, dans certaines situations, deux ou plusieurs EA doivent communiquer entre eux, par exemple, ils doivent créer et lire des données globales.

HedgeTerminal est l'un de ces EA. Pour informer chaque EA utilisant la bibliothèque HedgeTerminalAPI des actions des autres Expert Advisors, le HT organise l'échange de données via la lecture et l'écriture multithread du fichier ActivePositions.xml. Cette solution n'est pas triviale et est rarement utilisée par les programmeurs MQL. Par conséquent, nous allons créer un EA multithread avec un algorithme similaire à HedgeTerminal. Cela permettra de mieux comprendre la programmation multi-thread, et ainsi de mieux comprendre le fonctionnement de HedgeTerminal.


4.1. Programmation multi-thread à travers l'exemple de Quote Collector UnitedExchangeQuotes

Nous apprendrons les bases de la programmation multi-thread à travers un exemple précis : nous écrirons un collecteur de cotations de différents fournisseurs (brokers).

L'idée est la suivante : supposons que nous ayons 6 à 7 brokers qui fournissent des cotations pour le même instrument. Naturellement, les cotations de différents brokers peuvent varier légèrement. L'analyse de ces différences ouvre la voie à des stratégies d'arbitrage. De plus, la comparaison de la dynamique des cotations aidera à identifier le meilleur et le pire fournisseur. Par exemple, si un broker propose de meilleurs prix, nous préférons sélectionner ce broker avec lequel faire le trade. Nous ne recherchons pas la valeur pratique des résultats, nous décrivons plutôt le mécanisme par lequel ces résultats peuvent être atteints.

Voici une capture d'écran de l'EA que nous devrons écrire d'ici la fin de ce chapitre :

Fig. 8. L'apparition du collecteur de cotation UnitedExhangesQuotes.

Fig. 8. L'apparition du collecteur de cotation UnitedExhangesQuotes.

L'Expert Advisor affiche les résultats dans un tableau simple composé de quatre colonnes et d'un nombre illimité de lignes.

Chaque ligne représente un broker fournissant des cotations de symboles (dans ce cas, EURUSD). Ask et Bid sont la meilleure offre et demande du broker. La capture d'écran montre que les prix diffèrent légèrement. La différence entre l'offre du broker actuel et une autre apparaît dans la colonne D-ASK (Delta Ask). De même, la différence entre les valeurs de demande est affichée dans D-BID (Delta Bid). Par exemple, au moment où la capture d'écran a été prise, le meilleur Ask a été fourni par « Alpari Limited », et le plus cher était celui de « Bank VTB 24 ».

Les programmes MQL ne peuvent pas accéder à l'environnement des autres terminaux MetaTrader. En d'autres termes, si un programme s'exécute sur un terminal, il ne peut pas recevoir de données d'un autre. Cependant, tous les programmes MQL peuvent communiquer via les fichiers du répertoire partagé des terminaux MetaTrader. Si un programme écrit des informations, par exemple la cotation actuelle, dans un fichier, le programme MQL d'un autre terminal peut les lire. MQL n'a pas d'autre moyen sans DLL externes. Par conséquent, nous utiliserons cette méthode.

La plus grande difficulté est d'organiser un tel accès. D'une part, l'EA doit lire les cotations d'autres fournisseurs et, d'autre part, écrire la cotation de son fournisseur dans le même fichier. Un autre problème est le fait qu'au moment de la lecture des cotations, un autre EA peut écrire une nouvelle cotation dans ce fichier. Le résultat d'un tel travail parallèle est imprévisible. Au mieux, cela sera suivi d'un plantage et d'une interruption du programme, et au pire cela conduira à l'apparition occasionnelle d'étranges erreurs subtiles associées à l'affichage des cotations.

Pour éliminer ces erreurs, ou au moins minimiser la probabilité de leur apparition, nous élaborerons un plan clair.

Premièrement, toutes les informations seront stockées au format XML. Ce format a remplacé le fichier maladroit ini. XML permet un déploiement flexible de ses nœuds dans des structures de données complexes, telles que des classes. Ensuite, déterminons l'algorithme général de lecture et d'écriture. Il existe deux opérations de base : la lecture de données et l'écriture de données. Lorsqu'un programme MQL lit ou écrit, aucun autre programme ne peut accéder à ce fichier. Ainsi, nous éliminons une situation où un programme lit les données et le second les modifie. Pour cette raison, l'accès aux données ne sera pas toujours possible.

Créons une classe spéciale CQuoteList qui contiendra les algorithmes d'accès XML, ainsi que les données de toutes les cotations de ce fichier.

Une des fonctions de cette classe est TryGetHandle(), elle essaie d'accéder au fichier et renvoie sa gestion en cas de succès. Voici la mise en œuvre de la fonction :

int CQuoteList::TryGetHandle(void)
{
   int attempts = 10;
   int handle = INVALID_HANDLE;
   // We try to open 'attemps' times
   for(att = 0; att < attempts; att++)
   {
      handle = FileOpen("Quotes.xml", FILE_WRITE|FILE_READ|FILE_BIN|FILE_COMMON);
      if(handle == INVALID_HANDLE)
      {
         Sleep(15);
         continue;
      }
      break;
   }
   return handle;
}

Elle effectue plusieurs tentatives pour ouvrir le fichier en mode lecture/écriture combiné. Le nombre de tentatives par défaut est de dix.

Si une tentative échoue, la fonction se bloque pendant 15 millisecondes et réessaye d'ouvrir le fichier, faisant ainsi jusqu'à 10 tentatives.

Une fois le fichier ouvert, sa gestion est transmise à la fonction LoadQuotes(). Une liste complète de cette fonction et la classe CQuoteList sont disponibles en pièce jointe sur l'article. Nous décrivons donc ici uniquement la séquence des actions dans la fonction :

  1. TryGetHandle() ouvre le fichier pour lecture et écriture ;
  2. Le document XML est téléchargé dans la mémoire de l’EA à l'aide de la bibliothèque XML Parser ;
  3. Sur la base du document XML téléchargé, un nouveau tableau de cotations est formé pour stocker les informations requises ;
  4. Le tableau créé contient une cotation appartenant à l'EA actuel. Ses valeurs ​sont mis à jour ;
  5. Le tableau de cotation est reconverti en un document XML. Le contenu du fichier XML ouvert est remplacé par ce document XML ;
  6. Le fichier XML des cotations est fermé.

La fonction LoadQuotes() fait un excellent travail, mais dans la plupart des cas, cela prend moins d'une milliseconde.

La lecture et la mise à jour des données avec leur sauvegarde ultérieure sont combinées en un seul bloc. Ceci est fait exprès, afin de ne pas perdre le contrôle d'accès au fichier entre les opérations de lecture et d'écriture.

Une fois que les cotations sont chargées et se trouvent dans une classe, elles sont accessibles comme n'importe quelle autre donnée dans MetaTrader 5. Cela se fait via une interface de programme spéciale de style MetaTrader 5 implémentée dans la classe CQuotesList.

L'appel de fonction et le rendu des données sont effectués à l'intérieur du bloc OnTick(). Voici le contenu :

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
      return;
   if(!QuotesList.LoadQuotes())    
      return;   
   PrintQuote quote = {0};
   Panel.DrawAccess(QuotesList.CountAccess());
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   string brokerName = AccountInfoString(ACCOUNT_COMPANY);
   for(int i = 0; i < QuotesList.BrokersTotal(); i++)
   {
      if(!QuotesList.BrokerSelect(i))
         continue;
      if(!QuotesList.SymbolSelect(Symbol()))
         continue;
      quote.ask = QuotesList.QuoteInfoDouble(QUOTE_ASK);
      quote.bid = QuotesList.QuoteInfoDouble(QUOTE_BID);
      quote.delta_ask = ask - quote.ask;
      quote.delta_bid = quote.bid - bid;
      quote.broker_name = QuotesList.BrokerName();
      quote.index = i;
      Panel.DrawBroker(quote);
   }
  }

Il est à noter que l'exemple de code fonctionne à la fois dans MetaTrader 4 et dans MetaTrader 5 sans aucune modification supplémentaire !

Il n'y a que des différences cosmétiques mineures dans la façon dont les panneaux sont affichés dans différentes versions du terminal. Sans aucun doute, c'est un fait remarquable qui facilite le portage de code entre les plates-formes.

Le fonctionnement de l'EA est mieux observé dans les éléments dynamiques. La vidéo ci-dessous montre le fonctionnement de l'EA sur différents comptes :

 

La lecture et l'écriture dans un fichier présentent des avantages significatifs, mais il y a aussi quelques inconvénients.

Les principaux avantages sont :

  1. La flexibilité. Vous pouvez stocker et charger toutes les données, même des classes entières ;
  2. Vitesse relativement élevée L'ensemble du cycle de lecture et de réécriture prend presque toujours plus d'une milliseconde, ce qui est un bon timing par rapport aux opérations de trading relativement lentes qui prennent 80 à 150 millisecondes ou parfois même plus ;
  3. Basé sur les outils standards du langage MQL5 sans appel de DLL.

Le principal inconvénient d'une telle solution est une charge importante sur le système de stockage. Lorsqu'il y a un devis et deux brokers, le nombre d'opérations de réécriture est relativement faible, mais avec un flux important de devis et un grand nombre de brokers/symboles, le nombre d'opérations de réécriture devient très important. En moins d'une heure, l’EA démo a produit plus de 90 000 opérations de réécriture de fichiers Quotes.xml. Ces statistiques sont affichées en haut du panneau de l’EA : « I/O Rewrite » indique le nombre total de réécritures de fichiers, « fps » indique le taux entre les deux dernières opérations de réécriture et « Avrg » affiche la vitesse moyenne des réécritures par seconde.

Si vous stockez des fichiers sur SSD ou HDD, ces opérations auront un impact négatif sur la durée de vie du disque. Il est donc préférable d'utiliser un disque RAM virtuel pour un tel échange de données.

Contrairement à l'exemple ci-dessus, HedgeTerminal utilise avec parcimonie ActivePositions.xml, en écrivant uniquement les changements de position importants qui sont inaccessibles via le contexte global. Il produit donc beaucoup moins d'opérations de lecture/écriture que l'exemple ci-dessus et ne nécessite donc pas de conditions particulières, telles que des disques RAM.


4.2. Utilisation de l'interaction multithread entre les Expert Advisors

L'interaction en temps réel entre des programmes MQL indépendants est un sujet compliqué mais intéressant. L'article n'en contient qu'une très brève description, mais cela mérite un article séparé. Dans la plupart des cas, l'interaction multithread entre les Expert Advisors n'est pas requise. Cependant, voici une liste de tâches et la variété de programmes, pour lesquels l'organisation d'une telle interaction est requise :

  • Copieur de trade. Tout copieur de trade implique le lancement simultané d'au moins deux EA, dont l'un fournit des trades, et l'autre les copie. Dans ce cas, il est nécessaire d'organiser la lecture/écriture multithread d'un fichier de données commun pour fournir et copier les trades ;
  • Organisation des échanges de données globales entre EA, variables globales. Les variables globales standard dans MetaTrader 5 ne sont disponibles pour les Expert Advisors qu'au niveau d'un seul terminal. Une variable globale déclarée dans un terminal n'est pas disponible dans l'autre. Cependant, grâce à l'utilisation de données communes, vous pouvez organiser des variables globales complexes qui peuvent être disponibles pour tous les terminaux, même de versions différentes ;
  • Stratégies d'arbitrage. Analyseurs de cotations de différents fournisseurs de liquidité. Si la différence entre les prix fournis par différents brokers est importante, les traders peuvent en bénéficier en créant des stratégies d'arbitrage. Les analyseurs permettent également de collecter des statistiques sur les meilleurs prix et d'identifier objectivement le meilleur fournisseur de liquidité.


Description des pièces jointes

Voici une brève description des fichiers joints à l'article, ainsi que de la procédure de compilation.

Prototypes.mqh est un fichier avec la description des fonctions de la bibliothèque HedgeTerminalAPI. Ce fichier contient la description et les prototypes des fonctions de la bibliothèque HedgeTerminalAPI. Il permet à votre EA de savoir quelles fonctions et modificateurs sont disponibles dans la bibliothèque, comment appeler les fonctions et quelles valeurs elles renvoient.

Enregistrez ce fichier dans C:\Program Files\MetaTrader 5\MQL5\Include, où «C:\Program Files\MetaTrader 5\ » est le nom du répertoire où votre terminal MetaTrader 5 est installé. Une fois le fichier copié dans le bon répertoire, vous pouvez vous y référer dans votre programme MQL. Cela doit être fait chaque fois que vous devez utiliser la bibliothèque HedgeTerminalAPI. Pour faire référence au fichier Prototypes.mqh, ajoutez le répertoire spécial d'inclusion de fichier dans votre code :

#include <Prototypes.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //...
   // Here is the content of your program.
   //...
  }

Dans l'exemple ci-dessus, cette directive est marquée en jaune et s'appelle «#include <Ptototypes.mqh>». Maintenant, le script ci-dessus peut faire référence aux fonctions de la bibliothèque et utiliser leurs fonctionnalités.

Veuillez noter que dans le processus de développement de la bibliothèque HedgeTerminalAPI, le fichier des prototypes peut subir des modifications mineures. Souvent, avec la mise à jour de la version de la bibliothèque, vous devrez mettre à jour le fichier prototype, qui décrira les changements. Veuillez prendre ce facteur inconfortable avec compréhension. Dans tous les cas, la dernière version du fichier prototype peut toujours être installée manuellement depuis la bibliothèque (la procédure d'installation est décrite dans la section 1.1) ou téléchargée depuis la pièce jointe de cet article (des mises à jour régulières des pièces jointes sont attendues).

Chaos2.mqh est un code source de Chaos2 EA. Son fonctionnement est décrit au paragraphe 1.12 : «L'exemple de la fonction SendTradeRequest et de la fonction HedgeTradeRequest à travers l'exemple de Chaos II EA. Pour réussir à compiler le code, enregistrez le fichier des prototypes de fonction dans le répertoire correspondant \Include et enregistrez la bibliothèque HedgeTerminalAPI dans : C:\Program Files\MetaTrader 5\MQL5\Market\hedgeterminalapi.ex5. Où «C:\Program Files\MetaTrader 5\» est le nom du répertoire (dossier de données du terminal) où votre terminal MetaTrader 5 est installé.

Le code source UnitedExchangeQuotes est l'archive zip spéciale (unitedexchangequotes.zip) qui contient le projet décrit en détail au chapitre 4 : «Les bases de la programmation multithread dans l'IDE MetaTrader 5» Ce zip contient les fichiers suivants :

  • UnitedExchangeQuotes.mq5 - le fichier central de l'EA. Enregistrez-le dans le dossier des experts : \MetaTrader 5\MQL5\Experts. Compilez ce fichier dans MetaEditor.
  • MultiThreadXML.mqh est le fichier principal contenant les algorithmes d'accès multi-thread au fichier XML. Il organise l'échange d'informations entre des threads indépendants. Enregistrez dans \MetaTrader 5\MQL5\Include. Les algorithmes de ce fichier sont basés sur la bibliothèque spéciale développée par ya-sha, disponible dans CodeBase. Cependant, il a été légèrement modifié pour un fonctionnement multi-thread. La pièce jointe contient cette version modifiée. Elle se compose des fichiers suivants :
    • XmlBase.mqh ;
    • XmlDocument.mqh ;
    • XmlAttribute.mqh ;
    • XmlElement.mqh.
    Enregistrez ces fichiers dans le dossier \Include.
  • Panel.mqh contient la classe de panneau décrite dans l'exemple. Enregistrez ce fichier dans le même répertoire que celui où vous enregistrez UnitedEchangesQuotes.mqh, c'est-à-dire dans le dossier \Experts.

Tous les fichiers de l'archive contiennent des chemins relatifs. Par exemple, le fichier UnitedExchangeQuotes.mq5 se trouve dans le dossier \MQL5\Experts. Cela signifie qu'il doit être placé dans le même sous-répertoire du dossier de données du terminal MetaTrader 5, tel que C:\Program Files\MetaTrader 5\MQL5\Experts\UnitedExchangeQuotes.mq5.


Conclusion

Nous avons examiné les détails de l'utilisation de l'interface du programme HedgeTerminal.

Il a été démontré que les principes de cette bibliothèque sont très similaires à ceux de MetaTrader 4 API. Comme dans MetaTrader 4 API, avant de commencer à travailler avec une transaction (un analogue du concept d'« ordre » dans MetaTrader 4), vous devez d'abord la sélectionner à l'aide de TransactionSelect(). Une transaction dans Hedge Terminal est en règle générale une position bidirectionnelle. Une fois qu'une position est sélectionnée, vous pouvez obtenir ses propriétés ou lui appliquer une action de trading, par exemple, définir un niveau de stop loss ou la fermer. Cette séquence d'actions est presque identique à l'algorithme de travail avec les ordres dans MetaTrader 4.

En plus des informations de base sur le nombre de positions bidirectionnelles et leurs propriétés, HedgeTerminal donne accès aux valeurs ​​qui ne sont pas disponibles directement dans MetaTrader 5 et nécessitent des calculs analytiques complexes. Par exemple, vous pouvez voir la quantité de glissement de chaque position bidirectionnelle en ne demandant qu'une seule de ses propriétés. Vous pouvez vérifier le nombre de transactions dans une position sélectionnée. Tous ces calculs et la correspondance requise des transactions sont effectués « en arrière-plan » lors du démarrage de HedgeTerminal. C'est pratique car l'Expert Advisor en trading n'a pas besoin de calculer quoi que ce soit. Toutes les informations nécessaires ont déjà été calculées et sont disponibles via une API simple et intuitive.

L'utilisation des algorithmes communs par l'API HedgeTerminal et le panneau permet une présentation unifiée des données. Par conséquent, vous pouvez contrôler les EA à partir du panneau de HedgeTerminal, tandis que les modifications apportées par l'EA seront affichées directement sur le panneau.


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

Fichiers joints |
Prototypes.mqh (15.3 KB)
Chaos2.mq5 (23.56 KB)
Le MQL5 Cookbook : Implémentation d'un tableau associatif ou d'un dictionnaire pour un accès rapide aux données Le MQL5 Cookbook : Implémentation d'un tableau associatif ou d'un dictionnaire pour un accès rapide aux données
Cet article décrit un algorithme spécial permettant d'accéder aux éléments par leurs clés uniques. Tout type de données de base peut être utilisé comme clé. Par exemple, elles peuvent être représentées sous la forme d'une chaîne ou d'une variable entière. Un tel conteneur de données est communément appelé dictionnaire ou tableau associatif. Il fournit un moyen plus facile et plus efficace de résoudre les problèmes.
Trading bidirectionnel et couverture des positions dans MetaTrader 5 à l’aide du panneau HedgeTerminal, Partie 1 Trading bidirectionnel et couverture des positions dans MetaTrader 5 à l’aide du panneau HedgeTerminal, Partie 1
Cet article décrit une nouvelle approche de la couverture des positions et trace la ligne dans les débats entre les utilisateurs de MetaTrader 4 et MetaTrader 5 à ce sujet. Les algorithmes qui rendent cette couverture fiable sont décrits en termes simples et illustrés par des graphiques et des diagrammes simples. Cet article est dédié au nouveau panneau HedgeTerminal, qui est essentiellement un terminal de trading complet dans MetaTrader 5. En utilisant HedgeTerminal et la virtualisation du trade qu’il offre, les positions peuvent être gérées de la même manière que MetaTrader 4.
Étudier la classe CCanvas. Comment dessiner des objets transparents Étudier la classe CCanvas. Comment dessiner des objets transparents
Avez-vous besoin de plus que des graphiques maladroits de moyennes mobiles ? Voulez-vous dessiner quelque chose de plus beau qu'un simple rectangle rempli dans votre terminal ? Des graphiques attrayants peuvent être dessinés dans le terminal. Cela peut être implémenté via la classe CСanvas, qui est utilisée pour créer des graphiques personnalisés. Avec cette classe, vous pouvez implémenter la transparence, mélanger les couleurs et produire l'illusion de la transparence au moyen de superpositions et de mélanges de couleurs.
Principes de la tarification des changes à travers l'exemple du marché des dérivés de la Bourse de Moscou Principes de la tarification des changes à travers l'exemple du marché des dérivés de la Bourse de Moscou
Cet article décrit la théorie de la tarification des changes et les spécificités de compensation du marché des produits dérivés de la Bourse de Moscou. Ceci est un article complet pour les débutants qui veulent obtenir leur première expérience d’échange sur le trading de produits dérivés, ainsi que pour les traders forex expérimentés qui envisagent de négocier sur une plate-forme d’échange centralisée.