MQL5-RPC. Appels de procédure à distance depuis MQL5 : Accès aux services Web et analyseur XML-RPC ATC pour le plaisir et le profit

investeo | 12 janvier, 2022


Introduction

Cet article décrira le framework MQL5-RPC que j'ai construit au cours des dernières semaines. Il couvre les bases de l'accès XML-RPC, la description de la mise en œuvre de MQL5 et deux exemples d'utilisation de MQL5-RPC dans le monde réel. Le premier sera un appel de procédure à distance sur le service Web d'un site Web de forex externe et le second sera un client de notre propre serveur XML-RPC qui est utilisé pour analyser, analyser et fournir les résultats collectés sur Championnat de trading automatisé 2011. Si vous souhaitez savoir comment mettre en œuvre et analyser différentes statistiques de ATC 2011en temps réel, cet article est fait pour vous.


Les bases de XML-RPC

Commençons par les bases de XML-RPC. XML-RPC signifieXML Appel de procédure à distance. Il s'agit d'un protocole réseau qui utilise XML pour coder et décoder les paramètres transmis pour appeler une méthode externe. Il utilise le protocole HTTP comme mécanisme de transport pour échanger des données. Par méthode externe, j'entends un autre programme informatique ou un service Web qui expose des procédures à distance.

La méthode exposée peut être appelée par n'importe quel langage informatique à partir de n'importe quelle machine connectée au réseau à condition qu'elle utilise également la pile de protocoles XML-RPC et dispose d'un accès réseau au serveur. Cela signifie également que ce XML-RPC peut être utilisé pour appeler une méthode sur la même machine écrite dans un autre langage de programmation. Nous le verrons dans la deuxième partie de l'article.


Modèle de données XML-RPC

La spécification XML-RPC utilise six types de données de base : int, double, booléen, chaîne, datetime, base64 et deux types de données composées : tableau et struct. Le tableau peut être constitué de n'importe quel élément de base et la struct fournit des paires nom-valeur telles que des tableaux associatifs ou des propriétés d'objet.


Types de données de base en XML-RPC
 Type Valeur Exemples
int ou i4Entiers 32 bits compris entre - 2 147 483 648 et 2 147 483 647.<int>11<int>
<i4>12345<i4>
doublenombres à virgule flottante 64 bits <double>30.02354</double>
<double>-1.53525</double>
Booléenvrai (1) ou faux (0)<boolean>1</boolean>
<booléen>0</booléen>
chaînetexte ASCII, de nombreuses mises en œuvre supportent Unicode<string>Bonjour</string>
<string>MQL5</string>
dateTime.iso8601Dates au format ISO8601 : CCYYMMDDTHH:MM:SS<dateTime.iso8601>
20111125T02:20:04
</dateTime.iso8601>
<dateTime.iso8601>
20101104T17:27:30
</dateTime.iso8601>
base64Informations binaires codées telles que définies dans la RFC 2045<base64>
TDVsbG8sIFdvdwxkIE==
</base64>


Tableau 1. Types de données de base en XML-RPC

Un tableau peut contenir n'importe quel type de base, pas nécessairement du même type. L'élément de tableau doit être imbriqué dans l'élément de valeur. Il contient un élément de données et un ou plusieurs éléments de valeur dans l'élément de données. L'exemple ci-dessous montre un tableau de quatre valeurs entières.

<value>
   <array>
      <data>
         <value><int>111</int></value>
         <value><int>222</int></value>
         <value><int>-3456</int></value>
         <value><int>666</int></value>
      </data>
   </array>
</value>

Le deuxième exemple montre un tableau de cinq valeurs de chaîne.

<value>
   <array>
      <data>
         <value><string>MQL5</string></value>
         <value><string>is </string></value>
         <value><string>a</string></value>
         <value><string>great</string></value>
         <value><string>language.</string></value>
      </data>
   </array>
</value>

Je suis convaincu que vous pourrez repérer des similitudes dans ces deux exemples pour créer un autre tableau XML-RPC.

Les structures ont un élément struct à l'intérieur de l'élément value et des sections membres dans l'élément struct. Chaque membre se compose de son nom et de la valeur qu'il détient. Il est donc facile de transmettre les valeurs d'un tableau associatif ou les membres d'un objet en utilisant les structs.

Veuillez consulter l'exemple ci-dessous.

<value>
   <struct>
      <member>
         <name>AccountHolder</name>
         <value><string>John Doe</string></value>
      </member>
      <member>
         <name>Age</name>
         <value><int>77</int></value>
      </member>
      <member>
         <name>Equity</name>
         <value><double>1000000.0</double></value>
      </member>
   </struct>
</value>

Après avoir pris connaissance du modèle de données XML-RPC, nous allons plus loin dans les structures de demande et de réponse. Cela constituera une base pour la mise en œuvre du client XML-RPC dans MQL5.


Structures de demande XML-RPC

La requête XML-RPC est composée d'un en-tête de message et d'une charge utile de message. L'en-tête du message spécifie HTTP la méthode d'envoi (POST), le chemin relatif vers le service XML-RPC, la version du protocole HTTP, le nom de l'agent utilisateur, l'adresse IP de l'hôte, le type de contenu (text/xml) et la longueur du contenu en octets. 

POST /xmlrpc HTTP 1.1
User-Agent: mql5-rpc/1.0
Host: 10.12.10.10
Content-Type: text/xml
Content-Length: 188

La charge utile de la requête XML-RPC est un document XML. L'élément racine de l'arborescence XML doit être appelé methodCall. Un methodCall contient un seul élément methodName dont le contenu est le nom de la méthode exécutée. L'élément MethodName contient zéro ou un élément params.

L'élément params contient un ou plusieurs éléments value, array ou struct. Toutes les valeurs sont codées en fonction du type de données (voir le tableau ci-dessus). Vous trouverez ci-dessous un exemple de charge utile montrant une demande d'exécution de la méthode « multiplier » avec deux valeurs doubles à transmettre à la fonction.

<?xml version="1.0"?>
<methodCall>
   <methodName>multiply</methodName>
      <params>
         <param>
            <value><double>8654.41</double></value>
         </param>
         <param>
            <value><double>7234.00</double></value>
         </param>
      </params>
</methodCall>

L'en-tête et la charge utile sont envoyés via HTTP au serveur qui accepte l'entrée. Si le serveur est disponible, il vérifie le nom de la méthode et la liste des paramètres et exécute la méthode souhaitée. Une fois le traitement terminé, il prépare une structure de réponse XML-RPC qui peut être lue par le client.

 

Structures de réponse XML-RPC

Comme pour la demande XML-RPC, la réponse XML-RPC se compose d'un en-tête et d'une charge utile. L'en-tête est du texte et la charge utile est un document XML. Si la requête était correcte, la première ligne de l'en-tête informe que le serveur a été trouvé (code 200) et spécifie la version du protocole. L'en-tête doit également contenir Content-Type text/xml et Content-Length qui est la longueur de la charge utile en octets.

HTTP/1.1 200 OK
Date: Tue, 08 Nov 2011 23:00:01 GMT
Server: Unix
Connection: close
Content-Type: text/xml
Content-Length: 124

Il n'est pas surprenant que la charge utile de la réponse soit également un document XML. L'élément racine de l'arborescence XML doit être appelé methodResponse. L'élément methodResponse contient un élément params en cas de succès ou un élément fault en cas d'échec. L'élément params contient exactement un élément param. L'élément param contient exactement un élément value.

L'exemple d'une réponse réussie est présenté ci-dessous : 

<?xml version="1.0"?>
<methodResponse>
   <params>
      <param>
         <value><double>62606001.94</double></value>
      </param>
   </params>
</methodResponse>

Une réponse d'échec est préparée s'il y a eu un problème lors du traitement de la requête XML-RPC.

L'élément fault, comme l'élément params, n'a qu'une seule valeur de sortie.

<?xml version="1.0"?>
<methodResponse>
   <fault>
      <value><string>No such method!</string></value>
   </fault>
</methodResponse>

Étant donné que XML-RPC ne standardise pas les codes d'erreur, les messages d'erreur dépendent donc de la mise en œuvre.


Présentation de MQL5-RPC

Je suis tombé sur deux articles d'Alex Sergeev « Utilisation de WinInet.dll pour l'échange de données entre terminaux via Internet » et « Utilisation de WinInet dans MQL5. Partie 2 : POST Requêtes et fichiers » et j'ai réalisé que je pouvais mettre en œuvre un client XML-RPC pour MetaTrader 5. Après avoir examiné les spécifications, j'ai mis en place les miennes en partant de zéro. Il s'agit d'un projet en cours qui ne couvre pas encore l'ensemble de la spécification (la prise en charge de base64 sera ajoutée dans un avenir proche), mais vous pouvez déjà l'utiliser pour effectuer un grand nombre d'appels XML-RPC à partir de MetaTrader 5.


Modèle de données MQL5-RPC

La partie la plus difficile de la mise en œuvre pour moi a été de trouver un modèle de données correct pour MQL5. J'ai décidé que cela devait être le plus simple possible pour l'utilisateur du framework, j'ai donc construit plusieurs classes qui encapsulent ses fonctionnalités. La première décision a été de créer une demande de données param comme un seul pointeur CObject*. Ce pointeur conserve un tableau de pointeurs vers des tableaux dérivés de la classe CObject.

Il existe des classes standard qui contiennent des tableaux CObject ; CArrayInt, CArrayDouble, CArrayString, donc je me suis basé sur cela et j'ai mis en œuvre CArrayBool, CArrayDatetime pour compléter les types de données de base et CArrayMqlRates pour ajouter un tableau de structures. Seul le type base64 est manquant pour le moment mais il sera supporté dans un futur proche. Si le tableau ne contient qu'un seul élément, il est encapsulé dans le XML comme un seul élément de valeur.

J'ai écrit un exemple sur la façon d'ajouter différents tableaux au tableau CObject* et d'afficher l'ensemble du tableau de tableaux de différents types. Il est disponible ci-dessous. 

//+------------------------------------------------------------------+
//|                                                 ArrayObjTest.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                               http://Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://Investeo.pl"
#property version   "1.00"
//---
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Arrays\ArrayMqlRates.mqh>
#include <Arrays\ArrayDatetime.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CArrayObj* params = new CArrayObj;
   
   CArrayInt* arrInt = new CArrayInt;
   
   arrInt.Add(1001);
   arrInt.Add(1002);
   arrInt.Add(1003);
   arrInt.Add(1004);
   
   CArrayDouble* arrDouble = new CArrayDouble;
   
   arrDouble.Add(1001.0);
   arrDouble.Add(1002.0);
   arrDouble.Add(1003.0);
   arrDouble.Add(1004.0);
      
   CArrayString* arrString = new CArrayString;
   
   arrString.Add("s1001.0");
   arrString.Add("s1002.0");
   arrString.Add("s1003.0");
   arrString.Add("s1004.0");
   
   CArrayDatetime* arrDatetime = new CArrayDatetime;
   
   arrDatetime.Add(TimeCurrent());
   arrDatetime.Add(TimeTradeServer()+3600);
   arrDatetime.Add(TimeCurrent()+3600*24);
   arrDatetime.Add(TimeTradeServer()+3600*24*7);
   
   CArrayBool* arrBool = new CArrayBool;
   
   arrBool.Add(false);
   arrBool.Add(true);
   arrBool.Add(true);
   arrBool.Add(false);
   
   CArrayMqlRates* arrRates = new CArrayMqlRates;
   
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(Symbol(),0,0,4,rates);
   
   arrRates.Add(rates[0]);
   arrRates.Add(rates[1]);
   arrRates.Add(rates[2]);
   arrRates.Add(rates[3]);
   
   params.Add(arrInt);
   params.Add(arrDouble);
   params.Add(arrString);
   params.Add(arrDatetime);
   params.Add(arrBool);
   params.Add(arrRates);
   
   Print("params has " + IntegerToString(params.Total()) + " arrays.");
 
   for (int p=0; p<params.Total(); p++)
   {
      int type = params.At(p).Type();
      
      switch (type) {
         case TYPE_INT: { 
            CArrayInt *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %d", p, i, arr.At(i));
            break; }
         case TYPE_DOUBLE: { 
            CArrayDouble *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %f", p, i, arr.At(i));
            break; }
         case TYPE_STRING: { 
            CArrayString *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %s", p, i, arr.At(i));
            break; }
         case TYPE_BOOL: { 
            CArrayBool *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               if (arr.At(i) == true)
                  PrintFormat("%d %d true", p, i);
               else
                  PrintFormat("%d %d false", p, i);
            break; }
         case TYPE_DATETIME: { 
            CArrayDatetime *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %s", p, i, TimeToString(arr.At(i), TIME_DATE|TIME_MINUTES));
            break; }
         case TYPE_MQLRATES: {  //  
            CArrayMqlRates *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %f %f %f %f", p, i, arr.At(i).open, arr.At(i).high, arr.At(i).low, arr.At(i).close);
            break; }
      };
         
   };
   delete params;
   
  }
//+------------------------------------------------------------------+

Le résultat doit être clair : il y a 6 sous-tableaux : tableau de valeurs entières, tableau de valeurs doubles, tableau de chaînes, tableau de datetimes, tableau de valeurs booléennes et tableau de MqlRates.

ArrayObjTest (EURUSD,H1)        23:01:54        params has 6 arrays.
ArrayObjTest (EURUSD,H1)        23:01:54        0 0 1001
ArrayObjTest (EURUSD,H1)        23:01:54        0 1 1002
ArrayObjTest (EURUSD,H1)        23:01:54        0 2 1003
ArrayObjTest (EURUSD,H1)        23:01:54        0 3 1004
ArrayObjTest (EURUSD,H1)        23:01:54        1 0 1001.000000
ArrayObjTest (EURUSD,H1)        23:01:54        1 1 1002.000000
ArrayObjTest (EURUSD,H1)        23:01:54        1 2 1003.000000
ArrayObjTest (EURUSD,H1)        23:01:54        1 3 1004.000000
ArrayObjTest (EURUSD,H1)        23:01:54        2 0 s1001.0
ArrayObjTest (EURUSD,H1)        23:01:54        2 1 s1002.0
ArrayObjTest (EURUSD,H1)        23:01:54        2 2 s1003.0
ArrayObjTest (EURUSD,H1)        23:01:54        2 3 s1004.0
ArrayObjTest (EURUSD,H1)        23:01:54        3 0 2011.11.11 23:00
ArrayObjTest (EURUSD,H1)        23:01:54        3 1 2011.11.12 00:01
ArrayObjTest (EURUSD,H1)        23:01:54        3 2 2011.11.12 23:00
ArrayObjTest (EURUSD,H1)        23:01:54        3 3 2011.11.18 23:01
ArrayObjTest (EURUSD,H1)        23:01:54        4 0 false
ArrayObjTest (EURUSD,H1)        23:01:54        4 1 true
ArrayObjTest (EURUSD,H1)        23:01:54        4 2 true
ArrayObjTest (EURUSD,H1)        23:01:54        4 3 false
ArrayObjTest (EURUSD,H1)        23:01:54        5 0 1.374980 1.374980 1.374730 1.374730
ArrayObjTest (EURUSD,H1)        23:01:54        5 1 1.375350 1.375580 1.373710 1.375030
ArrayObjTest (EURUSD,H1)        23:01:54        5 2 1.374680 1.375380 1.373660 1.375370
ArrayObjTest (EURUSD,H1)        23:01:54        5 3 1.375270 1.377530 1.374360 1.374690

Vous serez peut-être intéressé par la façon dont j'ai mis en œuvre des tableaux d'autres types de données. Dans CArrayBool et CArrayDatetime, je me suis simplement basé sur CArrayInt, mais dans CArrayMqlRates, c'était un peu différent, car la structure doit être transmise comme référence et il n'y avait pas de TYPE_MQLRATES défini.

Vous trouverez ci-dessous un code source partiel de la classe CArrayMqlRates. D'autres classes sont disponibles en pièce jointe de l'article. 

//+------------------------------------------------------------------+
//|                                                ArrayMqlRates.mqh |
//|                                      Copyright 2011, Investeo.pl |
//|                                               http://Investeo.pl |
//|                                              Revision 2011.03.03 |
//+------------------------------------------------------------------+
#include "Array.mqh"
//+------------------------------------------------------------------+
//| Class CArrayMqlRates.                                            |
//| Purpose: Class of dynamic array of structs                       |
//|          of MqlRates type.                                       |
//|          Derived from CArray class.                              |
//+------------------------------------------------------------------+
#define TYPE_MQLRATES 7654

class CArrayMqlRates : public CArray
  {
protected:
   MqlRates          m_data[];           // data array
public:
                     CArrayMqlRates();
                    ~CArrayMqlRates();
   //--- method of identifying the object
   virtual int       Type() const        { return(TYPE_MQLRATES); }
   //--- methods for working with files
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
   //--- methods of managing dynamic memory
   bool              Reserve(int size);
   bool              Resize(int size);
   bool              Shutdown();
   //--- methods of filling the array
   bool              Add(MqlRates& element);
   bool              AddArray(const MqlRates &src[]);
   bool              AddArray(const CArrayMqlRates *src);
   bool              Insert(MqlRates& element,int pos);
   bool              InsertArray(const MqlRates &src[],int pos);
   bool              InsertArray(const CArrayMqlRates *src,int pos);
   bool              AssignArray(const MqlRates &src[]);
   bool              AssignArray(const CArrayMqlRates *src);
   //--- method of access to the array
   MqlRates          At(int index) const;
   //--- methods of changing
   bool              Update(int index,MqlRates& element);
   bool              Shift(int index,int shift);
   //--- methods of deleting
   bool              Delete(int index);
   bool              DeleteRange(int from,int to);
protected:
   int               MemMove(int dest,int src,int count);
  };
//+------------------------------------------------------------------+
//| Constructor CArrayMqlRates.                                      |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CArrayMqlRates::CArrayMqlRates()
  {
//--- initialize protected data
   m_data_max=ArraySize(m_data);
  }
//+------------------------------------------------------------------+
//| Destructor CArrayMqlRates.                                       |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CArrayMqlRates::~CArrayMqlRates()
  {
   if(m_data_max!=0) Shutdown();
  }
...

//+------------------------------------------------------------------+
//| Adding an element to the end of the array.                       |
//| INPUT:  element - variable to be added.                          |
//| OUTPUT: true if successful, false if not.                        |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CArrayMqlRates::Add(MqlRates& element)
  {
//--- checking/reserve elements of array
   if(!Reserve(1)) return(false);
//--- adding
   m_data[m_data_total++]=element;
   m_sort_mode=-1;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Adding an element to the end of the array from another array.    |
//| INPUT:  src - source array.                                      |
//| OUTPUT: true if successful, false if not.                        |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CArrayMqlRates::AddArray(const MqlRates &src[])
  {
   int num=ArraySize(src);
//--- checking/reserving elements of array
   if(!Reserve(num)) return(false);
//--- adding
   for(int i=0;i<num;i++) m_data[m_data_total++]=src[i];
   m_sort_mode=-1;
//---
   return(true);
  }

...

Toutes les données MQL5 doivent être converties en valeurs XML avant de les envoyer en tant que requête RPC. J'ai donc conçu une classe d'aide CXMLRPCEncoder qui prend une valeur et la code sous forme de chaîne XML.

class CXMLRPCEncoder
  {
    public:
                    CXMLRPCEncoder(){};
   string            header(string path,int contentLength);
   string            fromInt(int param);
   string            fromDouble(double param);
   string            fromBool(bool param);
   string            fromString(string param);
   string            fromDateTime(datetime param);
   string            fromMqlRates(MqlRates &param);
  };

J'ai collé ci-dessous trois des méthodes mises en œuvre. Tous prennent un paramètre (bool, chaîne, date/heure) et renvoient une chaîne qui est un type de données XML valide pour le protocole XML-RPC.

//+------------------------------------------------------------------+
//| fromBool                                                         |
//+------------------------------------------------------------------+
string CXMLRPCEncoder::fromBool(bool param)
  {
   CString s_bool;
   s_bool.Clear();
   s_bool.Append(VALUE_B);
   s_bool.Append(BOOL_B);
   if(param==true)
      s_bool.Append("1");
   else s_bool.Append("0");
   s_bool.Append(BOOL_E);
   s_bool.Append(VALUE_E);

   return s_bool.Str();
  }
//+------------------------------------------------------------------+
//| fromString                                                       |
//+------------------------------------------------------------------+
string CXMLRPCEncoder::fromString(string param)
  {
   CString s_string;
   s_string.Clear();
   s_string.Append(VALUE_B);
   s_string.Append(STRING_B);
   s_string.Append(param);
   s_string.Append(STRING_E);
   s_string.Append(VALUE_E);

   return s_string.Str();
  }
//+------------------------------------------------------------------+
//| fromDateTime                                                     |
//+------------------------------------------------------------------+
string CXMLRPCEncoder::fromDateTime(datetime param)
  {
   CString s_datetime;
   s_datetime.Clear();
   s_datetime.Append(VALUE_B);
   s_datetime.Append(DATETIME_B);
   CString s_iso8601;
   s_iso8601.Assign(TimeToString(param, TIME_DATE|TIME_MINUTES));
   s_iso8601.Replace(" ", "T");
   s_iso8601.Remove(":");
   s_iso8601.Remove(".");
   s_datetime.Append(s_iso8601.Str());
   s_datetime.Append(DATETIME_E);
   s_datetime.Append(VALUE_E);

   return s_datetime.Str();
  }

Vous remarquerez qu'il existe certaines balises avec le suffixe _B signifiant « début de balise » et le suffixe _E signifiant « fin de balise ».

J'ai décidé d'utiliser un fichier d'en-tête qui conserve les noms des balises et des en-têtes XML car il rend la mise en œuvre beaucoup plus transparente

//+------------------------------------------------------------------+
//|                                                   xmlrpctags.mqh |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"

#define HEADER_1a      "POST"
#define HEADER_1b      "HTTP/1.1"
#define HEADER_2       "User-Agent: MQL5RPC/1.1"
#define HEADER_3       "Host: host.com"
#define HEADER_4       "Content-Type: text/xml"
#define HEADER_5       "Content-Length: "
#define HEADER_6       "<?xml version='1.0'?>"
#define METHOD_B       "<methodCall>"
#define METHOD_E       "</methodCall>"
#define METHOD_NAME_B  "<methodName>"
#define METHOD_NAME_E  "</methodName>"
#define RESPONSE_B     "<methodResponse>"
#define RESPONSE_E     "</methodResponse>"
#define PARAMS_B       "<params>"
#define PARAMS_E       "</params>"
#define PARAM_B        "<param>"
#define PARAM_E        "</param>"
#define VALUE_B        "<value>"
#define VALUE_E        "</value>"
#define INT_B          "<int>"
#define INT_E          "</int>"
#define I4_B           "<i4>"
#define I4_E           "</i4>"
#define BOOL_B         "<boolean>"
#define BOOL_E         "</boolean>"
#define DOUBLE_B       "<double>"
#define DOUBLE_E       "</double>"
#define STRING_B       "<string>"
#define STRING_E       "</string>"
#define DATETIME_B     "<dateTime.iso8601>"
#define DATETIME_E     "</dateTime.iso8601>"
#define BASE64_B       "<base64>"
#define BASE64_E       "</base64>"
#define ARRAY_B        "<array>"
#define ARRAY_E        "</array>"
#define DATA_B         "<data>"
#define DATA_E         "</data>"
#define STRUCT_B       "<struct>"
#define STRUCT_E       "</struct>"
#define MEMBER_B       "<member>"
#define MEMBER_E       "</member>"
#define NAME_B         "<name>"
#define NAME_E         "</name>"
//+------------------------------------------------------------------+

Après avoir défini le modèle de données de MQL5-RPC, nous pouvons procéder à la création d'une requête XML-RPC complète.


Requête MQL5-RPC

Comme mentionné précédemment, la requête XML-RPC se compose d'un en-tête de requête et d'une charge utile XML. J'ai conçu la classe CXMLRPCQuery qui construit automatiquement un objet de requête à partir de tableaux de données MQL5. La classe utilise CXMLRPCEncoder pour encapsuler les données en XML et ajoute le nom de la méthode dans la balise methodName.

class CXMLRPCQuery
  {
private:
   CString           s_query;
   void              addValueElement(bool start,bool array);
public:
                    CXMLRPCQuery() {};
                    CXMLRPCQuery(string method="",CArrayObj *param_array=NULL);
   string            toString();
  };

Le constructeur de la classe a deux paramètres : le nom de la méthode et le pointeur vers CArrayObj contenant les paramètres pour appeler la méthode. Tous les paramètres sont encapsulés dans XML comme décrit dans la section précédente et un en-tête de requête est ajouté. La requête XML entière peut être affichée à l'aide de la méthode toString().

CXMLRPCQuery::CXMLRPCQuery(string method="",CArrayObj *param_array=NULL)
  {
//--- constructs a single XMLRPC Query
   this.s_query.Clear();

   CXMLRPCEncoder encoder;
   this.s_query.Append(HEADER_6);
   this.s_query.Append(METHOD_B);
   this.s_query.Append(METHOD_NAME_B);
   this.s_query.Append(method);
   this.s_query.Append(METHOD_NAME_E);
   this.s_query.Append(PARAMS_B);

   for(int i=0; i<param_array.Total(); i++)
     {
      int j=0;
      this.s_query.Append(PARAM_B);

      int type=param_array.At(i).Type();
      int elements=0;

      switch(type)
        {
         case TYPE_INT:
           {
            CArrayInt *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromInt(arr.At(j)));
            break;
           }
         case TYPE_DOUBLE:
           {
            CArrayDouble *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromDouble(arr.At(j)));
            break;
           }
         case TYPE_STRING:
           {
            CArrayString *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromString(arr.At(j)));
            break;
           }
         case TYPE_BOOL:
           {
            CArrayBool *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromBool(arr.At(j)));
            break;
           }
         case TYPE_DATETIME:
           {
            CArrayDatetime *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromDateTime(arr.At(j)));
            break;
           }
         case TYPE_MQLRATES:
           {
            CArrayMqlRates *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++)
              {
               MqlRates tmp=arr.At(j);
               this.s_query.Append(encoder.fromMqlRates(tmp));
              }
            break;
           }
        };

      if(elements==1) addValueElement(false,false); else addValueElement(false,true);

      this.s_query.Append(PARAM_E);
     }

   this.s_query.Append(PARAMS_E);
   this.s_query.Append(METHOD_E);
  }

Veuillez voir un exemple de test de requête ci-dessous. Ce n'est pas des plus simples, puisque je veux montrer qu'il est possible d'appeler des méthodes assez complexes.

Les paramètres d'entrée sont : un tableau de valeurs double, un tableau de valeurs entières, un tableau de valeurs de chaîne, un tableau de valeurs booléennes, une seule valeur datetime et un tableau de structures MqlRates.

//+------------------------------------------------------------------+
//|                                          MQL5-RPC_query_test.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Arrays\ArrayDatetime.mqh>
#include <Arrays\ArrayMqlRates.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- query test
   CArrayObj* params = new CArrayObj;
   
   CArrayDouble*   param1 = new CArrayDouble;
   CArrayInt*      param2 = new CArrayInt;
   CArrayString*   param3 = new CArrayString;
   CArrayBool*     param4 = new CArrayBool;
   CArrayDatetime* param5 = new CArrayDatetime;
   CArrayMqlRates* param6 = new CArrayMqlRates;
   
   for (int i=0; i<4; i++)
      param1.Add(20000.0 + i*100.0);
   
   params.Add(param1);
   
   for (int i=0; i<4; i++)
      param2.Add(i);
      
   params.Add(param2);
   
   param3.Add("first_string");
   param3.Add("second_string");
   param3.Add("third_string");
   
   params.Add(param3);
   
   param4.Add(false);
   param4.Add(true);
   param4.Add(false);
   
   params.Add(param4);
   
   param5.Add(TimeCurrent());
   params.Add(param5);
   
   const int nRates = 3;
   MqlRates rates[3];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(Symbol(),0,0,nRates,rates);
   if (copied==nRates) {
      param6.AddArray(rates);
      params.Add(param6);
   }
        
   CXMLRPCQuery query("sampleMethodname", params);
   
   Print(query.toString());
   
   delete params;
  }
//+------------------------------------------------------------------+

La requête résultante est la suivante. Normalement, il s'agit d'une chaîne de caractères d'une ligne, mais je la présente sous forme de texte tabulé pour faciliter la distinction entre les sections XML-RPC et les valeurs individuelles.

<?xml version="1.0" ?>
<methodCall>
      <methodName>sampleMethodname</methodName>
      <params>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <double>20000.0000000000000000</double>
                                    </value>
                                    <value>
                                          <double>20100.0000000000000000</double>
                                    </value>
                                    <value>
                                          <double>20200.0000000000000000</double>
                                    </value>
                                    <value>
                                          <double>20300.0000000000000000</double>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <int>0</int>
                                    </value>
                                    <value>
                                          <int>1</int>
                                    </value>
                                    <value>
                                          <int>2</int>
                                    </value>
                                    <value>
                                          <int>3</int>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <string>first_string</string>
                                    </value>
                                    <value>
                                          <string>second_string</string>
                                    </value>
                                    <value>
                                          <string>third_string</string>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <boolean>0</boolean>
                                    </value>
                                    <value>
                                          <boolean>1</boolean>
                                    </value>
                                    <value>
                                          <boolean>0</boolean>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <dateTime.iso8601>20111111T2042</dateTime.iso8601>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <struct>
                                                <member>
                                                      <name>open</name>
                                                      <value>
                                                            <double>1.02902000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>high</name>
                                                      <value>
                                                            <double>1.03032000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>low</name>
                                                      <value>
                                                            <double>1.02842000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>close</name>
                                                      <value>
                                                            <double>1.02867000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>time</name>
                                                      <value>
                                                            <dateTime.iso8601>
                                                                  <value>
                                                                        <dateTime.iso8601>20111111T1800</dateTime.iso8601>
                                                                  </value>
                                                            </dateTime.iso8601>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>tick_volume</name>
                                                      <value>
                                                            <double>4154.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>real_volume</name>
                                                      <value>
                                                            <double>0.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>spread</name>
                                                      <value>
                                                            <double>30.00000000</double>
                                                      </value>
                                                </member>
                                          </struct>
                                    </value>
                                    <value>
                                          <struct>
                                                <member>
                                                      <name>open</name>
                                                      <value>
                                                            <double>1.02865000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>high</name>
                                                      <value>
                                                            <double>1.02936000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>low</name>
                                                      <value>
                                                            <double>1.02719000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>close</name>
                                                      <value>
                                                            <double>1.02755000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>time</name>
                                                      <value>
                                                            <dateTime.iso8601>
                                                                  <value>
                                                                        <dateTime.iso8601>20111111T1900</dateTime.iso8601>
                                                                  </value>
                                                            </dateTime.iso8601>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>tick_volume</name>
                                                      <value>
                                                            <double>3415.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>real_volume</name>
                                                      <value>
                                                            <double>0.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>spread</name>
                                                      <value>
                                                            <double>30.00000000</double>
                                                      </value>
                                                </member>
                                          </struct>
                                    </value>
                                    <value>
                                          <struct>
                                                <member>
                                                      <name>open</name>
                                                      <value>
                                                            <double>1.02760000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>high</name>
                                                      <value>
                                                            <double>1.02901000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>low</name>
                                                      <value>
                                                            <double>1.02756000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>close</name>
                                                      <value>
                                                            <double>1.02861000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>time</name>
                                                      <value>
                                                            <dateTime.iso8601>
                                                                  <value>
                                                                        <dateTime.iso8601>20111111T2000</dateTime.iso8601>
                                                                  </value>
                                                            </dateTime.iso8601>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>tick_volume</name>
                                                      <value>
                                                            <double>1845.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>real_volume</name>
                                                      <value>
                                                            <double>0.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>spread</name>
                                                      <value>
                                                            <double>30.00000000</double>
                                                      </value>
                                                </member>
                                          </struct>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
      </params>
</methodCall>

Il s'agit d'un arbre XML assez développé et le protocole est flexible pour appeler des méthodes encore plus complexes.


Réponse MQL5-RPC

Comme pour la demande XML-RPC, la réponse XML-RPC est construite à partir d'en-têtes et de données utiles XML, mais cette fois l'ordre de traitement doit être inversé, c'est-à-dire que la réponse XML doit être convertie en données MQL5. La classe CXMLRPCResult a été conçue pour faire face à cette tâche.

Le résultat peut être reçu sous la forme d'une chaîne qui est transmise au constructeur de classe ou peut être automatiquement récupéré à partir de la classe CXMLServerProxy après l'exécution de la méthode souhaitée. Dans les situations où le résultat contient des structures qui peuvent ne pas être connues à l'avance, la méthode parseXMLResponseRAW() peut rechercher toutes les balises <value> et renvoie le pointeur CArrayObj qui contient un tableau de tous les éléments de valeur trouvés.

class CXMLRPCResult
  {
private:
   CArrayObj        *m_resultsArr;

   CString           m_cstrResponse;
   CArrayString      m_params;

   bool              isValidXMLResponse();
   bool              parseXMLValuesToMQLArray(CArrayString *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayDouble *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayInt *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayBool *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayDatetime *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayMqlRates *subArr,CString &val);
   bool              parseXMLResponse();

public:
                     CXMLRPCResult() {};
                    ~CXMLRPCResult();
                     CXMLRPCResult(string resultXml);
   
   CArrayObj        *getResults();
   bool              parseXMLResponseRAW();
   string            toString();
  };

Le constructeur de la classe parcourt chaque en-tête du XML de réponse et appelle la méthode privée parseXMLValuesToMQLArray() qui fait le travail difficile de convertir les données XML en données MQL5 en coulisses.

Il reconnaît si param est un tableau ou un élément unique et remplit les tableaux appropriés ajoutés au résultat du tableau CArrayObj.

bool CXMLRPCResult::parseXMLResponse()
  {
   CArrayObj *results=new CArrayObj;

   m_params.Clear();

   //--- find params and put them in m_params array
   int tagStartIdx= 0;
   int tagStopIdx = 0;
   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {

      tagStartIdx= m_cstrResponse.Find(tagStartIdx,PARAM_B);
      tagStopIdx = m_cstrResponse.Find(tagStopIdx,PARAM_E);

      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         m_params.Add(m_cstrResponse.Mid(tagStartIdx+StringLen(PARAM_B),tagStopIdx-tagStartIdx-StringLen(PARAM_B)));
         tagStartIdx++; tagStopIdx++;
        };

     };

   for(int i=0; i<m_params.Total(); i++)
     {
      CString val;
      val.Assign(m_params.At(i));

      //--- parse value tag

      val.Assign(val.Mid(StringLen(VALUE_B),val.Len()-StringLen(VALUE_B)-StringLen(VALUE_E)));

      //--- now check first tag and handle it approprietaly

      string param_type=val.Mid(0,val.Find(0,">")+1);

      if(param_type==INT_B || param_type==I4_B)
        {
         val.Assign(m_params.At(i));
         CArrayInt *subArr=new CArrayInt;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==BOOL_B)
        {
         val.Assign(m_params.At(i));
         CArrayBool *subArr=new CArrayBool;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==DOUBLE_B)
        {
         val.Assign(m_params.At(i));
         CArrayDouble *subArr=new CArrayDouble;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==STRING_B)
        {
         val.Assign(m_params.At(i));
         CArrayString *subArr=new CArrayString;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==DATETIME_B)
        {
         val.Assign(m_params.At(i));
         CArrayDatetime *subArr=new CArrayDatetime;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==ARRAY_B)
        {
         val.Assign(val.Mid(StringLen(ARRAY_B)+StringLen(DATA_B),val.Len()-StringLen(ARRAY_B)-StringLen(DATA_E)));
         //--- find first type and define array
         string array_type=val.Mid(StringLen(VALUE_B),val.Find(StringLen(VALUE_B)+1,">")-StringLen(VALUE_B)+1);

         if(array_type==INT_B || array_type==I4_B)
           {
            CArrayInt *subArr=new CArrayInt;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
         else if(array_type==BOOL_B)
           {
            CArrayBool *subArr=new CArrayBool;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
         else if(array_type==DOUBLE_B)
           {
            CArrayDouble *subArr=new CArrayDouble;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
         else if(array_type==STRING_B)
           {
            CArrayString *subArr=new CArrayString;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);

           }
         else if(array_type==DATETIME_B)
           {
            CArrayDatetime *subArr=new CArrayDatetime;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
        }
     };

   m_resultsArr=results;

   return true;
  }

La conversion a lieu dans les méthodes surchargées parseXMLValuesToMQLArray. La valeur de chaîne est extraite de XML et convertie en variables MQL5 de base.

Trois des méthodes utilisées pour la conversion sont collées ci-dessous à titre de référence.

//+------------------------------------------------------------------+
//| parseXMLValuesToMQLArray                                         |
//+------------------------------------------------------------------+
bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayBool *subArr,CString &val)
  {
   //--- parse XML values and populate MQL array
   int tagStartIdx=0; int tagStopIdx=0;

   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {
      tagStartIdx= val.Find(tagStartIdx,VALUE_B);
      tagStopIdx = val.Find(tagStopIdx,VALUE_E);
      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         CString e;
         e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(BOOL_B),
                  tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(BOOL_B)-StringLen(BOOL_E)));
         if(e.Str()=="0")
            subArr.Add(false);
         if(e.Str()=="1")
            subArr.Add(true);

         tagStartIdx++; tagStopIdx++;
        };
     }
   if(subArr.Total()<1) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| parseXMLValuesToMQLArray                                         |
//+------------------------------------------------------------------+
bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayInt *subArr,CString &val)
  {
    //--- parse XML values and populate MQL array
   int tagStartIdx=0; int tagStopIdx=0;

   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {
      tagStartIdx= val.Find(tagStartIdx,VALUE_B);
      tagStopIdx = val.Find(tagStopIdx,VALUE_E);
      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         CString e;
         e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(INT_B),
                  tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(INT_B)-StringLen(INT_E)));
         subArr.Add((int)StringToInteger(e.Str()));
         tagStartIdx++; tagStopIdx++;
        };
     }
   if(subArr.Total()<1) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| parseXMLValuesToMQLArray                                         |
//+------------------------------------------------------------------+
bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayDatetime *subArr,CString &val)
  {
   // parse XML values and populate MQL array
   int tagStartIdx=0; int tagStopIdx=0;

   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {
      tagStartIdx= val.Find(tagStartIdx,VALUE_B);
      tagStopIdx = val.Find(tagStopIdx,VALUE_E);
      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         CString e;
         e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(DATETIME_B),
                  tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(DATETIME_B)-StringLen(DATETIME_E)));
         e.Replace("T"," "); e.Insert(4,"."); e.Insert(7,".");
         subArr.Add(StringToTime(e.Str()));
         tagStartIdx++; tagStopIdx++;
        };
     }
   if(subArr.Total()<1) return false;
   return true;
  }

Comme vous pouvez le voir, les valeurs de chaîne sont extraites des balises XML-RPC à l'aide des méthodes disponibles dans la classe CString ; Assign(), Mid(), Find() et sont ensuite convertis en MQL5 à l'aide de StringToTime(), StringToInteger(), StringToDouble() ou d'une méthode personnalisée comme dans le cas des variables bool.

Après l'analyse de toutes les valeurs, toutes les données disponibles peuvent être affichées à l'aide de la méthode toString(). J'utilise simplement deux points pour séparer les valeurs.

string CXMLRPCResult::toString(void) 
  {
   // returns results array of arrays as a string
   CString r;

   for(int i=0; i<m_resultsArr.Total(); i++) 
     {
      int rtype=m_resultsArr.At(i).Type();
      switch(rtype) 
        {
         case(TYPE_STRING) : 
           {
            CArrayString *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(subArr.At(j)+":");
              }
            break;
           };
         case(TYPE_DOUBLE) : 
           {
            CArrayDouble *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(DoubleToString(NormalizeDouble(subArr.At(j),8))+":");
              }
            break;
           };
         case(TYPE_INT) : 
           {
            CArrayInt *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(IntegerToString(subArr.At(j))+":");
              }
            break;
           };
         case(TYPE_BOOL) : 
           {
            CArrayBool *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               if(subArr.At(j)==false) r.Append("false:");
               else r.Append("true:");
              }
            break;
           };
         case(TYPE_DATETIME) : 
           {
            CArrayDatetime *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(TimeToString(subArr.At(j),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+" : ");
              }
            break;
           };
        };
     }

   return r.Str();
  }

J'ai mis en place un test de résultat pour que vous voyiez explicitement comment les paramètres de résultat XML-RPC sont convertis en MQL5.

//+------------------------------------------------------------------+
//|                                         MQL5-RPC_result_test.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
//---
#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Arrays\ArrayDatetime.mqh>
#include <Arrays\ArrayMqlRates.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    string sampleXMLResult = "<?xml version='1.0'?>"
                            "<methodResponse>" +
                            "<params>" +
                            "<param>" +
                            "<value><array><data>" +
                            "<value><string>Xupypr</string></value>" +
                            "<value><string>anuta</string></value>" +
                            "<value><string>plencing</string></value>" +
                            "<value><string>aharata</string></value>" +
                            "<value><string>beast</string></value>" +
                            "<value><string>west100</string></value>" +
                            "<value><string>ias</string></value>" +
                            "<value><string>Tim</string></value>" +
                            "<value><string>gery18</string></value>" +
                            "<value><string>ronnielee</string></value>" +
                            "<value><string>investeo</string></value>" +
                            "<value><string>droslan</string></value>" +
                            "<value><string>Better</string></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><double>1.222</double></value>" +
                            "<value><double>0.456</double></value>" +
                            "<value><double>1000000000.10101</double></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><boolean>1</boolean></value>" +
                            "<value><boolean>0</boolean></value>" +
                            "<value><boolean>1</boolean></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><int>-1</int></value>" +
                            "<value><int>0</int></value>" +
                            "<value><int>1</int></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><dateTime.iso8601>20021125T02:20:04</dateTime.iso8601></value>" +
                            "<value><dateTime.iso8601>20111115T00:00:00</dateTime.iso8601></value>" +
                            "<value><dateTime.iso8601>20121221T00:00:00</dateTime.iso8601></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param><value><string>Single string value</string></value></param>" +
                            "<param><value><dateTime.iso8601>20111115T00:00:00</dateTime.iso8601></value></param>" +
                            "<param><value><int>-111</int></value></param>" +
                            "<param><value><boolean>1</boolean></value></param>" +
                            "</params>" +
                            "</methodResponse>";

   CXMLRPCResult* testResult = new CXMLRPCResult(sampleXMLResult);
      
   Print(testResult.toString());

   delete testResult;
  }
//+------------------------------------------------------------------+

Vous pouvez trouver le résultat ci-dessous :

MQL5-RPC__result_test (EURUSD,H1)       23:16:57        
Xupypr:anuta:plencing:aharata:beast:west100:ias:Tim:gery18:ronnielee:investeo:
droslan:Better:1.22200000:0.45600000:1000000000.10099995:true:false:true:-1:0:
1:2002.11.25 02:20:04 : 2011.11.15 00:00:00 : 2012.12.21 00:00:00 :
Single string value:2011.11.15 00:00:00 :-111:true:

Il s'agit d'un tableau affiché de tableaux de différentes valeurs MQL5 converties à partir de XML. Comme vous pouvez le constater, des chaînes de caractères, des valeurs doubles, des valeurs booléennes, des valeurs entières et des valeurs datetime sont accessibles à partir d'un seul pointeur CArrayObj.


Classe proxy MQL5-RPC

Le proxy CXMLRPCServer est une classe de base pour la communication HTTP. J'ai utilisé « Utiliser WinInet dans MQL5. Partie 2 : POST Requêtes et fichiers » pour mettre en œuvre la fonctionnalité HTTP et ajouter un en-tête personnalisé conforme à la spécification XML-RPC.

CXMLRPCServerProxy::CXMLRPCServerProxy(string s_proxy,int timeout=0)
  {
   CString proxy;
   proxy.Assign(s_proxy);
   //--- find query path
   int sIdx = proxy.Find(0,"/");
   if (sIdx == -1)  
      m_query_path = "/";
   else  {
       m_query_path = proxy.Mid(sIdx, StringLen(s_proxy) - sIdx) + "/";
       s_proxy = proxy.Mid(0, sIdx);
    };
   //--- find query port. 80 is default
   int query_port = 80;
   int pIdx = proxy.Find(0,":");
   if (pIdx != -1) {
      query_port = (int)StringToInteger(proxy.Mid(pIdx+1, sIdx-pIdx));
      s_proxy = proxy.Mid(0, pIdx);
   };
   //Print(query_port);
   //Print(proxy.Mid(pIdx+1, sIdx-pIdx));
   if(InternetAttemptConnect(0)!=0)
     {
      this.m_connectionStatus="InternetAttemptConnect failed.";
      this.m_session=-1;
      this.m_isConnected=false;
      return;
     }
   string agent = "Mozilla";
   string empty = "";

   this.m_session=InternetOpenW(agent,OPEN_TYPE_PRECONFIG,empty,empty,0);

   if(this.m_session<=0)
     {
      this.m_connectionStatus="InternetOpenW failed.";
      this.m_session=-2;
      this.m_isConnected=true;
      return;
     }
   this.m_connection=InternetConnectW(this.m_session,s_proxy,query_port,empty,empty,SERVICE_HTTP,0,0);
   if(this.m_connection<=0)
     {
      this.m_connectionStatus="InternetConnectW failed.";
      return;
     }
   this.m_connectionStatus="Connected.";

  }

L'objet CXMLRPCQuery doit être transmis à la méthode CXMLRPCServerProxy execute() pour déclencher l'appel XML-RPC.

Les méthodes renvoient un pointeur vers l'objet CXMLRPCResult qui peut être utilisé ultérieurement dans le script qui a appelé l'appel XML-RPC.

CXMLRPCResult *CXMLRPCServerProxy::execute(CXMLRPCQuery &query)
  {
   
   //--- creating descriptor of the request
   string empty_string = "";
   string query_string = query.toString();
   string query_method = HEADER_1a;
   string http_version = HEADER_1b;
   
   uchar data[];
   
   StringToCharArray(query.toString(),data);
   
   int ivar=0;
   int hRequest=HttpOpenRequestW(this.m_connection,query_method,m_query_path,http_version,
                                 empty_string,0,FLAG_KEEP_CONNECTION|FLAG_RELOAD|FLAG_PRAGMA_NOCACHE,0);
   if(hRequest<=0)
     {
      Print("-Err OpenRequest");
      InternetCloseHandle(this.m_connection);
      return(new CXMLRPCResult);
     }
   //-- sending the request
   CXMLRPCEncoder encoder;
   string header=encoder.header(m_query_path,ArraySize(data));

   int aH=HttpAddRequestHeadersW(hRequest,header,StringLen(header),HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
   bool hSend=HttpSendRequestW(hRequest,empty_string,0,data,ArraySize(data)-1);
   if(hSend!=true)
     {
      int err=0;
      err=GetLastError();
      Print("-Err SendRequest= ",err);
     }
   string res;

   ReadPage(hRequest,res,false);
   CString out;
   out.Assign(res);
   out.Remove("\n");
   
   //--- closing all handles
   InternetCloseHandle(hRequest); InternetCloseHandle(hSend);
   CXMLRPCResult* result = new CXMLRPCResult(out.Str());
   return result;
  }


Exemple 1 - Accès au service Web

Le premier exemple fonctionnel de MQL5-RPC est un appel à un service Web externe. L'exemple que j'ai trouvé utilise le taux de change actuel pour convertir un montant spécifique d'une devise en une autre. La spécification exacte des paramètres de la méthode est disponible en ligne.

Le service Web expose la méthode foxrate.currencyConvert qui accepte trois paramètres, deux chaînes et une valeur flottante :

La mise en œuvre est assez courte et ne prend que quelques lignes.

//+------------------------------------------------------------------+
//|                              MQL5-RPC_ex1_ExternalWebService.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
//---
#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //--- web service test
   CArrayObj* params = new CArrayObj;
   
   CArrayString* from   = new CArrayString;
   CArrayString* to     = new CArrayString;
   CArrayDouble* amount = new CArrayDouble;
   
   from.Add("GBP"); to.Add("USD"); amount.Add(10000.0);
   params.Add(from); params.Add(to); params.Add(amount);
   
   CXMLRPCQuery query("foxrate.currencyConvert", params);
   
   Print(query.toString());
   
   CXMLRPCServerProxy s("foxrate.org/rpc");
   CXMLRPCResult* result;
   
   result = s.execute(query);
   
   result.parseXMLResponseRAW();
   Print(result.toString());
     
   delete params;
   delete result;
  }
//+------------------------------------------------------------------+

Étant donné que la méthode renvoie une structure complexe, j'ai analysé le résultat à l'aide de la méthode parseXMLResponseRAW().

La requête XML-RPC dans cet exemple se présente comme suit :

<?xml version="1.0" ?>
<methodCall>
      <methodName>foxrate.currencyConvert</methodName>
      <params>
            <param>
                  <value>
                        <string>GBP</string>
                  </value>
            </param>
            <param>
                  <value>
                        <string>USD</string>
                  </value>
           </param>
           <param>
                  <value>
                        <double>10000.00000000</double>
                  </value>
           </param>
      </params>
</methodCall>

Et la réponse est renvoyée sous la forme d'une structure enveloppée dans XML.

<?xml version="1.0" ?>
<methodResponse>
      <params>
            <param>
                  <value>
                        <struct>
                              <member>
                                    <name>flerror</name>
                                    <value>
                                          <int>0</int>
                                    </value>
                              </member>
                              <member>
                                    <name>amount</name>
                                    <value>
                                          <double>15773</double>
                                    </value>
                              </member>
                              <member>
                                    <name>message</name>
                                    <value>
                                          <string>cached</string>
                                    </value>
                              </member>
                        </struct>
                  </value>
            </param>
      </params>
</methodResponse>

La sortie de toString() révèle que trois valeurs sont disponibles dans le résultat : 0 - pas d'erreur, la deuxième valeur est le montant de la devise de base nécessaire pour l'échange et le troisième paramètre est l'heure du dernier taux de change (il a été mis en cache).

0:15773.00000000:cached:

Poursuivons avec un cas d'utilisation plus intéressant.


Exemple 2 - Analyseur XML-RPC ATC 2011

Imaginez que vous vouliez récupérer des statistiques surChampionnat de trading automatisé 2011 (je l'ai fait) et utiliser ces informations dans le terminal MetaTrader 5. Si vous souhaitez savoir comment y parvenir, lisez ce qui suit.

Étant donné que MetaQuotes Software Corp. prépare un service de suivi des signaux qui permettra de s'abonner aux signaux de certains Expert Advisors, je pense que la combinaison de ce service avec l'analyseur sera un moyen très puissant d'effectuer une analyse sophistiquée et de fournir de nouvelles façons de bénéficier de ATC. En fait, vous pourriez récupérer des données de plusieurs sources différentes en utilisant cette méthode et créer un Expert Advisor complexe à partir de ces données.


Serveur d'analyse XML-RPC ATC 2011

La première étape de la création d'un serveur ATC Analyzer consiste à préparer l'analyse de sortie. Supposons que nous voulions saisir tous les participants dont les actions des comptes sont supérieures à un certain seuil et que nous soyons intéressés par les positions qu'ils détiennent actuellement et que nous affichions les statistiques sur le nombre de positions d'achat et de vente de la paire de devises qui nous intéresse.

J'ai utilisé le langage Python et la bibliothèque BeautifulSoup pour récupérer et analyser les données. Si vous n'avez jamais utilisé Python auparavant, je vous recommande vivement de pointer votre navigateur sur http:/Python.org et de lire ce que ce langage a à offrir, vous ne le regretterez pas.

Si vous souhaitez plutôt mettre rapidement la main sur l'analyseur, téléchargez le programme d'installation Python 2.7.1 et setup_tools packages et installez les deux. Après cela, exécutez la console Windows et changez le répertoire en C:\Python27\Scripts ou tout autre dossier dans lequel vous avez installé Python. Ensuite, lancez la commande « easy_install BeautifulSoup ». Cela va installer le paquet BeautifulSoup :

C:\Python27\Scripts>easy_install BeautifulSoup
Searching for BeautifulSoup
Reading http://pypi.python.org/simple/BeautifulSoup/
Reading http://www.crummy.com/software/BeautifulSoup/
Reading http://www.crummy.com/software/BeautifulSoup/download/
Best match: BeautifulSoup 3.2.0
Downloading http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulS
oup-3.2.0.tar.gz
Processing BeautifulSoup-3.2.0.tar.gz
Running BeautifulSoup-3.2.0\setup.py -q bdist_egg --dist-dir c:\users\przemek\ap
pdata\local\temp\easy_install-zc1s2v\BeautifulSoup-3.2.0\egg-dist-tmp-hnpwoo
zip_safe flag not set; analyzing archive contents...
C:\Python27\lib\site-packages\setuptools\command\bdist_egg.py:422: UnicodeWarnin
g: Unicode equal comparison failed to convert both arguments to Unicode - interp
reting them as being unequal
  symbols = dict.fromkeys(iter_symbols(code))
Adding beautifulsoup 3.2.0 to easy-install.pth file

Installed c:\python27\lib\site-packages\beautifulsoup-3.2.0-py2.7.egg
Processing dependencies for BeautifulSoup
Finished processing dependencies for BeautifulSoup

C:\Python27\Scripts>
-

Après cela, vous devriez être en mesure d'exécuter la console Python et de lancer la commande « import BeautifulSoup » sans erreur.

Python 2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import BeautifulSoup
>>> 

Et lancez l'analyseur ci-joint avec la commande « python ContestantParser.py ». L'analyseur récupère le site Web, effectue une recherche par mot-clé et affiche les résultats dans la console.

import re
import urllib
from BeautifulSoup import BeautifulSoup

class ContestantParser(object):
    URL_PREFIX = 'https://championship.mql5.com/2011/en/users/'
    
    def __init__(self, name):
        print "User:", name
        url = ContestantParser.URL_PREFIX + name
        feed = urllib.urlopen(url)
        s = BeautifulSoup(feed.read())
        account = s.findAll('td', attrs={'class' : 'alignRight'})
        self.balance = float(account[0].contents[0].replace(' ', ''))
        self.equity = float(account[2].contents[0].replace(' ', ''))
        terminals = s.findAll('table', attrs={'class': 'terminal'})
        terminal = terminals[0]
        trs = terminal.findAll('tr')
        pairs = terminal.findAll('span', attrs={'class':'stateText'})
        self.stats = {}
        for i in range(len(pairs)-1):
            if len(pairs[i].string)> 6:
                break
            self.stats[str(pairs[i].string)] = str(trs[i+1].contents[7].string)

    def __str__(self):
        for k in self.stats.keys():
            print k, self.stats[k] 
        
        return "Bal " + str(self.balance) + " Equ " +  str(self.equity)
    
    def position(self, pair):
        if pair in self.stats.keys():
            return self.stats[pair]
        else:
            return "none"
        
class ContestantsFinder(object):
    URL_PREFIX = "https://championship.mql5.com/2011/en/users/index/page"
    URL_SUFFIX = "?orderby=equity&dir=down"
    
    def __init__(self, min_equity):
        self.min_equity = min_equity        
        self.user_list = []
        self.__find()
        
    def __find(self):
        isLastPage = False
        pageCnt = 1
        while isLastPage == False:
            url = ContestantsFinder.URL_PREFIX + str(pageCnt) + \
                  ContestantsFinder.URL_SUFFIX
            feed = urllib.urlopen(url)
            s = BeautifulSoup(feed.read())
            urows = s.findAll('tr', attrs={'class' : re.compile('row.*')})
            for row in urows:
                user_name = row.contents[5].a.string
                equity = float(row.contents[19].string.replace(' ',''))
                if equity <= self.min_equity:
                    isLastPage = True
                    break
                self.user_list.append(str(user_name))
                print user_name, equity
            pageCnt += 1
    
        
    def list(self):
        return self.user_list
    
    
 
if __name__ == "__main__":
        
    # find all contestants with equity larger than threshold
    contestants = ContestantsFinder(20000.0)
    print contestants.list()
    # display statistics
    print "* Statistics *"
    for contestant in contestants.list():
        u = ContestantParser(contestant)    
        print u
        print u.position('eurusd')
        print '-' * 60
    

Pour ce faire, il suffisait de regarder dans le code source HTML et de trouver les relations entre les balises. Si ce code est exécuté directement à partir de la console Python, il affiche les noms des concurrents, leur solde et leurs fonds propres, ainsi que toutes les positions ouvertes actuelles.

Veuillez observer le résultat d'un exemple de requête :

* Statistics *
User: Tim
Bal 31459.2 Equ 31459.2
none
------------------------------------------------------------
User: enivid
eurusd sell
euraud sell
Bal 26179.98 Equ 29779.89
sell
------------------------------------------------------------
User: ias
eurjpy sell
usdchf sell
gbpusd buy
eurgbp buy
eurchf sell
audusd buy
gbpjpy sell
usdjpy buy
usdcad buy
euraud buy
Bal 15670.0 Equ 29345.66
none
------------------------------------------------------------
User: plencing
eurusd buy
Bal 30233.2 Equ 29273.2
buy
------------------------------------------------------------
User: anuta
audusd buy
usdcad sell
gbpusd buy
Bal 28329.85 Equ 28359.05
none
------------------------------------------------------------
User: gery18
Bal 27846.7 Equ 27846.7
none
------------------------------------------------------------
User: tornhill
Bal 27402.4 Equ 27402.4
none
------------------------------------------------------------
User: rikko
eurusd sell
Bal 25574.8 Equ 26852.8
sell
------------------------------------------------------------
User: west100
eurusd buy
Bal 27980.5 Equ 26255.5
buy
------------------------------------------------------------
...

Ça a l'air bien, n'est-ce pas ? Avec ces données, nous pouvons rassembler des statistiques intéressantes et mettre en place un service XML-RPC qui les fournira à la demande. Le client XMLRPC de MetaTrader 5 pourrait récupérer ces statistiques si nécessaire.

Afin de créer un serveur XMLRPC, j'ai utilisé la bibliothèque de serveur SimpleXMLRPC de python. Le serveur expose deux méthodes au monde extérieur : listContestants et getStats. Alors que le premier n'affiche que les noms des concurrents dont les capitaux propres dépassent un certain seuil, le second affiche combien d'entre eux ont une position ouverte sur une paire de devises donnée, et quel est le rapport achat/vente sur ces positions.

Avec le service de signaux et/ou le Trade Copier de l'article, vous pouvez confirmer que la configuration que vous tradez actuellement a plus de chances d'être rentable.

from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

from PositionGrabberTest import ContestantParser, ContestantsFinder

class RequestHandler( SimpleXMLRPCRequestHandler ):
    rpc_path = ( '/RPC2', )
    
class ATC2011XMLRPCServer(SimpleXMLRPCServer):

    def __init__(self):
        SimpleXMLRPCServer.__init__( self, ("192.168.235.168", 6666), requestHandler=RequestHandler, logRequests = False)
        
        self.register_introspection_functions()
        self.register_function( self.xmlrpc_contestants, "listContestants" )        
        self.register_function( self.xmlrpc_get_position_stats, "getStats" )        
        
    
    def xmlrpc_contestants(self, min_equity):
        try:
            min_equity = float(min_equity)
        except ValueError as error:
            print error
            return []
    
        self.contestants = ContestantsFinder(min_equity)
        
        return self.contestants.list()
    
    def xmlrpc_get_position_stats(self, pair):
        total_users = len(self.contestants.list())
        holding_pair = 0
        buy_positions = 0
        
        for username in self.contestants.list():
            u = ContestantParser(username)
            position = u.position(pair)
            if position != "none":
                holding_pair += 1
                if position == "buy":
                    buy_positions += 1
        
        return [ total_users, holding_pair, buy_positions ]            
    

if __name__ == '__main__':
    server = ATC2011XMLRPCServer() 
    server.serve_forever()        

Ce serveur est accessible directement depuis la console Python. Lors de l'exécution de ce script, n'oubliez pas de remplacer l'adresse IP par celle que votre système utilise.

C:\Program Files\MetaTrader 5\MQL5>python
Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> from xmlrpclib import ServerProxy
>>> u = ServerProxy('http://192.168.235.168:6666')
>>> u.listContestants(20000.0)
['lf8749', 'ias', 'aharata', 'Xupypr', 'beast', 'Tim', 'tmt0086', 'yyy999', 'bob
sley', 'Diubakin', 'Pirat', 'notused', 'AAA777', 'ronnielee', 'samclider-2010',
'gery18', 'p96900', 'Integer', 'GradyLee', 'skarkalakos', 'zioliberato', 'kgo',
'enivid', 'Loky', 'Gans-deGlucker', 'zephyrrr', 'InvestProm', 'QuarkSpark', 'ld7
3', 'rho2011', 'tornhill', 'botaxuper']
>>>

Il est probable qu'au moment où vous lisez ces lignes, les résultats des participants ont totalement changé, mais vous devriez avoir une idée de ce que j'ai essayé de faire.

L'appel de la méthode getStats() avec le paramètre « eurusd » renvoie trois nombres :

In : u.getStats("eurusd")
Out: [23, 12, 9]

Le premier étant un certain nombre de concurrents qui ont des capitaux propres au-dessus du seuil, le deuxième étant un certain nombre de concurrents qui détiennent une position EURUSD ouverte et le troisième nombre est un certain nombre de concurrents qui ont une position longue eurusd. Dans ce cas, deux tiers des participants gagnants ont ouvert une position longue sur l'EURUSD, ce qui peut servir de signal de confirmation lorsque votre robot ou vos indicateurs ont généré le signal « open eurusd long ».


Client analyseur MQL5-RPC ATC 2011

Il est temps d'utiliser le framework MQL5-RPC pour récupérer ces données à la demande dans MetaTrader 5. En fait, son utilisation est explicite si vous suivez le code source.

//+------------------------------------------------------------------+
//|                           MQL5-RPC_ex2_ATC2011AnalyzerClient.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //--- ATC 2011 analyzer test
   CXMLRPCServerProxy s("192.168.235.168:6666");
   CXMLRPCResult* result;
   
   //--- Get list of contestants
   CArrayObj* params1 = new CArrayObj;
   CArrayDouble* amount = new CArrayDouble;
   
   amount.Add(20000.0); params1.Add(amount);
   CXMLRPCQuery query("listContestants", params1);
   
   Print(query.toString());
   
   result = s.execute(query);
   Print(result.toString());
   delete result;

   //--- Get position statistics
   CArrayObj* params2 = new CArrayObj;
   CArrayString* pair = new CArrayString;
   
   pair.Add("eurusd"); params2.Add(pair);
   CXMLRPCQuery query2("getStats", params2);
   
   Print(query2.toString());
   
   result = s.execute(query2);
   
   CArrayObj* resultArray = result.getResults();
   CArrayInt* stats = resultArray.At(0);
   
   Print("Contestants = " + IntegerToString(stats.At(0)) + 
         ". EURUSD positions = " + IntegerToString(stats.At(1)) +
         ". BUY positions = " + IntegerToString(stats.At(2)));
         
   delete params1;
   delete params2;
   
   delete result;
  }
//+------------------------------------------------------------------+

C'est ce qui se passe lorsque le script est appelé. La requête est encapsulée dans XML :

<?xml version="1.0" ?>
<methodCall>
      <methodName>listContestants</methodName>
      <params>
            <param>
                  <value>
                        <double>20000.00000000</double>
                  </value>
            </param>
      </params>
</methodCall>

et après un certain temps, une réponse est reçue du serveur XML-RPC. Dans mon cas, j'ai utilisé un hôte Linux qui exécute le service python XML-RPC et l'ai appelé à partir de l'installation invitée de Windows VirtualBox qui exécute MetaTrader 5.

<?xml version="1.0" ?>
<methodResponse>
      <params>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <string>lf8749</string>
                                    </value>
                                    <value>
                                          <string>ias</string>
                                    </value>
                                    <value>
                                          <string>Xupypr</string>
                                    </value>
                                    <value>
                                          <string>aharata</string>
                                    </value>
                                    <value>
                                          <string>beast</string>
                                    </value>
                                    <value>
                                          <string>Tim</string>
                                    </value>
                                    <value>
                                          <string>tmt0086</string>
                                    </value>
                                    <value>
                                          <string>yyy999</string>
                                    </value>
                                    <value>
                                          <string>bobsley</string>
                                    </value>
                                    <value>
                                          <string>Diubakin</string>
                                    </value>
                                    <value>
                                          <string>Pirat</string>
                                    </value>
                                    <value>
                                          <string>AAA777</string>
                                    </value>
                                    <value>
                                          <string>notused</string>
                                    </value>
                                    <value>
                                          <string>ronnielee</string>
                                    </value>
                                    <value>
                                          <string>samclider-2010</string>
                                    </value>
                                    <value>
                                          <string>gery18</string>
                                    </value>
                                    <value>
                                          <string>Integer</string>
                                    </value>
                                    <value>
                                          <string>GradyLee</string>
                                    </value>
                                    <value>
                                          <string>p96900</string>
                                    </value>
                                    <value>
                                          <string>skarkalakos</string>
                                    </value>
                                    <value>
                                          <string>Loky</string>
                                    </value>
                                    <value>
                                          <string>zephyrrr</string>
                                    </value>
                                    <value>
                                          <string>Medvedev</string>
                                    </value>
                                    <value>
                                          <string>Gans-deGlucker</string>
                                    </value>
                                    <value>
                                          <string>InvestProm</string>
                                    </value>
                                    <value>
                                          <string>zioliberato</string>
                                    </value>
                                    <value>
                                          <string>QuarkSpark</string>
                                    </value>
                                    <value>
                                          <string>rho2011</string>
                                    </value>
                                    <value>
                                          <string>ld73</string>
                                    </value>
                                    <value>
                                          <string>enivid</string>
                                    </value>
                                    <value>
                                          <string>botaxuper</string>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
      </params>
</methodResponse>

Le résultat de la première requête affichée à l'aide de la méthode toString() est le suivant :

lf8749:ias:Xupypr:aharata:beast:Tim:tmt0086:yyy999:bobsley:Diubakin:Pirat:
AAA777:notused:ronnielee:samclider-2010:gery18:Integer:GradyLee:p96900:
skarkalakos:Loky:zephyrrr:Medvedev:Gans-deGlucker:InvestProm:zioliberato:
QuarkSpark:rho2011:ld73:enivid:botaxuper:

La deuxième requête est utilisée pour appeler la méthode getStats().

<?xml version="1.0" ?>
<methodCall>
      <methodName>getStats</methodName>
      <params>
            <param>
                  <value>
                        <string>eurusd</string>
                  </value>
            </param>
      </params>
</methodCall>

La réponse XML-RPC est simple, elle ne contient que trois valeurs entières.

<?xml version="1.0" ?>
<methodResponse>
      <params>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <int>31</int>
                                    </value>
                                    <value>
                                          <int>10</int>
                                    </value>
                                    <value>
                                          <int>3</int>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
      </params>
</methodResponse>

Cette fois, j'ai adopté une autre approche et j'ai accédé aux valeurs renvoyées en tant que variables MQL5.

MQL5-RPC_ex2_ATC2011AnalyzerClient (AUDUSD,H1)  12:39:23

lf8749:ias:Xupypr:aharata:beast:Tim:tmt0086:yyy999:bobsley:Diubakin:Pirat:
AAA777:notused:ronnielee:samclider-2010:gery18:Integer:GradyLee:p96900:
skarkalakos:Loky:zephyrrr:Medvedev:Gans-deGlucker:InvestProm:zioliberato:
QuarkSpark:rho2011:ld73:enivid:botaxuper:

MQL5-RPC_ex2_ATC2011AnalyzerClient (AUDUSD,H1)  12:39:29        

Contestants = 31. EURUSD positions = 10. BUY positions = 3

Comme vous pouvez le voir, le résultat est sous une forme facilement compréhensible.


Conclusion

J'ai présenté un nouveau framework MQL5-RPC qui permet à MetaTrader 5 d'exécuter des appels de procédure à distance en utilisant le protocole XML-RPC. Deux cas d'utilisation ont été présentés, le premier étant l'accès à un service Web et le second était un analyseur personnalisé pour Championnat de trading automatisé 2011. Ces deux exemples devraient servir de base à d'autres expériences.

Je pense que MQL5-RPC est une fonctionnalité très puissante qui peut être utilisée de différentes manières. J'ai décidé de rendre le framework Open Source et de le publier sous la licence GPL licence at http://code.google.com/p/mql5-rpc/. Si quelqu'un souhaite contribuer à rendre le code inaltérable, à le remanier ou à corriger des bogues, vous êtes les bienvenus pour rejoindre le projet. Le code source avec tous les exemples est également disponible en pièce jointe de l'article.