
Criando um Expert Advisor Integrado com MQL5-Telegram (Parte 3): Enviando Capturas de Tela de Gráficos com Legendas de MQL5 para o Telegram
Introdução
No artigo anterior, a segunda parte da nossa série, examinamos cuidadosamente o processo de integração da MetaQuotes Language 5 (MQL5) com o Telegram para geração e envio de sinais. O resultado foi claro: permitiu-nos enviar sinais de trading para o Telegram e, claro, a necessidade de usar esses sinais de trading para que o processo fosse eficaz. Então, por que devemos dar o próximo passo no processo de integração? O que fazemos nesta terceira parte da série é, de fato, um "próximo passo" ao ilustrar o potencial de integrar o MQL5 com Telegram no envio de sinais de trading. No entanto, em vez de enviar apenas a parte de texto do sinal de trading, enviamos uma captura de tela do gráfico associado ao sinal de trading. Às vezes, é melhor não apenas receber um sinal no qual se pode agir, mas também visualizar a configuração do sinal, como as configurações de ação de preço no gráfico, por meio de uma representação visual, neste caso, a captura de tela do gráfico.
Assim, neste artigo, vamos focar nos detalhes de como converter os dados de imagem em um formato compatível para incorporar nas requisições do HyperText Transfer Protocol Secure (HTTPS). Essa conversão deve ocorrer se quisermos incluir imagens no nosso bot do Telegram. Vamos detalhar o processo que nos leva de um gráfico no MQL5, passando pelo terminal de trading MetaTrader 5 até uma mensagem artificalmente organizada do bot, com uma legenda e uma imagem do gráfico como parte visualmente impressionante da nossa notificação de trading. Este artigo será dividido em quatro partes.
Para começar, daremos uma visão geral de como funciona a codificação e transmissão de imagens via HTTPS. Nesta primeira seção, explicaremos os conceitos fundamentais envolvidos e as técnicas usadas para realizar essa tarefa. Em seguida, vamos mergulhar no processo de implementação em MQL5, a linguagem de programação usada para escrever programas de trading na plataforma MetaTrader 5. Detalharemos como usar os métodos de codificação e transmissão de imagens. Depois disso, testaremos o programa implementado para verificar se ele funciona corretamente. Concluiremos o artigo com um resumo, revisitando os pontos principais e descrevendo os benefícios de realizar esse trabalho em sistemas de trading. Aqui estão os tópicos que seguiremos para criar o Expert Advisor (EA):
- Visão geral da Codificação e Transmissão de Imagens via HTTPS
- Implementação no MQL5
- Testando a Integração
- Conclusão
Ao final, teremos criado um Expert Advisor que envia capturas de tela de gráficos e imagens com informações de trading, como sinais gerados e ordens realizadas, do terminal de trading para o chat especificado no Telegram. Vamos começar então.
Visão geral da Codificação e Transmissão de Imagens via HTTPS
Enviar imagens pela Internet e, particularmente, integrar com APIs (Interfaces de Programação de Aplicações) ou plataformas de mensagens exige que os dados da imagem sejam primeiro codificados e depois enviados sem atraso ou comprometimento da segurança. Um arquivo de imagem direto envia muitos bits e bytes, o que torna difícil o processamento por comandos que permitem que um usuário da Internet acesse um site ou serviço específico. Para uma API como o Telegram, que serve como intermediário entre um usuário e um serviço (como uma interface baseada na web para várias tarefas), enviar uma imagem exige que o arquivo da imagem seja primeiro codificado e depois enviado como parte da carga útil de um comando de usuário para serviço ou vice-versa, e isso é conseguido especialmente através de protocolos como HTTP ou HTTPS.
A abordagem mais comum para converter imagens para envio é transformar a imagem em uma string Base64 codificada. A codificação Base64 pega os dados binários de uma imagem e cria uma representação textual. Isso é feito usando caracteres de um conjunto específico que faz a chamada "imagem codificada" funcionar corretamente quando enviada por protocolos de texto. Para criar a imagem codificada em Base64, seus dados brutos (o arquivo real, antes de qualquer operação de "leitura") são lidos byte a byte. Os bytes lidos são então representados por símbolos Base64. Uma vez feito isso, o arquivo pode ser enviado via protocolo de texto.
Depois que os dados da imagem são codificados, eles são enviados via HTTPS, que é uma forma segura de HTTP. Ao contrário do HTTP, que envia dados em texto simples, o HTTPS usa o protocolo de criptografia Secure Socket Layer (SSL)/Transport Layer Security (TLS) para garantir que os dados passados de e para um servidor permaneçam privados e seguros. A importância do HTTPS para a proteção de sinais de trading e outras comunicações relacionadas a finanças não pode ser exagerada. Uma terceira parte sem escrúpulos que conseguir interceptar sinais de trading, por exemplo, pode usar essas informações para fazer negociações e manipular o mercado, prejudicando as vítimas inocentes dos sinais de trading e beneficiando a parte que interceptou o sinal. A visualização do processo é a seguinte:
Resumindo, o método de codificação e envio de imagens converte arquivos de imagem em um formato baseado em texto, adequado para comunicações na web. Ele também garante a entrega segura via HTTPS. É vital entender esses dois conceitos se alguém quiser integrar dados de imagem em aplicativos. Um exemplo óbvio são sistemas de trading que automatizam notificações através do Telegram—uma plataforma que faz um excelente trabalho de entregar mensagens de forma rápida e confiável.
Implementação no MQL5
A implementação do envio de imagens em MQL5 começará com o processo de capturar uma captura de tela do gráfico de trading atual dentro de um Expert Advisor (EA) em MQL5. Vamos codificar a captura de tela e enviá-la via Telegram. Implementamos essa funcionalidade principalmente no manipulador de eventos OnInit, que é executado quando o EA é inicializado. Como já mencionamos, o objetivo do manipulador de eventos OnInit é preparar e configurar os componentes necessários do EA, garantindo que tudo esteja corretamente configurado antes que a lógica principal da operação de trading seja executada. Primeiro, definimos o nome do arquivo da captura de tela da nossa imagem.
//--- Get ready to take a chart screenshot of the current chart #define SCREENSHOT_FILE_NAME "Our Chart ScreenShot.jpg"
Aqui, damos o primeiro passo no fluxo de trabalho, que é estabelecer uma constante para o nome do arquivo da captura de tela. Conseguimos isso com a diretiva #define, que nos permite atribuir um valor constante que pode ser referenciado ao longo do código. Aqui, criamos uma constante chamada "SCREENSHOT_FILE_NAME", que armazena o valor "Our Chart ScreenShot.jpg". E estamos fazendo isso por um motivo muito bom: Se precisarmos do nome do arquivo para carregar ou salvar algo, ele pode usar apenas essa constante. Se precisarmos mudar o nome ou o formato do arquivo, só precisamos alterá-lo neste único lugar. Você pode perceber que escolhemos o tipo de imagem para ser um Joint Photographic Experts Group (JPEG). Você pode escolher qualquer formato que julgue adequado, como Portable Network Graphics (PNG). No entanto, deve lembrar que existem diferenças significativas nos formatos de imagem. Por exemplo, o JPG usa um algoritmo de compressão com perdas, o que significa que alguns dados da imagem são perdidos, mas o tamanho da imagem é reduzido. Um exemplo dos formatos que você pode usar está visualizado abaixo:
Integrando a função de captura de tela no manipulador OnInit. Isso garante que o sistema esteja pronto para capturar e salvar o estado do gráfico assim que o Expert Advisor for iniciado. Declaramos a constante "SCREENSHOT_FILE_NAME", que serve como um substituto para o nome real do arquivo de imagem do gráfico. Usando esse marcador, evitamos o erro de tentar salvar dois arquivos com o mesmo nome quase ao mesmo tempo. Dessa forma, garantimos que o arquivo de imagem do gráfico tenha a estrutura básica necessária quando formos codificar e transmitir a imagem neste ponto.
Este passo é essencial porque define a convenção de nomenclatura de arquivos e garante que seremos capazes de capturar a imagem do gráfico quando o EA for inicializado. A partir deste momento, podemos nos concentrar em capturar os dados do gráfico, codificá-los em uma forma adequada para visualização humana e enviá-los ao nosso canal do Telegram escolhido.
Em seguida, para evitar o caso em que tentamos sobrescrever e criar um arquivo com um nome semelhante, precisamos excluir o existente, se disponível, e criar um novo. Isso é realizado através do seguinte trecho de código.
//--- First delete an instance of the screenshot file if it already exists if(FileIsExist(SCREENSHOT_FILE_NAME)){ FileDelete(SCREENSHOT_FILE_NAME); Print("Chart Screenshot was found and deleted."); ChartRedraw(0); }
Aqui, começamos garantindo que nenhuma instância do arquivo de captura de tela exista antes de capturarmos uma nova. Isso é importante porque queremos evitar qualquer confusão entre o estado atual do gráfico e as capturas de tela salvas anteriormente. Para isso, verificamos se um arquivo com o nome armazenado na constante "SCREENSHOT_FILE_NAME" existe no sistema. Fazemos isso usando a função FileIsExist, que verifica o diretório especificado e retorna verdadeiro se o arquivo estiver presente. Se o arquivo realmente existir, o deletamos usando a função FileDelete. Ao garantir que o diretório especificado esteja livre da nossa captura de tela antiga, criamos espaço para a nova que será criada mais tarde no processo.
Após a exclusão, enviamos uma mensagem para o terminal usando a função Print para indicar que a captura de tela do gráfico foi encontrada e foi erradicada com sucesso. Este pequeno feedback pode ser muito útil para depuração, pois serve como uma confirmação de que o sistema está lidando corretamente com a exclusão das capturas de tela anteriores. Afinal, não gostaríamos de criar o hábito de "deletar" arquivos inexistentes. Também redesenhamos imediatamente o gráfico (chamamos esta função de ChartRedraw) para garantir que estamos trabalhando com o estado visual atualizado do gráfico. Após essa limpeza, podemos agora prosseguir para tirar a captura de tela.
ChartScreenShot(0,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT);
Aqui, capturamos a captura de tela do gráfico usando a função ChartScreenShot, que tira uma foto do gráfico especificado e a salva como um arquivo de imagem. No nosso caso, passamos os parâmetros "0", "SCREENSHOT_FILE_NAME", "1366", "768" e "ALIGN_RIGHT" para a função para controlar como a captura de tela é feita e salva.
- O primeiro parâmetro, "0", especifica o ID do gráfico do qual queremos tirar a captura de tela. O valor 0 se refere ao gráfico ativo no momento. Se quisermos capturar um gráfico diferente, precisaríamos passar o ID específico do gráfico.
- O segundo parâmetro, "SCREENSHOT_FILE_NAME", é o nome do arquivo onde a captura de tela será salva. No nosso caso, esta é a constante "Our Chart ScreenShot.jpg". Este arquivo será criado no diretório "Arquivos" do terminal, e se ele ainda não existir, será gerado após a captura de tela ser feita.
- Os terceiros e quartos parâmetros, 1366 e 768, definem as dimensões da captura de tela em pixels. Aqui, 1366 representa a largura da captura de tela, e 768 representa a altura. Esses valores podem ser ajustados com base nas preferências do usuário ou no tamanho da tela sendo capturada.
- O parâmetro final, ALIGN_RIGHT, especifica como a captura de tela deve ser alinhada dentro da janela do gráfico. Usando ALIGN_RIGHT, alinhamos a captura de tela ao lado direito do gráfico. Outras opções de alinhamento, como ALIGN_LEFT ou ALIGN_CENTER, podem ser usadas dependendo do resultado desejado.
Por algumas razões, embora muito insignificantes, a captura de tela pode demorar para ser salva. Assim, para garantir que não deixemos nenhuma pedra por virar, precisamos iniciar uma iteração onde podemos esperar alguns segundos até que a captura de tela seja salva.
// Wait for 30 secs to save screenshot if not yet saved int wait_loops = 60; while(!FileIsExist(SCREENSHOT_FILE_NAME) && --wait_loops > 0){ Sleep(500); }
Aqui, implementamos um while loop que espera até que o arquivo de captura de tela seja salvo com sucesso, garantindo que ele tenha sido salvo no local correto e com o nome correto antes de continuarmos. A espera em si é longa o suficiente para que, em circunstâncias normais, o arquivo de captura de tela seja facilmente encontrado no sistema de arquivos (se realmente foi salvo durante o teste). Começamos com a variável integer "wait_loops" inicializada em 60. A cada iteração do loop, se não encontrar o arquivo, introduzimos uma espera de meio segundo (500 milissegundos (ms))—resultando em 30 segundos (60 iterações * 500 ms) do início do loop até o final se não encontrar o arquivo.
Em cada iteração, também decrementamos o contador "wait_loops" para evitar que o loop rode indefinidamente se o arquivo não for criado no tempo especificado. Além disso, usamos a função Sleep para criar um atraso de 500 milissegundos entre cada verificação. Isso nos impede de verificar com muita frequência e sobrecarregar nosso sistema com muitas solicitações de verificação da existência do arquivo.
Por fim, precisamos verificar a existência do arquivo depois e, se ele não existir, não há sentido em continuar, pois todo o nosso algoritmo e lógica dependem do arquivo de imagem. Se ele existir, podemos então prosseguir com os próximos passos.
if(!FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!"); return (INIT_FAILED); }
Aqui, definimos um mecanismo para tratar erros e verificar se o arquivo da captura de tela foi salvo com sucesso. Após esperar algum tempo para que o arquivo seja criado, verificamos a presença do arquivo usando a função FileIsExist. Se a verificação retornar falso, significando que o arquivo não está presente, emitimos a seguinte mensagem: "A CAPTURA DE TELA ESPECIFICADA NÃO EXISTE (NÃO FOI SALVA). REVERTENDO AGORA!" Esta mensagem indica que não conseguimos salvar o arquivo da captura de tela. Após a emissão dessa mensagem de erro, o programa não pode continuar, pois precisamos totalmente desse arquivo de imagem como base para a lógica do programa. Portanto, saímos com um valor de retorno "INIT_FAILED", indicando que a inicialização não pôde ser concluída com sucesso. Se a captura de tela foi salva, vamos continuar e informar o sucesso também.
else if(FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE."); }
Ao executar o código, esses são os resultados:
Aqui, você pode ver que conseguimos excluir com sucesso uma existência inicial do arquivo de imagem e salvar outra Para acessar a imagem no seu computador, clique com o botão direito no nome do arquivo, escolha "Abrir pasta de destino" e localize o arquivo na pasta "files".
Alternativamente, você pode acessar diretamente o arquivo de imagem abrindo o navegador, expandindo-o, clicando com o botão direito na aba "files" e escolhendo "Abrir pasta".
Isso abre a pasta "files" onde o arquivo de imagem foi registrado.
Aqui, você pode ver que o nome exato da imagem está registrado. Por fim, vamos verificar o tamanho e tipo do arquivo para ver se as informações corretas especificadas estão sendo levadas em consideração.
Podemos ver que o tipo do arquivo é JPG e a largura e altura da captura de tela são 1366 por 768 pixels, respectivamente, exatamente como especificado. Se, por exemplo, alguém quiser ter um tipo de arquivo diferente, digamos PNG, basta mudar o tipo de arquivo, como abaixo:
#define SCREENSHOT_FILE_NAME "Our Chart ScreenShot.png"
Quando compilamos e executamos este trecho de código, criamos outra imagem do tipo PNG, como visualizado abaixo em um formato de imagem Graphics Interchange Format (GIF):
Até este ponto, é evidente que conseguimos tirar a captura de tela do gráfico e salvá-la diretamente nos arquivos. Agora podemos prosseguir com a codificação do arquivo de imagem para que ele possa ser transmitido via HTTPS. Primeiro, precisaremos abrir o arquivo de imagem e lê-lo.
int screenshot_Handle = INVALID_HANDLE; screenshot_Handle = FileOpen(SCREENSHOT_FILE_NAME,FILE_READ|FILE_BIN); if(screenshot_Handle == INVALID_HANDLE){ Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!"); return(INIT_FAILED); }
No trecho de código acima, focamos nas operações de arquivos em MQL5 para manipular um arquivo de captura de tela que salvamos anteriormente. Declaramos uma variável integer chamada "screenshot_Handle" e a inicializamos com o valor "INVALID_HANDLE". O "screenshot_Handle" serve como uma referência para o arquivo, e o valor "INVALID_HANDLE" atua como um marcador que nos informa que nenhum arquivo válido foi aberto ainda. Manter esse valor garante que podemos referenciar um arquivo por meio de seu identificador e que podemos tratar qualquer erro que surja nas operações de arquivo caso algo dê errado.
Em seguida, tentamos usar a função FileOpen para abrir a nossa captura de tela salva. Damos a ela o nome da captura de tela, que contém o caminho para o arquivo da captura de tela. Também damos a ela duas flags: FILE_READ e FILE_BIN. A primeira flag informa ao sistema que queremos ler o arquivo. A segunda flag, que provavelmente é a mais importante das duas, informa ao sistema que o arquivo contém dados binários (o que não deve ser confundido com o fato de a captura de tela ser uma série de uns e zeros). Como a captura de tela é uma imagem, e a imagem está em um formato um tanto padrão (transformar esse formato em algo realmente "padrão", "fácil" ou "natural" para trabalhar, e a imagem se torna uma série de uns e zeros—sem formatação, sem estrutura, apenas matemática pura—uma série diferente de uns e zeros, e a imagem parecerá completamente diferente, embora isso não seja nossa preocupação aqui), esperamos encontrar uma série de bytes que correspondam, de alguma forma, à imagem.
A função "FileOpen" retorna um identificador válido ou "INVALID_HANDLE" após tentarmos abrir o arquivo. Verificamos a validade do identificador com uma declaração if. Um identificador inválido significa que o arquivo não foi aberto com sucesso. Imprimimos uma mensagem de erro informando que o identificador da captura de tela é inválido. Então, ou a captura de tela não foi salva ou não pode ser acessada, o que nos sinaliza que o programa encontrou um bloqueio. Não continuamos mais, e em vez disso, retornamos "INIT_FAILED", pois não faz sentido continuar se não conseguirmos ler o arquivo da imagem. Se o identificador for válido, informamos ao usuário sobre o sucesso.
else if (screenshot_Handle != INVALID_HANDLE){ Print("SCREENSHOT WAS SAVED & OPENED SUCCESSFULLY FOR READING."); Print("HANDLE ID = ",screenshot_Handle,". IT IS NOW READY FOR ENCODING."); }
Aqui, adicionamos outra etapa de verificação para garantir que o arquivo de captura de tela foi aberto corretamente. Após verificar que "screenshot_Handle" é válido (não é igual a "INVALID_HANDLE"), imprimimos algumas mensagens que indicam que o arquivo foi aberto corretamente. Esta é apenas mais uma forma de afirmar que o "screenshot_Handle" é bom e que estamos prontos para seguir em frente. Usamos a função Print para a primeira mensagem, que diz a mesma coisa que a segunda mensagem: que a captura de tela foi salva com sucesso e aberta para leitura. Ambas as declarações servem para confirmar que a etapa atual do nosso fluxo de trabalho foi concluída com sucesso.
Em seguida, exibimos o ID do identificador, que identifica de forma única o arquivo e permite que operações subsequentes (que serão leitura, gravação e codificação) sejam realizadas no arquivo. O ID do identificador também é útil para depuração. Ele confirma que o sistema obteve e alocou recursos para gerenciar esse arquivo em particular. Concluímos com uma declaração de impressão que nos informa que o sistema está agora pronto para realizar a próxima operação, que é codificar a captura de tela para que possa ser transmitida pela rede usando o protocolo HTTPS.
Em seguida, podemos verificar e confirmar se o identificador está realmente registrado e armazenado e se ele tem conteúdo válido.
int screenshot_Handle_Size = (int)FileSize(screenshot_Handle); if (screenshot_Handle_Size > 0){ Print("CHART SCREENSHOT FILE SIZE = ",screenshot_Handle_Size); }
Aqui, obtemos e verificamos o tamanho do arquivo de captura de tela aberto anteriormente com seu identificador. Chamamos a função FileSize no identificador da captura de tela, que retorna o tamanho do arquivo em bytes. Em seguida, atribuímos esse valor a uma variável integer chamada "screenshot_Handle_Size". Se o tamanho for maior que zero, o que indica que o arquivo contém algum tipo de dado, imprimimos o tamanho do arquivo no log. Esta etapa é útil porque nos permite saber que, antes de codificar e enviar o arquivo via HTTP, a captura de tela foi salva corretamente e tem conteúdo válido.
Se o identificador realmente contiver conteúdo válido, isso significa que temos o arquivo correto aberto e podemos nos preparar para ler os dados binários do arquivo de captura de tela em um array para decodificação.
uchar photoArr_Data[]; ArrayResize(photoArr_Data,screenshot_Handle_Size); FileReadArray(screenshot_Handle,photoArr_Data,0,screenshot_Handle_Size); if (ArraySize(photoArr_Data) > 0){ Print("READ SCREENSHOT FILE DATA SIZE = ",ArraySize(photoArr_Data)); }
Começamos declarando um array uchar chamado "photoArr_Data" que armazenará os dados binários. Em seguida, redimensionamos esse array para corresponder ao tamanho do arquivo da captura de tela chamando a função ArrayResize. Em seguida, lemos o conteúdo do arquivo de captura de tela para o array "photoArr_Data", começando do índice 0 até o final do arquivo (o "screenshot_Handle_Size") usando a função FileReadArray. Depois, verificamos o tamanho do array "photoArr_Data" após carregá-lo e, se for maior que 0, significando que não está vazio, registramos seu tamanho. Normalmente, esta é a parte do código que lida com a leitura e processamento do arquivo de captura de tela, para que possa ser usado para codificação e transmissão.
Após ler o conteúdo do arquivo e armazená-lo, agora precisamos fechar o arquivo de imagem. Isso é feito pelo seu identificador.
FileClose(screenshot_Handle);
Aqui, finalmente fechamos o arquivo de captura de tela após ler com sucesso seus dados para o array de armazenamento. Invocamos a função FileClose para liberar o identificador associado ao arquivo de captura de tela. Isso libera os recursos do sistema que foram alocados quando o arquivo foi aberto. É crucial garantir que o arquivo seja fechado antes de tentarmos realizar qualquer outra operação no arquivo, como acessar, ler ou gravá-lo de qualquer forma. A função sinaliza que completamos todas as operações de acesso no arquivo e agora estamos indo para a próxima etapa do processo: codificar os dados da captura de tela e prepará-los para transmissão. Quando executamos isso, obtemos o seguinte resultado:
Você pode ver que lemos e armazenamos corretamente os dados binários da imagem no array de armazenamento. Para visualizar os dados, podemos imprimi-los no log usando a função ArrayPrint da seguinte forma:
ArrayPrint(photoArr_Data);
Ao imprimir, obtemos os seguintes dados:
É evidente que lemos, copiamos e armazenamos todos os dados, ou seja, até 320894.
Em seguida, precisamos preparar os dados da foto para transmissão via HTTP, codificando-os no formato Base64. Como dados binários, como imagens, não podem ser transmitidos diretamente via HTTP, precisamos usar a codificação Base64 para converter os dados binários em uma string ASCII. Isso garante que os dados possam ser incluídos com segurança na requisição HTTP. Isso é realizado através do seguinte trecho de código.
//--- create boundary: (data -> base64 -> 1024 bytes -> md5) //Encodes the photo data into base64 format //This is part of preparing the data for transmission over HTTP. uchar base64[]; uchar key[]; CryptEncode(CRYPT_BASE64,photoArr_Data,key,base64); if (ArraySize(base64) > 0){ Print("Transformed BASE-64 data = ",ArraySize(base64)); //Print("The whole data is as below:"); //ArrayPrint(base64); }
Para começar, configuramos dois arrays. O primeiro é "base64". Este armazena os dados codificados. O segundo array é "key". Nunca usamos "key" neste contexto, mas a função de codificação requer isso. A função que realiza o trabalho de codificação Base64 é chamada CryptEncode. Ela recebe quatro parâmetros: o tipo de codificação ("CRYPT_BASE64"), os dados binários de origem ("photoArr_Data"), a chave de criptografia ("key") e o array de saída ("base64"). Esta função CryptEncode faz o trabalho real de converter os dados binários em formato Base64 e armazenar o resultado no array "base64". Quando verificamos o tamanho de "base64" com a função ArraySize, se "base64" contiver algum elemento, ou seja, se for maior que 0, isso significa que a codificação foi bem-sucedida.
Para imprimir esses dados no diário, usamos a função ArrayPrint.
Print("Transformed BASE-64 data = ",ArraySize(base64)); Print("The whole data is as below:"); ArrayPrint(base64);
Obtivemos o seguinte resultado:
Podemos ver que há uma diferença significativa entre os dados binários originais de tamanho 320894 e os dados recém-convertidos com tamanho 427860. Essa diferença é resultado da transformação e codificação dos dados.
Em seguida, precisamos preparar um subconjunto dos dados codificados em Base64 para garantir que lidemos com uma parte gerenciável deles para os próximos passos do nosso processo. Especificamente, precisamos focar em copiar os primeiros 1024 bytes dos dados codificados para um array temporário para uso posterior.
//Copy the first 1024 bytes of the base64-encoded data into a temporary array uchar temporaryArr[1024]= {0}; ArrayCopy(temporaryArr,base64,0,0,1024);
Para começar, configuramos um array temporário, "temporaryArr", com tamanho de 1024 bytes. Inicializamos todos os seus valores como zero. Usamos este array para armazenar o primeiro segmento dos dados codificados em Base64. Como o valor de inicialização é zero, evitamos qualquer problema potencial com informações residuais na memória onde o array temporário está armazenado.
Então, usamos a função ArrayCopy para mover os primeiros 1024 bytes de "base64" para "temporaryArr". Isso lida com a operação de cópia de maneira limpa e eficiente. Os motivos para isso e os detalhes da operação de cópia são um tópico à parte, mas mencionarei apenas algumas coisas. O efeito colateral da inicialização é remover qualquer preocupação que você possa ter sobre a primeira parte dos dados codificados em Base64, caso os visualize como algum tipo de gibberish aleatório. Vamos registrar o array temporário vazio. Conseguimos isso através do seguinte código.
Print("FILLED TEMPORARY ARRAY WITH ZERO (0) IS AS BELOW:"); ArrayPrint(temporaryArr);
Ao compilar, isso é o que temos:
Podemos ver que o array temporário foi preenchido com zeros puros. Esses zeros são então substituídos pelos primeiros 1024 valores dos dados formatados originalmente. Podemos visualizar esses dados através de uma lógica semelhante novamente.
Print("FIRST 1024 BYTES OF THE ENCODED DATA IS AS FOLLOWS:"); ArrayPrint(temporaryArr);
A apresentação dos dados temporários preenchidos é a seguinte:
Depois de obter esses dados temporários, precisamos gerar um hash MD5 (Message-Digest Algorithm 5) dos primeiros 1024 bytes dos dados codificados em Base64. Esse hash MD5 será usado como parte do delimitador em uma estrutura multipart/form-data, que é frequentemente usada em requisições HTTP POST para lidar com uploads de arquivos.
//Create an MD5 hash of the temporary array //This hash will be used as part of the boundary in the multipart/form-data uchar md5[]; CryptEncode(CRYPT_HASH_MD5,temporaryArr,key,md5); if (ArraySize(md5) > 0){ Print("SIZE OF MD5 HASH OF TEMPORARY ARRAY = ",ArraySize(md5)); Print("MD5 HASH boundary in multipart/form-data is as follows:"); ArrayPrint(md5); }
Para começar, declaramos um array chamado "md5" para armazenar o resultado do hash MD5. O algoritmo MD5 (onde "MD" significa "Message Digest") é uma função de hash criptográfica que produz um valor de hash de 128 bits. O hash é comumente representado como uma string de 32 dígitos hexadecimais.
Neste caso, usamos a função interna do MQL5 CryptEncode com o parâmetro CRYPT_HASH_MD5 para calcular o hash. Passamos para a função um array temporário chamado "temporaryArr", que contém os primeiros 1024 bytes dos dados codificados em Base64. O parâmetro "key" é tipicamente usado para operações criptográficas adicionais, mas não é necessário para o MD5 e é definido como um array vazio neste contexto. O resultado da operação de hash é armazenado no array "md5".
Após calcular o hash, verificamos o array "md5" para ver se não está vazio, verificando o número de elementos no array com a função ArraySize. Se o array contiver elementos, registramos o tamanho do hash MD5 e, em seguida, o valor real do hash. Esse valor do hash é usado para criar uma string delimitadora no formato multipart/form-data que ajuda a separar as diferentes partes da requisição HTTP sendo transmitida. O algoritmo MD5 é usado aqui estritamente por sua popularidade e pelo valor único que ele gera, não porque seja o melhor ou mais seguro algoritmo a ser usado. Quando executamos isso, obtemos os seguintes dados:
Aqui, você pode ver que obtemos os dados do hash MD5 em forma numérica. Assim, precisamos converter o hash MD5 em uma string hexadecimal e depois truncá-la para atender a um requisito de comprimento específico para usá-la como delimitador nas requisições multipart/form-data HTTP, o que geralmente é 16.
//Format MD5 hash as a hexadecimal string & //truncate it to 16 characters to create the boundary. string HexaDecimal_Hash=NULL;//Used to store the hexadecimal representation of MD5 hash int total=ArraySize(md5); for(int i=0; i<total; i++){ HexaDecimal_Hash+=StringFormat("%02X",md5[i]); } Print("Formatted MD5 Hash String is: \n",HexaDecimal_Hash); HexaDecimal_Hash=StringSubstr(HexaDecimal_Hash,0,16);//truncate HexaDecimal_Hash string to its first 16 characters //done to comply with a specific length requirement for the boundary //in the multipart/form-data of the HTTP request. Print("Final Truncated (16 characters) MD5 Hash String is: \n",HexaDecimal_Hash);
Para começar, declaramos uma variável string chamada "HexaDecimal_Hash", que armazenará a forma hexadecimal do hash MD5. Essa string servirá como um marcador de delimitador para separar as diferentes partes do corpo da nossa requisição HTTP.
Em seguida, fazemos um loop por cada byte do hash armazenado no array md5. Convertendo cada byte em uma string de dois caracteres, usando o especificador de formato "%02X". A parte "%0" do especificador indica que a string deve ser preenchida com zeros à esquerda, se necessário, para garantir que cada byte seja representado por dois caracteres. O "02" indica dois caracteres (no mínimo) para a representação; o "X" indica que os caracteres devem ser numerais hexadecimais (com letras maiúsculas, se necessário).
Esses caracteres hexadecimais são adicionados à string "HexaDecimal_Hash". Finalmente, imprimimos o conteúdo da string no log para verificar se foi formada corretamente. A execução do programa gera as seguintes informações:
Isso foi um sucesso. Agora, precisamos construir e preparar os dados do arquivo para uma requisição HTTP POST multipart/form-data, que será usada para enviar a foto ao chat do Telegram via a API do Telegram. Isso envolverá preparar o corpo da requisição para incluir tanto os campos do formulário quanto os dados do arquivo em um formato que o servidor possa processar corretamente. Conseguimos isso através do seguinte trecho de código.
//--- WebRequest char DATA[]; string URL = NULL; URL = TG_API_URL+"/bot"+botTkn+"/sendPhoto"; //--- add chart_id //Append a carriage return and newline character sequence to the DATA array. //In the context of HTTP, \r\n is used to denote the end of a line //and is often required to separate different parts of an HTTP request. ArrayAdd(DATA,"\r\n"); //Append a boundary marker to the DATA array. //Typically, the boundary marker is composed of two hyphens (--) //followed by a unique hash string and then a newline sequence. //In multipart/form-data requests, boundaries are used to separate //different pieces of data. ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); //Add a Content-Disposition header for a form-data part named chat_id. //The Content-Disposition header is used to indicate that the following data //is a form field with the name chat_id. ArrayAdd(DATA,"Content-Disposition: form-data; name=\"chat_id\"\r\n"); //Again, append a newline sequence to the DATA array to end the header section //before the value of the chat_id is added. ArrayAdd(DATA,"\r\n"); //Append the actual chat ID value to the DATA array. ArrayAdd(DATA,chatID); //Finally, Append another newline sequence to the DATA array to signify //the end of the chat_id form-data part. ArrayAdd(DATA,"\r\n");
Agora configuramos o array "DATA" e a "URL" para a requisição HTTP. A "URL" é construída a partir de três partes: a URL base para a API ("TG_API_URL"); o token para o bot, que identifica o bot para a API ("botTkn"); e o endpoint para enviar uma foto a um chat ("/sendPhoto"). Essa URL especifica para qual "servidor remoto" estamos enviando nossa "carga útil" — a foto que queremos enviar e as informações que queremos anexar à foto. O endpoint da URL não muda; é o mesmo para cada requisição que fazemos. Nossas requisições irão para o mesmo lugar, seja para enviar uma foto ou várias, seja para enviar fotos para diferentes chats, e assim por diante.
Após isso, adicionamos um marcador de delimitador à borda do nosso bloco de dados. Ele é composto por dois hífens (--) e nosso hash de delimitador único ("HexaDecimal_Hash"). No total, ele aparece assim: "--HexaDecimal_Hash". Este marcador de delimitador aparece no início do bloco de dados para a próxima parte da requisição, que é um campo de formulário "chart_id". O cabeçalho Content-Disposition especifica que a próxima parte (o próximo bloco de dados) da requisição multipart/form-data é um campo de formulário e fornece o nome desse campo ("chart_id").
Adicionamos esse cabeçalho e uma sequência de nova linha ("/r/n") para indicar o fim da seção de cabeçalhos. Após a seção de cabeçalhos, adicionamos o valor de "chartID" ao array DATA, seguido por outra sequência de nova linha ("/r/n") para indicar o fim da parte "chart_id" do formulário de dados. Esse processo garante que o campo do formulário seja corretamente formatado e separado das outras partes da requisição para garantir que a API do Telegram receba e processe corretamente os dados.
Você pode ter notado que usamos duas "funções sobrecarregadas" no código. Vamos mostrar o trecho de código delas abaixo.
//+------------------------------------------------------------------+ // ArrayAdd for uchar Array void ArrayAdd(uchar &destinationArr[],const uchar &sourceArr[]){ int sourceArr_size=ArraySize(sourceArr);//get size of source array if(sourceArr_size==0){ return;//if source array is empty, exit the function } int destinationArr_size=ArraySize(destinationArr); //resize destination array to fit new data ArrayResize(destinationArr,destinationArr_size+sourceArr_size,500); // Copy the source array to the end of the destination array. ArrayCopy(destinationArr,sourceArr,destinationArr_size,0,sourceArr_size); } //+------------------------------------------------------------------+ // ArrayAdd for strings void ArrayAdd(char &destinationArr[],const string text){ int length = StringLen(text);// get the length of the input text if(length > 0){ uchar sourceArr[]; //define an array to hold the UTF-8 encoded characters for(int i=0; i<length; i++){ // Get the character code of the current character ushort character = StringGetCharacter(text,i); uchar array[];//define an array to hold the UTF-8 encoded character //Convert the character to UTF-8 & get size of the encoded character int total = ShortToUtf8(character,array); //Print("text @ ",i," > "text); // @ "B", IN ASCII TABLE = 66 (CHARACTER) //Print("character = ",character); //ArrayPrint(array); //Print("bytes = ",total) // bytes of the character int sourceArr_size = ArraySize(sourceArr); //Resize the source array to accommodate the new character ArrayResize(sourceArr,sourceArr_size+total); //Copy the encoded character to the source array ArrayCopy(sourceArr,array,sourceArr_size,0,total); } //Append the source array to the destination array ArrayAdd(destinationArr,sourceArr); } }
Aqui, definimos duas funções personalizadas para lidar com a adição de dados aos arrays em MQL5, projetadas especificamente para manipular tanto tipos uchar quanto string. Essas funções facilitam a construção dos dados da requisição HTTP, acrescentando várias partes de informação a um array existente, garantindo que o formato final dos dados esteja correto e seja adequado para transmissão. Adicionamos comentários nas funções para facilitar o entendimento, mas vamos explicar rapidamente o código novamente.
A primeira função, "ArrayAdd", se aplica a arrays de caracteres não assinados (uchar). Ela é configurada para adicionar dados de um array de origem a um array de destino. Primeiro, determina quantos elementos existem no array de origem. Isso é feito chamando a função simples ArraySize no array de origem. Com essa informação, verificamos se o array de origem contém dados. Se não contiver, evitamos a situação de continuar inutilmente, saindo da função mais cedo. Se contiver dados, passamos para a próxima etapa, que é redimensionar o array de destino para aceitar esses dados. Fazemos isso chamando a função ArrayResize no array de destino, que agora podemos chamar com confiança, pois sabemos que funcionará corretamente.
A outra função, que adiciona strings a um array char, funciona da seguinte forma: ela calcula o comprimento da string de entrada. Se a string de entrada não estiver vazia, ela pega cada caractere da string, obtém seu código e o adiciona ao array de destino, convertendo-o para UTF-8 no processo. Para converter a string e adicioná-la ao array de destino, essa função redimensiona o array de origem para armazenar temporariamente os dados da string a ser adicionada. Ela garante que a representação UTF-8 da string, bem como a string em si, sejam corretamente armazenadas no array final de dados que será usado para construir os corpos das requisições HTTP ou outras estruturas de dados.
Para visualizar o que fizemos, vamos implementar uma lógica que imprimirá os dados resultantes relacionados ao chat ID que será enviado na requisição HTTP.
Print("CHAT ID DATA:"); ArrayPrint(DATA); string chatID_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_UTF8); Print("SIMPLE CHAT ID DATA IS AS FOLLOWS:",chatID_Data);
Para começar, usamos a função ArrayPrint para apresentar o array de dados bruto. A função imprime o conteúdo do array para nós. Em seguida, realizamos a conversão do array "DATA" de um array de caracteres para o formato de string. A função que usamos é CharArrayToString, que traduz os dados binários brutos em "DATA" para uma string codificada em UTF-8. Os parâmetros usados aqui especificam que queremos converter o array inteiro ("WHOLE_ARRAY") e que a codificação de caracteres é UTF-8 (CP_UTF8). Essa conversão é necessária porque a requisição HTTP exige que os dados estejam no formato de string.
Em conclusão, o que temos é uma string, "chatID_Data", cujo formato final é tal que será incluída na requisição HTTP. Usando a função Print, podemos ver como será a saída final na requisição.
Podemos ver que conseguimos adicionar corretamente os dados do chat ID ao array. Através da mesma lógica, podemos adicionar os dados da imagem também para construir o corpo da requisição multipart/form-data para enviar a foto via HTTP para a API do Telegram.
ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,photoArr_Data); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,"--"+HexaDecimal_Hash+"--\r\n");
Para começar, inserimos o marcador de delimitador para a parte da fotografia no multipart/form-data. Fazemos isso com a linha ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"). O marcador de delimitador, composto por dois hífens e o "HexaDecimal_Hash", serve para separar as diferentes partes da requisição multipart. O "HexaDecimal_Hash", um identificador único para o delimitador, garante que cada parte da requisição seja claramente separada da próxima.
Em seguida, incluímos o cabeçalho Content-Disposition para a parte da foto dos dados do formulário. Adicionamos isso usando a função ArrayAdd, da seguinte forma: ArrayAdd(DATA, "Content-Disposition: form-data; name="photo"; filename="Upload_ScreenShot.jpg"\r\n"). Este cabeçalho indica que os dados seguintes são um arquivo, especificamente, o arquivo chamado "Upload_ScreenShot.jpg". Como especificamos, através da parte name="photo" do cabeçalho, que o campo de dados do formulário com o qual estamos lidando tem o nome "photo", o servidor sabe que deve esperar o arquivo "Upload_ScreenShot.jpg" como parte desse campo quando processar a requisição recebida. O arquivo é apenas um identificador e você pode alterá-lo para algo de sua escolha.
Após isso, usamos ArrayAdd(DATA, "\r\n") para adicionar uma sequência de nova linha aos cabeçalhos da requisição. Isso indica o fim da seção de cabeçalhos e o início dos dados reais do arquivo. Em seguida, usamos ArrayAdd(DATA, photoArr_Data) para adicionar os dados reais da foto ao array DATA. Essa linha de código adiciona os dados binários da captura de tela (anteriormente codificados em base64) ao corpo da requisição. A carga útil multipart/form-data agora contém os dados da foto.
Finalmente, adicionamos outra sequência de nova linha com ArrayAdd(DATA, "\r\n") e o marcador de delimitador para fechar a parte da foto com ArrayAdd(DATA, "--" + HexaDecimal_Hash + "--\r\n"). O "--" no final do marcador de delimitador indica o fim da seção multipart. Este delimitador final garante que o servidor identifique corretamente o fim da seção de dados da foto dentro da requisição. Para visualizar os dados que estão sendo enviados, vamos novamente imprimi-los no log através de uma função semelhante à anterior.
Print("FINAL FULL PHOTO DATA BEING SENT:"); ArrayPrint(DATA); string final_Simple_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_ACP); Print("FINAL FULL SIMPLE PHOTO DATA BEING SENT:",final_Simple_Data);
Estes são os resultados que obtemos.
Finalmente, construímos os cabeçalhos da requisição HTTP necessários para enviar uma requisição multipart/form-data para a API do Telegram.
string HEADERS = NULL; HEADERS = "Content-Type: multipart/form-data; boundary="+HexaDecimal_Hash+"\r\n";
Começamos definindo uma string "HEADERS", que é inicializada como "NULL". Essa string contém os cabeçalhos HTTP que precisamos definir para a requisição. O cabeçalho que devemos definir absolutamente é o Content-Type. O cabeçalho Content-Type transmite o tipo de dados que está sendo enviado e como ele está formatado.
Atribuímos à string o valor correto de Content-Type. A parte crucial aqui é a string "HEADERS" em si. Devemos entender o "formato" da requisição HTTP se quisermos compreender por que essa atribuição específica à string "HEADERS" é necessária. O formato para a requisição diz que a requisição está sendo enviada usando o cabeçalho "Content-Type: multipart/form-data". Após fazer tudo isso, agora podemos iniciar a requisição web. Primeiro, vamos informar o usuário enviando a requisição abaixo.
Print("SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY.");
A partir do código inicial, comentamos os parâmetros desnecessários do WebRequest e passamos para os mais recentes.
//char data[]; // Array to hold data to be sent in the web request (empty in this case) char res[]; // Array to hold the response data from the web request string resHeaders; // String to hold the response headers from the web request //string msg = "EA INITIALIZED ON CHART " + _Symbol; // Message to send, including the chart symbol //const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID + // "&text=" + msg; // Send the web request to the Telegram API int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders);
Aqui, apenas adicionamos à função a nova URL, os cabeçalhos e os dados do arquivo de imagem a serem enviados. A lógica da resposta permanece intacta e inalterada como abaixo:
// Check the response status of the web request if (send_res == 200) { // If the response status is 200 (OK), print a success message Print("TELEGRAM MESSAGE SENT SUCCESSFULLY"); } else if (send_res == -1) { // If the response status is -1 (error), check the specific error code if (GetLastError() == 4014) { // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal Print("PLEASE ADD THE ", TG_API_URL, " TO THE TERMINAL"); } // Print a general error message if the request fails Print("UNABLE TO SEND THE TELEGRAM MESSAGE"); } else if (send_res != 200) { // If the response status is not 200 or -1, print the unexpected response code and error code Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError()); }
Quando executamos o programa, obtemos o seguinte:
No MetaTrader 5:
No Telegram:
Agora é evidente que enviamos com sucesso o arquivo de imagem do terminal de trading MetaTrader 5 para o chat do Telegram. No entanto, acabamos de enviar uma captura de tela vazia. Para adicionar uma legenda ao arquivo de imagem, implementamos a seguinte lógica, que adiciona uma legenda opcional à requisição multipart/form-data, que será enviada junto com a captura de tela do gráfico para a API do Telegram.
//--- Caption string CAPTION = NULL; CAPTION = "Screenshot of Symbol: "+Symbol()+ " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+ ") @ Time: "+TimeToString(TimeCurrent()); if(StringLen(CAPTION) > 0){ ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"caption\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,CAPTION); ArrayAdd(DATA,"\r\n"); } //---
Começamos inicializando a string "CAPTION" como "NULL" e, em seguida, a construímos com os detalhes relevantes. A legenda inclui o símbolo de trading, o período do gráfico e a hora atual, formatados como uma string. Depois, verificamos se a string "CAPTION" tem um comprimento maior que zero. Se tiver, prosseguimos para adicionar a legenda ao array "DATA", que é usado para construir os dados do formulário multipart. Isso envolve adicionar um marcador de delimitador, especificar a parte dos dados do formulário como legenda e incluir o conteúdo da legenda em si. Quando executamos isso, obtemos os seguintes resultados:
Isso foi um sucesso. Podemos ver que não apenas recebemos o arquivo de imagem, mas também uma legenda descritiva que mostra o nome do símbolo, o período e a hora do gráfico em questão.
Até este ponto, obtemos a captura de tela do gráfico onde o programa está anexado. Caso alguém queira abrir e modificar um gráfico diferente, precisaremos implementar uma lógica diferente para isso.
long chart_id=ChartOpen(_Symbol,_Period); ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true); // update chart int wait=60; while(--wait>0){//decrease the value of wait by 1 before loop condition check if(SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)){ break; // if prices up to date, terminate the loop and proceed } } ChartRedraw(chart_id); ChartSetInteger(chart_id,CHART_SHOW_GRID,false); ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BEAR,clrRed); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BULL,clrBlue); ChartSetInteger(chart_id,CHART_COLOR_BACKGROUND,clrLightSalmon); ChartScreenShot(chart_id,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT); //Sleep(10000); // sleep for 10 secs to see the opened chart ChartClose(chart_id); //---
Aqui, começamos abrindo um novo gráfico para o símbolo e o período dados usando a função ChartOpen, utilizando as variáveis predefinidas _Symbol e _Period. Atribuímos o ID do novo gráfico à variável "chart_id". Em seguida, usamos "chart_id" para garantir que o novo gráfico esteja visível à frente no ambiente MetaTrader e não esteja coberto por gráficos anteriores.
Depois disso, começamos um loop que pode rodar por no máximo 60 iterações. Dentro desse loop, continuamos verificando se o gráfico está sincronizado. Para testar a sincronização, usamos a função SeriesInfoInteger com os parâmetros _Symbol, _Period e SERIES_SYNCHRONIZED. Se verificarmos que o gráfico está sincronizado, saímos do loop. Assim que confirmarmos que o gráfico está sincronizado, usamos a função ChartRedraw com o parâmetro "chart_id" para atualizar o gráfico.
Personalizamos várias configurações do gráfico para ajustá-lo de acordo com nossas preferências. Usamos a função ChartSetInteger para definir as cores de fundo do gráfico e das velas de baixa e alta. As cores que definimos fornecem clareza visual, ajudando a distinguir facilmente os diferentes elementos do gráfico. Também tornamos o gráfico menos visualmente carregado, desabilitando a grade e os separadores de período. Você pode modificar seu gráfico como achar adequado neste ponto. Por fim, tiramos uma captura de tela do gráfico para uso na transmissão. Não queremos que o gráfico fique aberto desnecessariamente, então o fechamos depois de tirar a captura de tela. Para isso, usamos a função ChartClose. Quando executamos o programa, obtemos os seguintes resultados:
Está claro que abrimos um gráfico, o modificamos conforme desejado e o fechamos no final depois de tirar a captura de tela dele. Para visualizar o processo de abrir e fechar o gráfico, vamos adicionar um atraso de 10 segundos para que possamos ver o gráfico.
Sleep(10000); // sleep for 10 secs to see the opened chart
Aqui, apenas deixamos o gráfico aberto por 10 segundos para permitir que vejamos o que está acontecendo em segundo plano no nosso programa. Ao compilar, isso é o que temos:
O código completo responsável por tirar as capturas de tela, codificar, criptografar e enviá-las do terminal de trading para o chat do Telegram é o seguinte:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Get ready to take a chart screenshot of the current chart #define SCREENSHOT_FILE_NAME "Our Chart ScreenShot.jpg" //--- First delete an instance of the screenshot file if it already exists if(FileIsExist(SCREENSHOT_FILE_NAME)){ FileDelete(SCREENSHOT_FILE_NAME); Print("Chart Screenshot was found and deleted."); ChartRedraw(0); } //--- long chart_id=ChartOpen(_Symbol,_Period); ChartSetInteger(chart_id,CHART_BRING_TO_TOP,true); // update chart int wait=60; while(--wait>0){//decrease the value of wait by 1 before loop condition check if(SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)){ break; // if prices up to date, terminate the loop and proceed } } ChartRedraw(chart_id); ChartSetInteger(chart_id,CHART_SHOW_GRID,false); ChartSetInteger(chart_id,CHART_SHOW_PERIOD_SEP,false); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BEAR,clrRed); ChartSetInteger(chart_id,CHART_COLOR_CANDLE_BULL,clrBlue); ChartSetInteger(chart_id,CHART_COLOR_BACKGROUND,clrLightSalmon); ChartScreenShot(chart_id,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT); Print("OPENED CHART PAUSED FOR 10 SECONDS TO TAKE SCREENSHOT.") Sleep(10000); // sleep for 10 secs to see the opened chart ChartClose(chart_id); //--- //ChartScreenShot(0,SCREENSHOT_FILE_NAME,1366,768,ALIGN_RIGHT); // Wait for 30 secs to save screenshot if not yet saved int wait_loops = 60; while(!FileIsExist(SCREENSHOT_FILE_NAME) && --wait_loops > 0){ Sleep(500); } if(!FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE SPECIFIED SCREENSHOT DOES NOT EXIST (WAS NOT SAVED). REVERTING NOW!"); return (INIT_FAILED); } else if(FileIsExist(SCREENSHOT_FILE_NAME)){ Print("THE CHART SCREENSHOT WAS SAVED SUCCESSFULLY TO THE DATA-BASE."); } int screenshot_Handle = INVALID_HANDLE; screenshot_Handle = FileOpen(SCREENSHOT_FILE_NAME,FILE_READ|FILE_BIN); if(screenshot_Handle == INVALID_HANDLE){ Print("INVALID SCREENSHOT HANDLE. REVERTING NOW!"); return(INIT_FAILED); } else if (screenshot_Handle != INVALID_HANDLE){ Print("SCREENSHOT WAS SAVED & OPENED SUCCESSFULLY FOR READING."); Print("HANDLE ID = ",screenshot_Handle,". IT IS NOW READY FOR ENCODING."); } int screenshot_Handle_Size = (int)FileSize(screenshot_Handle); if (screenshot_Handle_Size > 0){ Print("CHART SCREENSHOT FILE SIZE = ",screenshot_Handle_Size); } uchar photoArr_Data[]; ArrayResize(photoArr_Data,screenshot_Handle_Size); FileReadArray(screenshot_Handle,photoArr_Data,0,screenshot_Handle_Size); if (ArraySize(photoArr_Data) > 0){ Print("READ SCREENSHOT FILE DATA SIZE = ",ArraySize(photoArr_Data)); } FileClose(screenshot_Handle); //ArrayPrint(photoArr_Data); //--- create boundary: (data -> base64 -> 1024 bytes -> md5) //Encodes the photo data into base64 format //This is part of preparing the data for transmission over HTTP. uchar base64[]; uchar key[]; CryptEncode(CRYPT_BASE64,photoArr_Data,key,base64); if (ArraySize(base64) > 0){ Print("Transformed BASE-64 data = ",ArraySize(base64)); //Print("The whole data is as below:"); //ArrayPrint(base64); } //Copy the first 1024 bytes of the base64-encoded data into a temporary array uchar temporaryArr[1024]= {0}; //Print("FILLED TEMPORARY ARRAY WITH ZERO (0) IS AS BELOW:"); //ArrayPrint(temporaryArr); ArrayCopy(temporaryArr,base64,0,0,1024); //Print("FIRST 1024 BYTES OF THE ENCODED DATA IS AS FOLLOWS:"); //ArrayPrint(temporaryArr); //Create an MD5 hash of the temporary array //This hash will be used as part of the boundary in the multipart/form-data uchar md5[]; CryptEncode(CRYPT_HASH_MD5,temporaryArr,key,md5); if (ArraySize(md5) > 0){ Print("SIZE OF MD5 HASH OF TEMPORARY ARRAY = ",ArraySize(md5)); Print("MD5 HASH boundary in multipart/form-data is as follows:"); ArrayPrint(md5); } //Format MD5 hash as a hexadecimal string & //truncate it to 16 characters to create the boundary. string HexaDecimal_Hash=NULL;//Used to store the hexadecimal representation of MD5 hash int total=ArraySize(md5); for(int i=0; i<total; i++){ HexaDecimal_Hash+=StringFormat("%02X",md5[i]); } Print("Formatted MD5 Hash String is: \n",HexaDecimal_Hash); HexaDecimal_Hash=StringSubstr(HexaDecimal_Hash,0,16);//truncate HexaDecimal_Hash string to its first 16 characters //done to comply with a specific length requirement for the boundary //in the multipart/form-data of the HTTP request. Print("Final Truncated (16 characters) MD5 Hash String is: \n",HexaDecimal_Hash); //--- WebRequest char DATA[]; string URL = NULL; URL = TG_API_URL+"/bot"+botTkn+"/sendPhoto"; //--- add chart_id //Append a carriage return and newline character sequence to the DATA array. //In the context of HTTP, \r\n is used to denote the end of a line //and is often required to separate different parts of an HTTP request. ArrayAdd(DATA,"\r\n"); //Append a boundary marker to the DATA array. //Typically, the boundary marker is composed of two hyphens (--) //followed by a unique hash string and then a newline sequence. //In multipart/form-data requests, boundaries are used to separate //different pieces of data. ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); //Add a Content-Disposition header for a form-data part named chat_id. //The Content-Disposition header is used to indicate that the following data //is a form field with the name chat_id. ArrayAdd(DATA,"Content-Disposition: form-data; name=\"chat_id\"\r\n"); //Again, append a newline sequence to the DATA array to end the header section //before the value of the chat_id is added. ArrayAdd(DATA,"\r\n"); //Append the actual chat ID value to the DATA array. ArrayAdd(DATA,chatID); //Finally, Append another newline sequence to the DATA array to signify //the end of the chat_id form-data part. ArrayAdd(DATA,"\r\n"); // EXAMPLE OF USING CONVERSIONS //uchar array[] = { 72, 101, 108, 108, 111, 0 }; // "Hello" in ASCII //string output = CharArrayToString(array,0,WHOLE_ARRAY,CP_ACP); //Print("EXAMPLE OUTPUT OF CONVERSION = ",output); // Hello Print("CHAT ID DATA:"); ArrayPrint(DATA); string chatID_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_UTF8); Print("SIMPLE CHAT ID DATA IS AS FOLLOWS:",chatID_Data); //--- Caption string CAPTION = NULL; CAPTION = "Screenshot of Symbol: "+Symbol()+ " ("+EnumToString(ENUM_TIMEFRAMES(_Period))+ ") @ Time: "+TimeToString(TimeCurrent()); if(StringLen(CAPTION) > 0){ ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"caption\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,CAPTION); ArrayAdd(DATA,"\r\n"); } //--- ArrayAdd(DATA,"--"+HexaDecimal_Hash+"\r\n"); ArrayAdd(DATA,"Content-Disposition: form-data; name=\"photo\"; filename=\"Upload_ScreenShot.jpg\"\r\n"); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,photoArr_Data); ArrayAdd(DATA,"\r\n"); ArrayAdd(DATA,"--"+HexaDecimal_Hash+"--\r\n"); Print("FINAL FULL PHOTO DATA BEING SENT:"); ArrayPrint(DATA); string final_Simple_Data = CharArrayToString(DATA,0,WHOLE_ARRAY,CP_ACP); Print("FINAL FULL SIMPLE PHOTO DATA BEING SENT:",final_Simple_Data); string HEADERS = NULL; HEADERS = "Content-Type: multipart/form-data; boundary="+HexaDecimal_Hash+"\r\n"; Print("SCREENSHOT SENDING HAS BEEN INITIATED SUCCESSFULLY."); //char data[]; // Array to hold data to be sent in the web request (empty in this case) char res[]; // Array to hold the response data from the web request string resHeaders; // String to hold the response headers from the web request //string msg = "EA INITIALIZED ON CHART " + _Symbol; // Message to send, including the chart symbol //const string url = TG_API_URL + "/bot" + botTkn + "/sendmessage?chat_id=" + chatID + // "&text=" + msg; // Send the web request to the Telegram API int send_res = WebRequest("POST",URL,HEADERS,10000, DATA, res, resHeaders); // Check the response status of the web request if (send_res == 200) { // If the response status is 200 (OK), print a success message Print("TELEGRAM MESSAGE SENT SUCCESSFULLY"); } else if (send_res == -1) { // If the response status is -1 (error), check the specific error code if (GetLastError() == 4014) { // If the error code is 4014, it means the Telegram API URL is not allowed in the terminal Print("PLEASE ADD THE ", TG_API_URL, " TO THE TERMINAL"); } // Print a general error message if the request fails Print("UNABLE TO SEND THE TELEGRAM MESSAGE"); } else if (send_res != 200) { // If the response status is not 200 or -1, print the unexpected response code and error code Print("UNEXPECTED RESPONSE ", send_res, " ERR CODE = ", GetLastError()); } return(INIT_SUCCEEDED); // Return initialization success status }
Agora está claro que alcançamos nosso terceiro objetivo, que é enviar arquivos de captura de tela do gráfico e legendas do terminal de trading para o chat ou grupo do Telegram. Isso é um sucesso, parabéns para nós! Agora, o que precisamos fazer é testar a integração para garantir que funcione corretamente e identificar qualquer problema que surja. Isso será feito na próxima seção.
Testando a Integração
Para garantir que nosso Expert Advisor (EA) envie corretamente as capturas de tela do terminal de trading MetaTrader 5 para o Telegram, precisamos testar a integração minuciosamente. Para juntar as coisas, vamos ter a lógica de teste em formato GIF.GIF DE TESTE
No GIF fornecido acima, mostramos a interação sem falhas entre o MetaTrader 5 e o Telegram, demonstrando o processo de envio de uma captura de tela de gráfico. O GIF começa mostrando a plataforma MetaTrader 5, onde uma janela de gráfico é aberta, trazida para o primeiro plano, e então pausada por 10 segundos, permitindo tempo para quaisquer ajustes finais. Durante essa pausa, a guia de Journal no MetaTrader 5 registra mensagens indicando o progresso da operação, como o gráfico sendo redesenhado e a captura de tela sendo feita. O gráfico é então fechado automaticamente, e a captura de tela é empacotada e enviada para o Telegram. No lado do Telegram, vemos a captura de tela chegar no chat, confirmando que a integração funciona como esperado. Este GIF reforça visualmente como o sistema automatizado opera em tempo real, desde a preparação do gráfico até a entrega bem-sucedida da imagem no Telegram.
Conclusão
Para concluir, este artigo descreveu, passo a passo, como enviar uma captura de tela de gráfico da plataforma de trading MetaTrader 5 para um chat no Telegram. Primeiro, geramos a captura de tela do gráfico usando MetaTrader 5. Configuramos as configurações do gráfico para garantir que estivesse claro e, em seguida, o capturamos usando a função ChartScreenShot para obter a imagem em um arquivo. Depois de salvar o arquivo no nosso computador, abrimos o arquivo e lemos seu conteúdo binário. Em seguida, enviamos o gráfico, codificado em formato Base64, para uma requisição HTTP que a API do Telegram poderia entender. Fazendo isso, conseguimos colocar a imagem em um chat no Telegram em tempo real.
Codificar a imagem para transmissão revelou as complexidades envolvidas no envio de dados binários brutos pelo protocolo HTTP, especialmente quando o destino é uma plataforma de mensagens como o Telegram. A primeira coisa a entender é que enviar dados binários diretamente simplesmente não é viável. Em vez disso, Telegram (e muitos outros serviços) exigem que os dados sejam enviados em formato de texto. Atendemos a esse requisito com facilidade, usando um algoritmo amplamente conhecido para converter os dados binários brutos da imagem para Base64. Depois disso, inserimos a imagem codificada em Base64 em uma requisição HTTP multipart/form-data. Esta demonstração não apenas ressaltou o poder da plataforma MetaTrader 5 como meio para criar automações personalizadas, mas também destacou como integrar um serviço externo—o Telegram, neste caso—em uma estratégia de trading.
Olhando para a Parte 4, vamos pegar o código deste artigo e moldá-lo em componentes reutilizáveis. Faremos isso para que possamos criar múltiplas instâncias da integração do Telegram, permitindo-nos, nas próximas partes do tutorial, enviar diferentes mensagens e capturas de tela para o Telegram a qualquer momento e da maneira que quisermos—não apenas quando quisermos, mas também quando e como quisermos—sem precisar depender de uma única chamada de função para isso. Ao colocar o código em classes, tornaremos o sistema mais modular e escalável. Também faremos isso para integrar o código de maneira mais fácil nos diferentes cenários de trading que descrevemos na Parte 1. Isso é importante porque a integração do mecanismo Telegram deve funcionar de maneira dinâmica e flexível com nossos Expert Advisors, permitindo que múltiplas estratégias e cenários de conta enviem uma variedade de mensagens e imagens em pontos críticos durante um trade ou no final de um dia de trading. Fique atento enquanto continuamos a construir e refinar este sistema integrado.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15616





- 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