Développer un Expert Advisor de trading à partir de zéro (Partie 16) : Accès aux données sur le web (2)

Daniel Jose | 7 avril, 2023

Introduction

Dans l'article précédent "Développer un Expert Advisor de trading à partir de zéro (Partie 15) : Accéder aux données sur le web (1)", nous avons présenté toute la logique et les idées derrière l’utilisation de la plateforme MetaTrader 5 pour accéder à des données marquées provenant de sites web spécialisés.

Dans cet article, nous avons examiné comment accéder à ces sites et comment y trouver et extraire des informations afin de les utiliser dans la plateforme. Mais cela ne s'arrête pas là, car la simple capture de données n'a pas beaucoup de sens en soi. La partie la plus intéressante est d'apprendre à prendre ces données de la plateforme et à les utiliser dans un Expert Advisor. La méthode pour y parvenir n'est pas évidente. Il est donc difficile de la mettre en œuvre sans connaître et comprendre toutes les fonctions disponibles dans MetaTrader 5.


Plan et mise en œuvre

Si vous n'avez pas lu l'article précédent ou que vous ne l’ayez pas compris, je vous recommande de le faire et d'essayer de comprendre tous les concepts qui y sont présentés. Nous allons continuer ce sujet ici - nous allons étudier un grand nombre de choses, résoudre une série de problèmes et nous arriverons à une belle solution puisque nous allons utiliser MetaTrader 5 d'une manière qui n'a pas encore été explorée. Je le dis car qu'il a été difficile de trouver des liens pour utiliser certaines des fonctionnalités présentes dans la plateforme. Mais je vais essayer d'expliquer ici comment utiliser l'une de ces ressources.

Alors, préparons-nous et mettons-nous au travail.


1. Accès aux données Internet par l'intermédiaire d'un Expert Advisor

C'est la partie la plus intéressante mise en œuvre dans ce système. Bien qu'il s'agisse d'une chose simple, c'est de loin la plus dangereuse si elle est mal conçue. Elle est dangereuse parce qu'elle peut laisser l'EA dans l'attente d'une réponse du serveur, ne serait-ce même qu'un instant.

Cette logique est illustrée dans la figure ci-dessous :

Voyons comment l'EA interagit directement avec le serveur web contenant les informations que nous voulons capturer. Vous trouverez ci-dessous un exemple complet de code qui fonctionne exactement de cette manière.

#property copyright "Daniel Jose"
#property version "1.00"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

Si vous regardez de près, vous pouvez voir que le code est exactement le même que celui qui a été créé dans l'article précédent. Mais ce code fait maintenant partie de l'EA, et a été adapté pour cela, c'est-à-dire que si quelque chose a déjà fonctionné ailleurs, cela devrait fonctionner ici. La différence est que l'EA contient une nouvelle condition, qui implique que le code sera exécuté toutes les secondes C'est-à-dire que l'EA adressera une demande au serveur web souhaité toutes les secondes et attendra une réponse. Il fournit ensuite les données saisies et retourne à d'autres fonctions internes. Cette boucle sera répétée tout au long de la durée de vie de l'EA. Le résultat de l'exécution est visible ci-dessous :


Bien que cela soit fait exactement de cette façon, je ne recommande pas cette pratique car l'EA restera bloqué en attendant la réponse du serveur, ne serait-ce que quelques instants - ce qui peut mettre en danger le système de trading de la plateforme et l'EA lui-même. En revanche, si vous êtes intéressé par l'apprentissage de la méthode, vous pouvez EN apprendre beaucoup grâce à ce système.

si vous disposez d'un serveur local qui achemine les informations entre internet et la plateforme, cette méthode sera peut-être suffisante. Dans ce cas, si le système fait une demande, le serveur local n'aura pas encore d'informations et répondra rapidement, ce qui vous épargnera les étapes suivantes.

Examinons maintenant une autre façon, un peu plus sûre, d'effectuer ce type de tâche. Comme nous allons utiliser le système de threading de MetaTrader 5 pour obtenir au moins une certaine sécurité et pour empêcher l'EA d'être soumis aux conditions du serveur web distant, nous pouvons attendre quelques instants que le serveur distant nous réponde. Nous créerons des conditions supplémentaires pour que l'EA puisse savoir ce qui se passe, tout en étant capable de collecter des informations sur le web.


2. Créer un canal de communication

Un canal est un moyen plus simple et plus efficace d'obtenir des données collectées en ligne et de les utiliser dans un EA. Bien que cela fonctionne dans certains cas, ce n'est pas la meilleure solution pour nous, car il y a des limites à l'utilisation de ces canaux. Mais l'EA pourra au moins accéder aux informations collectées sur le web sans avoir à attendre une réponse d'un serveur distant.

Il a déjà été mentionné précédemment que la façon la plus simple de résoudre le problème est le routage des données : créer un serveur local qui téléchargerait les données et les fournirait à la plateforme MetaTrader 5. Mais cela nécessite certaines connaissances et une certaine puissance de calcul et cela complique presque tous les cas. Mais nous pouvons utiliser les fonctionnalités de MetaTrader 5 pour créer un canal très similaire, ce qui serait beaucoup plus simple que de passer par un serveur local.

La figure ci-dessous montre comment nous allons faire :

Le canal est créé sous la forme d'un objet. Notez que l'EA recherchera les informations que le script a placé à l'intérieur de cet objet. Pour comprendre comment cela fonctionne, examinons 3 codes, qui sont présentés dans leur intégralité ci-dessous. L'un d'entre eux sera un EA, l'autre un en-tête contenant l'objet et le troisième un script.

#property copyright "Daniel Jose"
#property description "Testing Inner Channel"
#property version "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetInfoInnerChannel());
}
//+------------------------------------------------------------------+

Le code suivant est l'en-tête que nous devons utiliser. Veuillez noter que l'objet est ici déclaré comme étant partagé entre l'EA et le script :

//+------------------------------------------------------------------+
//|                                          Canal Intra Process.mqh |
//|                                                      Daniel Jose |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_NameObjectChannel   "Inner Channel Info WEB"
//+------------------------------------------------------------------+
void CreateInnerChannel(void)
{
        long id;
        
        ObjectCreate(id = ChartID(), def_NameObjectChannel, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(id, def_NameObjectChannel, OBJPROP_COLOR, clrNONE);
}
//+------------------------------------------------------------------+
void RemoveInnerChannel(void)
{
        ObjectDelete(ChartID(), def_NameObjectChannel);
}
//+------------------------------------------------------------------+
inline void SetInfoInnerChannel(string szArg)
{
        ObjectSetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT, szArg);
}
//+------------------------------------------------------------------+
inline string GetInfoInnerChannel(void)
{
        return ObjectGetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT);
}
//+------------------------------------------------------------------+

Et voici un script : Il remplacera la création d'un serveur local et fera réellement le travail du serveur.

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        CreateInnerChannel();
        while (!IsStopped())
        {
                SetInfoInnerChannel(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
                Sleep(200);
        }
        RemoveInnerChannel();
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); (!_StopFlag) && (c0 < c1); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); (!_StopFlag) && (c0 < c1); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; (!_StopFlag) && (charResultPage[counter + iInfo] == 0x20); counter++);
        for (;(!_StopFlag) && (charResultPage[counter + iInfo] != cLimit); counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return (_StopFlag ? "" : szInfo);
}
//+------------------------------------------------------------------+

Nous avons ici un objet que l'EA peut voir et qui est créé par le script. L'idée est que l'EA et le script puissent coexister sur le même graphique, et que cet objet soit le canal de communication entre eux. L'EA sera le client, le script sera le serveur et l'objet sera le canal de communication entre eux. Ainsi, le script capturera les valeurs sur le serveur web distant et introduira ensuite la valeur souhaitée dans l'objet. L'EA verra de temps en temps quelle valeur se trouve dans l'objet (si l'objet existe, car si le script n'est pas en cours d'exécution, l'objet ne devrait pas être disponible). Le moment où l'EA examine la valeur de l'objet n'enfreint en rien le script. Si le script est bloqué parce qu'il attend une réponse d'un serveur distant, l'EA n’est pas affecté, il continuera à fonctionner indépendamment de ce que fait le script.

Bien que cette solution soit excellente, elle n'est pas parfaite : il reste un problème au niveau du script.

Pour le comprendre, regardez la vidéo suivante en prêtant attention à chaque détail :


Tout fonctionne très bien. C'était prévisible. Ce type de solution est largement utilisé en programmation lors du développement d'un programme client-serveur, où l'on ne veut pas que l'un bloque l'autre. En d'autres termes, nous ne faisons qu’utiliser un canal pour communiquer entre les processus. Souvent, lorsqu'ils se trouvent dans le même environnement, le canal est créé en utilisant la mémoire (une zone isolée spécifiquement allouée à cet effet, mais partagée et visible à la fois par le client et par le serveur). Le serveur ajoute les données à cet endroit et le client consulte cette même zone pour récupérer les données existantes. L'un ne dépend donc pas de l'autre, alors qu'ils sont tous les deux liés.

L'idée est donc d'utiliser le même principe. Mais le mode de fonctionnement du script pose un problème. Lorsque nous changeons de période, le script est fermé. Et même lorsque nous utilisons une boucle infinie, le script est fermé par MetaTrader 5. Lorsque cela se produit, nous devons réinitialiser le script et le replacer sur le graphique. Mais si nous devons constamment changer de période, cela pose un problème, sans parler de la nécessité de relancer le script sur le graphique à chaque fois.

Nous pouvons également oublier de vérifier si le script est sur le graphique ou non, et vous finirez alors par utiliser des informations erronées car, en raison de la manière dont l'EA est codé, nous ne pouvons pas savoir si le script est sur le graphique. Ce problème pourrait être résolu en vérifiant si un script se trouve sur le graphique ou non. Cette tâche n'est pas difficile : il suffit juste de vérifier l’heure de publication du dernier script dans l'objet. Cela résoudrait le problème.

Cependant, il est possible de créer une solution bien meilleure (au moins dans certains cas). Et pour être honnête, c'est presque la solution idéale. Nous utiliserons le même concept que celui présenté ci-dessus, mais au lieu d'un script, nous utiliserons un service.


3. Création d'un service

Il s'agit d'une solution extrême, mais comme le script est interrompu à chaque changement de période, nous devons pouvoir utiliser une autre méthode. Par contre, en résolvant un problème, nous en créons un autre. Mais il est toujours bon de savoir quelles sont les solutions possibles et comment elles peuvent être utilisées. L'essentiel est de connaître les limites que présente chaque solution et donc d'essayer de trouver un juste milieu qui permette de résoudre le problème de la meilleure façon possible.

La programmation est ainsi faite : lorsque nous essayons de résoudre un problème, nous en créons souvent un nouveau.

Notre objectif est de créer quelque chose de similaire à l'image ci-dessous :

Bien que cela puisse sembler simple, les ressources impliquées sont généralement très peu explorées. Je vais donc essayer d'entrer dans les détails pour aider tous ceux qui veulent en savoir plus sur la façon de travailler avec ce type de ressources.


3.1. Accès aux variables globales

Cette partie est si peu étudiée que j'ai d'abord pensé à créer une dll juste pour prendre en charge cette fonction. Mais après avoir consulté la documentation de MQL5, je l'ai trouvée. Le problème est que nous devons accéder ou créer à un point commun au service et à l'EA. Lorsque nous utilisions un script, ce point était un objet. Mais lorsque nous utilisons un service, nous ne pouvons pas faire la même chose. La solution serait d'utiliser une variable externe. Mais lorsque j'ai essayé de le faire, la performance n'était pas celle attendue. Pour plus de détails, vous pouvez consulter la documentation relative aux variables externes. Elle explique ce qu'il faut faire.

Cette idée n'étant pas bonne, j'ai préféré utiliser une dll. Mais j’ai toujours voulu apprendre MetaTrader 5 et MQL5. Donc en regardant dans le terminal, j'ai trouvé ce que vous pouvez voir dans l'image ci-dessous :

         

C'est ce que nous recherchions : nous avons ajouté une variable pour pouvoir vérifier comment cette procédure peut être configurée. Malheureusement, nous ne pouvons utiliser que des valeurs de type double. On pourrait penser que c'est là le problème (bien qu'il s'agisse en réalité d'une limitation), mais cela suffit lorsque nous voulons transmettre des messages courts (ce qui est notre cas). Le type double est une chaîne courte de 8 caractères, ce qui permet de transmettre des valeurs ou des messages courts entre les programmes.

La 1ère partie du problème est donc résolue. MetaTrader 5 fournit des méthodes pour créer un canal sans avoir à créer une dll. Mais nous avons maintenant un autre problème : comment accéder à ces variables avec le programme ? Est-il possible de créer des variables globales à l'intérieur du programme : à l'intérieur d'un Expert Advisor, d'un script, d'un indicateur ou d'un service ? Ou devons-nous utiliser uniquement celles qui sont déclarées dans le terminal ?

Ces questions sont très importantes si nous voulons pouvoir utiliser cette solution. S'il n'était pas possible de les utiliser par le biais de programmes, nous devrions utiliser des dll. Mais cela est possible. Pour plus d'informations, voir Variables Globales du Terminal.


3.2. Utilisation d'une variable du terminal pour échanger des informations

Maintenant que nous avons examiné les principes de base, créons quelque chose de simple afin de pouvoir tester et comprendre comment le processus d'utilisation des variables terminales fonctionne dans la pratique.

Pour cela, j'ai créé les codes suivants. Le 1er est le fichier d'en-tête :

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+

Nous définissons ici simplement le nom de la variable globale du terminal (qui sera la même pour les deux processus qui s'exécuteront sur le terminal graphique).

Voici le code qui représente le service à exécuter :

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        double count = 0;
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                GlobalVariableSet(def_GlobalNameChannel, count);
                count += 1.0;
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

Son fonctionnement est simple : il vérifie si la variable a déjà été déclarée grâce à GlobalVariableCheck. Si la variable n'existe pas, elle sera temporairement créée par la fonction GlobalVariableTemp et une valeur sera ensuite affectée avec la fonction GlobalVariableSet. En d'autres termes, nous testons, créons et écrivons des informations. Le service agit comme un serveur, tout comme un script, sauf que nous n'accédons pas encore au site web. Nous devons d’abord comprendre comment le système fonctionne.

L'étape suivante consiste à créer un client qui est dans notre cas un Expert Advisor :

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        double value;
        if (GlobalVariableCheck(def_GlobalNameChannel))
        {
                GlobalVariableGet(def_GlobalNameChannel, value);
                Print(value);           
        }
}
//+------------------------------------------------------------------+

Le code est simple : toutes les secondes, l'EA vérifie si la variable existe. Si elle existe, l'EA lit la valeur à l'aide de GlobalVariableGet et l'affiche dans le terminal.

Voyons comment ce processus peut être mis en œuvre. Tout d'abord, nous devons lancer le service. La procédure est la suivante :

Mais un autre scénario est possible : lorsque le service s'est arrêté et que nous le redémarrons. Dans ce cas, nous procéderons de la façon suivante :

Nous vérifions ensuite les variables du terminal et nous obtenons le résultat suivant :

Le système fonctionne manifestement. Mais nous devons maintenant placer l'EA sur le graphique, obtenir les valeurs et confirmer ainsi la connexion avec le canal. Après avoir placé l'EA sur le graphique, nous obtenons ainsi le résultat suivant :

C'est tout. Le système fonctionne comme nous le souhaitons. Nous disposons maintenant du modèle illustré ci-dessous. Il s'agit d'un format client-serveur typique. C'est exactement ce que nous voulions faire. Nous essayons de mettre en œuvre exactement ce format en raison des avantages mentionnés précédemment.

Il ne nous reste plus qu'à ajouter un système permettant de lire et d'obtenir des valeurs depuis le web. Il ne nous restera plus que le modèle final à tester. Cette partie est assez simple : il s'agit de reprendre le code que nous utilisons depuis le début et de l'ajouter au service. Pour réaliser le test, il suffit de modifier le fichier du serveur afin qu'il lise la valeur du site web et qu'il publie ensuite cette valeur pour que le client puisse la lire. Le nouveau code de service est le suivant :

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        string szRet;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                szRet = GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D);
                GlobalVariableSet(def_GlobalNameChannel, StringToDouble(szRet));
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

Nous avons maintenant un système qui fonctionne comme indiqué ci-dessous :

Il est donc prêt. Et nous obtenons les résultats suivants. La modification de la période ne posera plus de problème.



Conclusion

Nous avons examiné aujourd’hui plusieurs fonctionnalités de MetaTrader 5 qui ont été peu explorées jusqu’à présent. L'une d'entre elles est le canal de communication. Mais nous n’en tirons pas encore pleinement parti. Nous pouvons aller encore plus loin - et nous le ferons dans le prochain article. Tout ce que nous avons vu jusqu'à présent dans cette série nous montre ce qu'il est possible de faire avec la plateforme MetaTrader 5. Il suffit de choisir une voie et de continuer jusqu'à ce que vous obteniez les résultats souhaités. Et il est utile de connaître les limites, les avantages et les risques associés à chacune des voies possibles.