WebSocket para o MetaTrader 5: Usando a API do Windows
Introdução
Em nosso artigo WebSocket para MetaTrader 5, cobrimos os conceitos básicos do protocolo WebSocket e implementamos o cliente em MQL5. Desta vez vamos usar a API do Windows para criar um cliente WebSocket para os programas MetaTrader 5. Esta é a segunda melhor alternativa, pois não requer o uso de programas de terceiros, todos os componentes necessários estão disponíveis no sistema operacional. Executaremos o cliente como uma classe e realizaremos testes enviando os dados de tick atuais ao MetaTrader 5 usando o WebSocket API da Binary.com.
WebSocket no Windows
Ao falar em API do Windows e em Internet, convém notar que os desenvolvedores da MQL5 estão mais familiarizados com a biblioteca Windows Internet (WinINeT). Ela suporta protocolos de Internet, incluindo protocolo de transferência de arquivos (FTP) e HTTP. A biblioteca Windows HTTP Services (WinHTTP) é uma biblioteca especial para o protocolo HTTP, com funções para o desenvolvimento de aplicativos de servidor. Algumas das funções WinHTTP são projetadas para manusear conexões WebSocket.
O protocolo WebSocket é suportado nos sistemas operacionais Windows a partir do Windows 8.1 e Windows Server 2012 R2. O Windows 7 e versões anteriores não têm suporte nativo para o protocolo, por isso os programas deste artigo não funcionarão em máquinas com estes sistemas operacionais.
Biblioteca Winhttp
Para criar uma conexão cliente WebSocket, precisaremos das seguintes funções:
| WinHttpOpen | Inicializa a biblioteca, preparando-a para ser utilizada pelo aplicativo |
| WinHttpConnect | Define o nome de domínio do servidor com o qual o aplicativo precisa interagir. |
| WinHttpOpenRequest | Cria o identificador da solicitação HTTP |
| WinHttpSetOption | Define várias alternativas de configuração para conexão HTTP |
| WinHttpSendRequest | Envia a solicitação para o servidor |
| WinHttpReceiveResponse | Recebe uma resposta do servidor depois que a solicitação é enviada |
| WinHttpWebSocketCompleteUpgrade | Confirma que a resposta do servidor corresponde ao protocolo WebSocket |
| WinHttpCloseHandle | Desativa quaisquer descritores de recursos utilizados anteriormente |
| WinHttpWebSocketSend | Envia dados através de uma conexão WebSocket |
| WinHttpWebSocketReceive | Recebe dados usando uma conexão WebSocket |
| WinHttpWebSocketClose | Fecha a conexão WebSocket |
| WinHttpWebSocketQueryCloseStatus | Verifica a mensagem de status de fechamento enviada do servidor |
Todas as funções da biblioteca estão documentadas pela Microsoft. Uma descrição detalhada de todas as funções, seus parâmetros de entrada e tipos de valores de retorno pode ser encontrada nos links acima.
O cliente que estamos criando para o MetaTrader 5 funciona em modo síncrono. Isso significa que a chamada de função bloqueia a execução até que o valor retorne. Por exemplo, uma chamada do WinHttpWebSocketReceive() irá bloquear a execução até que os dados estejam disponíveis para serem lidos. Tenha isto em mente ao criar aplicativos para o MetaTrader 5.
As funções winhttp são declaradas e importadas no arquivo winhttp.mqh.
#include <WinAPI\errhandlingapi.mqh> #define WORD ushort #define DWORD ulong #define BYTE uchar #define INTERNET_PORT WORD #define HINTERNET long #define LPVOID uint& #define WINHTTP_ERROR_BASE 12000 #define ERROR_WINHTTP_OUT_OF_HANDLES (WINHTTP_ERROR_BASE + 1) #define ERROR_WINHTTP_TIMEOUT (WINHTTP_ERROR_BASE + 2) #define ERROR_WINHTTP_INTERNAL_ERROR (WINHTTP_ERROR_BASE + 4) #define ERROR_WINHTTP_INVALID_URL (WINHTTP_ERROR_BASE + 5) #define ERROR_WINHTTP_UNRECOGNIZED_SCHEME (WINHTTP_ERROR_BASE + 6) #define ERROR_WINHTTP_NAME_NOT_RESOLVED (WINHTTP_ERROR_BASE + 7) #define ERROR_WINHTTP_INVALID_OPTION (WINHTTP_ERROR_BASE + 9) #define ERROR_WINHTTP_OPTION_NOT_SETTABLE (WINHTTP_ERROR_BASE + 11) #define ERROR_WINHTTP_SHUTDOWN (WINHTTP_ERROR_BASE + 12) #define ERROR_WINHTTP_LOGIN_FAILURE (WINHTTP_ERROR_BASE + 15) #define ERROR_WINHTTP_OPERATION_CANCELLED (WINHTTP_ERROR_BASE + 17) #define ERROR_WINHTTP_INCORRECT_HANDLE_TYPE (WINHTTP_ERROR_BASE + 18) #define ERROR_WINHTTP_INCORRECT_HANDLE_STATE (WINHTTP_ERROR_BASE + 19) #define ERROR_WINHTTP_CANNOT_CONNECT (WINHTTP_ERROR_BASE + 29) #define ERROR_WINHTTP_CONNECTION_ERROR (WINHTTP_ERROR_BASE + 30) #define ERROR_WINHTTP_RESEND_REQUEST (WINHTTP_ERROR_BASE + 32) #define ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED (WINHTTP_ERROR_BASE + 44) #define ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN (WINHTTP_ERROR_BASE + 100) #define ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND (WINHTTP_ERROR_BASE + 101) #define ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND (WINHTTP_ERROR_BASE + 102) #define ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN (WINHTTP_ERROR_BASE + 103) #define ERROR_WINHTTP_HEADER_NOT_FOUND (WINHTTP_ERROR_BASE + 150) #define ERROR_WINHTTP_INVALID_SERVER_RESPONSE (WINHTTP_ERROR_BASE + 152) #define ERROR_WINHTTP_INVALID_HEADER (WINHTTP_ERROR_BASE + 153) #define ERROR_WINHTTP_INVALID_QUERY_REQUEST (WINHTTP_ERROR_BASE + 154) #define ERROR_WINHTTP_HEADER_ALREADY_EXISTS (WINHTTP_ERROR_BASE + 155) #define ERROR_WINHTTP_REDIRECT_FAILED (WINHTTP_ERROR_BASE + 156) #define ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR (WINHTTP_ERROR_BASE + 178) #define ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT (WINHTTP_ERROR_BASE + 166) #define ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT (WINHTTP_ERROR_BASE + 167) #define ERROR_WINHTTP_UNHANDLED_SCRIPT_TYPE (WINHTTP_ERROR_BASE + 176) #define ERROR_WINHTTP_SCRIPT_EXECUTION_ERROR (WINHTTP_ERROR_BASE + 177) #define ERROR_WINHTTP_NOT_INITIALIZED (WINHTTP_ERROR_BASE + 172) #define ERROR_WINHTTP_SECURE_FAILURE (WINHTTP_ERROR_BASE + 175) #define ERROR_WINHTTP_SECURE_CERT_DATE_INVALID (WINHTTP_ERROR_BASE + 37) #define ERROR_WINHTTP_SECURE_CERT_CN_INVALID (WINHTTP_ERROR_BASE + 38) #define ERROR_WINHTTP_SECURE_INVALID_CA (WINHTTP_ERROR_BASE + 45) #define ERROR_WINHTTP_SECURE_CERT_REV_FAILED (WINHTTP_ERROR_BASE + 57) #define ERROR_WINHTTP_SECURE_CHANNEL_ERROR (WINHTTP_ERROR_BASE + 157) #define ERROR_WINHTTP_SECURE_INVALID_CERT (WINHTTP_ERROR_BASE + 169) #define ERROR_WINHTTP_SECURE_CERT_REVOKED (WINHTTP_ERROR_BASE + 170) #define ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE (WINHTTP_ERROR_BASE + 179) #define ERROR_WINHTTP_AUTODETECTION_FAILED (WINHTTP_ERROR_BASE + 180) #define ERROR_WINHTTP_HEADER_COUNT_EXCEEDED (WINHTTP_ERROR_BASE + 181) #define ERROR_WINHTTP_HEADER_SIZE_OVERFLOW (WINHTTP_ERROR_BASE + 182) #define ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW (WINHTTP_ERROR_BASE + 183) #define ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW (WINHTTP_ERROR_BASE + 184) #define ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY (WINHTTP_ERROR_BASE + 185) #define ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY (WINHTTP_ERROR_BASE + 186) #define ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED_PROXY (WINHTTP_ERROR_BASE + 187) #define ERROR_WINHTTP_SECURE_FAILURE_PROXY (WINHTTP_ERROR_BASE + 188) #define ERROR_WINHTTP_RESERVED_189 (WINHTTP_ERROR_BASE + 189) #define ERROR_WINHTTP_HTTP_PROTOCOL_MISMATCH (WINHTTP_ERROR_BASE + 190) #define WINHTTP_ERROR_LAST (WINHTTP_ERROR_BASE + 188) enum WINHTTP_WEB_SOCKET_BUFFER_TYPE { WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE = 0, WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE = 1, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE = 2, WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE = 3, WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE = 4 }; enum _WINHTTP_WEB_SOCKET_CLOSE_STATUS { WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS = 1000, WINHTTP_WEB_SOCKET_ENDPOINT_TERMINATED_CLOSE_STATUS = 1001, WINHTTP_WEB_SOCKET_PROTOCOL_ERROR_CLOSE_STATUS = 1002, WINHTTP_WEB_SOCKET_INVALID_DATA_TYPE_CLOSE_STATUS = 1003, WINHTTP_WEB_SOCKET_EMPTY_CLOSE_STATUS = 1005, WINHTTP_WEB_SOCKET_ABORTED_CLOSE_STATUS = 1006, WINHTTP_WEB_SOCKET_INVALID_PAYLOAD_CLOSE_STATUS = 1007, WINHTTP_WEB_SOCKET_POLICY_VIOLATION_CLOSE_STATUS = 1008, WINHTTP_WEB_SOCKET_MESSAGE_TOO_BIG_CLOSE_STATUS = 1009, WINHTTP_WEB_SOCKET_UNSUPPORTED_EXTENSIONS_CLOSE_STATUS = 1010, WINHTTP_WEB_SOCKET_SERVER_ERROR_CLOSE_STATUS = 1011, WINHTTP_WEB_SOCKET_SECURE_HANDSHAKE_ERROR_CLOSE_STATUS = 1015 }; #define WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH 123 #define WINHTTP_FLAG_SECURE 0x00800000 #define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 #define WINHTTP_OPTION_SECURITY_FLAGS 31 #define WINHTTP_OPTION_SECURE_PROTOCOLS 84 #define WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET 114 #define WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT 115 #define WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL 116 #define WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE 122 #define WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE 123 #define SECURITY_FLAG_IGNORE_UNKNOWN_CA 0x00000100 #define SECURITY_FLAG_IGNORE_CERT_DATE_INVALID 0x00002000 #define SECURITY_FLAG_IGNORE_CERT_CN_INVALID 0x00001000 #define SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE 0x00000200 #define ERROR_INVALID_PARAMETER 87L #define ERROR_INVALID_OPERATION 4317L #import "winhttp.dll" HINTERNET WinHttpOpen(string,DWORD,string,string,DWORD); HINTERNET WinHttpConnect(HINTERNET,string,INTERNET_PORT,DWORD); HINTERNET WinHttpOpenRequest(HINTERNET,string,string,string,string,string,DWORD); bool WinHttpSetOption(HINTERNET,DWORD,LPVOID[],DWORD); bool WinHttpQueryOption(HINTERNET,DWORD,LPVOID[],DWORD&); bool WinHttpSetTimeouts(HINTERNET,int,int,int,int); HINTERNET WinHttpSendRequest(HINTERNET,string,DWORD,LPVOID[],DWORD,DWORD,DWORD); bool WinHttpReceiveResponse(HINTERNET,LPVOID[]); HINTERNET WinHttpWebSocketCompleteUpgrade(HINTERNET,DWORD&); bool WinHttpCloseHandle(HINTERNET); DWORD WinHttpWebSocketSend(HINTERNET,WINHTTP_WEB_SOCKET_BUFFER_TYPE,BYTE&[],DWORD); DWORD WinHttpWebSocketReceive(HINTERNET,BYTE&[],DWORD,DWORD&,WINHTTP_WEB_SOCKET_BUFFER_TYPE&); DWORD WinHttpWebSocketClose(HINTERNET,ushort,BYTE&[],DWORD); DWORD WinHttpWebSocketQueryCloseStatus(HINTERNET,ushort&,BYTE&[],DWORD,DWORD&); #import //+------------------------------------------------------------------+
Usando as funções Winhttp
Para instalar o cliente WebSocket com as funções necessárias, primeiro precisamos chamar WinHttpOpen() para inicializar a biblioteca. Esta função retorna um identificador de sessão para chamadas posteriores a outras funções winhttp.
#include<winhttp.mqh> HINTERNET sessionhandle,connectionhandle,requesthandle,websockethandle; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- sessionhandle=connectionhandle=requesthandle=websockethandle=NULL; sessionhandle=WinHttpOpen("MT5 app",WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,NULL,NULL,0); if(sessionhandle==NULL) { Print("WinHttpOpen error" +string(kernel32::GetLastError())); return; }
O segundo passo é criar um identificador de conexão usando WinHttpConnect(). Aqui especificamos o endereço do servidor e o número da porta. Note que nesta etapa precisamos especificar o nome de domínio para o servidor sem esquema e caminho. Também é possível usar um endereço IP público, se for conhecido. A maioria dos erros que ocorrem com winhttp está relacionada à transmissão de um endereço de servidor formatado incorretamente. Por exemplo, se o endereço completo do servidor é como wss://ws.example.com/path, WinHttpConnect() espera apenas ws.example.com.
connectionhandle=WinHttpConnect(sessionhandle,server,Port,0); if(connectionhandle==NULL) { Print("WinHttpConnect error "+string(kernel32::GetLastError())); if(sessionhandle!=NULL) WinHttpCloseHandle(sessionhandle); return; }
Depois de criar com sucesso o identificador de conexão, nós o usamos para definir o identificador de solicitação através de WinHttpOpenRequest(). Aqui especificamos o caminho (se houver) a partir do endereço do servidor e também definimos a opção para tornar a conexão segura.
requesthandle=WinHttpOpenRequest(connectionhandle,"GET",path,NULL,NULL,NULL,(ExtTLS)?WINHTTP_FLAG_SECURE:0); if(requesthandle==NULL) { Print("WinHttpOpenRequest error "+string(kernel32::GetLastError())); if(connectionhandle!=NULL) WinHttpCloseHandle(connectionhandle); if(sessionhandle!=NULL) WinHttpCloseHandle(sessionhandle); return; }
Agora que temos o identificador de solicitação correto, precisamos nos preparar para o aperto de mão, chamando WinHttpSetOption().
uint nullpointer[]= {}; if(!WinHttpSetOption(requesthandle,WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,nullpointer,0)) { Print("WinHttpSetOption upgrade error "+string(kernel32::GetLastError())); if(requesthandle!=NULL) WinHttpCloseHandle(requesthandle); if(connectionhandle!=NULL) WinHttpCloseHandle(connectionhandle); if(sessionhandle!=NULL) WinHttpCloseHandle(sessionhandle); return; }
Isto permite que os cabeçalhos necessários sejam adicionados à solicitação http de acordo com o protocolo WebSocket. O aperto de mão é iniciado por meio da chamada de WinHttpSendRequest() e, em seguida, de WinHttpReceiveResponse() para confirmar que a resposta à nossa solicitação foi recebida.
if(!WinHttpSendRequest(requesthandle,NULL,0,nullpointer,0,0,0)) { Print("WinHttpSendRequest error "+string(kernel32::GetLastError())); if(requesthandle!=NULL) WinHttpCloseHandle(requesthandle); if(connectionhandle!=NULL) WinHttpCloseHandle(connectionhandle); if(sessionhandle!=NULL) WinHttpCloseHandle(sessionhandle); return; } if(!WinHttpReceiveResponse(requesthandle,nullpointer)) { Print("WinHttpRecieveResponse no response "+string(kernel32::GetLastError())); if(requesthandle!=NULL) WinHttpCloseHandle(requesthandle); if(connectionhandle!=NULL) WinHttpCloseHandle(connectionhandle); if(sessionhandle!=NULL) WinHttpCloseHandle(sessionhandle); return; }
WinHttpWebSocketCompleteUpgrade() verifica a resposta em relação ao protocolo WebSocket. Se tudo estiver bem, ele devolve o identificador WebSocket necessário.
ulong nv=0; websockethandle=WinHttpWebSocketCompleteUpgrade(requesthandle,nv); if(websockethandle==NULL) { Print("WinHttpWebSocketCompleteUpgrade error "+string(kernel32::GetLastError())); if(requesthandle!=NULL) WinHttpCloseHandle(requesthandle); if(connectionhandle!=NULL) WinHttpCloseHandle(connectionhandle); if(sessionhandle!=NULL) WinHttpCloseHandle(sessionhandle); return; } WinHttpCloseHandle(requesthandle); requesthandle=NULL;
Nosso cliente WebSocket está agora pronto para operar. Podemos enviar dados com WinHttpWebSocketSend() e recebê-los com WinHttpWebSocketReceive(). O identificador da solicitação não é mais necessária porque o identificador do WebSocket já foi criado e nossa conexão http foi atualizada para uma conexão WebSocket. Podemos liberar todos os recursos associados ao idetificador da solicitação por meio da chamada de WinHttpCloseHandle().
bool WebsocketSend(const string message) { BYTE msg_array[]; StringToCharArray(message,msg_array,0,WHOLE_ARRAY); ArrayRemove(msg_array,ArraySize(msg_array)-1,1); DWORD len=(ArraySize(msg_array)); ulong send=WinHttpWebSocketSend(websockethandle,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,msg_array,len); if(send) return(false); return(true); } //+------------------------------------------------------------------+ bool WebSocketRecv(uchar &rxbuffer[],ulong &bytes_read) { WINHTTP_WEB_SOCKET_BUFFER_TYPE rbuffertype=-1; BYTE rbuffer[65539]; ulong rbuffersize=ulong(ArraySize(rbuffer)); ulong done=0; ulong transferred=0; ZeroMemory(rxbuffer); ZeroMemory(rbuffer); bytes_read=0; int called=0; do { called++; ulong get=WinHttpWebSocketReceive(websockethandle,rbuffer,rbuffersize,transferred,rbuffertype); if(get) { return(false); } ArrayCopy(rxbuffer,rbuffer,(int)done,0,(int)transferred); done+=transferred; transferred=0; ZeroMemory(rbuffer); } while(rbuffertype==WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE || rbuffertype==WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE); Print("Buffer type is "+EnumToString(rbuffertype)+" bytes read "+IntegerToString(done)+" looped "+IntegerToString(called)); bytes_read=done; return(true); } //+------------------------------------------------------------------+
A chamada de WinHttpWebSocketClose() fecha a conexão WebSocket. Após o fechamento da conexão, todos os identificadores associados devem ser desinicializados por meio da chamada de
WinHttpCloseHandle() para cada um deles.
BYTE closearray[]= {};
ulong close=WinHttpWebSocketClose(websockethandle,WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS,closearray,0);
if(close)
{
Print("websocket close error "+string(kernel32::GetLastError()));
if(requesthandle!=NULL)
WinHttpCloseHandle(requesthandle);
if(websockethandle!=NULL)
WinHttpCloseHandle(websockethandle);
if(connectionhandle!=NULL)
WinHttpCloseHandle(connectionhandle);
if(sessionhandle!=NULL)
WinHttpCloseHandle(sessionhandle);
return;
}
Classe CWebsocket
O arquivo websocket.mqh conterá a classe CWebsocket, que servirá como wrapper para as funções da biblioteca winhttp necessárias para ativar o cliente WebSocket.
O arquivo começa com uma diretiva include para incluir todas as funções e declarações importadas das bibliotecas API do Windows.
#include<winhttp.mqh> #define WEBSOCKET_ERROR_FIRST WINHTTP_ERROR_LAST+1000 #define WEBSOCKET_ERROR_NOT_INITIALIZED WEBSOCKET_ERROR_FIRST+1 #define WEBSOCKET_ERROR_EMPTY_SEND_BUFFER WEBSOCKET_ERROR_FIRST+2 #define WEBSOCKET_ERROR_NOT_CONNECTED WEBSOCKET_ERROR_FIRST+3 //+------------------------------------------------------------------+ //| websocket state enumeration | //+------------------------------------------------------------------+ enum ENUM_WEBSOCKET_STATE { CLOSED = 0, CLOSING, CONNECTING, CONNECTED };
O método Connect() é chamado primeiro para iniciar uma conexão com o servidor WebSocket.
Parâmetros Connect():
- _serveraddress — endereço completo do servidor (type:string)
- _port — número da porta do servidor (type:ushort)
- _appname — parâmetro de string para identificar o aplicativo usando o cliente WebSocket. Será enviado como um dos cabeçalhos na solicitação http inicial (type:string)
- _secure — valor booleano que especifica se deve ou não ser utilizada uma conexão segura (type:boolean)
O método Connect() chama os métodos privados initialize() e upgrade(). O método privado initialize() processa o endereço completo do servidor e o divide em um nome de domínio e um caminho. Finalmente, createSessionConnection() cria uma sessão e os identificadores de conexão. O método upgrade() cria uma solicitação e os identificadores WebSocket antes de definir o novo estado de conexão de cliente.
bool CWebsocket::Connect(const string _serveraddress, const INTERNET_PORT _port=443, const string _appname=NULL,bool _secure=true) { if(clientState==CONNECTED) { if(StringCompare(_serveraddress,serveraddress,false)) Abort(); else return(true); } if(!initialize(_serveraddress,_port,appname,_secure)) return(false); return(upgrade()); } bool CWebsocket::initialize(const string _serveraddress,const ushort _port,const string _appname,bool _secure) { if(initialized) return(true); if(_secure) isSecure=true; if(_port==0) { if(isSecure) serverPort=443; else serverPort=80; } else { serverPort=_port; isSecure=_secure; if(serverPort==443 && !isSecure) isSecure=true; } if(_appname!=NULL) appname=_appname; else appname="Mt5 app"; serveraddress=_serveraddress; int dot=StringFind(serveraddress,"."); int ss=(dot>0)?StringFind(serveraddress,"/",dot):-1; serverPath=(ss>0)?StringSubstr(serveraddress,ss+1):"/"; int sss=StringFind(serveraddress,"://"); if(sss<0) sss=-3; serverName=StringSubstr(serveraddress,sss+3,ss); initialized=createSessionConnection(); return(initialized); } bool CWebsocket::createSessionConnection(void) { hSession=WinHttpOpen(appname,WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,NULL,NULL,0); if(hSession==NULL) { setErrorDescription(); return(false); } hConnection=WinHttpConnect(hSession,serverName,serverPort,0); if(hSession==NULL) { setErrorDescription(); reset(); return(false); } return(true); } bool CWebsocket::upgrade(void) { clientState=CONNECTING; hRequest=WinHttpOpenRequest(hConnection,"GET",serverPath,NULL,NULL,NULL,(isSecure)?WINHTTP_FLAG_SECURE:0); if(hRequest==NULL) { setErrorDescription(); reset(); return(false); } uint nullpointer[]= {}; if(!WinHttpSetOption(hRequest,WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,nullpointer,0)) { setErrorDescription(); reset(); return(false); } if(!WinHttpSendRequest(hRequest,NULL,0,nullpointer,0,0,0)) { setErrorDescription(); reset(); return(false); } if(!WinHttpReceiveResponse(hRequest,nullpointer)) { setErrorDescription(); reset(); return(false); } ulong nv=0; hWebSocket=WinHttpWebSocketCompleteUpgrade(hRequest,nv); if(hWebSocket==NULL) { setErrorDescription(); reset(); return(false); } WinHttpCloseHandle(hRequest); hRequest=NULL; clientState=CONNECTED; return(true); }
Se o método Connect() retornar true, podemos começar a enviar dados através do cliente WebSocket. O processo é simplificado através de dois métodos.
O método SendString() aceita uma string como entrada, enquanto o método Send() aceita um array de caracteres não assinados como o único parâmetro de função. No caso de sucesso, ambos retornam true e chamam o método private clientend(), que trata de todas as operações de envio para a classe.
//+------------------------------------------------------------------+ //| helper method for sending data to the server | //+------------------------------------------------------------------+ bool CWebsocket::clientsend(BYTE &txbuffer[],WINHTTP_WEB_SOCKET_BUFFER_TYPE buffertype) { DWORD len=(ArraySize(txbuffer)); if(len<=0) { setErrorDescription(WEBSOCKET_ERROR_EMPTY_SEND_BUFFER); return(false); } ulong send=WinHttpWebSocketSend(hWebSocket,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,txbuffer,len); if(send) { setErrorDescription(); return(false); } return(true); } //+------------------------------------------------------------------+ //|public method for sending raw string messages | //+------------------------------------------------------------------+ bool CWebsocket::SendString(const string msg) { if(!initialized) { setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED); return(false); } if(clientState!=CONNECTED) { setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED); return(false); } if(StringLen(msg)<=0) { setErrorDescription(WEBSOCKET_ERROR_EMPTY_SEND_BUFFER); return(false); } BYTE msg_array[]; StringToCharArray(msg,msg_array,0,WHOLE_ARRAY); ArrayRemove(msg_array,ArraySize(msg_array)-1,1); DWORD len=(ArraySize(msg_array)); return(clientsend(msg_array,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE)); } //+------------------------------------------------------------------+ //|Public method for sending data prepackaged in an array | //+------------------------------------------------------------------+ bool CWebsocket::Send(BYTE &buffer[]) { if(!initialized) { setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED); return(false); } if(clientState!=CONNECTED) { setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED); return(false); } return(clientsend(buffer,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE)); }
Podemos usar Read() ou ReadString() para ler os dados enviados a partir do servidor. Os métodos retornam o tamanho dos dados recebidos. ReadString() requer uma string, passada por referência, na qual os dados recebidos serão escritos, enquanto Read() escreve em um array de caracteres não assinados.
//+------------------------------------------------------------------+ //|helper method for reading received messages from the server | //+------------------------------------------------------------------+ void CWebsocket::clientread(BYTE &rbuffer[],ulong &bytes) { WINHTTP_WEB_SOCKET_BUFFER_TYPE rbuffertype=-1; ulong done=0; ulong transferred=0; ZeroMemory(rbuffer); ZeroMemory(rxbuffer); bytes=0; do { ulong get=WinHttpWebSocketReceive(hWebSocket,rxbuffer,rxsize,transferred,rbuffertype); if(get) { setErrorDescription(); return; } ArrayCopy(rbuffer,rxbuffer,(int)done,0,(int)transferred); done+=transferred; ZeroMemory(rxbuffer); transferred=0; } while(rbuffertype==WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE || rbuffertype==WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE); bytes=done; return; } //+------------------------------------------------------------------+ //|public method for reading data sent from the server | //+------------------------------------------------------------------+ ulong CWebsocket::Read(BYTE &buffer[]) { if(!initialized) { setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED); return(false); } if(clientState!=CONNECTED) { setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED); return(false); } ulong bytes_read_from_socket=0; clientread(buffer,bytes_read_from_socket); return(bytes_read_from_socket); } //+------------------------------------------------------------------+ //|public method for reading data sent from the server | //+------------------------------------------------------------------+ ulong CWebsocket::ReadString(string &_response) { if(!initialized) { setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED); return(false); } if(clientState!=CONNECTED) { setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED); return(false); } ulong bytes_read_from_socket=0; BYTE buffer[]; clientread(buffer,bytes_read_from_socket); _response=(bytes_read_from_socket)?CharArrayToString(buffer):NULL; return(bytes_read_from_socket); }
Quando o cliente WebSocket não for mais necessário, a conexão com o servidor pode ser fechada usando Close() ou Abort(). O método Abort() difere do Close() na medida em que não apenas fecha a conexão WebSocket, mas também redefine algumas propriedades de classe para seu estado padrão.
//+------------------------------------------------------------------+ //| Closes a websocket client connection | //+------------------------------------------------------------------+ void CWebsocket::Close(void) { if(clientState==CLOSED) return; clientState=CLOSING; BYTE nullpointer[]= {}; ulong result=WinHttpWebSocketClose(hWebSocket,WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS,nullpointer,0); if(result) setErrorDescription(); reset(); return; } //+--------------------------------------------------------------------------+ //|method for abandoning a client connection. All previous server connection | //| parameters are reset to their default state | //+--------------------------------------------------------------------------+ void CWebsocket::Abort(void) { Close(); //--- serveraddress=serverName=serverPath=NULL; serverPort=0; isSecure=false; last_error=0; StringFill(errormsg,0); //--- return; }
ClientState() consulta o estado atual do cliente WebSocket
DomainName(), Port() e ServerPath() retornam, respectivamente, o nome de domínio, a porta e o caminho para a conexão atual.
LastErrorMessage() pode ser usado para recuperar o último erro como uma string detalhada, enquanto que chamar LastError() retorna o código de erro como um valor inteiro.
//public getter methods string LastErrorMessage(void) { return(errormsg); } uint LastError(void) { return(last_error); } ENUM_WEBSOCKET_STATE ClientState(void) { return(clientState); } string DomainName(void) { return(serverName); } INTERNET_PORT Port(void) { return(serverPort); } string ServerPath(void) { return(serverPath); }
Abaixo está a classe inteira.
//+------------------------------------------------------------------+ //|Class CWebsocket | //| Purpose: class for websocket client | //+------------------------------------------------------------------+ class CWebsocket { private: ENUM_WEBSOCKET_STATE clientState; //websocket state HINTERNET hSession; //winhttp session handle HINTERNET hConnection; //winhttp connection handle HINTERNET hWebSocket; //winhttp websocket handle HINTERNET hRequest; //winhtttp request handle string appname; //optional application name sent as one of the headers in initial http request string serveraddress; //full server address string serverName; //server domain name INTERNET_PORT serverPort; //port number string serverPath; //server path bool initialized; //boolean flag that denotes the state of underlying winhttp infrastruture required for client BYTE rxbuffer[]; //internal buffer for reading from the socket bool isSecure; //secure connection flag ulong rxsize; //rxbuffer arraysize string errormsg; //internal buffer for error messages uint last_error; //last winhttp/win32/class specific error // private methods bool initialize(const string _serveraddress, const INTERNET_PORT _port, const string _appname,bool _secure); bool createSessionConnection(void); bool upgrade(void); void reset(void); bool clientsend(BYTE &txbuffer[],WINHTTP_WEB_SOCKET_BUFFER_TYPE buffertype); void clientread(BYTE &rxbuffer[],ulong &bytes); void setErrorDescription(uint error=0); public: CWebsocket(void):clientState(0), hSession(NULL), hConnection(NULL), hWebSocket(NULL), hRequest(NULL), serveraddress(NULL), serverName(NULL), serverPort(0), initialized(false), isSecure(false), rxsize(65539), errormsg(NULL), last_error(0) { ArrayResize(rxbuffer,(int)rxsize); ArrayFill(rxbuffer,0,rxsize,0); StringInit(errormsg,1000); } ~CWebsocket(void) { Close(); ArrayFree(rxbuffer); } //public methods bool Connect(const string _serveraddress, const INTERNET_PORT _port=443, const string _appname=NULL,bool _secure=true); void Close(void); bool SendString(const string msg); bool Send(BYTE &buffer[]); ulong ReadString(string &response); ulong Read(BYTE &buffer[]); void Abort(void); void ResetLastError(void) { last_error=0; StringFill(errormsg,0); ::ResetLastError(); } //public getter methods string LastErrorMessage(void) { return(errormsg); } uint LastError(void) { return(last_error); } ENUM_WEBSOCKET_STATE ClientState(void) { return(clientState); } string DomainName(void) { return(serverName); } INTERNET_PORT Port(void) { return(serverPort); } string ServerPath(void) { return(serverPath); } };
Agora que temos a classe WebSocket, podemos olhar para um exemplo de como usá-la.
Testando a classe CWebsocket
Para testes, criarei um aplicativo para MetaTrader 5 que adicionará um símbolo personalizado da Binary.com. Ao ser lançado em um gráfico, o aplicativo carrega o histórico e abre um novo gráfico do símbolo personalizado, que é atualizado em tempo real a cada tick.
Estão previstas duas versões do aplicativo. BinaryCustomSymboWithTickHistory.ex5 utilizará o histórico de tick, enquanto BinaryCustomSymboWithBarHistory.ex5 carregará o histórico de barras no formato OHLC. O código de ambos os aplicativos é quase idêntico.
Binary.com tem uma API bem documentada que permite aos desenvolvedores criar interfaces que interagem com seus sistemas. A API se baseia em soquete web com solicitações e respostas em formato JSON.

O aplicativo será realizado como um EA que usa três bibliotecas:
- A primeira é websocket.mqh para processamento das conexões WebSocket,
- A segunda é JAson.mqh por trabalhar com dados em formato JSON, ela foi desenvolvida por Alexey Sergeev. Pode ser encontrada no repositório vivazzi do GitHub,
- A terceira é FileTxt.mqh para operações de arquivo.
O EA tem os seguintes parâmetros de entrada configuráveis:
- binary_appid é um parâmetro de string para acesso de aplicativo à API. O ID do aplicativo pode ser obtido seguindo as instruções no portal do desenvolvedor. A assinatura dos dados por símbolo não requer autorização no Binary.com, portanto não é necessário nenhum token API.
- binary_symbol é uma enumeração que permite aos usuários selecionar o símbolo a ser importado para o MetaTrader 5.
- binary_timeframe é o período do gráfico que se abre após o carregamento e a adição do histórico no MetaTrader 5.
#include<websocket.mqh> #include<JAson.mqh> #include<Files/FileTxt.mqh> #define BINARY_URL "ws.binaryws.com/websockets/v3?app_id=" #define BINARY_SYMBOL_SETTINGS "binarysymbolset.json" #define BINARY_SYMBOL_BASE_PATH "Binary.com\\" enum ENUM_BINARY_SYMBOL { BINARY_1HZ10V=0,//Volatility 10 (1s) BINARY_1HZ25V,//Volatility 25 (1s) BINARY_1HZ50V,//Volatility 50 (1s) BINARY_1HZ75V,//Volatility 75 (1s) BINARY_1HZ100V,//Volatility 100 (1s) BINARY_1HZ200V,//Volatility 200 (1s) BINARY_1HZ300V,//Volatility 300 (1s) BINARY_BOOM300N,//BOOM 300 BINARY_BOOM500,//BOOM 500 BINARY_BOOM1000,//BOOM 1000 BINARY_CRASH300N,//CRASH 300 BINARY_CRASH500,//CRASH 500 BINARY_CRASH1000,//CRASH 1000 BINARY_cryBTCUSD,//BTCUSD BINARY_cryETHUSD,//ETHUSD BINARY_frxAUDCAD,//AUDCAD BINARY_frxAUDCHF,//AUDCHF BINARY_frxAUDJPY,//AUDJPY BINARY_frxAUDNZD,//AUDNZD BINARY_frxAUDUSD,//AUDUSD BINARY_frxBROUSD,//BROUSD BINARY_frxEURAUD,//EURAUD BINARY_frxEURCAD,//EURCAD BINARY_frxEURCHF,//EURCHF BINARY_frxEURGBP,//EURGBP BINARY_frxEURJPY,//EURJPY BINARY_frxEURNZD,//EURNZD BINARY_frxEURUSD,//EURUSD BINARY_frxGBPAUD,//GBPAUD BINARY_frxGBPCAD,//GBPCAD BINARY_frxGBPCHF,//GBPCHF BINARY_frxGBPJPY,//GBPJPY BINARY_frxGBPNOK,//GBPNOK BINARY_frxGBPNZD,//GBPNZD BINARY_frxGBPUSD,//GBPUSD BINARY_frxNZDJPY,//NZDJPY BINARY_frxNZDUSD,//NZDUSD BINARY_frxUSDCAD,//USDCAD BINARY_frxUSDCHF,//USDCHF BINARY_frxUSDJPY,//USDJPY BINARY_frxUSDMXN,//USDMXN BINARY_frxUSDNOK,//USDNOK BINARY_frxUSDPLN,//USDPLN BINARY_frxUSDSEK,//USDSEK BINARY_frxXAUUSD,//XAUUSD BINARY_frxXAGUSD,//XAGUSD BINARY_frxXPDUSD,//XPDUSD BINARY_frxXPTUSD,//XPTUSD BINARY_JD10,//Jump 10 Index BINARY_JD25,//Jump 25 Index BINARY_JD50,//Jump 50 Index BINARY_JD75,//Jump 75 Index BINARY_JD100,//Jump 100 Index BINARY_OTC_AEX,//Dutch Index BINARY_OTC_AS51,//Australian Index BINARY_OTC_DJI,//Wall Street Index BINARY_OTC_FCHI,//French Index BINARY_OTC_FTSE,//UK Index BINARY_OTC_GDAXI,//German Index BINARY_OTC_HSI,//Hong Kong Index BINARY_OTC_N225,//Japanese Index BINARY_OTC_NDX,//US Tech Index BINARY_OTC_SPC,//US Index BINARY_OTC_SSMI,//Swiss Index BINARY_OTC_SX5E,//Euro 50 Index BINARY_R_10,//Volatility 10 Index BINARY_R_25,//Volatility 25 Index BINARY_R_50,//Volatility 50 Index BINARY_R_75,//Volatility 75 Index BINARY_R_100,//Volatility 100 Index BINARY_RDBEAR,//Bear Market Index BINARY_RDBULL,//Bull Market Index BINARY_stpRNG,//Step Index BINARY_WLDAUD,//AUD Index BINARY_WLDEUR,//EUR Index BINARY_WLDGBP,//GBP Index BINARY_WLDUSD,//USD Index BINARY_WLDXAU//Gold Index }; input string binary_appid="";//Binary.com registered application ID input ENUM_BINARY_SYMBOL binary_symbol=BINARY_R_100;//Binary.com symbol input ENUM_TIMEFRAMES binary_timeframe=PERIOD_M1;//Chart period
O EA será composto por duas classes - CCustomSymbol e CBinarySymbol.
Classe CCustomSymbol
CCustomSymbol é uma classe para manipulação de símbolos personalizados de fontes externas. A classe é baseada na biblioteca SYMBOL elaborada por fxsaber. Entre outras coisas, a classe fornece métodos para recuperar e manipular as propriedades dos símbolos, e para abrir e fechar os gráficos correspondentes. O mais importante é que ela dispõe de três métodos virtuais que podem ser substituídos por classes filhas para poder acrescentar um símbolo personalizado de forma mais flexível.
//+------------------------------------------------------------------+ //|General class for creating custom symbols from external source | //+------------------------------------------------------------------+ class CCustomSymbol { protected: string m_symbol_name; //symbol name datetime m_history_start; //existing tick history start date datetime m_history_end; //existing tick history end date bool m_new; //flag specifying whether a symbol has just been created or already exists in the terminal ENUM_TIMEFRAMES m_chart_tf; //chart timeframe public: //constructor CCustomSymbol(void) { m_symbol_name=NULL; m_chart_tf=PERIOD_M1; m_history_start=0; m_history_end=0; m_new=false; } //destructor ~CCustomSymbol(void) { } //method for initializing symbol, sets the symbol name and chart timeframe properties virtual bool Initialize(const string sy,string sy_path=NULL, ENUM_TIMEFRAMES chart_tf=PERIOD_M1) { m_symbol_name=sy; m_chart_tf=chart_tf; return(InitSymbol(sy_path)); } //gets the symbol name string Name(void) const { return(m_symbol_name); } //sets the history start date bool SetHistoryStartDate(const datetime startime) { if(startime>=TimeLocal()) { Print("Invalid history start time"); return(false); } m_history_start=startime; return(true); } //gets the history start date datetime GetHistoryStartDate(void) { return(m_history_start); } //general methods for setting the properties of the custom symbol bool SetProperty(const ENUM_SYMBOL_INFO_DOUBLE Property, double Value) const { return(::CustomSymbolSetDouble(m_symbol_name, Property, Value)); } bool SetProperty(const ENUM_SYMBOL_INFO_INTEGER Property, long Value) const { return(::CustomSymbolSetInteger(m_symbol_name, Property, Value)); } bool SetProperty(const ENUM_SYMBOL_INFO_STRING Property, string Value) const { return(::CustomSymbolSetString(m_symbol_name, Property, Value)); } //general methods for getting the symbol properties of the custom symbol long GetProperty(const ENUM_SYMBOL_INFO_INTEGER Property) const { return(::SymbolInfoInteger(m_symbol_name, Property)); } double GetProperty(const ENUM_SYMBOL_INFO_DOUBLE Property) const { return(::SymbolInfoDouble(m_symbol_name, Property)); } string GetProperty(const ENUM_SYMBOL_INFO_STRING Property) const { return(::SymbolInfoString(m_symbol_name, Property)); } //method for deleting a custom symbol bool Delete(void) { return((bool)(GetProperty(SYMBOL_CUSTOM)) && DeleteAllCharts() && ::CustomSymbolDelete(m_symbol_name) && SymbolSelect(m_symbol_name,false)); } //unimplemented virtual method for adding new ticks virtual void AddTick(void) { return; } //unimplemented virtual method for aquiring the either ticks or candle history from an external source virtual bool UpdateHistory(void) { return(false); } protected: //checks if the symbol already exists or not bool SymbolExists(void) { return(SymbolSelect(m_symbol_name,true)); } //method that opens a new chart according to the m_chart_tf property void OpenChart(void) { long Chart = ::ChartFirst(); bool opened=false; while(Chart != -1) { if((::ChartSymbol(Chart) == m_symbol_name)) { ChartRedraw(Chart); if(ChartPeriod(Chart)==m_chart_tf) opened=true; } Chart = ::ChartNext(Chart); } if(!opened) { long id = ChartOpen(m_symbol_name,m_chart_tf); if(id == 0) { Print("Can't open new chart for " + m_symbol_name + ", code: " + (string)GetLastError()); return; } else { Sleep(1000); ChartSetSymbolPeriod(id, m_symbol_name, m_chart_tf); ChartSetInteger(id, CHART_MODE,CHART_CANDLES); } } } //deletes all charts for the specified symbol bool DeleteAllCharts(void) { long Chart = ::ChartFirst(); while(Chart != -1) { if((Chart != ::ChartID()) && (::ChartSymbol(Chart) == m_symbol_name)) if(!ChartClose(Chart)) { Print("Error closing chart id ", Chart, m_symbol_name, ChartPeriod(Chart)); return(false); } Chart = ::ChartNext(Chart); } return(true); } //helper method that initializes a custom symbol bool InitSymbol(const string _path=NULL) { if(!SymbolExists()) { if(!CustomSymbolCreate(m_symbol_name,_path)) { Print("error creating custom symbol ", ::GetLastError()); return(false); } if(!SetProperty(SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID) || !SetProperty(SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_DISABLED) || !SetProperty(SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_DISABLED)) { Print("error setting symbol properties"); return(false); } if(!SymbolSelect(m_symbol_name,true)) { Print("error adding symbol to market watch",::GetLastError()); return(false); } m_new=true; return(true); } else { long custom=GetProperty(SYMBOL_CUSTOM); if(!custom) { Print("Error, symbol is not custom ",m_symbol_name,::GetLastError()); return(false); } m_history_end=GetLastBarTime(); m_history_start=GetFirstBarTime(); m_new=false; return(true); } } //gets the last tick time for an existing custom symbol datetime GetLastTickTime(void) { MqlTick tick; ZeroMemory(tick); if(!SymbolInfoTick(m_symbol_name,tick)) { Print("symbol info tick failure ", ::GetLastError()); return(0); } else return(tick.time); } //gets the last bar time of the one minute timeframe in candle history datetime GetLastBarTime(void) { MqlRates candle[1]; ZeroMemory(candle); int bars=iBars(m_symbol_name,PERIOD_M1); if(bars<=0) return(0); if(CopyRates(m_symbol_name,PERIOD_M1,0,1,candle)>0) return(candle[0].time); else return(0); } //gets the first bar time of the one minute timeframe in candle history datetime GetFirstBarTime(void) { MqlRates candle[1]; ZeroMemory(candle); int bars=iBars(m_symbol_name,PERIOD_M1); if(bars<=0) return(0); if(CopyRates(m_symbol_name,PERIOD_M1,bars-1,1,candle)>0) return(candle[0].time); else return(0); } };
Parâmetros do método Initialize():
- sy - este parâmetro de string define o nome do símbolo personalizado
- sy_path - parâmetro de string que define a propriedade de caminho do símbolo
- chart_tf - este parâmetro define o período gráfico a ser aberto após o histórico de símbolos ser carregado
Os dois métodos virtuais restantes, UpdateHistory() e AddTick(), não são adicionados ao CCustomSymbol. Qualquer classe derivada precisaria substituir estes métodos.
Classe CBinarySymbol
É aqui que entra em jogo a classe CBinarySymbol. Ela é herdada do CCustomSymbol e fornece métodos que substituem os métodos virtuais de sua classe mãe. É aqui que utilizaremos o cliente WebSocket para aplicar a API do Binary.com.
//+------------------------------------------------------------------+ //|Class for creating custom Binary.com specific symbols | //+------------------------------------------------------------------+ class CBinarySymbol:public CCustomSymbol { private: //private properties string m_appID; //app id string issued by Binary.com string m_url; //final url string m_stream_id; //stream identifier for a symbol int m_index; //array index CWebsocket* websocket; //websocket client CJAVal* json; //utility json object CJAVal* symbolSpecs; //json object storing symbol specification //private methods bool CheckBinaryError(CJAVal &j); bool GetSymbolSettings(void); public: //Constructor CBinarySymbol(void):m_appID(NULL), m_url(NULL), m_stream_id(NULL), m_index(-1) { json=new CJAVal(); symbolSpecs=new CJAVal(); websocket=new CWebsocket(); } //Destructor ~CBinarySymbol(void) { if(CheckPointer(websocket)==POINTER_DYNAMIC) { if(m_stream_id!="") StopTicksStream(); delete websocket; } if(CheckPointer(json)==POINTER_DYNAMIC) delete json; if(CheckPointer(symbolSpecs)==POINTER_DYNAMIC) delete symbolSpecs; Comment(""); } //public methods virtual void AddTick(void) override; virtual bool Initialize(const string sy,string sy_path=NULL,ENUM_TIMEFRAMES chart_tf=PERIOD_M1) override; virtual bool UpdateHistory(void) override; void SetAppID(const string id); bool StartTicksStream(void); bool StopTicksStream(void); };
Depois de criar uma cópia da classe CBinarySymbol, precisamos definir o identificador app_id correto usando o método SetAppID(). Somente então poderemos inicializar o símbolo personalizado.
//+------------------------------------------------------------------+ //|sets the the application id used to consume binary.com api | //+------------------------------------------------------------------+ void CBinarySymbol::SetAppID(const string id) { if(m_appID!=NULL && StringCompare(id,m_appID,false)) websocket.Abort(); m_appID=id; m_url=BINARY_URL+m_appID; }
O método Initialize() usa o método privado getSymbolSpecs() para obter as propriedades do símbolo selecionado. As informações necessárias são então utilizadas para definir as propriedades do novo símbolo personalizado.
//+------------------------------------------------------------------+ //|Begins process of creating custom symbol | //+------------------------------------------------------------------+ bool CBinarySymbol::Initialize(const string sy,string sy_path=NULL, ENUM_TIMEFRAMES chart_tf=PERIOD_M1) { if(CheckPointer(websocket)==POINTER_INVALID || CheckPointer(json)==POINTER_INVALID || CheckPointer(symbolSpecs)==POINTER_INVALID) { Print("Invalid pointer found "); return(false); } if(m_appID=="") { Alert("Application ID has not been set, It is required for the program to work"); return(false); } m_symbol_name=(StringFind(sy,"BINARY_")>=0)?StringSubstr(sy,7):sy; m_chart_tf=chart_tf; Comment("Initializing Symbol "+m_symbol_name+"......."); if(!GetSymbolSettings()) return(false); string s_path=BINARY_SYMBOL_BASE_PATH+symbolSpecs["active_symbols"][m_index]["market_display_name"].ToStr(); string symbol_description=symbolSpecs["active_symbols"][m_index]["display_name"].ToStr(); double s_point=symbolSpecs["active_symbols"][m_index]["pip"].ToDbl(); int s_digits=(int)MathAbs(MathLog10(s_point)); if(!InitSymbol(s_path)) return(false); if(m_new) { if(!SetProperty(SYMBOL_DESCRIPTION,symbol_description) || !SetProperty(SYMBOL_POINT,s_point) || !SetProperty(SYMBOL_DIGITS,s_digits)) { Print("error setting symbol properties ", ::GetLastError()); return(false); } } Comment("Symbol "+m_symbol_name+" initialized......."); return(true); }
Depois de inicializar um símbolo, precisamos obter preços ou dados para o gráfico. O método UpdateHistory() é responsável por isso. Após o histórico ter sido carregado no terminal, um novo gráfico do símbolo personalizado é aberto, se ele ainda não existir. O código abaixo mostra duas variantes do método UpdateHistory(): a primeira utiliza dados de barras para preencher o histórico, a segunda se baseia em dados de tick.
//+------------------------------------------------------------------+ //|method for updating the tick history for a particular symbol | //+------------------------------------------------------------------+ bool CBinarySymbol::UpdateHistory(void) { if(websocket.ClientState()!=CONNECTED && !websocket.Connect(m_url)) { Print(websocket.LastErrorMessage()," : ",websocket.LastError()); return(false); } Comment("Updating history for "+m_symbol_name+"......."); MqlRates history_candles[]; string history=NULL; json.Clear(); json["ticks_history"]=m_symbol_name; if(m_new) { if(m_history_start>0) { json["start"]=(int)(m_history_start); } } else if(m_history_end!=0) { json["start"]=(int)(m_history_start); } json["end"]="latest"; json["style"]="candles"; if(!websocket.SendString(json.Serialize())) { Print(websocket.LastErrorMessage()); return(false); } if(websocket.ReadString(history)) { json.Deserialize(history); if(CheckBinaryError(json)) return(false); int i=0; if(ArrayResize(history_candles,(json["candles"].Size()),100)<0) { Print("Last error is "+IntegerToString(::GetLastError())); return(false); } while(json["candles"][i]["open"].ToDbl()!=0.0) { history_candles[i].close=json["candles"][i]["close"].ToDbl(); history_candles[i].high=json["candles"][i]["high"].ToDbl(); history_candles[i].low=json["candles"][i]["low"].ToDbl(); history_candles[i].open=json["candles"][i]["open"].ToDbl(); history_candles[i].tick_volume=4; history_candles[i].real_volume=0; history_candles[i].spread=0; history_candles[i].time=(datetime)json["candles"][i]["epoch"].ToInt(); i++; } if(ArraySize(history_candles)>0) { if(CustomRatesUpdate(m_symbol_name,history_candles)<0) { Print("Error adding history "+IntegerToString(::GetLastError())); return(false); } } else { Print("Received unexpected response from server ",IntegerToString(::GetLastError()), " "+history); return(false); } } else { Print("error reading "," error: ",websocket.LastError(), websocket.LastErrorMessage()); return(false); } OpenChart(); return(true); } //+------------------------------------------------------------------+ //|method for updating the tick history for a particular symbol | //+------------------------------------------------------------------+ bool CBinarySymbol::UpdateHistory(void) { if(websocket.ClientState()!=CONNECTED && !websocket.Connect(m_url)) { Print(websocket.LastErrorMessage()," : ",websocket.LastError()); return(false); } Comment("Updating history for "+m_symbol_name+"......."); MqlTick history_ticks[]; string history=NULL; json.Clear(); json["ticks_history"]=m_symbol_name; if(m_new) { if(m_history_start>0) { json["start"]=(int)(m_history_start); } } else if(m_history_end!=0) { json["start"]=(int)(m_history_start); } json["count"]=m_max_ticks; json["end"]="latest"; json["style"]="ticks"; if(!websocket.SendString(json.Serialize())) { Print(websocket.LastErrorMessage()); return(false); } if(websocket.ReadString(history)) { json.Deserialize(history); if(CheckBinaryError(json)) return(false); int i=0; int z=i; int diff=0; while(json["history"]["prices"][i].ToDbl()!=0.0) { diff=(i>0)?(int)(json["history"]["times"][i].ToInt() - json["history"]["times"][i-1].ToInt()):0;//((m_history_end>0)?(json["history"]["times"][i].ToInt() - (int)(m_history_end)):0); if(diff > 1) { int k=z+diff; int p=1; if(ArrayResize(history_ticks,k,100)!=k) { Print("Memory allocation error, "+IntegerToString(::GetLastError())); return(false); } while(z<(k-1)) { history_ticks[z].bid=json["history"]["prices"][i-1].ToDbl(); history_ticks[z].ask=0; history_ticks[z].time=(datetime)(json["history"]["times"][i-1].ToInt()+p); history_ticks[z].time_msc=(long)((json["history"]["times"][i-1].ToInt()+p)*1000); history_ticks[z].last=0; history_ticks[z].volume=0; history_ticks[z].volume_real=0; history_ticks[z].flags=TICK_FLAG_BID; z++; p++; } history_ticks[z].bid=json["history"]["prices"][i].ToDbl(); history_ticks[z].ask=0; history_ticks[z].time=(datetime)(json["history"]["times"][i].ToInt()); history_ticks[z].time_msc=(long)((json["history"]["times"][i].ToInt())*1000); history_ticks[z].last=0; history_ticks[z].volume=0; history_ticks[z].volume_real=0; history_ticks[z].flags=TICK_FLAG_BID; i++; z++; } else { if(ArrayResize(history_ticks,z+1,100)==(z+1)) { history_ticks[z].bid=json["history"]["prices"][i].ToDbl(); history_ticks[z].ask=0; history_ticks[z].time=(datetime)json["history"]["times"][i].ToInt(); history_ticks[z].time_msc=(long)(json["history"]["times"][i].ToInt()*1000); history_ticks[z].last=0; history_ticks[z].volume=0; history_ticks[z].volume_real=0; history_ticks[z].flags=TICK_FLAG_BID; } else { Print("Memory allocation error, "+IntegerToString(::GetLastError())); return(false); } i++; z++; } } //Print("z is ",z,". Arraysize is ",ArraySize(history_ticks)); if(m_history_end>0 && z>0) { DeleteAllCharts(); if(CustomTicksDelete(m_symbol_name,int(m_history_start)*1000,(history_ticks[0].time_msc-1000))<0) { Print("error deleting ticks ", ::GetLastError()); return(false); } else { m_history_end=history_ticks[z-1].time; m_history_start=history_ticks[0].time; } } if(ArraySize(history_ticks)>0) { //ArrayPrint(history_ticks); if(CustomTicksAdd(m_symbol_name,history_ticks)<0)//CustomTicksReplace(m_symbol_name,history_ticks[0].time_msc,history_ticks[z-1].time_msc,history_ticks) { Print("Error adding history "+IntegerToString(::GetLastError())); return(false); } } else { Print("Received unexpected response from server ",IntegerToString(::GetLastError()), " "+history); return(false); } } else { Print("error reading "," error: ",websocket.LastError(), websocket.LastErrorMessage()); return(false); } OpenChart(); return(true); }
Após atualizar o histórico e abrir o gráfico, você precisa assinar e receber os dados de tick da Binary.com. O método StartTicksStream() envia uma solicitação correspondente. Se tiver sucesso, o servidor começa a enviar a cotação em tempo real, que é então processada usando o método AddTick(). O método StopTicksStream(), por sua vez, notifica o servidor para parar de enviar as cotações.
//+---------------------------------------------------------------------+ //|method that enables the reciept of new ticks as they become available| //+---------------------------------------------------------------------+ bool CBinarySymbol::StartTicksStream(void) { Comment("Starting live ticks stream for "+m_symbol_name+"......."); if(m_stream_id!="") StopTicksStream(); json.Clear(); json["subscribe"]=1; json["ticks"]=m_symbol_name; return(websocket.SendString(json.Serialize())); } //+------------------------------------------------------------------+ //|Used to cancel all tick streams that may have been initiated | //+------------------------------------------------------------------+ bool CBinarySymbol::StopTicksStream(void) { json.Clear(); json["forget_all"]="ticks"; if(websocket.SendString(json.Serialize())) { m_stream_id=NULL; if(websocket.ReadString(m_stream_id)>0) { m_stream_id=NULL; Comment("Stopping live ticks stream for "+m_symbol_name+"......."); return(true); } } return(false); } //+------------------------------------------------------------------+ //|Overridden method that handles new ticks streamed from binary.com | //+------------------------------------------------------------------+ void CBinarySymbol::AddTick(void) { string str_tick; MqlTick current_tick[1]; json.Clear(); if(websocket.ReadString(str_tick)) { json.Deserialize(str_tick); ZeroMemory(current_tick); if(CheckBinaryError(json)) return; if(!json["tick"]["ask"].ToDbl()) return; current_tick[0].ask=json["tick"]["ask"].ToDbl(); current_tick[0].bid=json["tick"]["bid"].ToDbl(); current_tick[0].last=0; current_tick[0].time=(datetime)json["tick"]["epoch"].ToInt(); current_tick[0].time_msc=(long)((json["tick"]["epoch"].ToInt())*1000); current_tick[0].volume=0; current_tick[0].volume_real=0; if(current_tick[0].ask) current_tick[0].flags|=TICK_FLAG_ASK; if(current_tick[0].bid) current_tick[0].flags|=TICK_FLAG_BID; if(m_stream_id==NULL) m_stream_id=json["tick"]["id"].ToStr(); if(CustomTicksAdd(m_symbol_name,current_tick)<0) { Print("failed to add new tick ", ::GetLastError()); return; } Comment("New ticks for "+m_symbol_name+"......."); } else { Print("read error ",websocket.LastError(), websocket.LastErrorMessage()); websocket.ResetLastError(); if(websocket.ClientState()!=CONNECTED && websocket.Connect(m_url)) { if(m_stream_id!=NULL) if(StopTicksStream()) { if(InitSymbol()) if(UpdateHistory()) { StartTicksStream(); return; } } } } //--- }
Abaixo está o código do Expert Advisor.
CBinarySymbol b_symbol; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { b_symbol.SetAppID(binary_appid); //--- if(!b_symbol.Initialize(EnumToString(binary_symbol))) return(INIT_FAILED); //--- if(!b_symbol.UpdateHistory()) return(INIT_FAILED); //--- if(!b_symbol.StartTicksStream()) return(INIT_FAILED); //--- create timer EventSetMillisecondTimer(500); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- stop the ticks stream b_symbol.StopTicksStream(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- b_symbol.AddTick(); } //+------------------------------------------------------------------+
Ambos EAs têm código similar, exceto pelo método UpdateHistory().

Conclusão
Usamos o Win32 API para criar um cliente WebSocket para o MetaTrader 5. Criamos uma classe que encapsula esta funcionalidade e demonstramos seu uso em um EA que interage com o WebSockets API da Binary.com.
| Pasta | Sumário | Descrição |
|---|---|---|
| MT5zip\Mt5zip\Mql5\include | JAson.mqh, websocket.mqh, winhttp.mqh | Os arquivos Include contêm o código para o analisador JSON (classe CJAval), o cliente WebSocket (classe CWebsocket), a função de importação WinHttp e as declarações de tipos |
| MT5zip\ Mt5zip\Mql5\ Experts | BinaryCustomSymboWithTickHistory.mq5, BinaryCustomSymbolWithBarHistory.mq5 | Experts que usam a classe CWebsocket para criar símbolos personalizados usando o WebSocket API da Binary.com |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/10275
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Desenvolvendo um EA de negociação do zero (Parte 11): Sistema CROSS ORDER
Desenvolvendo um EA de negociação do zero (Parte 12): Times And Trade (I)
Desenvolvendo um EA de negociação do zero (Parte 10): Acessando indicadores personalizados
- 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
Sim, boa tentativa :)
Eu também esperava que isso fosse possível, mas se você pesquisar no fórum, verá que a função no MQL é um cabo, não um endereço de memória, o que é exigido pelo "C/C++" calllback-API.
Talvez um dia a MQL acrescentaria o "real" Ponteiro de Função.
Sim, espero que em breve isto seja suportado nativamente.
@Francis Dube É possível criar um serviço MQL5 que atua como servidor WebSocket? Você tem alguns exemplos?
@Francis Dube É possível criar um serviço MQL5 que atua como servidor WebSocket? Você tem alguns exemplos?
é um cliente websocket e não um servidor.
Olá Francisco. Muito grato pelo valioso código fornecido. Descobri que ao solicitar o símbolo EURUSD, ou seja: "frxEURUSD" para o DERIV, ele apresenta o seguinte erro:
* "Nome do símbolo correspondente não encontrado, verifique o nome do símbolo em Binary.com"
Alguma ideia de por que o DERIV parece não reconhecer o símbolo que solicitamos? Fiquei preso no desenvolvimento/teste aqui. Muito obrigado :)