MQL5-RPC. Chamadas de procedimento remoto de MQL5: Acesso de serviço da Web e analisador XML-RPC ATC para diversão e lucro
Introdução
Este artigo irá descrever o quadro MQL5-RPC o qual construí durante as últimas semanas. Ele abrange conceitos básicos de acesso XML-RPC, de descrição da implementação da MQL5 e dois exemplos reais de uso da MQL5-RPC. O primeiro será uma chamada de procedimento remoto em um webservice do website de forex externo e o segundo será um cliente do nosso próprio servidor XML-RPC o qual é usado para analise sintática, analisar e fornecer resultados reunidos do Automated Trading Championship 2011. Se você está interessado em como implementar e analisar estatísticas diferentes do ATC 2011 em tempo real, este artigo é para você.
XML-RPC básicos
Vamos começar com os XML-RPC básicos O XML-RPC representa a chamada de procedimento remoto XML. Este é um protocolo de rede que utiliza XML para codificar e decodificar os parâmetros passados para chamar um método externo. Ele utiliza o protocolo HTTP como o mecanismo de transporte para a troca de dados. Pelo método externo, quero dizer, outro programa de computador ou um webservice que expõe procedimentos remotos.
O método exposto pode ser chamado por qualquer linguagem de computador a partir de qualquer máquina conectada à rede, desde que ela também use pilhas do protocolo XML-RPC e tenha acesso à rede para o servidor. Isto também significa que que o XML-RPC pode ser usado para chamar um método na mesma máquina escrito em outra linguagem de programação. Isso será mostrado na segunda parte do artigo.
Modelo de dados XML-RPC
A especificação XML-RPC usa seis tipos básicos de dados: inteiros, duplos, booleanos, de sequência, datetime, base64 e dois tipos de dados compostos: matriz e estrutura. A matriz pode consistir de quaisquer elementos básicos e a estrutura fornece nomes de pares de valor como matrizes associativas ou propriedades de objetos.
Tipos básicos de dados em XML-RPC | ||
---|---|---|
Tipo | Valor | Exemplos |
int ou i4 | 32-bit números inteiros entre - 2,147,483,648 e 2,147,483,647. | 11 12345 |
duplo | 64-bit de pontos de números flutuantes | 30.02354 -1.53525 |
Booleano | verdadeiro (1) ou falso (0) | 1 0 |
sequência | Texto ASCII, muitas implementações suportam o Unicode | Hello MQL5 |
dateTime.iso8601 | Datas no formato ISO8601: CCYYMMDDTHH:MM:SS | 20111125T02:20:04 20101104T17:27:30 |
base64 | Informação binária codificada como definido no RFC 2045 | TDVsbG8sIFdvdwxkIE== |
Tabela 1. Tipos de dados básicos em XML-RPC
A matriz pode conter qualquer um dos tipos básicos, não necessariamente do mesmo tipo. O elemento da matriz deve ser aninhado dentro do elemento de valor. Ele contém um elemento de dados e um ou mais elementos de valor no elemento de dados. O exemplo abaixo mostra uma matriz de quatro valores inteiros.
<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>
O segundo exemplo mostra uma matriz de cinco valores da sequência.
<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>
Estou convencido de que você será capaz de detectar semelhanças entre esses dois exemplos para construir outra matriz XML-RPC.
As estruturas têm um elemento estrutural dentro do elemento de valor e as seções de membros dentro do elemento estrutural. Cada membro consiste de seu nome e o valor que ele possui. Por isso, é fácil de passar valores de uma matriz associativa ou membros de um objeto usando as estruturas.
Por favor, veja o exemplo abaixo.
<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>
Tomando conhecimento do modelo de dados de XML-RPC vamos mais longe com as estruturas de solicitações e respostas. Isto formará uma base para a implementação de XML-RPC do cliente em MQL5.
Estruturas de solicitação XML-RPC
A solicitação XML-RPC é composta de cabeçalho e carga útil da mensagem. Cabeçalho da mensagem especifica o método de envio HTTP (POST), o caminho relativo para o serviço XML-RPC, versão do protocolo HTTP, o nome do usuário agente, o endereço IP da hospedagem, o tipo de conteúdo (text/xml) e comprimento do conteúdo em bytes.
POST /xmlrpc HTTP 1.1 User-Agent: mql5-rpc/1.0 Host: 10.12.10.10 Content-Type: text/xml Content-Length: 188
A carga útil do pedido XML-RPC é um documento XML. O elemento da raiz da árvore XML deve ser chamado MethodCall. Um MethodCall contém um elemento methodName único cujo conteúdo é o nome do método executado. O elemento MethodName contém zero ou um elemento de parâmetro.
O elemento de parâmetro contém um ou mais valores, matriz ou elementos estruturais. Todos os valores são codificados de acordo com o tipo de dados (ver tabela acima). Por favor, veja o exemplo de carga útil abaixo mostrando 'multiplicar' a solicitação de execução do método com dois valores duplos para passar à função.
<?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>
O cabeçalho e a carga útil são enviados através de HTTP para o servidor que aceita a entrada. Se o servidor estiver disponível, ele verifica o nome do método, a lista de parâmetros, e executa o método desejado. Depois de terminado o processamento ele prepara a estrutura de resposta XML-RPC que pode ser lida pelo cliente.
Estruturas de respostas XML-RPC
Semelhante ao XML-RPC Request, o XML-RPC Response consiste de um cabeçalho e uma carga útil. O cabeçalho é um texto e a carga útil é o documento XML. Se o pedido estava correto, a primeira linha do cabeçalho informa que o servidor foi encontrado (código 200) e especifica a versão do protocolo. O cabeçalho deve conter também Content-Type text/xml e Content-Length, os quais são o comprimento da carga útil em bytes.
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
De maneira não surpreendente, a carga da resposta também é o documento XML. O elemento raiz da árvore XML deve ser chamado de methodResponse. O elemento methodResponse contém um parâmetro no elemento sucesso ou um elemento com defeito na falha. O elemento parâmetro contém exatamente um elemento de parâmetro. O elemento parâmetro contém exatamente um elemento de valor.
O exemplo de uma resposta de sucesso é apresentada a seguir:
<?xml version="1.0"?> <methodResponse> <params> <param> <value><double>62606001.94</double></value> </param> </params> </methodResponse>
A resposta de falha está preparada caso houvesse um problema no processamento do pedido XML-RPC.
O elemento de defeito, como o elemento de parâmetros, tem apenas um único valor de saída.
<?xml version="1.0"?> <methodResponse> <fault> <value><string>No such method!</string></value> </fault> </methodResponse>
Visto que o XML-RPC não padroniza os códigos de erro, as mensagens de erro, portanto, são dependentes de implementação.
Introduzindo MQL5-RPC
Me deparei com dois artigos desenvolvidos por Alex Sergeev "Using WinInet.dll for Data Exchange between Terminals via the Internet" e "Using WinInet in MQL5. Parte 2: ENVIE pedidos e arquivos" e perceba que eu poderia implementar um cliente XML-RPC para o MetaTrader 5. Depois de passar por especificações eu implementei a minha do zero. Este é um projeto em andamento e não cobre toda a especificação ainda (o suporte de base64 será adicionado mais para frente), mas você já pode usá-lo para fazer um grande subconjunto de chamadas XML-RPC a partir do MetaTrader 5.
Modelo de dados MQL5-RPC
A parte mais difícil na implementação para mim foi descobrir um modelo de dados correto para MQL5. Eu decidi que eles devem ser os mais simples possível para o quadro do usuário, portanto eu construí várias classes que encapsulam a sua funcionalidade. A primeira decisão foi fazer uma base de dados de parâmetros de solicitação como um único ponteiro CObject*. Este ponteiro mantém uma matriz de ponteiros para matrizes derivadas da classe CObject.
Há aulas padrões que possuem matrizes CObject; CArrayInt, CArrayDouble, CArrayString, portanto eu me baseei nisto e implementei CArrayBool, CArrayDatetime para completar os tipos de dados básicos e CArrayMqlRates para adicionar matrizes de estruturas. Apenas o tipo de base64 está faltando agora, mas ele vai ser sustentado num futuro próximo. Se a matriz contém apenas um elemento, ele é encapsulado em XML como um único elemento de valor.
Eu escrevi um exemplo de como adicionar diferentes matrizes na matriz de CObject e exibir toda a matriz das matrizes de tipos diferentes. Está disponível abaixo.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
O resultado deve ser claro: há 6 sub-matrizes: matriz de valores inteiros, de valores duplos, de sequências, de datetimes, de valores booleanos e de MqlRates.
ArrayObjTest (EURUSD,H1) 23:01:54 params has 6 arrays. ArrayObjTest (EURUSD,H1) 23:01:54 0 0 1001 ArrayObjTest (EURUSD,H1) 23:01:54 0 1 1002 ArrayObjTest (EURUSD,H1) 23:01:54 0 2 1003 ArrayObjTest (EURUSD,H1) 23:01:54 0 3 1004 ArrayObjTest (EURUSD,H1) 23:01:54 1 0 1001.000000 ArrayObjTest (EURUSD,H1) 23:01:54 1 1 1002.000000 ArrayObjTest (EURUSD,H1) 23:01:54 1 2 1003.000000 ArrayObjTest (EURUSD,H1) 23:01:54 1 3 1004.000000 ArrayObjTest (EURUSD,H1) 23:01:54 2 0 s1001.0 ArrayObjTest (EURUSD,H1) 23:01:54 2 1 s1002.0 ArrayObjTest (EURUSD,H1) 23:01:54 2 2 s1003.0 ArrayObjTest (EURUSD,H1) 23:01:54 2 3 s1004.0 ArrayObjTest (EURUSD,H1) 23:01:54 3 0 2011.11.11 23:00 ArrayObjTest (EURUSD,H1) 23:01:54 3 1 2011.11.12 00:01 ArrayObjTest (EURUSD,H1) 23:01:54 3 2 2011.11.12 23:00 ArrayObjTest (EURUSD,H1) 23:01:54 3 3 2011.11.18 23:01 ArrayObjTest (EURUSD,H1) 23:01:54 4 0 false ArrayObjTest (EURUSD,H1) 23:01:54 4 1 true ArrayObjTest (EURUSD,H1) 23:01:54 4 2 true ArrayObjTest (EURUSD,H1) 23:01:54 4 3 false ArrayObjTest (EURUSD,H1) 23:01:54 5 0 1.374980 1.374980 1.374730 1.374730 ArrayObjTest (EURUSD,H1) 23:01:54 5 1 1.375350 1.375580 1.373710 1.375030 ArrayObjTest (EURUSD,H1) 23:01:54 5 2 1.374680 1.375380 1.373660 1.375370 ArrayObjTest (EURUSD,H1) 23:01:54 5 3 1.375270 1.377530 1.374360 1.374690
Você pode estar interessado na maneira em como eu implementei matrizes de outros tipos de dados. Em CArrayBool e CArrayDatetime eu simplesmente me baseei em CArrayInt, mas em CArrayMqlRates foi um pouco diferente, uma vez que a estrutura deve ser passada como referência e não houve TYPE_MQLRATES definidos.
Abaixo, você pode encontrar um código fonte parcial da classe CArrayMqlRates. Outras classes estão disponíveis como o anexo ao artigo.
//+------------------------------------------------------------------+ //| 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); } ...
Todos os dados MQL5 devem ser convertidos em valores XML antes de enviá-los como pedido RPC, portanto, eu projetei uma classe auxiliar CXMLRPCEncoder, que assume um valor, e codifica-o como sequência de 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 ¶m); };
Eu colei três dos métodos implementados abaixo. Todos levam um parâmetro (booleanos, de sequência, datetime) e a sequência de retorno é o tipo de dados válidos de XML para o protocolo 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(); }
Você pode notar que há certas marcas com sufixo _B significando 'tag begin' e o sufixo _E significando 'tag end'.
Eu decidi usar um arquivo de cabeçalho que mantém nomes de tags e cabeçalhos de XML, uma vez que ele fez uma implementação muito mais transparente
//+------------------------------------------------------------------+ //| xmlrpctags.mqh | //| Copyright 2011, Investeo.pl | //| http://www.investeo.pl | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, Investeo.pl" #property link "http://www.investeo.pl" #define HEADER_1a "POST" #define HEADER_1b "HTTP/1.1" #define HEADER_2 "User-Agent: MQL5RPC/1.1" #define HEADER_3 "Host: host.com" #define HEADER_4 "Content-Type: text/xml" #define HEADER_5 "Content-Length: " #define HEADER_6 "<?xml version='1.0'?>" #define METHOD_B "<methodCall>" #define METHOD_E "</methodCall>" #define METHOD_NAME_B "<methodName>" #define METHOD_NAME_E "</methodName>" #define RESPONSE_B "<methodResponse>" #define RESPONSE_E "</methodResponse>" #define PARAMS_B "<params>" #define PARAMS_E "</params>" #define PARAM_B "<param>" #define PARAM_E "</param>" #define VALUE_B "<value>" #define VALUE_E "</value>" #define INT_B "<int>" #define INT_E "</int>" #define I4_B "<i4>" #define I4_E "</i4>" #define BOOL_B "<boolean>" #define BOOL_E "</boolean>" #define DOUBLE_B "<double>" #define DOUBLE_E "</double>" #define STRING_B "<string>" #define STRING_E "</string>" #define DATETIME_B "<dateTime.iso8601>" #define DATETIME_E "</dateTime.iso8601>" #define BASE64_B "<base64>" #define BASE64_E "</base64>" #define ARRAY_B "<array>" #define ARRAY_E "</array>" #define DATA_B "<data>" #define DATA_E "</data>" #define STRUCT_B "<struct>" #define STRUCT_E "</struct>" #define MEMBER_B "<member>" #define MEMBER_E "</member>" #define NAME_B "<name>" #define NAME_E "</name>" //+------------------------------------------------------------------+
Uma vez definido o modelo de dados de MQL5-RPC podemos prosseguir para a construção de um pedido completo de XML-RPC.
Pedido MQL5-RPC
Como mencionado anteriormente, o pedido XML-RPC é composto por um cabeçalho de solicitação e carga útil XML. Eu projetei a classe CXMLRPCQuery que cria automaticamente um objeto de consulta a partir das matrizes de dados de MQL5. A classe usa CXMLRPCEncoder para encapsular os dados em XML e adiciona o nome do método dentro da 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(); };
O construtor da classe tem dois parâmetros: o nome do método e ponteiro para CArrayObj segurando os parâmetros para chamar o método. Todos os parâmetros são envoltos em XML como descrito na seção anterior, e um cabeçalho de consulta é adicionado. A consulta XML inteira pode ser exibida usando o método 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); }
Por favor, veja um exemplo de teste de consulta abaixo. Este não é o mais simples, uma vez que quero mostrar que é possível chamar métodos bastante complexos.
Os parâmetros de entrada são: matriz de valores duplos, inteiros,de sequência, booleanos, de um único valor datetime e matriz de estruturas 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; } //+------------------------------------------------------------------+
A consulta resultante é como se segue. Normalmente este é um valor de sequência de uma linha, mas eu o apresento como um texto tabulado para fazer com que seja fácil distinguir seções XML-RPC e valores individuais.
<?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>
Isto é uma árvore XML bem desenvolvida e o protocolo é flexível para chamar mesmo os métodos mais complexos.
Resposta MQL5-RPC
Como no pedido XML-RPC, a resposta XML-RPC é construída a partir de cabeçalhos e da carga útil XML, mas desta vez a ordem de processamento deve ser invertida, ou seja, a resposta XML deve ser convertida em dados MQL5. A classe CXMLRPCResult foi projetada para lidar com essa tarefa.
O resultado pode ser recebido como uma sequência que é passada ao construtor da classe ou pode ser obtido automaticamente a partir de classe CXMLServerProxy depois que o método desejado for executado. Nas situações em que o resultado contém estruturas que não podem ser conhecidas de antemão, o método parseXMLResponseRAW() pode procurar por todas as marcas e retornar o ponteiro CArrayObj que contém a matriz de todos os elementos de valor encontrados.
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(); };
O construtor da classe escaneia cada cabeçalho na resposta XML e chama o método privado parseXMLValuesToMQLArray() que faz o trabalho duro de conversão de XML para dados MQL5 por trás das cenas.
Ele reconhece se o parâmetro é uma matriz ou um elemento único e preenche as matrizes apropriadas adicionadas ao resultado da matriz 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; }
A conversão ocorre dentro dos métodos parseXMLValuesToMQLArray sobrecarregados. O valor da sequência é extraído a partir de XML e convertido para variáveis MQL5 básicas.
Três dos métodos utilizados para a conversão são colados abaixo para referência.
//+------------------------------------------------------------------+ //| 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; }
Como você pode ver, os valores de sequência são extraídos de tags XML-RPC usando métodos disponíveis na classe CString; Assign(), Mid(), Find() e são, posteriormente, convertidos em MQL5 usando StringToTime(), StringToInteger(), StringToDouble() ou um método personalizado, como no caso de variáveis booleanas.
Depois que todos os valores são analisados,todos os dados disponíveis podem ser exibidos usando o método toString(). Eu simplesmente uso dois pontos para separar valores.
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(); }
Eu implementei um resultado de teste para você ver explicitamente como os parâmetros de resultado XML-RPC são convertidos em 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; } //+------------------------------------------------------------------+
Você pode encontrar o resultado abaixo:
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:
Esta é uma matriz apresentada de matrizes de diferentes valores MQL5 convertidos de XML. Como você pode ver, existem sequências, valores duplos, booleanos, inteiros, datetime, que podem ser acessados a partir do ponteiro CArrayObj único.
Classe de proxy MQL5-RPC
O proxy CXMLRPCServer é uma classe central para comunicação HTTP. Eu usei "Usando o WinInet em MQL5. Parte 2: O artigo POST Requests and Files" implementou a funcionalidade HTTP e acrescentou um cabeçalho personalizado consistente com a especificação 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."; }
O objeto CXMLRPCQuery deve ser passado ao método CXMLRPCServerProxy execute() para acionar a chamada XML-RPC.
Os métodos retornam o ponteiro para o objeto CXMLRPCResult que pode ser ainda utilizado no script que acionou a chamada 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; }
Exemplo 1 - Acesso do serviço da Web
O primeiro exemplo de trabalho de MQL5-RPC é uma chamada para o webservice externo. O exemplo que eu encontrei usa a taxa de câmbio atual para converter uma determinada quantidade de uma moeda em outra. A especificação exata dos parâmetros do método está disponível online.
O serviço da web expõem o método foxrate.currencyConvert que aceita três parâmetros, duas sequências e um valor flutuante:
- de moeda (por exemplo: USD) = sequência;
- à moeda (por exemplo: GBP) = sequência;
- quantidade para converter (por exemplo: 100,0) = flutuador.
A implementação é bastante curta e leva apenas um par de linhas.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Uma vez que o método retorna a estrutura complexa eu analiso o resultado utilizando o método parseXMLResponseRAW().
A consulta XML-RPC, neste exemplo tem o seguinte aspecto:
<?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 a resposta é retornada como um estrutura envolta em 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>
A saída do toString() revela que três valores estão disponíveis no resultado: 0 - nenhum erro, o segundo valor é a quantidade de moeda base necessária para troca e o terceiro é o tempo da última taxa de troca (que foi armazenada em cache).
0:15773.00000000:cached:
Vamos continuar com o caso de uso mais interessante.
Exemplo 2 - Analyzer XML-RPC ATC 2011
Imagine que você queria pegar as estatísticas de Automated Trading Championship 2011 (que eu fiz) e usar essas informações no terminal MetaTrader 5. Se você gostaria de saber como conseguir isto, continue lendo.
Desde que a corporação de software da MetaQuotes está preparando um sinal decorrente do serviço que irá permitir sinais de assinantes de determinados Expert Advisors, eu sinto que a combinação desse serviço com o analisador será uma forma muito poderosa para realizar uma análise sofisticada e proporcionar novas maneiras de se beneficiar a partir do ATC. Na verdade, você poderia pegar os dados de algumas fontes diferentes, utilizando este método e fazer um Expert Advisor complexo com base nele.
Servidor Analyzer XML-RPC ATC 2011
O primeiro passo em fazer um servidor Analyser ATC é preparar a análise de saída. Vamos dizer que queremos capturar todos os participantes em que os investimentos da conta estão acima de certo limite e estão interessados nas posições que todos eles atualmente possuem, e exibir a quantidade das estatísticas das posições de compra e venda do par de moedas em que estamos interessados.
Eu usei linguagem Python e a biblioteca BeautifulSoup para recuperar e analisar dados. Se você nunca usou Python antes, eu recomendo com veemência que você aponte seu navegador para http:/Python.org e leia o que esta linguagem tem a oferecer, você não vai se arrepender.
Se você gostaria de colocar suas mãos rapidamente no analisador, baixe o instalador Python 2.7.1 e os pacotes setup_tools e instale os dois. Após isso, execute o terminal do Windows e altere o diretório para C:\Python27\Scripts ou para qualquer pasta que você tenha instalado o Python. Então acione o comando 'easy_install BeautifulSoup'. Isto irá instalar o pacote 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>
Depois disso, você deve ser capaz de executar o terminal do Python e acionar o comando 'import BeautifulSoup' sem erro.
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 >>>
E executar o analisador anexado com o comando 'python ContestantParser.py'. O analisador busca o site, faz algumas pesquisas de palavras-chave e coloca o resultado no terminal.
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
Isto foi conseguido por simplesmente olhar dentro do código da fonte HTML e encontrar relações entre tags. Se esse código é executado diretamente a partir do terminal Python ele gera nomes de concorrentes, seu equilíbrio e equidade, e todas as posições abertas atuais.
Por favor, observe a saída de uma amostra de consulta:
* 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 ------------------------------------------------------------ ...
Parece bom, não é? Com esses dados podemos reunir estatísticas interessantes e implementar um serviço XML-RPC que irá proporciona-las em demanda. O cliente XMLRPC do MetaTrader 5 poderia buscar essas estatísticas quando necessário.
A fim de fazer um servidor XMLRPC eu usei a biblioteca do servidor SimpleXMLRPC do python. O servidor apresenta dois métodos para o mundo exterior: listContestants e getstats. Enquanto o primeiro apenas apresenta os nomes do concorrente que têm patrimônio acima de certo limite, o último mostra quantos deles têm a posição aberta em um determinado par de moedas, e qual é a razão de comprar para vender nessas posições.
Juntamente com o serviço de sinais e/ou copiador de negociações do artigo, você pode querer confirmar que a configuração que você está negociando de fato tem mais chance de ser rentável.
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()
Este servidor pode ser acessado diretamente a partir do terminal Python. Ao executar este script lembre-se de alterar o endereço de IP para um que o seu sistema está usando.
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'] >>>
Provavelmente quando você estiver lendo isso os resultados concorrentes mudaram totalmente, mas você deve ter pego uma idéia sobre o que eu estava tentando alcançar.
A chamada do método getStats(), com parâmetro "eurusd", retorna três números:
In : u.getStats("eurusd") Out: [23, 12, 9]
O primeiro sendo um número de concorrentes que têm patrimônio acima do limiar, o segundo uma série de concorrentes que possuem posição EURUSD aberta e o terceiro é um número de concorrentes que têm posição eurusd há muito tempo. Neste caso, dois terços dos concorrentes vencedores abriram EURUSD há muito tempo e, consequentemente, isso pode servir como um sinal de confirmação quando o robô ou seus indicadores gerados 'abrem um sinal longo de eurusd'.
Cliente do Analyzer MQL5-RPC ATC 2011
é hora de usar a estrutura MQL5-RPC para buscar esses dados sob demanda em MetaTrader 5. Na verdade, seu uso é auto explicativo, se você seguir o código fonte.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Isto é o que acontece quando o script é chamado. A consulta é envolta em XML:
<?xml version="1.0" ?> <methodCall> <methodName>listContestants</methodName> <params> <param> <value> <double>20000.00000000</double> </value> </param> </params> </methodCall>
e depois de um tempo de resposta é recebida a partir do servidor XML RPC. No meu caso eu usei a hospedagem do Linux que executa o serviço XML-RPC do python e o chama de dentro da instalação de convidado do Windows VirtualBox que roda o 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>
O resultado da primeira consulta exibida usando o método toString() é como se segue:
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:
A segunda consulta é usada para chamar o método getStats().
<?xml version="1.0" ?> <methodCall> <methodName>getStats</methodName> <params> <param> <value> <string>eurusd</string> </value> </param> </params> </methodCall>
A resposta XML-RPC é simples, contém apenas três valores inteiros.
<?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>
Desta vez eu levei uma outra abordagem e valores retornados acessados como variáveis 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
Como você pode ver a saída está em uma forma compreensível.
Conclusão
Eu apresentei um novo quadro de MQL5-RPC que permite o MetaTrader 5 executar chamadas de procedimento remoto usando o protocolo XML-RPC. Foram apresentados dois casos de uso, sendo o primeiro o acesso a um serviço da web e o segundo um analisador personalizado para o Automated Trading Championship 2011. Esses dois exemplos devem servir como base para novas experiências.
Eu acredito que a MQL5-RPC é um recurso muito poderoso que pode ser usado de muitas maneiras diferentes. Eu decidi fazer o quadro de fonte aberto e publica-lo sob a licença GPL em http://code.google.com/p/mql5-rpc/. Se alguém gostaria de ajudar a fazer a prova de bala do código, refatorá-lo ou fazer correções, as portas estão abertas para participarem do projeto. O código fonte com todos os exemplos também está disponível como anexo ao artigo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/342
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso