Utilisation de WinInet.dll pour l’échange de données entre terminaux via Internet
MetaTrader 5 offre des possibilités uniques aux utilisateurs, en utilisant un certain nombre de nouveaux éléments d'interface utilisateur dans son arsenal. De ce fait, les fonctions qui n'étaient pas disponibles auparavant peuvent désormais être utilisées au maximum.
Dans cette leçon, nous apprendrons à :
- utiliser les technologies Internet de base ;
- échanger des données entre les terminaux via le serveur ;
- créer une classe de bibliothèque générique pour travailler avec Internet dans l’environnement MQL5.
La MQL5 CodeBase contient un exemple de script, qui fonctionne avec la bibliothèque wininet.dll et montre un exemple de requête de page de serveur. Mais aujourd'hui, nous allons aller beaucoup plus loin et faire en sorte que le serveur ne se contente pas de nous donner la page, mais qu'il envoie et stocke ces données pour les transférer ensuite à d'autres terminaux demandeurs.
Remarque: pour ceux qui n’ont pas accès à un serveur, configuré avec PHP, nous vous suggérons de télécharger le kit Denwer et de l’utiliser comme plate-forme de travail. Nous vous recommandons également d'utiliser le serveur Apache et PHP sur votre hôte local pour les tests.
Pour envoyer une requête au serveur, nous avons besoin des 7 fonctions principales de la bibliothèque.
InternetAttemptConnect | Essayez de trouver une connexion Internet et de l'établir. |
InternetOpen | Initialise la structure pour le travail des fonctions de la bibliothèque WinInet. Cette fonction doit être activée avant d’activer toute autre fonction de la bibliothèque. |
InternetConnect | Ouvre la ressource spécifiée par l'adresse URL HTTP ou FTP. Renvoie le descripteur à une connexion ouverte |
HttpOpenRequest | Crée un descripteur pour les requêtes HTTP pour la configuration d’une connexion |
HttpSendRequest | Envoie une requête à l’aide du descripteur créé |
InternetReadFile | Lit les données reçues du serveur après le traitement de la requête |
InternetCloseHandle | Libère le descripteur transféré |
Vous trouverez une description détaillée de toutes les fonctions et de leurs paramètres dans le système d’aide MSDN .
La déclaration des fonctions est restée la même que dans MQL4, à l’exception de l’utilisation d’Unicode et des transferts de ligne par le lien.
#import "wininet.dll" int InternetAttemptConnect(int x); int InternetOpenW(string &sAgent,int lAccessType,string &sProxyName,string &sProxyBypass,int lFlags); int InternetConnectW(int hInternet,string &szServerName,int nServerPort,string &lpszUsername,string &lpszPassword,int dwService,int dwFlags,int dwContext); int HttpOpenRequestW(int hConnect,string &Verb,string &ObjectName,string &Version,string &Referer,string &AcceptTypes,uint dwFlags,int dwContext); int HttpSendRequestW(int hRequest,string &lpszHeaders,int dwHeadersLength,uchar &lpOptional[],int dwOptionalLength); int HttpQueryInfoW(int hRequest,int dwInfoLevel,int &lpvBuffer[],int &lpdwBufferLength,int &lpdwIndex); int InternetReadFile(int hFile,uchar &sBuffer[],int lNumBytesToRead,int &lNumberOfBytesRead); int InternetCloseHandle(int hInet); #import //To make it clear, we will use the constant names from wininet.h. #define OPEN_TYPE_PRECONFIG 0 // use the configuration by default #define FLAG_KEEP_CONNECTION 0x00400000 // do not terminate the connection #define FLAG_PRAGMA_NOCACHE 0x00000100 // no cashing of the page #define FLAG_RELOAD 0x80000000 // receive the page from the server when accessing it #define SERVICE_HTTP 3 // the required protocol
Une description détaillée des indicateurs se trouve dans la même section MSDN pour chacune des fonctions. Si vous souhaitez voir la déclaration des autres constantes et fonctions, vous pouvez télécharger le fichier original wininet.h, situé dans les pièces jointes de l'article.
1. Guides de création et de suppression de session Internet
La première chose à faire est de créer une session et d’ouvrir une connexion à l’hôte. Il est préférable de ne créer une session qu'une seule fois pendant l'initialisation du programme (par exemple, dans une fonction OnInit). Ou cela peut être fait au tout début du lancement de l’Expert Advisor, mais il est important de s’assurer que sa création réussie n’a été faite qu’une seule fois avant la clôture de la session. Et il ne devrait pas être invoqué de manière répétée et inutilement, à chaque nouvelle itération de l'implémentation OnStart ou OnTimer. Il est important d’éviter les appels fréquents et la création des structures requises pour chaque appel.
Par conséquent, nous n'utiliserons qu'une seule instance de classe globale pour décrire les descripteurs de session et de connexion.
string Host; // host name int Port; // port int Session; // session descriptor int Connect; // connection descriptor bool MqlNet::Open(string aHost,int aPort) { if(aHost=="") { Print("-Host is not specified"); return(false); } // checking the DLL resolution in the terminal if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) { Print("-DLL is not allowed"); return(false); } // if the session was identifies, then we close if(Session>0 || Connect>0) Close(); // record of attempting to open into the journal Print("+Open Inet..."); // if we were not able to check for the presence of an Internet connection, then we exit if(InternetAttemptConnect(0)!=0) { Print("-Err AttemptConnect"); return(false); } string UserAgent="Mozilla"; string nill=""; // open a session Session=InternetOpenW(UserAgent,OPEN_TYPE_PRECONFIG,nill,nill,0); // if we were not able to open a session, then exit if(Session<=0) { Print("-Err create Session"); Close(); return(false); } Connect=InternetConnectW(Session,aHost,aPort,nill,nill,SERVICE_HTTP,0,0); if(Connect<=0) { Print("-Err create Connect"); Close(); return(false); } Host=aHost; Port=aPort; // otherwise all attempts were successful return(true); }
Après l’initialisation, les descripteurs Session et Connect peuvent être utilisés dans toutes les fonctions suivantes. Une fois que tous les travaux sont terminés et que les programmes MQL sont désinstallés, ils doivent être supprimés. Pour ce faire, utilisez la fonction InternetCloseHandle.
void MqlNet::CloseInet() { Print("-Close Inet..."); if(Session>0) InternetCloseHandle(Session); Session=-1; if(Connect>0) InternetCloseHandle(Connect); Connect=-1; }
Attention ! Lorsque l'on travaille avec des fonctions Internet, il est nécessaire de libérer tous les descripteurs qui en sont dérivés, en utilisant InternetCloseHandle.
2. Envoi d’une requête au serveur et réception de la page
Pour envoyer une requête et recevoir une page en réponse à cette requête, nous aurons besoin des trois fonctions restantesHttpOpenRequest, HttpSendRequest и InternetReadFile. L'essentiel de la réception de la page en réponse à une demande consiste essentiellement à enregistrer son contenu dans un fichier local.
Pour faciliter le travail avec les demandes et les contenus, nous allons créer deux fonctions universelles.
Envoi d’une demande :
bool MqlNet::Request(string Verb,string Object,string &Out,bool toFile=false,string addData="",bool fromFile=false) { if(toFile && Out=="") { Print("-File is not specified "); return(false); } uchar data[]; int hRequest,hSend,h; string Vers="HTTP/1.1"; string nill=""; if(fromFile) { if(FileToArray(addData,data)<0) { Print("-Err reading file "+addData); return(false); } } // read file in the array else StringToCharArray(addData,data); if(Session<=0 || Connect<=0) { Close(); if(!Open(Host,Port)) { Print("-Err Connect"); Close(); return(false); } } // create a request descriptor hRequest=HttpOpenRequestW(Connect,Verb,Object,Vers,nill,nill,FLAG_KEEP_CONNECTION|FLAG_RELOAD|FLAG_PRAGMA_NOCACHE,0); if(hRequest<=0) { Print("-Err OpenRequest"); InternetCloseHandle(Connect); return(false); } // send request // headline for request string head="Content-Type: application/x-www-form-urlencoded"; // sent file hSend=HttpSendRequestW(hRequest,head,StringLen(head),data,ArraySize(data)-1); if(hSend<=0) { Print("-Err SendRequest"); InternetCloseHandle(hRequest); Close(); } // read the page ReadPage(hRequest,Out,toFile); // close all handles InternetCloseHandle(hRequest); InternetCloseHandle(hSend); return(true); }
Paramètres de fonction de MqlNet:: Demande :
- string Verb – type de requête « GET » ou « POST »;
- string Object – nom de la page avec ses paramètres transmis ;
- string &Out – ligne à laquelle la réponse est reçue ;
- bool toFile – si toFile=true, out indique le nom du fichier où la réponse doit être reçue ;
- string addData - Données supplémentaires ;
- bool fromFile - Si fromFile = true, addData est le nom du fichier qui doit être envoyé.
Lecture du contenu du descripteur reçu
void MqlNet::ReadPage(int hRequest,string &Out,bool toFile) { // read the page uchar ch[100]; string toStr=""; int dwBytes,h; while(InternetReadFile(hRequest,ch,100,dwBytes)) { if(dwBytes<=0) break; toStr=toStr+CharArrayToString(ch,0,dwBytes); } if(toFile) { h=FileOpen(Out,FILE_BIN|FILE_WRITE); FileWriteString(h,toStr); FileClose(h); } else Out=toStr; }
Paramètres de fonction de MqlNet:: ReadPage :
- Int hRequest - descripteur de demande, à partir duquel les données sont lues;
- string &Out – ligne à laquelle la réponse est reçue ;
- bool toFile - Si toFile = true, alors Out est le nom du fichier où la réponse sera reçue.
Et en rassemblant tout cela, nous obtiendrons une classe de bibliothèque MqlNet pour travailler avec Internet.
class MqlNet { string Host; // host name int Port; // port int Session; // session descriptor int Connect; // connection descriptor public: MqlNet(); // class constructor ~MqlNet(); // destructor bool Open(string aHost,int aPort); // create a session and open a connection void Close(); // close session and connection bool Request(string Verb,string Request,string &Out,bool toFile=false,string addData="",bool fromFile=false); // send request bool OpenURL(string URL,string &Out,bool toFile); // somply read the page into the file or the variable void ReadPage(int hRequest,string &Out,bool toFile); // read the page int FileToArray(string FileName,uchar &data[]); // copy the file into the array for sending };
Voilà en gros toutes les fonctions requises susceptibles de satisfaire les besoins diversifiés du travail avec Internet. Considérez les exemples de leur utilisation.
Exemple 1. Téléchargement automatique des programmes MQL dans les dossiers du terminal. Script MetaGrabber
Pour commencer à tester le travail de la classe, commençons par les tâches les plus simples : lire la page et enregistrer son contenu dans le dossier spécifié. Mais une simple lecture des pages ne risque pas d'être très intéressante, aussi, afin de tirer profit du travail du script, attribuons-lui une fonction de récupérateur de programmes mql sur les sites. La tâche du script MetaGrabber sera :
- Analyse d’URL et séparation de celui-ci en hôte, requête et nom de fichier ;
- envoyer une demande à l’hôte, recevoir et enregistrer le fichier dans le dossier du terminal \\ Fichiers ;
- en le déplaçant des fichiers vers l’un des dossiers de données requis :
\Experts, \Indicateurs, \Scripts, \Include, \Bibliothèques, \Tester(set), \Templates.
Pour résoudre le deuxième problème, nous utilisons la classe MqlNet. Pour la troisième tâche, nous utilisons la fonction MoveFileEx de Kernel32.dll
#import "Kernel32.dll" bool MoveFileExW(string &lpExistingFileName, string &lpNewFileName, int dwFlags); #import "Kernel32.dll"
Pour le premier problème, faisons une petite fonction de service d’analyse de la ligne d’URL.
Nous devons allouer trois lignes à partir de l’adresse : l’hôte, le chemin d’accès au site et le nom du fichier.
Par exemple, dans la ligne http://www.mysite.com/folder/page.html
- Hôte = www.mysite.com
- Demande = / dossier / page.html
- Nom du fichier = page.html
Dans le cas de CodeBase sur le site MQL5, les chemins d’accès ont la même structure. Par exemple, le chemin d’accès à la bibliothèque ErrorDescription.mq5 sur la page https://www.mql5.com/ru/code/79 ressemble à http://p.mql5.com/data/18/79/ErrorDescription.mqh. Ce chemin est facilement obtenu en faisant un clic droit sur le lien et en sélectionnant « Copier le lien ». Ainsi, l’URL est divisée en deux parties, une pour la demande et une pour le nom du fichier pour faciliter le stockage des fichiers.
- Hôte = p.mql5.com
- Demande = / data/18/79/5/ErrorDescription.mqh
- Nom du fichier = ErrorDescription.mqh
C'est le type d'analyse syntaxique des lignes que la fonction ParseURL suivante traitera.
void ParseURL(string path,string &host,string &request,string &filename) { host=StringSubstr(URL,7); // removed int i=StringFind(host,"/"); request=StringSubstr(host,i); host=StringSubstr(host,0,i); string file=""; for(i=StringLen(URL)-1; i>=0; i--) if(StringSubstr(URL,i,1)=="/") { file=StringSubstr(URL,i+1); break; } if(file!="") filename=file; }
Dans les paramètres externes du script, nous ne ferons que deux paramètres - URL (chemin du fichier mql5) et le type de dossier de placement ultérieur - c'est-à-dire, dans quel dossier terminal vous souhaitez le placer.
Nous obtenons ainsi un script court mais très utile.
//+------------------------------------------------------------------+ //| MetaGrabber.mq5 | //| Copyright © 2010 www.fxmaster.de | //| Coding by Sergeev Alexey | //+------------------------------------------------------------------+ #property copyright "www.fxmaster.de © 2010" #property link "www.fxmaster.de" #property version "1.00" #property description "Download files from internet" #property script_show_inputs #include <InternetLib.mqh> #import "Kernel32.dll" bool MoveFileExW(string &lpExistingFileName,string &lpNewFileName,int dwFlags); #import #define MOVEFILE_REPLACE_EXISTING 0x1 enum _FolderType { Experts=0, Indicators=1, Scripts=2, Include=3, Libraries=4, Files=5, Templates=6, TesterSet=7 }; input string URL=""; input _FolderType FolderType=0; //------------------------------------------------------------------ OnStart int OnStart() { MqlNet INet; // variable for working in the Internet string Host,Request,FileName="Recieve_"+TimeToString(TimeCurrent())+".mq5"; // parse url ParseURL(URL,Host,Request,FileName); // open session if(!INet.Open(Host,80)) return(0); Print("+Copy "+FileName+" from http://"+Host+" to "+GetFolder(FolderType)); // obtained file if(!INet.Request("GET",Request,FileName,true)) { Print("-Err download "+URL); return(0); } Print("+Ok download "+FileName); // move to the target folder string to,from,dir; // if there is no need to move it elsewhere if(FolderType==Files) return(0); // from from=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\"+FileName; // to to=TerminalInfoString(TERMINAL_DATA_PATH)+"\\"; if(FolderType!=Templates && FolderType!=TesterSet) to+="MQL5\\"; to+=GetFolder(FolderType)+"\\"+FileName; // move file if(!MoveFileExW(from,to,MOVEFILE_REPLACE_EXISTING)) { Print("-Err move to "+to); return(0); } Print("+Ok move "+FileName+" to "+GetFolder(FolderType)); return(0); } //------------------------------------------------------------------ GetFolder string GetFolder(_FolderType foldertype) { if(foldertype==Experts) return("Experts"); if(foldertype==Indicators) return("Indicators"); if(foldertype==Scripts) return("Scripts"); if(foldertype==Include) return("Include"); if(foldertype==Libraries) return("Libraries"); if(foldertype==Files) return("Files"); if(foldertype==Templates) return("Profiles\\Templates"); if(foldertype==TesterSet) return("Tester"); return(""); } //------------------------------------------------------------------ ParseURL void ParseURL(string path,string &host,string &request,string &filename) { host=StringSubstr(URL,7); // removed int i=StringFind(host,"/"); request=StringSubstr(host,i); host=StringSubstr(host,0,i); string file=""; for(i=StringLen(URL)-1; i>=0; i--) if(StringSubstr(URL,i,1)=="/") { file=StringSubstr(URL,i+1); break; } if(file!="") filename=file; } //+------------------------------------------------------------------+
Effectuons les expériences sur notre section préférée https://www.mql5.com/fr/code. Les fichiers téléchargés apparaîtront immédiatement dans le navigateur de l’éditeur, et ils peuvent être compilés sans redémarrer le terminal ou l’éditeur. Et ils n'erreront pas dans les longs chemins du système de fichiers à la recherche du dossier souhaité pour y déplacer les fichiers.
Attention ! De nombreux sites mettent en place une sécurité contre le téléchargement massif de contenu, et votre adresse IP, en cas de téléchargement massif, peut être bloquée par cette ressource. Prêtez donc une attention particulière à l’utilisation du téléchargement « machine » de fichiers à partir de ressources, auxquelles vous accédez souvent et dont vous ne voulez pas être interdit d'utilisation.
Ceux qui souhaitent aller encore plus loin, en améliorant le service proposé, peuvent utiliser le script Presse-papiers avec l’interception du contenu du presse-papiers et le téléchargement automatique ultérieur.
Exemple 2. Suivi des cotations de plusieurs courtiers sur un seul graphique
Nous avons donc appris à obtenir des fichiers sur Internet. Considérons maintenant une question plus intéressante - comment envoyer et stocker ces données sur le serveur. Pour cela, nous avons besoin d’un petit script PHP supplémentaire, qui sera situé sur le serveur. En utilisant la classe MqlNet écrite, nous créons un Expert Advisor pour le suivi - MetaArbitrage . La tâche de l’expert en conjonction avec le script PHP sera :
- Envoi d’une requête Expert Advisor au serveur ;
- formation de la page de réponse (PHP) sur le serveur ;
- réception de cette page par l’Expert Advisor ;
- son analyse et la transmission des résultats à l'écran.
Le diagramme schématique de l’interaction entre le module MQL et le script PHP est le suivant :
Nous utiliserons la classe MqlNet pour résoudre ces tâches.
Pour éviter la duplication des données, ainsi que pour éliminer les cotations obsolètes - nous enverrons 4 paramètres principaux : le nom du serveur du courtier (la source des prix actuels), la devise, le prix et l’heure des cotations en UTC. Par exemple, une demande d’accès au script à partir des ressources de notre société est la suivante :
www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794
Ces paramètres et la cotation réelle sont stockés sur le serveur et seront publiés sur la page de réponse, avec toutes les autres cotations stockées de cette devise.
La commodité « collatérale » de cet échange est que les cotations peuvent être envoyées à partir de MT5, ainsi que de MT4 !
La page, qui est formée par le serveur, est un fichier CSV normal. Dans ce script, cela donne :
Nom_serveur1 ; Enchère1; Heure1
Nom de serveur 2 ; Enchère2; Heure2
Nom de serveur 3 ; Enchère3; Heure3
…
Nom de serveur N; EnchèreN; HeureN
Mais vous pouvez y ajouter vos propres paramètres supplémentaires (par exemple, type de serveur - démo ou réel). Nous stockons ce fichier CSV et l'analysons ligne par ligne, avec pour résultat un tableau des prix des valeurs et des lignes affiché à l'écran.
Le traitement de ce fichier peut se faire de plusieurs manières différentes, en choisissant celles qui sont nécessaires dans chaque cas particulier. Par exemple, filtrez les cotations reçues du serveur de démonstration MetaTrader 4, etc.
Les avantages de l’utilisation du serveur Internet sont évidents - vous envoyez vos cotations, qui peuvent être reçues et consultées par un autre trader. De même, vous recevrez les cotations qui sont envoyées aux autres traders. C’est-à-dire que l’interaction entre les terminaux est bilatérale, l’échange de données est effectué comme indiqué dans le schéma suivant :
Ce système sert de base au principe de l’échange d’informations entre un nombre quelconque de terminaux. Un MetaArbitrage Expert Advisor complet et le script PHP avec des commentaires peuvent être téléchargés à partir du lien dans les pièces jointes. Pour en savoir plus sur les fonctions utilisées par PHP, consultez le site suivant php.su
Exemple 3. Échange de messages (mini chat) au sein du terminal. MetaChat Expert Advisor
Laissons de côté le trading et les chiffres, et créons une application qui nous permettra de discuter avec plusieurs personnes à la fois, sans quitter le terminal. Pour ce faire, nous aurons besoin d'un autre script PHP, qui est en général très similaire au précédent. Sauf que dans ce script, au lieu d’analyser les citations de temps, nous analyserons le nombre de lignes dans un fichier. La tâche de l’Expert Advisor sera :
- Envoi d’une ligne de texte au serveur ;
- ajout de cette ligne dans le fichier partagé, contrôle de la taille du fichier, émission du fichier de réponse (php) ;
- recevoir le chat en cours et l’afficher à l’écran.
Le travail de MetaChat ne sera pas différent de celui de l’Expert Advisor précédent. Le même principe, et le même fichier CSV simple pour la sortie.
MetaChat et MetaArbitrage sont maintenus sur le site de ses développeurs. Les scripts PHP pour leur travail s’y trouvent également.
Par conséquent, si vous souhaitez tester une œuvre ou utiliser ce service, vous pouvez y accéder via le lien suivant :
MetaСhat - www.fxmaster.de/metachat.php
MetaArbitrage - www.fxmaster.de/metaarbitr.php
Conclusion
Nous nous sommes donc familiarisés avec les requêtes HTTP. Nous avons acquis la capacité d’envoyer et de recevoir des données via Internet et d’organiser le processus de travail plus confortablement. Mais toute capacité peut toujours être améliorée. Les points suivants peuvent être considérés comme les nouvelles directions potentielles de leurs améliorations :
- lire les nouvelles ou recevoir d’autres informations directement dans le terminal pour l’analyse des Expert Advisors ;
- gestion à distance des Expert Advisors ;
- Mises à jour automatiques des Expert Advisors / indicateurs ;
- copieurs / traducteurs de trades, envoi de signaux ;
- téléchargement de modèles ainsi que de lumières et de fichiers de configuration pour Expert Advisors :
- Et bien plus encore...
Dans cet article, nous avons utilisé des requêtes de type GET. Ils remplissent suffisamment la tâche lorsque vous devez obtenir un fichier, ou envoyer une requête, avec un petit nombre de paramètres, pour une analyse de serveur.
Dans notre prochaine leçon, nous examinerons attentivement les requêtes POST - envoi de fichiers au serveur ou partage de fichiers entre terminaux, et nous examinerons les exemples de leur utilisation.
Ressources utiles
- Denver Set pour l’installation du serveur Apache + PHP http://www.denwer.ru/
- Un proxy pour afficher les titres envoyés http://www.charlesproxy.com/
- Types de demandes de titre http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
- Типы запросов http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
- Types de demandes ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types
- Description de WinHTTP http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
- Description de la session HTTP http://msdn.microsoft.com/en-us/library/aa384322%28VS.85%29.aspx
- Structure d’utilisation de HINTERNET http://msdn.microsoft.com/en-us/library/aa383766%28VS.85%29.aspx
- Travailler avec des fichiers http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
- Types de données pour les transferts vers MQL http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/73
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation