English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
MQL5-RPC. Chiamate di procedura remota da MQL5: Accesso al servizio web e analizzatore ATC XML-RPC per divertimento e profitto

MQL5-RPC. Chiamate di procedura remota da MQL5: Accesso al servizio web e analizzatore ATC XML-RPC per divertimento e profitto

MetaTrader 5Integrazione | 11 gennaio 2022, 15:20
82 0
investeo
investeo

Introduzione

Questo articolo descriverà il framework MQL5-RPC che ho creato nelle ultime settimane. Copre gli accessi di base XML-RPC, la descrizione dell'implementazione MQL5 e due esempi di utilizzo del mondo reale MQL5-RPC. Il primo sarà una chiamata di procedura remota su un servizio web di un sito esterno di forex e il secondo sarà un client per il nostro server XML-RPC utilizzato per interpretare, analizzare e fornire risultati raccolti dall’ Automated Trading Championship 2011. Se sei interessato a come implementare e analizzare diverse statistiche da ATC 2011 in tempo reale, questo articolo fa per te.


Nozioni di base su XML-RPC

Cominciamo con le basi di XML-RPC. XML-RPC sta per XML Remote Procedure Call. Questo è un protocollo di rete che utilizza XML per codificare e decodificare i parametri passati per chiamare un metodo esterno. Utilizza il protocollo HTTP come meccanismo di trasporto per scambiare dati. Per metodo esterno intendo un altro programma per computer o un servizio web che espone procedure remote.

Il metodo esposto può essere chiamato da qualsiasi linguaggio di computer da qualsiasi macchina connessa alla rete a condizione che utilizzi anche lo stack di protocollo XML-RPC e abbia accesso di rete al server. Ciò significa anche che XML-RPC può essere utilizzato per chiamare un metodo sulla stessa macchina scritto in un altro linguaggio di programmazione. Questo verrà mostrato nella seconda parte dell'articolo.


Modello dati XML-RPC

La specifica XML-RPC utilizza sei tipi di dati di base: int, double, boolean, string, datetime, base64 e due tipi di dati composti: array e struct. L'array può essere costituito da qualsiasi elemento di base e la struttura fornisce coppie nome-valore come array associativi o proprietà dell'oggetto.


Tipi di dati di base in XML-RPC
 Tipo Valore Esempi
int o i4Interi a 32 bit compresi tra - 2.147.483.648 e 2.147.483.647.<int>11<int>
<i4>12345<i4>
doubleNumeri in virgola mobile a 64 bit <double>30.02354</double>
<double>-1.53525</double>
Booleantrue (1) o falsa (0)<boolean>1</boolean>
<boolean>0</boolean>
stringTesto ASCII, molte implementazioni supportano Unicode<string>Hello</string>
<string>MQL5</string>
dateTime.iso8601Date in formato ISO8601: CCYYMMDDTHH:MM:SS<dateTime.iso8601>
20111125T02:20:04
</dateTime.iso8601>
<dateTime.iso8601>
20101104T17:27:30
</dateTime.iso8601>
base64Informazioni binarie codificate come definito in RFC 2045<base64>
TDVsbG8sIFdvdwxkIE==
</base64>


Tabella 1. Tipi di dati di base in XML-RPC

L'array può contenere qualsiasi tipo di base, non necessariamente dello stesso tipo. L'elemento dell'array deve essere nidificato all'interno dell'elemento value. Contiene un elemento dati e uno o più elementi valore nell'elemento dati. L'esempio seguente mostra un array di quattro valori interi.

<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>

Il secondo esempio mostra un array di cinque valori stringa.

<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>

Sono convinto che sarai in grado di individuare somiglianze in questi due esempi per costruire un altro array XML-RPC.

Gli struct hanno un elemento struct all'interno dell'elemento value e sezioni membro all'interno dell'elemento struct. Ogni membro è costituito dal suo nome e dal valore che detiene. È quindi facile passare i valori di un array associativo o dei membri di un oggetto utilizzando le strutture.

Si prega di vedere l'esempio qui sotto.

<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>

Avendo familiarizzato con il modello dati XML-RPC, andiamo oltre alle strutture di richiesta e risposta. Ciò costituirà una base per l'implementazione del client XML-RPC in MQL5.


Strutture di richiesta XML-RPC

La richiesta XML-RPC è composta dall'intestazione del messaggio e dal payload del messaggio. L'intestazione del messaggio specifica HTTP il metodo di invio (POST), il percorso relativo al servizio XML-RPC, la versione del protocollo HTTP, il nome agente utente, l’indirizzo IP host, il tipo di contenuto (testo/xml) e la lunghezza del contenuto in byte. 

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

Il payload della richiesta XML-RPC è un documento XML. L'elemento radice dell'albero XML deve essere chiamato methodCall. Un methodCall contiene un singolo elemento methodName il cui contenuto è il nome del metodo eseguito. L'elemento MethodName contiene zero o un elemento params.

L'elemento params contiene uno o più elementi value, array o struct. Tutti i valori sono codificati di conseguenza con il tipo di dati (vedi tabella sopra). Osserva l’esempio di payload riportato di seguito e che mostra la richiesta di esecuzione del metodo "moltiplica" con due valori doppi da passare alla funzione.

<?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'intestazione e il payload vengono inviati tramite HTTP al server che accetta l'input. Se il server è disponibile, controlla il nome del metodo e l'elenco dei parametri ed esegue il metodo desiderato. Al termine dell'elaborazione, prepara la struttura di risposta XML-RPC che può essere letta dal client.

 

Strutture di risposta XML-RPC

Simile alla richiesta XML-RPC, la risposta XML-RPC consiste in un'intestazione e un payload. L'intestazione è testo e il payload è documento XML. Se la richiesta era corretta, la prima riga dell'intestazione informa che il server è stato trovato (codice 200) e specifica la versione del protocollo. L'intestazione deve contenere anche Content-Type text/xml e Content-Length, cioè la lunghezza del payload in byte.

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

Non sorprende che anche il carico utile della risposta sia un documento XML. L'elemento radice dell'albero XML deve essere chiamato methodResponse. L'elemento methodResponse contiene un parametro in caso di successo o un elemento di errore in caso di fallimento. L'elemento params contiene esattamente un elemento param. L'elemento param contiene esattamente un elemento value.

L'esempio di una risposta di successo è presentato di seguito: 

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

La risposta di errore viene preparata se si è verificato un problema nell'elaborazione della richiesta XML-RPC.

L'elemento fault, come l'elemento params, ha un solo valore di output.

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

Poiché XML-RPC non standardizza i codici di errore, i messaggi di errore dipendono quindi dall'implementazione.


Introduzione a MQL5-RPC

Mi sono imbattuto in due articoli di Alex Sergeev, "Using WinInet.dll for Data Exchange between Terminals via the Internet" e "Using WinInet in MQL5. Part 2: POST Requests and Files" e ho capito che potevo implementare un client XML-RPC per MetaTrader 5. Dopo aver esaminato le specifiche, ho implementato il mio da zero. Questo è un progetto in corso e non affronta ancora l'intera specifica (il supporto base64 verrà aggiunto nel prossimo futuro), ma puoi già usarlo per effettuare un ampio sottoinsieme di chiamate XML-RPC da MetaTrader 5.


Modello dati MQL5-RPC

La parte più difficile dell'implementazione per me è stata quella di trovare un modello di dati corretto per MQL5. Ho deciso che doveva essere il più semplice possibile per l'utente del framework, quindi ho creato diverse classi che ne incapsulano le funzionalità. La prima decisione è stata quella di effettuare una richiesta di dati param come un singolo puntatore CObject*. Questo puntatore mantiene una matrice di puntatori a matrici derivate dalla classe CObject.

Esistono classi standard che contengono array CObject; CArrayInt, CArrayDouble, CArrayString, quindi mi sono basato su questo e ho implementato CArrayBool, CArrayDatetime per completare i tipi di dati di base e CArrayMqlRates per aggiungere array di strutture. Manca solo il tipo base64 per ora, ma sarà supportato nel prossimo futuro. Se l'array contiene un solo elemento, viene incapsulato in XML come elemento a valore singolo.

Ho scritto un esempio su come aggiungere array diversi all'array CObject* e visualizzare l'intero array di array di tipi diversi. È disponibile di seguito. 

//+------------------------------------------------------------------+
//|                                                 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;
   
  }
//+------------------------------------------------------------------+

Il risultato dovrebbe essere chiaro: ci sono 6 sotto-array: array di valori interi, array di valori double, array di stringhe, array di datetime, array di valori booleani e array di 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

Potresti essere interessato a come ho implementato array di altri tipi di dati. In CArrayBool e CArrayDatetime mi sono basato semplicemente su CArrayInt, ma in CArrayMqlRates era leggermente diverso poiché la struttura deve essere passata come riferimento e non è stato definito TYPE_MQLRATES.

Di seguito puoi trovare un codice sorgente parziale della classe CArrayMqlRates. Altre classi sono disponibili come allegato all'articolo. 

//+------------------------------------------------------------------+
//|                                                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);
  }

...

Tutti i dati MQL5 devono essere convertiti in valori XML prima di inviarli come richiesta RPC, quindi ho progettato una classe helper CXMLRPCEncoder, la quale prende un valore e lo codifica come stringa 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);
  };

Ho incollato tre dei metodi implementati di seguito. Tutti accettano un parametro (bool, string, datetime) e restituiscono una stringa, che è un tipo di dati valido XML per il protocollo 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();
  }

Potresti notare che ci sono alcuni tag con il suffisso _B che significa "inizio tag" e il suffisso _E che significa "fine tag".

Ho deciso di utilizzare un file di intestazione che conserva i nomi dei tag e delle intestazioni XML poiché ha reso l'implementazione molto più trasparente

//+------------------------------------------------------------------+
//|                                                   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>"
//+------------------------------------------------------------------+

Dopo aver definito il modello dati di MQL5-RPC possiamo procedere alla costruzione di una richiesta XML-RPC completa.


Richiesta MQL5-RPC

Come accennato in precedenza, la richiesta XML-RPC consiste in un'intestazione della richiesta e un payload XML. Ho progettato la classe CXMLRPCQuery che costruisce automaticamente un oggetto query da array di dati MQL5. La classe utilizza CXMLRPCEncoder per incapsulare i dati in XML e aggiunge il nome del metodo all'interno del tag methodName.

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

Il costruttore della classe ha due parametri: nome del metodo e puntatore a CArrayObj, che contiene i parametri per chiamare il metodo. Tutti i parametri sono racchiusi in XML come descritto nella sezione precedente e viene aggiunta un'intestazione di query. L'intera query XML può essere visualizzata utilizzando il metodo 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);
  }

Osserva l’esempio di test di query di seguito. Questo non è il più semplice, poiché voglio mostrare che è possibile chiamare metodi abbastanza complessi.

I parametri di input sono: array di valori double, array di valori interi, array di valori string, array di valori bool, un singolo valore datetime e array di strutture 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 query risultante è la seguente. Normalmente questo è un valore di stringa di una riga, ma lo presento come un testo a schede per distinguere facilmente le sezioni XML-RPC e i singoli valori.

<?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>

Questo è un albero XML abbastanza sviluppato e il protocollo è flessibile per chiamare metodi ancora più complessi.


Risposta MQL5-RPC

Come con la richiesta XML-RPC, la risposta XML-RPC è costituita da intestazioni e payload XML, ma questa volta l'ordine di elaborazione deve essere invertito, ovvero la risposta XML deve essere convertita in dati MQL5. La classe CXMLRPCResult è stata progettata per far fronte a questa attività.

Il risultato può essere ricevuto come una stringa passata al costruttore della classe o può essere recuperato automaticamente dalla classe CXMLServerProxy dopo l'esecuzione del metodo desiderato. In situazioni in cui il risultato contiene strutture che potrebbero non essere note in anticipo, il metodo parseXMLResponseRAW() può cercare tutti i tag <value> e restituisce il puntatore CArrayObj, il quale contiene l'array di tutti gli elementi di valore trovati.

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();
  };

Il costruttore della classe esegue la scansione di ogni intestazione nell'XML di risposta e chiama il metodo privato parseXMLValuesToMQLArray(), il quale esegue il duro lavoro di conversione dei dati XML in MQL5 dietro le quinte.

Riconosce se param è un array o un singolo elemento e riempie gli array appropriati aggiunti al risultato dell'array 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 conversione avviene all'interno di metodi parseXMLValuesToMQLArray sovraccaricati. Il valore della stringa viene estratto da XML e convertito in variabili MQL5 di base.

Tre dei metodi utilizzati per la conversione sono incollati di seguito per riferimento.

//+------------------------------------------------------------------+
//| 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;
  }

Come puoi vedere, i valori stringa vengono estratti dai tag XML-RPC utilizzando i metodi disponibili nella classe CString, Assign(), Mid(), Find() e vengono ulteriormente convertiti in MQL5 utilizzando StringToTime(), StringToInteger(), StringToDouble() o un metodo personalizzato come nel caso delle variabili bool.

Dopo che tutti i valori sono stati analizzati, tutti i dati disponibili possono essere visualizzati utilizzando il metodo toString(). Uso semplicemente i due punti per separare i valori.

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();
  }

Ho implementato un test dei risultati per farti vedere esplicitamente come i parametri dei risultati XML-RPC vengono convertiti in 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;
  }
//+------------------------------------------------------------------+

Puoi trovare il risultato qui sotto:

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:

Questo è un array visualizzato di array di diversi valori MQL5 convertiti da XML. Come puoi vedere ci sono stringhe, valori doppi, valori booleani, valori interi, valori datetime a cui è possibile accedere dal singolo puntatore CArrayObj.


Classe proxy MQL5-RPC

Il proxy CXMLRPCServer è una classe principale per la comunicazione HTTP. Ho usato "Using WinInet in MQL5. Part 2: POST Requests and Files" per implementare la funzionalità HTTP e l’aggiunta di un'intestazione personalizzata coerente con la specifica 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'oggetto CXMLRPCQuery deve essere passato al metodo CXMLRPCServerProxy execute() per attivare la chiamata XML-RPC.

I metodi restituiscono il puntatore all'oggetto CXMLRPCResult che può essere ulteriormente utilizzato nello script che ha chiamato la chiamata 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;
  }


Esempio 1 - Accesso al servizio web

Il primo esempio funzionante di MQL5-RPC è una chiamata a un servizio web esterno. L'esempio che ho trovato utilizza il tasso di cambio corrente per convertire un importo specifico di una valuta in un'altra. La specifica exact dei parametri del metodo è disponibile online.

Il servizio web espone il metodo foxrate.currencyConvert, il quale accetta tre parametri, due stringhe e un valore float:

  • dalla valuta (es: USD) = string;
  • alla valuta (es: GBP) = string;
  • importo da convertire (es:100.0) = float.

L'implementazione è piuttosto breve e richiede solo un paio di righe.

//+------------------------------------------------------------------+
//|                              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;
  }
//+------------------------------------------------------------------+

Poiché il metodo restituisce una struttura complessa, ho analizzato il risultato utilizzando il metodo parseXMLResponseRAW().

La query XML-RPC in questo esempio ha il seguente aspetto:

<?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>

E la risposta viene restituita come una struttura racchiusa in 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>

L'output di toString() rivela che nel risultato sono disponibili tre valori: 0 - nessun errore, il secondo valore è la quantità di valuta di base necessaria per lo scambio e il terzo parametro è l'ora dell'ultimo tasso di cambio (è stato memorizzato nella cache).

0:15773.00000000:cached:

Continuiamo con casi d'uso più interessanti.


Esempio 2 - Analizzatore XML-RPC ATC 2011

Immagina di voler ottenere statistiche dall’ Automated Trading Championship 2011 (io l'ho fatto) e di utilizzare queste informazioni nel terminale MetaTrader 5. Se vuoi sapere come ottenerlo, continua a leggere.

Poiché MetaQuotes Software Corp. sta preparando un Servizio che segue il segnale, il quale consentirà di sottoscrivere segnali da particolari Expert Advisor, ritengo che la combinazione di quel servizio con l'analizzatore sarà un modo molto potente per eseguire analisi sofisticate e fornire nuovi modi per beneficiare dell’ATC. In effetti, potresti prendere dati da alcune fonti diverse usando questo metodo e fare un Expert Advisor complesso basato su di esso.


Server analizzatore XML-RPC ATC 2011

Il primo passo per creare un server ATC Analyzer è preparare l'analisi dell'output. Diciamo che vogliamo catturare tutti i partecipanti i cui conti delle equità sono al di sopra di una certa soglia e sono interessati alle posizioni che tutti detengono attualmente e visualizzare la quantità di statistiche sulle posizioni di acquisto e vendita della coppia di valute a cui siamo interessati.

Ho usato il linguaggio Python e la libreria BeautifulSoup per recuperare e analizzare i dati. Se non hai mai usato Python prima, ti consiglio vivamente di puntare il tuo browser su http:/Python.org e leggere cosa ha da offrire questo linguaggio: non te ne pentirai.

Se invece desideri mettere rapidamente le mani sull'analizzatore, scarica Python 2.7.1 installer e setup_tools e installali entrambi. Dopodiché esegui la console di Windows e cambia la directory in C:\Python27\Scripts o qualunque sia la cartella in cui hai installato Python. Quindi emetti il comando 'easy_install BeautifulSoup'. Questo installerà il pacchetto 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>

Dopodiché dovresti essere in grado di eseguire la console Python ed emettere il comando "import BeautifulSoup" senza errori.

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
>>> 

Ed esegui l'analizzatore allegato con il comando 'python ContestantParser.py'. L'analizzatore recupera il sito web, esegue alcune ricerche di parole chiave e inserisce l'output sulla 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
    

Ciò è stato ottenuto semplicemente guardando all'interno del codice sorgente HTML e trovando le relazioni tra i tag. Se questo codice viene eseguito direttamente dalla console Python, restituisce i nomi dei concorrenti, il loro saldo e patrimonio e tutte le posizioni aperte correnti.

Osserva l'output di una query di esempio:

* 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
------------------------------------------------------------
...

Sembra buono, non è vero? Con questi dati possiamo raccogliere statistiche interessanti e implementare un servizio XML-RPC che le fornirà su richiesta. Il client XMLRPC da MetaTrader 5 potrebbe recuperare quelle statistiche quando necessario.

Per creare un server XMLRPC ho usato la libreria server SimpleXMLRPC di Python. Il server espone due metodi al mondo esterno: listContestants e getStats. Mentre il primo mostra solo i nomi dei concorrenti che hanno un'equità al di sopra di una certa soglia, il secondo mostra quanti di loro hanno una posizione aperta su una determinata coppia di valute e qual è il rapporto tra acquisto e vendita su quelle posizioni.

Insieme al servizio di segnali e/o al copiatore di trade dall'articolo, potresti voler confermare che la configurazione su cui stai effettivamente facendo trading ha più possibilità di essere redditizia.

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()        

È possibile accedere a questo server direttamente dalla console Python. Quando esegui questo script, ricorda di cambiare l'indirizzo IP con quello utilizzato dal tuo sistema.

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']
>>>

Probabilmente, mentre stai leggendo, i risultati dei concorrenti sono totalmente cambiati, ma dovresti avere un'idea di ciò che stavo cercando di ottenere.

La chiamata al metodo getStats() con il parametro "eurusd" restituisce tre numeri:

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

Il primo è un numero di concorrenti che hanno un'equità al di sopra della soglia, il secondo è un numero di concorrenti che detengono una posizione EURUSD aperta e il terzo numero è un numero di concorrenti che hanno una posizione long su EURUSD. In questo caso, due terzi dei vincitori hanno aperto long EURUSD, quindi questo può servire come segnale di conferma quando il tuo robot oi tuoi indicatori hanno generato un segnale 'open eurusd long'.


Analizzatore client MQL5-RPC ATC 2011

È tempo di utilizzare il framework MQL5-RPC per recuperare questi dati su richiesta su MetaTrader 5. In effetti il suo utilizzo è autoesplicativo se segui il codice sorgente.

//+------------------------------------------------------------------+
//|                           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;
  }
//+------------------------------------------------------------------+

Questo è ciò che accade quando viene chiamato lo script. La query è racchiusa in XML:

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

e dopo un po' si riceve una risposta dal server XML-RPC. Nel mio caso ho usato un host Linux che esegue il servizio Python XML-RPC e l'ho chiamato dall'installazione guest di Windows VirtualBox che esegue 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>

Il risultato della prima query visualizzata utilizzando il metodo toString() è il seguente:

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 seconda query viene utilizzata per chiamare il metodo getStats().

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

La risposta XML-RPC è semplice e contiene solo tre valori interi.

<?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>

Questa volta ho adottato un altro approccio e ho avuto accesso ai valori restituiti come variabili 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

Come puoi vedere, l'output presenta una forma facilmente comprensibile.


Conclusione

Ho presentato un nuovo framework MQL5-RPC che consente a MetaTrader 5 di eseguire chiamate di procedura remota utilizzando il protocollo XML-RPC. Sono stati presentati due casi d'uso, il primo relativo all'accesso a un servizio web e il secondo a un analizzatore personalizzato per l’ Automated Trading Championship 2011. Questi due esempi dovrebbero servire come base per ulteriori esperimenti.

Credo che MQL5-RPC sia una funzionalità molto potente che può essere utilizzata in molti modi diversi. Ho deciso di rendere il framework Open Source e di pubblicarlo sotto licenza GPL su http://code .google.com/p/mql5-rpc/. Se qualcuno volesse aiutare a rendere il codice a prova di proiettile, rifattorizzate o correggete i bug: le porte sono aperte per unirsi al progetto. Il codice sorgente con tutti gli esempi è disponibile anche come allegato all'articolo.


Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/342

File allegati |
mql5-rpc.zip (22.18 KB)
Utilizzo di MetaTrader 5 come Fornitore di segnali per MetaTrader 4 Utilizzo di MetaTrader 5 come Fornitore di segnali per MetaTrader 4
Analizza gli esempi di tecniche su come l'analisi del trading può essere eseguita sulla piattaforma MetaTrader 5 e MetaTrader 4. L'articolo ti mostrerà come creare un semplice fornitore di segnali nel tuo MetaTrader 5 e connetterti ad esso con più client, anche eseguendo MetaTrader 4. Inoltre scoprirai come puoi seguire i partecipanti all’Automated Trading Championship nel tuo vero conto MetaTrader 4.
Creazione di Expert Advisor MQL5 in pochi minuti utilizzando EA Tree: Prima parte Creazione di Expert Advisor MQL5 in pochi minuti utilizzando EA Tree: Prima parte
EA Tree è il primo generatore di Expert Advisor MetaTrader MQL5 drag and drop. È possibile creare MQL5 complessi utilizzando un'interfaccia utente grafica molto facile da usare. In EA Tree, gli Expert Advisor vengono creati collegando i riquadri insieme. Le caselle possono contenere funzioni MQL5, indicatori tecnici, indicatori personalizzati o valori. Utilizzando l'"albero dei riquadri", EA Tree genera il codice MQL5 dell'Expert Advisor.
Analisi di regressione multipla. Generatore di strategie e tester tutto in uno Analisi di regressione multipla. Generatore di strategie e tester tutto in uno
L'articolo fornisce una descrizione delle modalità di utilizzo dell'analisi di regressione multipla per lo sviluppo di sistemi di trading. Dimostra l'uso dell'analisi di regressione per l'automazione della ricerca strategica. Come esempio viene fornita un'equazione di regressione generata e integrata in un EA senza richiedere un'elevata competenza nella programmazione.
La strategia Forex Tutto o niente La strategia Forex Tutto o niente
Lo scopo di questo articolo è quello di creare la strategia di trading più semplice che implementa il principio di gioco "Tutto o niente". Non vogliamo creare un Expert Advisor redditizio: l'obiettivo è aumentare il deposito iniziale più volte con la massima probabilità possibile. È possibile vincere il jackpot su ForEx o perdere tutto senza sapere nulla di analisi tecnica e senza utilizzare alcun indicatore?