preview
Simulação de mercado (Parte 09): Sockets (III)

Simulação de mercado (Parte 09): Sockets (III)

MetaTrader 5Exemplos |
155 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Simulação de mercado (Parte 08): Sockets (II), começamos a desenvolver uma aplicação prática que faz uso de soquetes. Isto para que pudesse ser demonstrado o uso de tal ferramenta na programação voltada ao MetaTrader 5. É bem verdade que o MQL5, não nos permite criar um servidor diretamente usando MQL5 puro. Mas, como o uso de soquetes, independe de qualquer linguagem, ou mesmo do sistema operacional. Podemos ainda assim usar as coisas no MetaTrader 5. Isto fazendo a programação via MQL5.

Porém, por motivos internos, da própria plataforma MetaTrader 5. Não podemos utilizar indicadores junto com soquetes. Ou melhor dizendo: Não podemos colocar dentro do código de um indicador, chamadas a procedimentos que envolvem soquetes. O motivo disto, é que se isto fosse possível, poderíamos travar, ou comprometer a performance dos cálculos feitos dentro de indicadores.

No entanto, nada nos impede de ainda assim utilizar os indicadores para outras coisas. E é justamente isto que fizemos no artigo anterior. Onde criamos todo o nosso mini chat, incluindo os controles e o painel de texto, dentro de um indicador. Aqueles detalhes criados e colocados no indicador, não atrapalham em nada o fluxo de execução dos indicadores. Mas sem fazer uso de um indicador, seria bastante complicado, criar o que foi feito no artigo anterior. Isto por conta que acabaríamos interferindo em alguma região do gráfico do ativo que estaria sendo plotado.

Mas daquela forma, não temos tais problemas. Deixando assim o nosso mini chat, isolado em sua própria janela.

No entanto, aquele executável que estará sendo gerado no artigo anterior. Não é capaz de suprimir as nossas necessidades. Isto para que o mini chat de fato funcione. Precisamos implementar mais alguns detalhes. Aqui então vamos terminar de criar o suporte necessário para que tenhamos o mini chat funcionando no MetaTrader 5. Apesar de que, ainda assim, como foi dito no começo, o MQL5, não é capaz de suprimir, atualmente tudo que precisamos. Então será preciso, recorrermos a programação externa. Mas neste ponto, existe algo que poderá ser feito. Porém irei de fato explicar isto mais a frente neste mesmo artigo. Então vamos pôr as mãos na massa e finalizar a parte que será feita em MQL5.


Implementando a classe de conexão

Antes de olharmos o código fonte final do Expert Advisor. Que irá de fato permitir que o mini chat funcione. Vamos ver o arquivo de cabeçalho que implementará a classe de conexão. Este pode ser visto na íntegra logo abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_Connection
05. {
06.    private   :
07.       int      m_Socket;
08.       char     m_buff[];
09.       string   m_info;
10.       bool     m_NewLine;
11.    public   :
12. //+------------------------------------------------------------------+
13.       C_Connection(string addr, ushort port, ushort timeout = 1000)
14.          :m_Socket(INVALID_HANDLE),
15.           m_info(""),
16.           m_NewLine(true)
17.       {
18.          if ((m_Socket = SocketCreate()) == INVALID_HANDLE) Print("Unable to create socket. Error: ", GetLastError());
19.          else if (SocketConnect(m_Socket, addr, port, timeout)) return;
20.          else Print("Connection with the address [", addr,"] in port: ",port, " failed. Error code: ", GetLastError());
21.          SetUserError(1);
22.       }
23. //+------------------------------------------------------------------+
24.       ~C_Connection()
25.       {
26.          SocketClose(m_Socket);
27.       }
28. //+------------------------------------------------------------------+
29.       bool ConnectionWrite(string szMsg)
30.       {
31.          int len = StringToCharArray(szMsg, m_buff) - 1;
32. 
33.          if (m_Socket != INVALID_HANDLE)
34.             if (len >= 0)
35.                if (SocketSend(m_Socket, m_buff, len) == len)
36.                   return true;
37.          Print("Connection Write: FAILED...");
38. 
39.          return false;
40.       }
41. //+------------------------------------------------------------------+
42.       const string ConnectionRead(ushort timeout = 500)
43.       {
44.          int ret;
45. 
46.          if (m_NewLine)
47.          {
48.             m_info = "";
49.             m_NewLine = false;
50.          }
51.          ret = SocketRead(m_Socket, m_buff, SocketIsReadable(m_Socket), timeout);
52.          if (ret > 0)
53.          {
54.             m_info += CharArrayToString(m_buff, 0, ret);
55.             for (ret--; (ret >= 0) && (!m_NewLine); ret--)
56.                m_NewLine = (m_buff[ret] == '\n') || (m_buff[ret] == '\r');
57.          }
58.          
59.          return (m_NewLine ? m_info : "");
60.       }
61. //+------------------------------------------------------------------+
62. };
63. //+------------------------------------------------------------------+

Arquivo de cabeçalho C_Connection.mqh

Quero que você preste atenção a certos detalhes neste código. Isto por que, entender tais detalhes permitirá que você consiga realmente entender como fazer o mini chat funcionar. Nossa classe, contém algumas variáveis globais, que são privativas. Como você pode notar entre as linhas sete e dez. Observe que todas estas variáveis estão depois da cláusula private.

Então elas não poderão ser acessadas fora da classe. A primeira coisa que realmente fazendo, quando vamos utilizar esta classe é chamar o constructor da mesma. Então a primeira coisa a ser executada é a linha 13. Agora observe que nas linhas 14, 15 e 16 estamos fazendo algumas coisas. Isto que está sendo feito, é a inicialização das variáveis globais da classe. Um detalhe. Estou fazendo isto aqui, e desta forma, por motivos de costume. Mas principalmente para evitar de me esquecer de fazer isto, dentro do corpo do constructor. Acreditem, é muito comum, este tipo de esquecimento. Mas fazendo assim, não me esqueço de inicializar adequadamente as variáveis principais.

Muito bem. Então assim que o constructor inicia a sua execução. Isto na linha 18. Usamos uma chamada da biblioteca padrão para tentar criar um soquete. Se isto falhar, imprimiremos uma mensagem no MetaTrader 5. Caso tenhamos sucesso, iremos na linha 19, tentar nos conectar ao soquete indicado pelos parâmetros. Mas diferente do que aconteceu, na linha anterior. Aqui se tivermos sucesso, retornaremos imediatamente. O motivo disto, é por que, a conexão foi estabelecida. E em caso de falha, a linha 20 nos informará o que aconteceu, e o motivo da falha.

De qualquer forma, se não for possível ou criar um soquete, ou mesmo nos conectar a ele. A linha 21 irá de fato nos permitir informar ao código principal. Que no caso será o Expert Advisor, que o processo de conexão falhou. Quando formos ver o código do Expert Advisor, explicarei como você poderá saber disto. Mas a linha 21, somente será executada, em caso de falha durante o processo de tentativa de conexão.

Já o destructor, que se encontra na linha 24, tem como único objetivo, fechar o soquete. Por conta disto, que ele tem apenas uma única linha em seu corpo. Esta é a linha 26, que liberará o soquete que estávamos utilizando. Caso ele tenha sido criado.

Agora vamos ver as duas funções principais da nossa classe. A primeira delas é a responsável por enviar dados pela conexão. Isto mesmo, estamos falando da função de escrita, que se encontra na linha 29. Esta função precisa ser implementada, assim como a próxima, com uma certa calma. Isto por que todo o código não mudará em nada. Independentemente do que você queira enviar. Seja imagem, sons, vídeo ou texto como será feito aqui. O que realmente muda é apenas estas duas funções. A de escrita e a de leitura. No caso, aqui estamos fazendo a coisa da maneira o mais simples possível. Então não contaremos com criptografia, ou qualquer outro tipo de segurança no envio das informações. O motivo é simples. Estamos apenas testando e aprendendo como soquetes funcionam.

Então, dado o fato de que iremos de fato transmitir apenas texto. A primeira coisa a ser feita, é saber quantos caracteres serão transmitidos. Isto é feito na linha 31. O fato de estamos subtraindo um, no número de caráteres se deve ao fato de que a string recebida, segue o padrão C/C++. Ou seja, ela é terminada em um carácter nulo. Este carácter não será transmitido por nos. Já que pode ter ocorrido, do constructor ter falhado, durante a criação ou mesmo conexão, precisamos verificar se ele é válido. Isto antes de tentar transmitir algo por ele. Este teste é feito na linha 33. Caso o soquete seja invalido por qualquer motivo a transmissão não ocorrerá.

Testado isto, temos que testar uma outra coisa. Desta vez precisamos testar o número de caracteres a serem transferidos. Isto por que não faz sentido, tentar enviar algo, se não existe nada para ser enviado. Este teste é feito na linha 34. Se todos estes testes passarem com sucesso. Na linha 35, tentaremos enviar os dados desejados, pelo soquete aberto. No caso, estamos enviando via conexão normal. Ou seja, não estamos usando nenhum processo de criptografia ou autenticação. Mas o MQL5, nos permite fazer envios seguros. Estude a documentação para entender melhor a este respeito.

Caso tenhamos sucesso em enviar os dados, retornaremos na linha 36 um valor verdadeiro para o chamador. Caso qualquer um dos testes tenha falhado, na linha 37 iremos imprimir uma mensagem no MetaTrader 5, e na linha 39 retornaremos um valor falso para o chamador.

Agora vamos ver a função de leitura. Esta se encontra na linha 42. Aqui vale uma pequena pausa para explicar uma coisa. Cada função de escrita, deverá ter uma função de leitura correspondente. Não que isto seja obrigatório. Mas pense por um instante no seguinte cenário. Você poderá criar diversas funções para envio de dados diferentes. De certa forma é adequado, que os dados sejam recebidos por funções adequadas.

Ou melhor dizendo, por que você iria tentar ler um dado que representa um vídeo, em uma função para ler texto? Não faz sentido, não é mesmo. Por isto, procure sempre adequar, as coisas. Tanto com relação ao tipo de dados que estará sendo transmitido. Quanto ao tipo de protocolo que estará sendo usando.

Esta parte do protocolo nada tem a ver com uma conexão do tipo TCP ou UDP. E sim com relação a forma como a informação foi estruturada antes de ser transmitida. Pense sempre que os dados no soquete, se parecem com um arquivo que você estará lendo. Para entender corretamente o conteúdo, é preciso usar o protocolo correto de leitura.

Não adianta você tentar ler um bitmap, usando um protocolo para ler uma imagem JPEG. Apesar de ambos serem imagens, a forma como ela estará estruturada dentro do arquivo é completamente diferente. A mesma coisa se aplica aqui, quando o assunto é soquetes. Dado deste recado, vamos então a rotina de leitura do soquete.

Diferente do que foi visto no artigo Simulação de mercado (Parte 07): Sockets (I) onde produzíamos um eco no servidor. Aqui não ficamos necessariamente travados esperando o servidor responder. Na verdade, ficamos um pequeno tempo esperando o mesmo nos responder. Mas a questão é que independentemente de ele nos responder de forma, mais ou menos rápida, retornaremos ao chamador. Isto mesmo antes de toda a mensagem ter sido lida no soquete.

Mas como? Podemos voltar ao nosso código do Expert Advisor, mesmo antes de ler totalmente o soquete? Sim, podemos. Mas temos que tomar alguns cuidados ao fazer isto. Por isto eu disse a pouco, para que você se atente, a tratar de maneira adequada o tipo de informação esperada no soquete. Aqui na nossa função de leitura, por padrão iremos aguardar 500 milissegundos antes de retornar. Caso este tenho tenha se passado, ainda assim retornaremos ao código principal. Mesmo que nenhuma informação esteja no soquete.

Mas caso durante este tempo, alguma informação venha a aparecer, iremos ler a mesma na linha 51. Agora preste muita atenção ao seguinte fato. Isto é importante caso você venha a implementar o seu próprio servidor. Na linha 54, adicionamos o conteúdo que estiver no soquete a nossa string de retorno. Mas, porém, toda via e, entretanto, esta string de retorno somente e tão somente retornará algo para o chamador. Se e somente se, for encontrado um carácter de nova linha ou de retorno do carro na cadeia de caracteres. Para atestar isto, fazemos uso do laço na linha 55. Este irá varrer o conteúdo recebido, atrás dos caracteres acima mencionados.

Quando um destes caracteres forem encontrados, a variável m_NewLine receberá um valor verdadeiro. Assim na linha 59 será permitido que o conteúdo lido no soquete seja repassado ao chamador. Da mesma forma, quando uma nova chamada ocorrer, o teste na linha 46 permitirá que o conteúdo da string seja limpo. Assim um novo ciclo recomeça.

Agora preste atenção: No código da função de escrita no soquete. Que se encontra na linha 29. Não adicionamos tais caracteres esperados pela função de leitura. E o objeto OBJ_EDIT não adiciona tais caracteres. Da mesma forma que os códigos que efetuam as chamadas a função ConnectionWrite, também não o fazem. Então quem está adicionando tais caracteres? Quem fará isto será o servidor. Assim, se você for implementar o seu próprio servidor, deverá adicionar tais caracteres. Caso contrário o mini chat, não conseguirá distinguir entre uma mensagem postada e outra.

O fato de estarmos fazendo isto, desta forma, parece até um pouco de burrice e besteira. Mas fazendo assim, nos permite fazer algo que você poderá ver nos vídeos que estão neste artigo. Muito bem. Agora que já vimos o código da classe de conexão. Vamos ver o código do Expert Advisor.


Implementando o Expert Advisor

O código completo do Expert Advisor para implementar o mini chat, pode ser visto na íntegra logo abaixo:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Mini Chat for demonstration of using sockets in MQL5."
04. #property link      "https://www.mql5.com/pt/articles/12673"
05. #property version   "1.00"
06. //+------------------------------------------------------------------+
07. #define def_IndicatorMiniChat   "Indicators\\Mini Chat\\Mini Chat.ex5"
08. #resource "\\" + def_IndicatorMiniChat
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Mini Chat\C_Connection.mqh>
11. #include <Market Replay\Defines.mqh>
12. //+------------------------------------------------------------------+
13. input string   user00 = "127.0.0.1";      //Address
14. input ushort   user01 = 27015;            //Port
15. //+------------------------------------------------------------------+
16. long    gl_id;
17. int    gl_sub;
18. C_Connection *Conn;
19. //+------------------------------------------------------------------+
20. int OnInit()
21. {
22.    Conn = new C_Connection(user00, user01);
23.    if (_LastError > ERR_USER_ERROR_FIRST)
24.       return INIT_FAILED;   
25.    ChartIndicatorAdd(gl_id = ChartID(), gl_sub = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_IndicatorMiniChat));
26. 
27.    EventSetTimer(1);
28. 
29.    return INIT_SUCCEEDED;
30. }
31. //+------------------------------------------------------------------+
32. void OnDeinit(const int reason)
33. {
34.    EventKillTimer();
35.    delete Conn;
36.    ChartIndicatorDelete(gl_id, gl_sub, ChartIndicatorName(gl_id, gl_sub, 0));
37. }
38. //+------------------------------------------------------------------+
39. void OnTick()
40. {
41. }
42. //+------------------------------------------------------------------+
43. void OnTimer()
44. {
45.    string sz0 = (*Conn).ConnectionRead();
46.    if (sz0 != "")
47.       EventChartCustom(gl_id, evChatReadSocket, 0, 0, sz0);
48. }
49. //+------------------------------------------------------------------+
50. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
51. {
52.    if (id == CHARTEVENT_CUSTOM + evChatWriteSocket)
53.       (*Conn).ConnectionWrite(sparam);
54. }
55. //+------------------------------------------------------------------+

Código fonte do Expert Advisor

Observe que o código é bastante simples. Praticamente não precisamos programar muita coisa aqui. Mas mesmo assim existem alguns cuidados a serem tomados. Observe que na linha sete e oito, estamos adicionando o indicador que foi criado no artigo anterior, ao código do Expert Advisor. Isto de forma que uma vez compilado o código do Expert Advisor. O executável do indicador poderá ser deletado. Isto por que ele estará embutido no Expert Advisor. No artigo anterior expliquei os motivos de tal medida. O código se passa sem muitas mudanças. Isto perto do que foi visto no artigo anterior.

O detalhe agora é que foi incluída uma chamada a fim de receber eventos do relógio. Isto é feito na linha 27. Então a cada, aproximadamente um segundo, teremos a execução do código responsável por tratar eventos do relógio. Isto é, teremos uma chamada a OnTime. O que realmente nos interessa neste código do Expert Advisor, é justamente está chamada OnTime e a chamada OnChartEvent. Esta duas é que permitem o mini chat funcionar, da forma como ele está sendo implementado. Então vamos primeiro entender o procedimento OnTime.

Uma vez que o MetaTrader 5, dispare um evento OnTime, nosso Expert Advisor interceptará tal chamada. Então na linha 45, faremos uma tentativa de ler o soquete que estamos indicando na linha 13 e na linha 14. É isto mesmo, estaremos nos conectando a uma rede. Isto foi explicado a dois artigos atrás, onde falei sobre soquetes. Caso tenha dúvidas a respeito, leia o artigo Soquetes (I).

Ok. A tentativa de leitura, poderá retornar alguma string, ou não. Caso tenhamos um retorno por parte da função de leitura. O teste na linha 46 terá sucesso. Assim iremos disparar um evento customizado. Este se encontra na linha 47. O intuito deste evento é justamente permitir que o indicador mini chat, consiga acesso à informação postada no soquete. Caso o mini chat, estivesse implementado aqui no Expert Advisor, tal evento customizado seria completamente desnecessário. Mas como separamos as coisas. É preciso enviar os dados para o indicador. Assim ele irá tratar de apresentar a informação para nós no painel de texto. Veja como é algo bastante curioso. Isto por que, dependendo do que estaremos querendo apresentar, deveremos nos adequar a melhor maneira de implementar a solução.

Quanto ao procedimento OnTime, não existem mais detalhes a respeito dele. É simples assim. Da mesma forma, também temos algo bastante simples no procedimento OnChartEvent. Só que desta vez, iremos interceptar um pedido do indicador do mini chat, para poder escrever no soquete. Para verificar se temos uma chamada deste tipo, usamos o teste da linha 52. Se for confirmado que estamos lidando com um evento customizado, a linha 53, irá repassar para a função de escrita, os dados que o indicador de mini chat nos informou.

Neste sistema de comunicação temos um detalhe. O mesmo pode não estar tão claro, para quem não tem muita familiaridade com programação de eventos. O detalhe é que se um outro programa qualquer, seja um outro indicador, ou mesmo um script, e até mesmo um serviço. Disparar o evento customizado cujo valor seja o mesmo que evChatReadSocket, o indicador mini chat interpretará isto como se viesse do Expert Advisor. A mesma coisa se dá, caso um evento customizado, disparado tenha o mesmo valor que evChatWriteSocket. O Expert Advisor, interpretará as coisas como se o evento estivesse vindo do indicador mini chat.

Não sei se você, caro leitor, notou as possibilidades que isto abre. Ainda mais se você, souber qual o endereço e qual é a porta que deverá receber os dados que você colocará no soquete. Assim como também poder ler um endereço ou uma determinada porta. Este tipo de coisa, abre a possibilidade de você fazer um pequeno xereta, que fica observando o que está acontecendo em algum lugar específico. Por conta disto que temos sempre de criptografar e codificar adequadamente as coisas. Mas como aqui estamos apenas usando algo para demonstração, o perigo de algo vazar será minimizado. Sendo praticamente zero as chances de algo vazar. Como você poderá ver nos vídeos, você poderá testar uma conexão de rede. Mesmo sem ter uma rede instalada.

Mas como tudo até aqui, faz parte do MQL5. Você provavelmente estará satisfeito com os resultados e conseguirá implementar o servidor. Mas para quem não sabe, ou não tem a mínima ideia de como implementar um servidor. Vamos ver isto em um novo tópico. Apenas como curiosidade.


Implementando o servidor para o mini chat

Implementar um servidor está longe de ser uma tarefa complexa. Na verdade, é até bastante simples. Porém, implementar um servidor é diferente de colocar ele em funcionamento. Implementar tem a ver com criar o código. Por ele em funcionamento, tem a ver com não deixar ele vazar algo que ele não poderia dar acesso inicialmente.

Aqui não irei nem de longe tratar da parte em termos de colocar o servidor em funcionamento. Irei pura e simplesmente tratar da parte da implementação. Mas quero deixar claro, que o código que será visto. Não é e nem mesmo deverá ser usado para propósito de produção. O código mostrado é um código cujo objetivo é ser didático. Ou seja, não use ele sem entender de fato o que está fazendo. Porém o use, como uma forma de aprender e entender como realmente um servidor é implementado. O código completo pode ser visto logo abaixo, na íntegra.

001. #define WIN32_LEAN_AND_MEAN
002. 
003. #include <winsock2.h>
004. #include <iostream>
005. #include <sstream>
006. #include <stdlib.h>
007. 
008. #pragma comment (lib, "Ws2_32.lib")
009. 
010. #define DEFAULT_BUFLEN   256
011. #define DEFAULT_PORT     27015
012. 
013. using namespace std;
014. 
015. int __cdecl main(void)
016. {
017.    WSADATA wsData;
018.    SOCKET listening, slave;
019.    sockaddr_in hint;
020.    fd_set master;
021.    bool Looping = true;
022.    int ConnectCount;
023.    string szMsg;
024. 
025.    if (WSAStartup(MAKEWORD(2, 2), &wsData) != EXIT_SUCCESS)
026.    {
027.       cout << "Can't Initialize WinSock! Quitting..." << endl;
028.       return EXIT_FAILURE;
029.    }
030.    if ((listening = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
031.    {
032.       cerr << "Can't create a socket! Quitting" << endl;
033.       WSACleanup();
034.       return EXIT_FAILURE;
035.    }
036.    else
037.       cout << "Creating a Socket..." << endl;
038. 
039.    hint.sin_family = AF_INET;
040.    hint.sin_port = htons(DEFAULT_PORT);
041.    hint.sin_addr.S_un.S_addr = INADDR_ANY;
042. 
043.    if (bind(listening, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR)
044.    {
045.       cout << "Bind failed with error:" << WSAGetLastError() << endl;
046.       WSACleanup();
047.       return EXIT_FAILURE;
048.    }
049.    else
050.       cout << "Bind success..." << endl;
051. 
052.    if (listen(listening, SOMAXCONN) == SOCKET_ERROR)
053.    {
054.       cout << "Listen failed with error:" << WSAGetLastError() << endl;
055.       WSACleanup();
056.       return EXIT_FAILURE;
057.    }
058.    else
059.       cout << "Listen success..." << endl;
060.    
061.    FD_ZERO(&master);
062.    FD_SET(listening, &master);
063.    cout << "Waiting for client connection" << endl;
064. 
065.    while (Looping)
066.    {
067.       fd_set tmp = master;
068. 
069.       ConnectCount = select(0, &tmp, nullptr, nullptr, nullptr);
070.       for (int c0 = 0; c0 < ConnectCount; c0++)
071.       {
072.          if ((slave = tmp.fd_array[c0]) == listening)
073.          {
074.             SOCKET NewClient = accept(listening, nullptr, nullptr);
075. 
076.             szMsg = "Sent by SERVER: Welcome to Mini Chart\r\n";
077.             FD_SET(NewClient, &master);
078.             send(NewClient, szMsg.c_str(), (int)szMsg.size() + 1, 0);
079.             cout << "Client #" << NewClient << " connecting..." << endl;
080.          }
081.          else
082.          {
083.             char buff[DEFAULT_BUFLEN];
084.             int bytesIn;
085. 
086.             ZeroMemory(buff, DEFAULT_BUFLEN);
087.             if ((bytesIn = recv(slave, buff, DEFAULT_BUFLEN, 0)) <= 0)
088.             {
089.                closesocket(slave);
090.                cout << "Client #" << slave << " disconnected..." << endl;
091.                FD_CLR(slave, &master);
092.             }
093.             else
094.             {
095.                if (buff[0] == '\\') //Check command ...
096.                {
097.                   szMsg = string(buff, bytesIn);
098. 
099.                   if (szMsg == "\\shutdown")
100.                   {
101.                      Looping = false;
102.                      break;
103.                   }
104.                   continue;
105.                }
106.                for (u_int c1 = 0; c1 < master.fd_count; c1++)
107.                {
108.                   SOCKET out = master.fd_array[c1];
109. 
110.                   if ((out != listening) && (out != slave))
111.                   {
112.                      ostringstream s1;
113. 
114.                      s1 << "Client #" << slave << ": " << buff << "\r\n";
115.                      send(out, s1.str().c_str(), (int)s1.str().size() + 1, 0);
116.                   }
117.                }
118.             }
119. 
120.          }
121.       }
122.    }
123. 
124.    FD_CLR(listening, &master);
125.    closesocket(listening);
126.    szMsg = "Server is shutting down. Goodbye\r\n";
127.    while (master.fd_count > 0)
128.    {
129.       slave = master.fd_array[0];
130.       send(slave, szMsg.c_str(), (int)szMsg.size() + 1, 0);
131.       FD_CLR(slave, &master);
132.       closesocket(slave);
133.    }
134. 
135.    WSACleanup();
136. 
137.    return EXIT_SUCCESS;
138. }

Código fonte do servidor em C++

Um detalhe sobre este código do servidor. Apesar de estar escrito em C++, você deverá compilar ele usando as ferramentas do Visual Studio. Mas se você compreender o que está sendo feito, poderá modificar ele a ponto de poder compilar este código usando o GCC. Mas não fique muito apegado a este código. Procure entender o se funcionamento. Pois é isto que realmente importa.

Então vou explicar alguns pontos principais. Isto para quem não tem tanta familiaridade com C++ e programação de soquetes usando C/C++. A primeira linha assim com a linha oito, são relacionadas ao compilador do Visual Studio. A linha 10 define o tamanho do buffer a ser usado na leitura do soquete. Você pode usar um buffer maior ou menor. Mas tenha em mente que se o buffer for muito pequeno, você terá que fazer leituras múltiplas a fim de obter a informação completa do soquete. De qualquer forma para o que estamos fazendo. Que tem como objetivo apenas didático, um buffer de 256 caracteres é o suficiente.

Já a linha 11, está sim é importante para nós. Pois ela define qual será a porta a ser utilizada. Poderíamos fazer isto de outra forma, via argumento da linha de comando. Mas a ideia aqui é ser o mais simples possível. Para que a explicação possa ocorrer sem dificuldades. Muito bem, entre as linhas 17 e 23 declaro algumas variáveis que serão utilizadas de forma mais constante. Apesar de ter colocado todo o código dentro do corpo da função main. O melhor seria dividir o código em pequenas rotinas. Mas como este servidor é muito simples. Podemos nos dar ao luxo de fazer tudo dentro do procedimento main.

Agora observe uma coisa. A cada passo que é dado, a fim de configurar o soquete. E permitir que o servidor consiga ouvir o que está se passando em uma porta. Isto para que ocorra uma conexão com algum cliente. Estou colocando uma mensagem no console. Então quando o soquete foi criado, temos na linha 37 a confirmação desta informação no console. Note que precisamos executar dois passos para fazer isto. O primeiro é usar a chamada WSAStartup e o segundo a chamada socket propriamente dita. Sistema como o Linux, não precisam fazer uso da chamada WSAStartup. E mesmo alguns modelos de implementação de soquetes no Windows não usam esta chamada. Porém em grande parte das vezes é bom usar a chamada WSAStartup quando estiver no Windows.

De qualquer maneira, o soquete será criado. Então entre as linhas 39 e 41, criamos uma configuração dele. Esta configuração diz, qual a porta a ser usada e qual o endereço a ser observado. Assim como o tipo do soquete. Como estamos experimentando o soquete, iremos permitir que qualquer endereço consiga se conectar a ele. Mas é possível que você selecione o endereço. Mas para servidores, normalmente deixamos qualquer um habilitado. Novamente, saber configurar esta parte é importante. Pois se você não fizer as coisas da forma correta, poderá acabar abrindo as portas do inferno. Feito isto, precisamos dizer ao sistema como o soquete será configurado. Isto é feito na linha 43.

Já na linha 52, configuramos o número máximo de conexões, abertas ao mesmo tempo, que o servidor aceitará. Tal passo não tem nada complicado. A partir deste ponto, o servidor estará configurado e pronto para aceitar conexões. Então precisamos preparar algumas coisas, a fim de conseguir acompanhar as conexões que irão acontecer. Isto é feito nas linhas 61 e 62. A ideia aqui é criar um tipo de matriz dinâmica que conterá todas as conexões abertas. Isto para que o servidor possa trabalhar como você pode ver os vídeos de demonstração.

Uma vez feito isto, entramos no loop na linha 65. Este loop poderá ser encerrado pelo cliente. Como você pode ver no vídeo de demonstração. Mas existem algumas coisas que quero explicar dentro deste loop. A maior parte diz respeito a ler o que um cliente postou no soquete e repassar a todos os demais clientes. Não sendo assim algo que merece tanto destaque.

Porém durante a explicação do código em MQL5, mencionei que é importante termos um carácter de retorno de carro, ou um carácter de nova linha. E que o cliente não colocava tais caracteres na mensagem. Que isto era feito pelo servidor. Agora que estamos olhando o código do servidor. Veja na linha 114 onde colocamos os tais caracteres necessários para o cliente em MQL5, saber que a mensagem foi totalmente recebida. Observe que se isto não for feito, aqui no servidor. O mini chat, feito em MQL5 não funcionará adequadamente.

Esta é a parte, que se você deverá selar caso venha a implementar seu próprio servidor. Você tem que garantir que o servidor adicione estes mesmos caracteres. A ordem não importa, mas eles precisam estar no final da mensagem.

Agora vem a parte realmente interessante. Um pouco acima eu falei, e é possível ver no vídeo de demonstração, que o cliente consegue desligar o servidor. Mas como isto acontece? Simples. Observe a linha 95. Nela, dizemos que existirá um carácter no começo da mensagem que indicará um comando para o servidor executar. Caso este teste tenha sucesso, teremos a análise do comando. E a mensagem postada não será retransmitida a outros clientes conectados. Agora na linha 99 testamos um destes comandos. No caso ele é case sensitive. Ou seja, o comando tem que ser dado da maneira exata esperada pelo servidor. Se isto ocorrer, na linha 101, diremos ao servidor para encerrar suas atividades e fechar todas as conexões abertas.

Viram como é algo bastante simples. Apesar de tudo, você deve primeiramente ver que este é um código para fins didático. Já que qualquer cliente pode enviar o comando que será executado pelo servidor. Em um servidor real, haveria níveis de autenticação a fim de impedir isto. Além é claro alguns outros cuidados, que aqui não são necessários.


Considerações finais

Aqui neste artigo, finalizamos a apresentação de como você pode usando programação externa e MQL5, produzir um mini chat. Este tem como finalidade ser apenas para fins didáticos. Podendo ser modificado, melhorado e até mesmo usando entre amigos como uma forma de fazer algo bem mais profissional. Aproveitem bem este conhecimento. No anexo, você encontrará o código em MQL5 do mini chat já pronto e compilado. Tudo que você precisará fazer é criar o servidor. Que eu prefiro deixar você mesmo criar o código. Já que isto é uma questão que envolve abrir portas de seu computador. O programa usado no vídeo em conjunto com o mini chat é o PuTTY.

ArquivoDescrição
Experts\Expert Advisor.mq5
Demonstra a interação entre o Chart Trade e o Expert Advisor (É necessário o Mouse Study para interação)
Indicators\Chart Trade.mq5Cria a janela para configuração da ordem a ser enviada (É necessário o Mouse Study para interação)
Indicators\Market Replay.mq5Cria os controles para interação com o serviço de replay/simulador (É necessário o Mouse Study para interação)
Indicators\Mouse Study.mq5Permite interação entre os controles gráficos e o usuário (Necessário tanto para operar o replay simulador, quanto no mercado real)
Services\Market Replay.mq5Cria e mantém o serviço de replay e simulação de mercado (Arquivo principal de todo o sistema)
Code VS C++\Servidor.cppCria e mantém um soquete servidor criado em C++ (Versão Mini Chat)
Code in Python\Server.pyCria e mantém um soquete em python para comunicação entre o MetaTrader 5 e o Excel
Scripts\CheckSocket.mq5Permite efetuar um teste de conexão com um soquete externo
Indicators\Mini Chat.mq5Permite implementar um mini chat via indicador (Necessário uso de um servidor para funcionar)
Experts\Mini Chat.mq5Permite implementar um mini chat via Expert Advisor (Necessário uso de um servidor para funcionar)
Arquivos anexados |
Anexo.zip (560.03 KB)
Optimização por nuvens atmosféricas — Atmosphere Clouds Model Optimization (ACMO): Prática Optimização por nuvens atmosféricas — Atmosphere Clouds Model Optimization (ACMO): Prática
Neste artigo, continuaremos a explorar a implementação do algoritmo ACMO (Atmospheric Cloud Model Optimization). Em particular, discutiremos dois aspectos-chave: o movimento das nuvens para regiões de baixa pressão e a modelagem do processo de chuva, incluindo a inicialização das gotas e sua distribuição entre as nuvens. Analisaremos também outros métodos importantes para a gestão do estado das nuvens e para garantir sua interação com o ambiente.
Do básico ao intermediário: Eventos (I) Do básico ao intermediário: Eventos (I)
Com base em tudo que já foi mostrado e visto até este ponto. Acredito que já podemos começar a implementar algum tipo de aplicação para ser executada diretamente no gráfico de algum ativo. Mas antes mesmo de podermos fazer isto, precisamos falar de uma coisa que para iniciantes é bastante confusa. Que é justamente o fato de que o aplicações desenvolvidas em MQL5, e voltadas para serem vistas em um gráfico, não são criadas da mesma forma que vimos até este momento. Neste artigo começaremos a entender um pouco melhor sobre isto.
Redes neurais em trading: Explorando a estrutura local dos dados Redes neurais em trading: Explorando a estrutura local dos dados
A identificação eficaz e a preservação da estrutura local dos dados de mercado em meio ao ruído são tarefas cruciais no trading. Embora o uso do mecanismo Self-Attention tenha mostrado bons resultados no processamento desses dados, o método clássico não leva em conta as características locais da estrutura original. Neste artigo, proponho conhecer um algoritmo capaz de considerar essas dependências estruturais.
Abordagem quantitativa na gestão de riscos: aplicação do modelo VaR para otimização de portfólio multimoeda com Python e MetaTrader 5 Abordagem quantitativa na gestão de riscos: aplicação do modelo VaR para otimização de portfólio multimoeda com Python e MetaTrader 5
Neste artigo, revelamos o potencial do modelo Value at Risk (VaR) para a otimização de portfólios multimoeda. Utilizando o Python e as funcionalidades do MetaTrader 5, demonstramos como implementar a análise VaR para uma distribuição eficiente de capital e gerenciamento de posições. Desde os fundamentos teóricos até a implementação prática, o artigo abrange todos os aspectos da aplicação de um dos sistemas mais robustos de cálculo de risco — o VaR — no trading algorítmico.