
EA MQL5 integrado ao Telegram (Parte 2): Envio de sinais do MQL5 para o Telegram
Introdução
Na primeira parte da nossa série sobre o desenvolvimento de EAs MQL5 integrados ao Telegram, abordamos os passos fundamentais para conectar o MQL5 ao Telegram. O primeiro passo foi configurar o próprio aplicativo. Depois disso, partimos para a codificação. Espero que o motivo dessa ordem de ações fique mais claro adiante. Como resultado, agora temos um bot capaz de receber mensagens, bem como um programa que pode enviá-las. Também criamos um programa simples em MQL5 que demonstra como enviar uma mensagem para o aplicativo por meio do bot.
Com essa base estabelecida, podemos avançar para o próximo passo: o envio de sinais de negociação para o Telegram usando MQL5. Nosso novo EA aprimorado não apenas abre e fecha ordens com base em condições predefinidas, mas também envia um sinal para um grupo no Telegram para nos informar sobre a execução da negociação. Os próprios sinais de negociação foram reformulados para garantir que as informações enviadas ao Telegram sejam claras e concisas. Esse novo EA se comunica melhor no grupo do Telegram do que a versão anterior e faz isso com a mesma ou até maior velocidade, o que significa que podemos esperar receber sinais quase em tempo real à medida que as negociações são executadas ou fechadas.
Geraremos e enviaremos sinais com base no conhecido sistema de cruzamento de médias móveis. Além disso, se você se lembra, na primeira parte da série, havia apenas uma mensagem possível, que poderia ser bem longa, e adicionar segmentos a essa mensagem causava um erro. Dessa forma, só era possível enviar uma mensagem por vez, e se houvesse segmentos adicionais, eles precisariam ser enviados como mensagens separadas. Por exemplo, as mensagens "A buy signal has been generated" (Foi gerado um sinal de compra) e "Open a buy order" (Abrir uma ordem de compra) precisavam ser enviadas como uma única mensagem longa ou como duas mensagens curtas separadas. Nesta parte, combinaremos essas informações e modificaremos a mensagem para que ela possa conter vários segmentos de texto e símbolos em uma única transmissão. Exploraremos todo esse processo nas próximas seções:
- Visão geral da estratégia
- Implementação em MQL5
- Teste da integração
- Considerações finais
No final, criaremos um EA que enviará informações de negociação, como sinais gerados e ordens executadas, do terminal de negociação para um grupo específico no Telegram. Vamos começar.
Visão geral da estratégia
Geramos sinais de negociação com base nos cruzamentos de médias móveis, uma das ferramentas mais amplamente utilizadas na análise técnica. Descreveremos o método mais simples e intuitivo, em nossa opinião, para identificar oportunidades potenciais de compra ou venda por meio desse tipo de cruzamento. Esse método se baseia exclusivamente na natureza sinalizadora dos cruzamentos, sem a inclusão de outros instrumentos ou indicadores. Para simplificar, consideraremos apenas duas médias móveis de períodos distintos: uma de curto prazo e outra de longo prazo.
A seguir, veremos a função dos cruzamentos de médias móveis e como eles geram sinais de negociação que podem ser usados para tomar decisões operacionais. As médias móveis utilizam dados de preços e os suavizam, criando uma linha contínua que facilita a identificação de tendências, sendo mais clara e rastreável do que o gráfico de preços bruto. Isso ocorre porque uma linha média normalmente apresenta uma direção mais definida e é mais fácil de seguir do que uma linha irregular. Quando utilizamos duas médias móveis de períodos distintos, elas se cruzarão em algum momento.
Para aplicar na prática os sinais de cruzamento de médias móveis usando MQL5, começaremos definindo os períodos de curto e longo prazo que melhor se adequam à nossa estratégia de negociação. Para isso, utilizaremos períodos padrão, como 50 e 200 para tendências de longo prazo, e 10 e 20 para tendências de curto prazo. Após calcular as médias móveis, compararemos os valores dos cruzamentos a cada novo tick ou barra e transformaremos esses eventos detectados em sinais binários de compra (buy) ou venda (sell), que servirão como base para a ação do nosso EA. Para compreender melhor essa lógica, veremos dois casos.
Cruzamento de baixo para cima:
Cruzamento de cima para baixo:
Os sinais gerados serão integrados à nossa estrutura atual de troca de mensagens entre MQL5 e Telegram. Para isso, o código da primeira parte será adaptado para incluir a detecção e o formato do sinal. Quando um cruzamento for identificado, uma mensagem será criada contendo o nome do ativo, a direção do cruzamento (compra ou venda) e o horário do sinal. A entrega imediata dessa mensagem em um grupo específico do Telegram permitirá que nossa equipe de negociação acompanhe as possíveis oportunidades de entrada no mercado. Além disso, a certeza de receber a mensagem logo após o cruzamento nos permite agir rapidamente com base no sinal detectado, abrindo uma posição de mercado e compartilhando os dados da posição.
Implementação em MQL5
Primeiro, precisamos garantir que conseguimos segmentar e enviar corretamente nossa mensagem. Na primeira parte, ao tentar enviar uma mensagem complexa contendo caracteres especiais, como quebras de linha, encontramos um erro e fomos obrigados a enviá-la como um único bloco, sem estruturação. Por exemplo, tínhamos o seguinte trecho de código que capturava o evento de inicialização, o saldo da conta e a margem livre disponível:
double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY); double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); string msg = "🚀 EA INITIALIZED ON CHART " + _Symbol + " 🚀" +"📊 Account Status 📊; Equity: $" +DoubleToString(accountEquity,2) +"; Free Margin: $" +DoubleToString(accountFreeMargin,2);
Ao enviar a mensagem inteira, o resultado obtido foi o seguinte:
Embora a mensagem tenha sido enviada, sua estrutura não era visualmente agradável. O texto de inicialização deveria estar na primeira linha, seguido pelo status da conta na segunda, pelo patrimônio na terceira e pelas informações sobre a margem livre na última. Para alcançar esse formato, precisamos adicionar corretamente o caractere de quebra de linha \n:
double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY); double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); string msg = "🚀 EA INITIALIZED ON CHART " + _Symbol + " 🚀" +"\n📊 Account Status 📊" +"\nEquity: $" +DoubleToString(accountEquity,2) +"\nFree Margin: $" +DoubleToString(accountFreeMargin,2);
No entanto, ao executar o programa, encontramos um erro no log, conforme mostrado abaixo, e a mensagem não foi enviada para o chat do Telegram:
Para garantir que a mensagem seja enviada corretamente, precisamos definir sua codificação. Nossa integração exige que as mensagens sejam codificadas corretamente para que caracteres especiais sejam interpretados sem problemas. Por exemplo, se nossa mensagem contiver espaços, caracteres especiais como &, ? e outros, a API do Telegram pode interpretá-la incorretamente devido à falta de um tratamento adequado durante a integração. Esse aspecto deve ser tratado com muita atenção. Já observamos outros cenários em que a codificação de caracteres foi essencial, como na abertura de determinados tipos de documentos, conforme demonstrado na captura de tela.
A codificação resolve problemas quando a API não compreende a mensagem que estamos tentando enviar e, consequentemente, não executa corretamente as ações desejadas.
Por exemplo, uma mensagem enviada para a API contendo um caractere especial pode comprometer a estrutura do URL — ou seja, a forma como os computadores "veem" os URLs — resultando em erros de interpretação. A API pode interpretar um caractere especial como uma instrução ou outra parte do código, em vez de tratá-lo como parte do conteúdo da mensagem. Esse problema de comunicação pode ocorrer em qualquer estágio: no envio da mensagem a partir do programa ou na sua recepção pelo destinatário, caso a codificação não desempenhe corretamente sua função principal de garantir que a parte invisível da mensagem seja segura para "visualização". Além disso, utilizar um esquema de codificação garante que a mensagem esteja em um formato compatível com o sistema receptor — neste caso, a API do Telegram. Como lidamos com várias tecnologias diferentes, cada uma com suas próprias exigências quanto à transmissão de dados, a primeira coisa que faremos é criar uma função para codificar nossas mensagens.
// FUNCTION TO ENCODE A STRING FOR USE IN URL string UrlEncode(const string text) { string encodedText = ""; // Initialize the encoded text as an empty string int textLength = StringLen(text); // Get the length of the input text ... }
Começamos com uma função do tipo string UrlEncode, que recebe um único parâmetro — um argumento ou texto do tipo string — e tem como objetivo converter o texto fornecido para um formato compatível com URLs. Em seguida, inicializamos uma string vazia chamada "encodedText", que será utilizada para armazenar o resultado legível pelo URL conforme o texto de entrada é processado. Depois, determinamos o comprimento da string de entrada usando a função StringLen, que armazena esse valor em uma variável inteira chamada textLength. Esse passo é crucial, pois nos permite saber exatamente quantos caracteres devem ser processados. Com essa informação, podemos percorrer eficientemente cada caractere da string dentro de um laço, garantindo que todos sejam codificados corretamente de acordo com as regras de codificação de URLs. Para essa iteração, utilizaremos um laço.
// Loop through each character in the input string for (int i = 0; i < textLength; i++) { ushort character = StringGetCharacter(text, i); // Get the character at the current position ... }
Aqui, iniciamos um laço for para percorrer todos os caracteres contidos na mensagem ou texto de entrada, começando pelo primeiro caractere, que tem índice 0, e avançando para os seguintes. Utilizamos a função StringGetCharacter para obter o valor do caractere localizado na posição especificada da string. O índice dessa posição é determinado pela variável i. O caractere obtido é então armazenado em uma variável do tipo ushort, chamada character.
// Check if the character is alphanumeric or one of the unreserved characters if ((character >= 48 && character <= 57) || // Check if character is a digit (0-9) (character >= 65 && character <= 90) || // Check if character is an uppercase letter (A-Z) (character >= 97 && character <= 122) || // Check if character is a lowercase letter (a-z) character == '!' || character == '\'' || character == '(' || character == ')' || character == '*' || character == '-' || character == '.' || character == '_' || character == '~') { // Append the character to the encoded string without encoding encodedText += ShortToString(character); }
Neste ponto, verificamos se o caractere em questão é alfanumérico ou um dos caracteres não reservados, geralmente permitidos em URLs. O objetivo é determinar se o caractere precisa ser codificado ou se pode ser adicionado diretamente à string codificada. Primeiramente, verificamos se ele é um número, conferindo se seu valor ASCII está entre 48 e 57. Em seguida, verificamos se o caractere é uma letra maiúscula, conferindo se seu valor ASCII está entre 65 e 90. Do mesmo modo, verificamos se é uma letra minúscula, conferindo se seu valor ASCII está entre 97 e 122. Esses valores podem ser encontrados na "tabela ASCII". Os valores podem ser consultados na "Tabela ASCII".
Números - de 48 a 57:
Letras maiúsculas - de 65 a 90:
Letras minúsculas - de 97 a 122:
Além desses caracteres alfanuméricos, também verificamos a presença de determinados caracteres não reservados usados em URLs. Esses incluem '', ''', '(', ')', '*', '-', '.', '_' e '~'. Se um caractere corresponder a qualquer um desses critérios, isso significa que ele é alfanumérico ou um dos caracteres não reservados.
Se o caractere atender a qualquer um desses critérios, ele será adicionado diretamente à string encodedText, sem a necessidade de codificação. Isso é feito convertendo o caractere em sua representação textual por meio da função ShortToString, garantindo que ele seja inserido na string codificada em seu formato original. Se nenhuma dessas condições for atendida, passamos para a verificação de espaços.
// Check if the character is a space else if (character == ' ') { // Encode space as '+' encodedText += ShortToString('+'); }
Aqui, utilizamos a estrutura else if para verificar se o caractere é um espaço, comparando-o diretamente com o próprio espaço. Se for, precisamos codificá-lo de maneira adequada para URLs. Em vez de utilizar a codificação tradicional de espaços com o percentual (%20), conforme vimos anteriormente, optamos por codificá-los usando o sinal de mais (+). Esse método é amplamente utilizado para representar espaços em URLs, especialmente nos parâmetros de consulta. Assim, convertemos o sinal de mais em sua representação de string utilizando a função ShortToString e adicionamos o resultado à string encodedText.
Se, até este ponto, ainda houver caracteres não codificados, precisamos lidar com caracteres mais complexos, como emojis. Portanto, qualquer caractere que não seja alfanumérico, não reservado ou espaço será codificado no formato UTF-8, garantindo que possa ser incorporado com segurança ao URL.
// For all other characters, encode them using UTF-8 else { uchar utf8Bytes[]; // Array to hold the UTF-8 bytes int utf8Length = ShortToUtf8(character, utf8Bytes); // Convert the character to UTF-8 for (int j = 0; j < utf8Length; j++) { // Convert each byte to its hexadecimal representation prefixed with '%' encodedText += StringFormat("%%%02X", utf8Bytes[j]); } }
Primeiro, declaramos um array utf8Bytes para armazenar a representação binária do caractere no formato UTF-8. m seguida, chamamos a função ShortToUtf8, passando character e o array utf8Bytes como argumentos. Voltaremos a descrever essa função mais adiante. Por enquanto, basta notar que ela converte o caractere para sua representação UTF-8 e retorna o número de bytes usados na conversão, armazenando esses bytes no array utf8Bytes.
Em seguida, utilizamos um laço for para percorrer cada byte no array utf8Bytes. Cada byte é convertido para sua representação hexadecimal com um prefixo %, que é o padrão para codificação de caracteres em URLs. Usamos a função StringFormat para formatar cada byte como um número hexadecimal de dois dígitos, precedido pelo símbolo %. Por fim, adicionamos essa representação codificada à string textEncoded. Ao final do processo, simplesmente retornamos o resultado.
return encodedText; // Return the URL-encoded string
O trecho completo do código da função é o seguinte:
// FUNCTION TO ENCODE A STRING FOR USE IN URL string UrlEncode(const string text) { string encodedText = ""; // Initialize the encoded text as an empty string int textLength = StringLen(text); // Get the length of the input text // Loop through each character in the input string for (int i = 0; i < textLength; i++) { ushort character = StringGetCharacter(text, i); // Get the character at the current position // Check if the character is alphanumeric or one of the unreserved characters if ((character >= 48 && character <= 57) || // Check if character is a digit (0-9) (character >= 65 && character <= 90) || // Check if character is an uppercase letter (A-Z) (character >= 97 && character <= 122) || // Check if character is a lowercase letter (a-z) character == '!' || character == '\'' || character == '(' || character == ')' || character == '*' || character == '-' || character == '.' || character == '_' || character == '~') { // Append the character to the encoded string without encoding encodedText += ShortToString(character); } // Check if the character is a space else if (character == ' ') { // Encode space as '+' encodedText += ShortToString('+'); } // For all other characters, encode them using UTF-8 else { uchar utf8Bytes[]; // Array to hold the UTF-8 bytes int utf8Length = ShortToUtf8(character, utf8Bytes); // Convert the character to UTF-8 for (int j = 0; j < utf8Length; j++) { // Convert each byte to its hexadecimal representation prefixed with '%' encodedText += StringFormat("%%%02X", utf8Bytes[j]); } } } return encodedText; // Return the URL-encoded string }
Agora, vamos examinar a função responsável por converter caracteres para UTF-8.
//+-----------------------------------------------------------------------+ //| Function to convert a ushort character to its UTF-8 representation | //+-----------------------------------------------------------------------+ int ShortToUtf8(const ushort character, uchar &utf8Output[]) { ... }
A função é do tipo inteiro e recebe dois parâmetros de entrada: o valor do caractere e um array de saída.
Primeiro, convertemos os caracteres de um único byte.
// Handle single byte characters (0x00 to 0x7F) if (character < 0x80) { ArrayResize(utf8Output, 1); // Resize the array to hold one byte utf8Output[0] = (uchar)character; // Store the character in the array return 1; // Return the length of the UTF-8 representation }
A conversão de caracteres de um byte, cujos valores estão no intervalo de 0x00 a 0x7F, é simples, pois eles são representados diretamente por um único byte em UTF-8. Primeiro, verificamos se o valor do caractere é menor que 0x80. Se for o caso, redimensionamos o array utf8Output para um único byte usando a função ArrayResize. Isso nos permite garantir que o tamanho correto do resultado em UTF-8 seja mantido. Em seguida, inserimos o caractere no primeiro elemento do array, convertendo-o para o tipo uchar (conversão de tipo). Isso equivale a copiar diretamente o valor do caractere para o array. Retornamos 1, indicando que a representação UTF-8 tem um comprimento de um byte. Esse processo converte eficientemente qualquer caractere de um único byte para sua forma UTF-8, independentemente do sistema operacional.
Sua representação será a seguinte:
0x00, UTF-8:
0x7F, UTF-8:
Como podemos ver, a representação decimal desses valores abrange o intervalo de 0 a 127. Esses caracteres são idênticos aos caracteres Unicode originais. No sistema hexadecimal, 0x80 e 0x7F representam valores específicos que podem ser convertidos para o sistema decimal para facilitar a compreensão. O número hexadecimal 0x80 é equivalente a 128 no formato decimal. Isso ocorre porque o sistema hexadecimal tem base 16, onde cada dígito representa uma potência de 16. No número 0x80, o 8 representa 8 × 16^1 (que equivale a 128), enquanto o 0 representa 0 × 16^0 (que é 0), resultando em um total de 128.
Por outro lado, 0x7F é equivalente a 127 no sistema decimal. No sistema hexadecimal, 7F representa 7 multiplicado por 16^1, mais 15 multiplicado por 16^0. Realizando os cálculos, temos 7 × 16 (que é 112) mais F (que equivale a 15), resultando em 127. A conversão de A-F está mostrada abaixo. O valor decimal correspondente ao hexadecimal F é 15.
Portanto, 0x80 equivale a 128 no sistema decimal, enquanto 0x7F equivale a 127. Isso significa que 0x80 é apenas uma unidade maior que 0x7F, marcando o limite onde a representação de um byte na codificação UTF-8 se transforma em uma representação de múltiplos bytes.
Apresentei essas explicações detalhadas para evitar dúvidas sobre os formatos. Agora, vamos aos caracteres de dois bytes.
// Handle two-byte characters (0x80 to 0x7FF) if (character < 0x800) { ArrayResize(utf8Output, 2); // Resize the array to hold two bytes utf8Output[0] = (uchar)((character >> 6) | 0xC0); // Store the first byte utf8Output[1] = (uchar)((character & 0x3F) | 0x80); // Store the second byte return 2; // Return the length of the UTF-8 representation }
Aqui, convertemos caracteres que exigem dois bytes em sua representação UTF-8, especificamente aqueles cujos valores estão no intervalo de 0x80 a 0x7FF. Para isso, primeiro verificamos se o valor do caractere é menor que 0x800 (2048 em decimal), garantindo que ele se enquadre nesse intervalo. Se a condição for atendida, redimensionamos o array utf8Output para conter dois bytes, já que a representação UTF-8 desse caractere exigirá dois bytes. Em seguida, calculamos a codificação UTF-8 propriamente dita.
O primeiro byte é obtido deslocando o valor do caractere seis bits para a direita e, em seguida, combinando-o com 0xC0 usando a operação lógica OR. Esse cálculo define os bits mais significativos do primeiro byte com o prefixo adequado para caracteres de dois bytes em UTF-8. O segundo byte é calculado mascarando o valor do caractere com 0x3F para obter os seis bits inferiores e, em seguida, combinando-o com 0x80. Essa operação garante que o segundo byte possua o prefixo correto em UTF-8.
No final, armazenamos esses dois bytes no array utf8Output e retornamos 2 à função chamadora, indicando que a representação UTF-8 do caractere requer dois bytes. Essa codificação é essencial e correta para caracteres que utilizam o dobro de bits em relação a caracteres de um único byte. Em seguida, temos os caracteres de três bytes.
// Handle three-byte characters (0x800 to 0xFFFF) if (character < 0xFFFF) { ... }
Agora você entende o que isso significa. Aqui, o número hexadecimal 0xFFFF é convertido para 65 535 no formato decimal. Cada dígito hexadecimal representa uma potência de 16. Para 0xFFFF, cada dígito é F, que equivale a 15 em decimal — já vimos isso antes. Para calcular o valor decimal, avaliamos a contribuição de cada dígito com base em sua posição. Começamos pelo dígito de maior valor, que é (15 × 16^3), resultando em (15 × 4096 = 61 440). Em seguida, calculamos (15 × 16^2), que equivale a (15 × 256 = 3 840). Depois, (15 × 16^1) resulta em (15 × 16 = 240). Finalmente, (15 × 16^0) é (15 × 1 = 15). Somando esses valores, obtemos 61 440 + 3 840 + 240 + 15, o que totaliza 65 535. Isso significa que 0xFFFF equivale a 65 535 no sistema decimal. Com isso em mente, podemos inferir que existem três casos distintos para caracteres de três bytes. Vamos analisar o primeiro caso.
if (character >= 0xD800 && character <= 0xDFFF) { // Ill-formed characters ArrayResize(utf8Output, 1); // Resize the array to hold one byte utf8Output[0] = ' '; // Replace with a space character return 1; // Return the length of the UTF-8 representation }
Aqui, tratamos os caracteres que pertencem ao intervalo Unicode de 0xD800 a 0xDFFF, conhecidos como metades de substituição (surrogate halves), que não são caracteres válidos isoladamente. Começamos verificando se o caractere se encontra dentro desse intervalo. Quando encontramos um caractere malformado como esse, primeiro ajustamos o tamanho do array utf8Output para conter apenas um byte, garantindo que nossa estrutura de saída esteja preparada para armazenar apenas um único byte.
Em seguida, substituímos o caractere inválido por um espaço, definindo o primeiro elemento do array utf8Output como um espaço. Essa substituição é necessária para lidar corretamente com entradas inválidas. Por fim, retornamos 1, indicando que a representação UTF-8 desse caractere malformado tem um comprimento de um byte. A seguir, verificamos a presença de caracteres emoji. Isso significa que lidamos com símbolos dentro do intervalo Unicode de 0xE000 a 0xF8FF. Esses caracteres incluem emojis e outros símbolos estendidos.
else if (character >= 0xE000 && character <= 0xF8FF) { // Emoji characters int extendedCharacter = 0x10000 | character; // Extend the character to four bytes ArrayResize(utf8Output, 4); // Resize the array to hold four bytes utf8Output[0] = (uchar)(0xF0 | (extendedCharacter >> 18)); // Store the first byte utf8Output[1] = (uchar)(0x80 | ((extendedCharacter >> 12) & 0x3F)); // Store the second byte utf8Output[2] = (uchar)(0x80 | ((extendedCharacter >> 6) & 0x3F)); // Store the third byte utf8Output[3] = (uchar)(0x80 | (extendedCharacter & 0x3F)); // Store the fourth byte return 4; // Return the length of the UTF-8 representation }
Primeiro, verificamos se o caractere está dentro da faixa de emojis. Como esses caracteres exigem uma representação de quatro bytes em UTF-8, primeiro estendemos o valor do caractere realizando uma operação OR bit a bit com 0x10000. Esse passo nos permite tratar corretamente caracteres de planos suplementares.
Em seguida, ajustamos o tamanho do array utf8Output para quatro bytes. Isso garante que haja espaço suficiente para armazenar toda a codificação UTF-8 no array. Assim, o cálculo da representação UTF-8 se baseia na extração e combinação de quatro partes (quatro bytes). Para o primeiro byte, deslocamos extendedCharacter 18 bits para a direita. Depois, realizamos uma operação OR bit a bit com 0xF0 para obter os bits mais significativos do primeiro byte. Para o segundo byte, deslocamos extendedCharacter 12 bits para a direita e aplicamos a mesma técnica para obter a próxima parte.
Da mesma forma, calculamos o terceiro byte deslocando o caractere expandido 6 bits para a direita e mascarando os próximos 6 bits. Em seguida, combinamos esse valor com 0x80 para obter a primeira parte do terceiro byte. Para obter a segunda parte, mascaramos o caractere expandido com 0x3F (o que nos dá os últimos 6 bits do caractere expandido) e o combinamos com 0x80. Depois de calcular e armazenar esses dois bytes no array utf8Output, retornamos 4, indicando que o caractere ocupa 4 bytes em UTF-8. Por exemplo, podemos ter um emoji representado por 1F4B0, que é o emoji de um saco de dinheiro.
Para calcular sua representação decimal, começamos convertendo os dígitos hexadecimais para seus valores decimais correspondentes. O dígito 1 na posição 16^4 equivale a 1 × 65 536 = 65 536. O dígito F, que em decimal é 15, na posição 16^3 equivale a 15 × 4 096 = 61 440. O dígito 4 na posição 16^2 equivale a 4 × 256 = 1 024. O dígito B, que em decimal é 11, na posição 16^1 equivale a 11 × 16 = 176. Finalmente, o dígito 0 na posição 16^0 equivale a 0 × 1 = 0.
Somando todos esses valores, obtemos 65 536 + 61 440 + 1 024 + 176 + 0 = 128 176. Assim, 0x1F4B0 é convertido para 128 176 na numeração decimal. Você pode confirmar esse resultado observando a captura de tela.
Por fim, analisamos caracteres que estão fora dos intervalos processados anteriormente e que exigem uma representação UTF-8 de três bytes.
else { ArrayResize(utf8Output, 3); // Resize the array to hold three bytes utf8Output[0] = (uchar)((character >> 12) | 0xE0); // Store the first byte utf8Output[1] = (uchar)(((character >> 6) & 0x3F) | 0x80); // Store the second byte utf8Output[2] = (uchar)((character & 0x3F) | 0x80); // Store the third byte return 3; // Return the length of the UTF-8 representation }
Começamos ajustando o tamanho do array utf8Output para conter os três bytes necessários. Cada byte tem 8 bits, portanto, para armazenar três bytes, precisamos de espaço para 24 bits. Depois, calculamos byte a byte a codificação UTF-8. O primeiro byte é determinado pela parte superior do caractere. Para calcular o segundo byte, deslocamos o caractere 6 bits para a direita, mascaramos o valor obtido para extrair os próximos 6 bits e o combinamos com 0x80 para definir os bits de continuidade. O terceiro byte é obtido de maneira semelhante, exceto que não realizamos um deslocamento. Em vez disso, apenas mascaramos para extrair os últimos 6 bits e os combinamos com 0x80. Após definir os três bytes, armazenamos no array utf8Output e retornamos 3, indicando que a representação ocupa três bytes.
Finalmente, precisamos lidar com casos em que o caractere é inválido ou não pode ser corretamente codificado, substituindo-o pelo caractere de substituição Unicode U+FFFD.
// Handle invalid characters by replacing with the Unicode replacement character (U+FFFD) ArrayResize(utf8Output, 3); // Resize the array to hold three bytes utf8Output[0] = 0xEF; // Store the first byte utf8Output[1] = 0xBF; // Store the second byte utf8Output[2] = 0xBD; // Store the third byte return 3; // Return the length of the UTF-8 representation
Começamos ajustando o tamanho do array utf8Output para três bytes, garantindo espaço suficiente para substituir o caractere. Em seguida, definimos os bytes do array utf8Output para representar U+FFFD na codificação UTF-8. Esse caractere é exibido em UTF-8 como a sequência de bytes 0xEF, 0xBF e 0xBD, que são diretamente atribuídos a utf8Output, onde 0xEF é o primeiro byte, 0xBF é o segundo e 0xBD é o terceiro. Finalmente, retornamos 3, indicando que a representação UTF-8 do caractere de substituição ocupa três bytes. Essa é a função completa que garante que podemos converter caracteres para UTF-8. Poderíamos também usar o formato UTF-16 mais avançado, mas, por enquanto, evitaremos complicar a implementação. Assim, o código completo da função se apresenta da seguinte maneira:
//+-----------------------------------------------------------------------+ //| Function to convert a ushort character to its UTF-8 representation | //+-----------------------------------------------------------------------+ int ShortToUtf8(const ushort character, uchar &utf8Output[]) { // Handle single byte characters (0x00 to 0x7F) if (character < 0x80) { ArrayResize(utf8Output, 1); // Resize the array to hold one byte utf8Output[0] = (uchar)character; // Store the character in the array return 1; // Return the length of the UTF-8 representation } // Handle two-byte characters (0x80 to 0x7FF) if (character < 0x800) { ArrayResize(utf8Output, 2); // Resize the array to hold two bytes utf8Output[0] = (uchar)((character >> 6) | 0xC0); // Store the first byte utf8Output[1] = (uchar)((character & 0x3F) | 0x80); // Store the second byte return 2; // Return the length of the UTF-8 representation } // Handle three-byte characters (0x800 to 0xFFFF) if (character < 0xFFFF) { if (character >= 0xD800 && character <= 0xDFFF) { // Ill-formed characters ArrayResize(utf8Output, 1); // Resize the array to hold one byte utf8Output[0] = ' '; // Replace with a space character return 1; // Return the length of the UTF-8 representation } else if (character >= 0xE000 && character <= 0xF8FF) { // Emoji characters int extendedCharacter = 0x10000 | character; // Extend the character to four bytes ArrayResize(utf8Output, 4); // Resize the array to hold four bytes utf8Output[0] = (uchar)(0xF0 | (extendedCharacter >> 18)); // Store the first byte utf8Output[1] = (uchar)(0x80 | ((extendedCharacter >> 12) & 0x3F)); // Store the second byte utf8Output[2] = (uchar)(0x80 | ((extendedCharacter >> 6) & 0x3F)); // Store the third byte utf8Output[3] = (uchar)(0x80 | (extendedCharacter & 0x3F)); // Store the fourth byte return 4; // Return the length of the UTF-8 representation } else { ArrayResize(utf8Output, 3); // Resize the array to hold three bytes utf8Output[0] = (uchar)((character >> 12) | 0xE0); // Store the first byte utf8Output[1] = (uchar)(((character >> 6) & 0x3F) | 0x80); // Store the second byte utf8Output[2] = (uchar)((character & 0x3F) | 0x80); // Store the third byte return 3; // Return the length of the UTF-8 representation } } // Handle invalid characters by replacing with the Unicode replacement character (U+FFFD) ArrayResize(utf8Output, 3); // Resize the array to hold three bytes utf8Output[0] = 0xEF; // Store the first byte utf8Output[1] = 0xBF; // Store the second byte utf8Output[2] = 0xBD; // Store the third byte return 3; // Return the length of the UTF-8 representation }
Com a função de codificação pronta, agora podemos codificar nossa mensagem e reenviá-la.
double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY); double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); string msg = "🚀EA INITIALIZED ON CHART " + _Symbol + " 🚀" +"\n📊Account Status 📊" +"\nEquity: $" +DoubleToString(accountEquity,2) +"\nFree Margin: $" +DoubleToString(accountFreeMargin,2); string encloded_msg = UrlEncode(msg); msg = encloded_msg;
Aqui, simplesmente declaramos uma variável do tipo string chamada encoded_msg, que armazenará nossa mensagem codificada para URL. Por fim, adicionamos o resultado à mensagem original, sobrescrevendo seu conteúdo em vez de apenas declarar outra variável. Ao executar, obtemos o seguinte:
Temos um sucesso parcial. Recebemos a mensagem estruturada corretamente. No entanto, os emojis originalmente incluídos na mensagem foram descartados. Isso ocorre porque eles foram codificados, e agora precisamos reintroduzir seus formatos correspondentes para restaurá-los. Se não quisermos removê-los, devemos programá-los explicitamente. Como resultado, o trecho de código relacionado aos emojis dentro da função é ignorado. Para nós, é essencial mantê-los no formato correto para que possam ser codificados automaticamente.
double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY); double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); string msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680" +"\n\xF4CA Account Status \xF4CA" +"\nEquity: $" +DoubleToString(accountEquity,2) +"\nFree Margin: $" +DoubleToString(accountFreeMargin,2); string encloded_msg = UrlEncode(msg); msg = encloded_msg;
Aqui, representamos o caractere no formato "\xF*". Se houver uma palavra logo após a representação, é necessário usar um espaço ou uma barra invertida \ para diferenciar, ou seja, "\xF123 " ou "\xF123\". Ao executar, obtemos o seguinte resultado:
Agora vemos que nossa mensagem está no formato correto, com todos os caracteres devidamente codificados. Sucesso! Agora podemos prosseguir para a criação de sinais reais.
Como a função WebRequest não funciona no testador de estratégias e esperar a geração de um sinal baseado na estratégia de cruzamento de médias móveis pode levar algum tempo até sua confirmação, vamos implementar uma estratégia rápida para ser utilizada na inicialização do programa. Aplicaremos a estratégia de médias móveis posteriormente. Para isso, avaliamos a última barra ao iniciar o programa e, se for uma barra de alta, abrimos uma ordem de compra. Caso contrário, se for uma barra de baixa ou uma barra zero, abrimos uma ordem de venda.
O trecho de código utilizado para essa lógica é o seguinte:
double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); double Price_Open = iOpen(_Symbol,_Period,1); double Price_Close = iClose(_Symbol,_Period,1); bool isBuySignal = Price_Open < Price_Close; bool isSellSignal = Price_Open >= Price_Close;
Aqui, definimos as cotações de preços, ou seja, os preços bid e ask. Em seguida, obtemos o preço de abertura da barra anterior (índice 1) usando a função iOpen, que recebe três argumentos: o símbolo do ativo, o período e o índice da barra cujo valor queremos obter. Para recuperar o preço de fechamento, utilizamos a função iClose. Depois, definimos as variáveis lógicas isBuySignal e isSellSignal, que comparam os valores de abertura e fechamento. Se o preço de abertura for menor que o preço de fechamento, ou se o preço de abertura for igual ou maior que o preço de fechamento, os sinais de compra ou venda são armazenados nas variáveis correspondentes.
Para abrir ordens, precisamos de um método.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
No escopo global, preferencialmente no início do código, incluímos a biblioteca de negociação utilizando a diretiva #include. Isso nos permite acessar a classe CTrade, que usaremos para criar um objeto de negociação. Esse acesso é essencial para a execução de operações.
O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. As aspas angulares indicam que o arquivo será carregado do diretório padrão (geralmente localizado em caminho_de_instalação_do_terminal\MQL5\Include). O diretório atual não é incluído na busca. Embora essa linha possa ser colocada em qualquer parte do programa, geralmente todas as inclusões são posicionadas no início do código-fonte para melhorar a estrutura e facilitar as referências. Graças aos desenvolvedores do MQL5, a declaração do objeto obj_Trade da classe CTrade nos dará acesso direto aos métodos desse módulo.
Agora podemos abrir posições.
double lotSize = 0, openPrice = 0,stopLoss = 0,takeProfit = 0; if (isBuySignal == true){ lotSize = 0.01; openPrice = Ask; stopLoss = Bid-1000*_Point; takeProfit = Bid+1000*_Point; obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit); } else if (isSellSignal == true){ lotSize = 0.01; openPrice = Bid; stopLoss = Ask+1000*_Point; takeProfit = Ask-1000*_Point; obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit); }
Definimos variáveis do tipo double para armazenar o volume da operação, o preço de abertura das ordens, os níveis de stop-loss e take-profit, inicializando-os com o valor zero. Para abrir posições, primeiro verificamos se o valor true está presente em isBuySignal, o que indica que a barra anterior foi altista. Se essa condição for atendida, abrimos uma posição de compra. Inicializamos o tamanho do lote em 0,01, o preço de abertura em ask e os níveis de stop-loss e take-profit em base na cotação solicitada, para abrir a posição de compra. Da mesma forma, para abrir uma posição de venda, calculamos os valores necessários e os aplicamos à função de execução da ordem.
Após abrir as posições, podemos compilar as informações sobre o sinal gerado e a posição aberta em uma única mensagem e enviá-la para o Telegram.
string position_type = isBuySignal ? "Buy" : "Sell"; ushort MONEYBAG = 0xF4B0; string MONEYBAG_Emoji_code = ShortToString(MONEYBAG); string msg = "\xF680 OPENED "+position_type+" POSITION." +"\n====================" +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits) +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS) +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS) +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2) +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits) +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits) +"\n_________________________" +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE) +" @ "+TimeToString(TimeLocal(),TIME_SECONDS) ; string encloded_msg = UrlEncode(msg); msg = encloded_msg;
Aqui, criamos uma mensagem clara contendo informações relacionadas ao sinal de negociação. Utilizamos emojis e outros elementos que, em nossa opinião, facilitam a assimilação das informações pelos destinatários. Iniciamos determinando se o sinal é de compra ou venda, utilizando um operador ternário. Em seguida, criamos a mensagem incluindo um emoji de pilha de dinheiro, apropriado tanto para um sinal de compra quanto de venda. Utilizamos símbolos de emoji reais no formato ushort e convertemos o código do caractere em uma variável do tipo string usando a função ShortToString, demonstrando que não é sempre necessário usar formatos convencionais de string. Como você pode perceber, o processo de conversão exige um pouco de tempo e espaço, mas, se desejar atribuir nomes específicos aos símbolos, esse é o método mais adequado.
Depois, unificamos as informações sobre a posição de negociação aberta em uma única string. Essa string, ao ser transformada em mensagem, contém detalhes da operação, como tipo de transação, preço de abertura, horário da ordem, horário atual, tamanho do lote, níveis de stop-loss, take-profit e outras informações relevantes. O objetivo é tornar a mensagem visualmente atrativa e de fácil interpretação.
Após compor a mensagem, chamamos a função UrlEncode para codificá-la, garantindo sua transmissão segura por meio de URL. Temos um cuidado especial para que todos os caracteres especiais e emojis sejam processados corretamente e compatíveis com a exibição na web. Em seguida, armazenamos a mensagem codificada em uma variável chamada encoded_msg e a sobrescrevemos com a mensagem original ou a alteramos conforme necessário. O resultado obtido é o seguinte:
Como podemos ver, conseguimos codificar e enviar a mensagem ao Telegram com a estrutura correta. O código-fonte completo responsável pelo envio é o seguinte:
//+------------------------------------------------------------------+ //| TELEGRAM_MQL5_SIGNALS_PART2.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh> CTrade obj_Trade; // Define constants for Telegram API URL, bot token, and chat ID const string TG_API_URL = "https://api.telegram.org"; // Base URL for Telegram API const string botTkn = "7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc"; // Telegram bot token const string chatID = "-4273023945"; // Chat ID for the Telegram chat // The following URL can be used to get updates from the bot and retrieve the chat ID // CHAT ID = https://api.telegram.org/bot{BOT TOKEN}/getUpdates // https://api.telegram.org/bot7456439661:AAELUurPxI1jloZZl3Rt-zWHRDEvBk2venc/getUpdates //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { 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 ////--- Simple Notification with Emoji: //string msg = "🚀 EA INITIALIZED ON CHART " + _Symbol + " 🚀"; ////--- Buy/Sell Signal with Emoji: //string msg = "📈 BUY SIGNAL GENERATED ON " + _Symbol + " 📈"; //string msg = "📉 SELL SIGNAL GENERATED ON " + _Symbol + " 📉"; ////--- Account Balance Notification: //double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); //string msg = "💰 Account Balance: $" + DoubleToString(accountBalance, 2) + " 💰"; ////--- Trade Opened Notification: //string orderType = "BUY"; // or "SELL" //double lotSize = 0.1; // Example lot size //double price = 1.12345; // Example price //string msg = "🔔 " + orderType + " order opened on " + _Symbol + "; Lot size: " + DoubleToString(lotSize, 2) + "; Price: " + DoubleToString(price, 5) + " 🔔"; ////--- Stop Loss and Take Profit Update: //double stopLoss = 1.12000; // Example stop loss //double takeProfit = 1.13000; // Example take profit //string msg = "🔄 Stop Loss and Take Profit Updated on " + _Symbol + "; Stop Loss: " + DoubleToString(stopLoss, 5) + "; Take Profit: " + DoubleToString(takeProfit, 5) + " 🔄"; ////--- Daily Performance Summary: //double profitToday = 150.00; // Example profit for the day //string msg = "📅 Daily Performance Summary 📅; Symbol: " + _Symbol + "; Profit Today: $" + DoubleToString(profitToday, 2); ////--- Trade Closed Notification: //string orderType = "BUY"; // or "SELL" //double profit = 50.00; // Example profit //string msg = "❌ " + orderType + " trade closed on " + _Symbol + "; Profit: $" + DoubleToString(profit, 2) + " ❌"; // ////--- Account Status Update: // double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY); // double accountFreeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); // string msg = "\xF680 EA INITIALIZED ON CHART " + _Symbol + "\xF680" // +"\n\xF4CA Account Status \xF4CA" // +"\nEquity: $" // +DoubleToString(accountEquity,2) // +"\nFree Margin: $" // +DoubleToString(accountFreeMargin,2); // // string encloded_msg = UrlEncode(msg); // msg = encloded_msg; double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); double Price_Open = iOpen(_Symbol,_Period,1); double Price_Close = iClose(_Symbol,_Period,1); bool isBuySignal = Price_Open < Price_Close; bool isSellSignal = Price_Open >= Price_Close; double lotSize = 0, openPrice = 0,stopLoss = 0,takeProfit = 0; if (isBuySignal == true){ lotSize = 0.01; openPrice = Ask; stopLoss = Bid-1000*_Point; takeProfit = Bid+1000*_Point; obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit); } else if (isSellSignal == true){ lotSize = 0.01; openPrice = Bid; stopLoss = Ask+1000*_Point; takeProfit = Ask-1000*_Point; obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit); } string position_type = isBuySignal ? "Buy" : "Sell"; ushort MONEYBAG = 0xF4B0; string MONEYBAG_Emoji_code = ShortToString(MONEYBAG); string msg = "\xF680 OPENED "+position_type+" POSITION." +"\n====================" +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits) +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS) +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS) +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2) +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits) +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits) +"\n_________________________" +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE) +" @ "+TimeToString(TimeLocal(),TIME_SECONDS) ; string encloded_msg = UrlEncode(msg); msg = encloded_msg; // Construct the URL for the Telegram API request to send a message // Format: https://api.telegram.org/bot{HTTP_API_TOKEN}/sendmessage?chat_id={CHAT_ID}&text={MESSAGE_TEXT} 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, "", 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, precisamos integrar os sinais de negociação baseados no cruzamento de médias móveis. Para isso, começamos declarando dois handles para os indicadores de média móvel e seus respectivos arrays de armazenamento de dados.
int handleFast = INVALID_HANDLE; // -1 int handleSlow = INVALID_HANDLE; // -1 double bufferFast[]; double bufferSlow[]; long magic_no = 1234567890;
Primeiramente, declaramos variáveis do tipo int, chamadas handleFast e handleSlow, que armazenarão os indicadores de média móvel rápida e média móvel lenta, respectivamente. Inicializamos os handles com o valor -1, que indica que, no momento, eles não estão vinculados a nenhuma instância válida do indicador. Em seguida, definimos dois arrays do tipo double — bufferFast e bufferSlow — nos quais armazenamos os valores extraídos das médias móveis rápida e lenta, respectivamente. Por fim, declaramos uma variável do tipo long para armazenar o número mágico das posições abertas. Toda essa lógica é definida em um escopo global.
Na função OnInit, inicializamos os handles dos indicadores e configuramos os arrays de armazenamento como séries temporais.
handleFast = iMA(Symbol(),Period(),20,0,MODE_EMA,PRICE_CLOSE); if (handleFast == INVALID_HANDLE){ Print("UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!"); return (INIT_FAILED); }
Aqui, criamos o handle do indicador da média móvel rápida. Isso é feito utilizando a função iMA, que é chamada com os seguintes parâmetros: Symbol, Period, 20, 0, MODE_EMA e PRICE_CLOSE. O parâmetro Symbol é uma função embutida que retorna o nome do ativo atual. O parâmetro Period retorna o timeframe atual. O próximo parâmetro, 20, representa o período da média móvel. O quarto parâmetro, 0, indica que a média móvel será aplicada aos barras de preço mais recentes. O quinto parâmetro, MODE_EMA, especifica que queremos calcular a Média Móvel Exponencial (EMA). O último parâmetro, PRICE_CLOSE, indica que a média móvel será calculada com base nos preços de fechamento. A variável handleFast retorna um handle que identifica de forma única essa instância do indicador de média móvel.
Após tentar criar o indicador, verificamos se o handle foi gerado corretamente. Se o retorno de handleFast for INVALID_HANDLE, isso significa que não foi possível criar a instância do indicador de média móvel rápida. Nesse caso, registramos uma mensagem no log com o nível de erro (ERROR). O usuário recebe a seguinte mensagem: "UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!" ("Não foi possível criar o handle do indicador de média móvel rápida. Retornando agora!"). A mensagem deixa claro que a ausência do handle significa que o indicador não foi carregado. Como sem esse indicador a estratégia de negociação não pode ser aplicada, a execução do programa torna-se inútil. Assim, retornamos INIT_FAILED, o que impedirá que o programa continue rodando e o removerá do gráfico.
A mesma lógica se aplica ao indicador de média móvel lenta.
handleSlow = iMA(Symbol(),Period(),50,0,MODE_SMA,PRICE_CLOSE); if (handleSlow == INVALID_HANDLE){ Print("UNABLE TO CREATE FAST MA INDICATOR HANDLE. REVERTING NOW!"); return (INIT_FAILED); }
Se imprimirmos os valores dos handles dos indicadores, veremos que o valor inicial será 10. Caso existam mais instâncias de indicadores, o valor aumentará incrementalmente em 1 para cada handle. Vamos imprimir esses valores e observar o resultado. Fazemos isso com o seguinte código:
Print("HANDLE FAST MA = ",handleFast); Print("HANDLE SLOW MA = ",handleSlow);
O resultado obtido será:
Por fim, configuramos os arrays de armazenamento de dados como séries temporais e definimos o número mágico.
ArraySetAsSeries(bufferFast,true); ArraySetAsSeries(bufferSlow,true); obj_Trade.SetExpertMagicNumber(magic_no);
A configuração dos arrays como séries temporais é feita utilizando a função ArraySetAsSeries.
Na função OnDeinit, liberamos os handles dos indicadores da memória usando a função IndicatorRelease, além de liberar os arrays de armazenamento com a função ArrayFree. Isso garante que a memória seja desalocada de processos desnecessários.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Code to execute when the expert is deinitialized IndicatorRelease(handleFast); IndicatorRelease(handleSlow); ArrayFree(bufferFast); ArrayFree(bufferSlow); }
No manipulador de eventos OnTick, executamos o código que utilizará os handles dos indicadores e verificará a geração de sinais. Essa função é chamada a cada tick (ou seja, sempre que as cotações de preços mudam) para obter os valores mais recentes.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Code to execute on every tick event ... }
Esse é o evento principal do qual extrairemos os valores do indicador.
if (CopyBuffer(handleFast,0,0,3,bufferFast) < 3){ Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING"); return; }
Primeiro, tentamos obter os dados do buffer do indicador de média móvel rápida usando a função CopyBuffer. Chamamos essa função com os seguintes parâmetros: handleFast, 0, 0, 3, bufferFast. O primeiro parâmetro, handleFast, representa o indicador do qual queremos extrair valores. O segundo parâmetro, 0, define o número do buffer de onde estamos obtendo os valores, geralmente no formato em que são exibidos na janela de dados. Para médias móveis, esse valor é sempre 0. O terceiro parâmetro, 0, especifica a posição inicial da barra de onde buscamos os valores. O valor 0 indica que queremos a barra atual. O quarto parâmetro, 3, indica a quantidade de valores a serem extraídos (isto é, as barras consideradas). No nosso caso, 3 barras serão analisadas. O quinto parâmetro, bufferFast, representa o array de destino onde os três valores extraídos serão armazenados.
Agora verificamos se a função conseguiu recuperar os 3 valores solicitados. Se o valor retornado for menor que 3, significa que a função não conseguiu obter os dados necessários. Nesse caso, exibimos uma mensagem de erro: "UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING" ("Não foi possível recuperar os dados para análise. Retornando.") Isso nos alerta de que a extração de dados falhou e, como não temos dados suficientes para o processo, não podemos continuar a busca por sinais. Assim, a execução dessa parte do programa é pausada até o próximo tick.
O mesmo processo é aplicado para a média móvel lenta, garantindo que tenhamos os dados necessários para comparação.
if (CopyBuffer(handleSlow,0,0,3,bufferSlow) < 3){ Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING"); return; }
Como a função OnTick é chamada a cada tick, precisamos projetar uma lógica que garanta que o código de busca de sinais seja executado apenas uma vez por barra. Essa lógica é implementada da seguinte forma.
int currBars = iBars(_Symbol,_Period); static int prevBars = currBars; if (prevBars == currBars) return; prevBars = currBars;
Primeiro, declaramos uma variável inteira chamada currBars, que armazenará a quantidade de barras do gráfico para o ativo e timeframe especificados. Utilizamos a função iBars, que recebe dois argumentos: o símbolo do ativo (symbol) e o período do gráfico (period).
Em seguida, declaramos outra variável inteira estática, prevBars, que armazenará a quantidade total de barras anteriores no gráfico no momento da criação de uma nova barra. Inicializamos essa variável com a quantidade atual de barras no gráfico para garantir que, na primeira execução da função, ambos os valores sejam iguais. Essa variável será usada para comparar o número atual de barras com o anterior, permitindo identificar quando uma nova barra surge no gráfico.
Por fim, utilizamos um operador condicional para verificar se a quantidade atual de barras no gráfico é igual à quantidade anterior. Se os valores forem iguais, significa que nenhuma nova barra foi formada, e então interrompemos a execução do código com um return. Caso contrário, se o número de barras mudou, uma nova barra foi gerada. Nesse caso, atualizamos a variável prevBars para refletir o valor de currBars, garantindo que a contagem de barras esteja sincronizada com o gráfico no próximo tick, a menos que uma nova barra seja formada.
A seguir, definimos as variáveis que armazenarão nossos dados para análise futura, como mostrado abaixo.
double fastMA1 = bufferFast[1]; double fastMA2 = bufferFast[2]; double slowMA1 = bufferSlow[1]; double slowMA2 = bufferSlow[2];
Com essas variáveis, podemos agora verificar cruzamentos das médias móveis e tomar as ações necessárias.
if (fastMA1 > slowMA1 && fastMA2 <= slowMA2){ for (int i = PositionsTotal()-1; i>= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_no){ if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ obj_Trade.PositionClose(ticket); } } } } } double lotSize = 0.01; double openPrice = Ask; double stopLoss = Bid-1000*_Point; double takeProfit = Bid+1000*_Point; obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit); }
Aqui, buscamos uma condição específica de cruzamento: se a média móvel rápida mais recente (fastMA1) for maior do que a correspondente média móvel lenta (slowMA1), e a média móvel rápida anterior (fastMA2) for menor ou igual à média móvel lenta anterior (slowMA2), então detectamos um cruzamento de alta, o que sinaliza uma possível oportunidade de compra.
Ao detectar um cruzamento de alta, verificamos as posições abertas para identificar posições de venda existentes. Se houver ordens de venda abertas, fechamos essas posições antes de abrir uma nova posição de compra. Para isso, percorremos as posições abertas da mais recente para a mais antiga.
Para cada posição de negociação, obtemos o número do ticket usando a função PositionGetTicket. Se o número do ticket for maior que 0, isso indica que a posição é válida, e então a selecionamos usando PositionSelectByTicket. Continuamos verificando se a posição pertence ao ativo atual e se corresponde ao número mágico da estratégia. Se a posição for de venda, utilizamos o obj_Trade.PositionClose para fechá-la. Após encerrar todas as posições de venda existentes, abrimos uma nova posição de compra, configurando os parâmetros da negociação: tamanho do lote, preço de abertura, stop-loss e take-profit. Após abrir a nova posição, registramos a operação no log para manter um histórico das negociações.
// BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
Por fim, enviamos uma mensagem informando a execução da operação, da mesma forma que fizemos na inicialização do programa.
ushort MONEYBAG = 0xF4B0; string MONEYBAG_Emoji_code = ShortToString(MONEYBAG); string msg = "\xF680 Opened Buy Position." +"\n====================" +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits) +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS) +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS) +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2) +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits) +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits) +"\n_________________________" +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE) +" @ "+TimeToString(TimeLocal(),TIME_SECONDS) ; string encloded_msg = UrlEncode(msg); msg = encloded_msg;
Para o cruzamento de baixa, seguimos a mesma estrutura de código, apenas invertendo as condições.
else if (fastMA1 < slowMA1 && fastMA2 >= slowMA2){ for (int i = PositionsTotal()-1; i>= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_no){ if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ obj_Trade.PositionClose(ticket); } } } } } double lotSize = 0.01; double openPrice = Bid; double stopLoss = Ask+1000*_Point; double takeProfit = Ask-1000*_Point; obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit); // SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW.");
Neste ponto, a estrutura do código está praticamente finalizada. Agora, precisamos adicionar os indicadores automaticamente ao gráfico assim que o programa for carregado, permitindo uma visualização clara das médias móveis. Para isso, no manipulador de eventos de inicialização, criamos a lógica para a adição automática dos indicadores da seguinte maneira:
//--- Add indicators to the chart automatically ChartIndicatorAdd(0,0,handleFast); ChartIndicatorAdd(0,0,handleSlow);
Aqui, simplesmente chamamos a função ChartIndicatorAdd para adicionar os indicadores ao gráfico. O primeiro e o segundo parâmetros especificam, respectivamente, a janela do gráfico e a subjanela. O terceiro parâmetro é o handle do indicador que desejamos adicionar.
Dessa forma, o código completo do manipulador de eventos OnTick, responsável pela geração e distribuição dos sinais, fica assim:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Code to execute on every tick event if (CopyBuffer(handleFast,0,0,3,bufferFast) < 3){ Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING"); return; } if (CopyBuffer(handleSlow,0,0,3,bufferSlow) < 3){ Print("UNABLE TO RETRIEVE THE REQUESTED DATA FOR FURTHER ANALYSIS. REVERTING"); return; } double Ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double Bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); int currBars = iBars(_Symbol,_Period); static int prevBars = currBars; if (prevBars == currBars) return; prevBars = currBars; double fastMA1 = bufferFast[1]; double fastMA2 = bufferFast[2]; double slowMA1 = bufferSlow[1]; double slowMA2 = bufferSlow[2]; if (fastMA1 > slowMA1 && fastMA2 <= slowMA2){ for (int i = PositionsTotal()-1; i>= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_no){ if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ obj_Trade.PositionClose(ticket); } } } } } double lotSize = 0.01; double openPrice = Ask; double stopLoss = Bid-1000*_Point; double takeProfit = Bid+1000*_Point; obj_Trade.Buy(lotSize,_Symbol,openPrice,stopLoss,takeProfit); // BUY POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM Print("BUY POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW."); 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 ushort MONEYBAG = 0xF4B0; string MONEYBAG_Emoji_code = ShortToString(MONEYBAG); string msg = "\xF680 Opened Buy Position." +"\n====================" +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits) +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS) +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS) +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2) +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits) +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits) +"\n_________________________" +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE) +" @ "+TimeToString(TimeLocal(),TIME_SECONDS) ; string encloded_msg = UrlEncode(msg); msg = encloded_msg; 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, "", 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()); } } else if (fastMA1 < slowMA1 && fastMA2 >= slowMA2){ for (int i = PositionsTotal()-1; i>= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_no){ if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ obj_Trade.PositionClose(ticket); } } } } } double lotSize = 0.01; double openPrice = Bid; double stopLoss = Ask+1000*_Point; double takeProfit = Ask-1000*_Point; obj_Trade.Sell(lotSize,_Symbol,openPrice,stopLoss,takeProfit); // SELL POSITION OPENED. GET READY TO SEND MESSAGE TO TELEGRAM Print("SELL POSITION OPENED. SEND MESSAGE TO TELEGRAM NOW."); 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 ushort MONEYBAG = 0xF4B0; string MONEYBAG_Emoji_code = ShortToString(MONEYBAG); string msg = "\xF680 Opened Sell Position." +"\n====================" +"\n"+MONEYBAG_Emoji_code+"Price = "+DoubleToString(openPrice,_Digits) +"\n\xF412\Time = "+TimeToString(iTime(_Symbol,_Period,0),TIME_SECONDS) +"\n\xF551\Time Current = "+TimeToString(TimeCurrent(),TIME_SECONDS) +"\n\xF525 Lotsize = "+DoubleToString(lotSize,2) +"\n\x274E\Stop loss = "+DoubleToString(stopLoss,_Digits) +"\n\x2705\Take Profit = "+DoubleToString(takeProfit,_Digits) +"\n_________________________" +"\n\xF5FD\Time Local = "+TimeToString(TimeLocal(),TIME_DATE) +" @ "+TimeToString(TimeLocal(),TIME_SECONDS) ; string encloded_msg = UrlEncode(msg); msg = encloded_msg; 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, "", 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()); } } } //+------------------------------------------------------------------+
Agora, atingimos nosso segundo objetivo: enviar sinais do terminal de negociação para um chat ou grupo no Telegram. O próximo passo é testar a integração para garantir que tudo funcione corretamente e identificar eventuais problemas.
Teste da integração
Para validar a integração, desativamos a lógica de inicialização do teste, comentando essa parte do código para evitar o envio excessivo de sinais. Em seguida, alteramos o timeframe para 1 minuto e modificamos os períodos dos indicadores para 5 e 10, permitindo que os sinais sejam gerados mais rapidamente. Abaixo, temos os resultados obtidos.
Confirmação do sinal de venda no terminal:
Confirmação do sinal de venda no Telegram:
Confirmação do sinal de compra no terminal:
Confirmação do sinal de compra no Telegram:
As imagens mostram que a integração foi bem-sucedida. Após a detecção e confirmação de um sinal, seus dados são codificados em uma única mensagem e enviados do terminal de negociação para o grupo no Telegram. Dessa forma, conseguimos atingir nosso objetivo com êxito.
Considerações finais
Esse artigo representou um avanço significativo no desenvolvimento do nosso EA MQL5 integrado ao Telegram, alcançando seu principal objetivo: o envio de sinais de negociação diretamente do terminal para um chat no Telegram. No entanto, não nos limitamos apenas à conexão entre MQL5 e Telegram, como na primeira parte da série. Em vez disso, focamos na geração dos próprios sinais de negociação, utilizando uma das estratégias mais populares da análise técnica: o cruzamento de médias móveis. Exploramos detalhadamente a lógica dos sinais, bem como o desenvolvimento de um sistema robusto e confiável para transmiti-los via Telegram. tornando-o mais eficiente e prático para o acompanhamento de operações em tempo real.
Neste artigo, exploramos detalhadamente os aspectos técnicos da geração e envio de sinais de negociação Analisamos cuidadosamente como codificar e transmitir mensagens de forma segura, como gerenciar os handles dos indicadores e como executar operações com base nos sinais detectados. Criamos o código necessário e o integramos ao Telegram, permitindo que recebêssemos notificações instantâneas sobre sinais de negociação, mesmo quando estivéssemos longe da nossa plataforma de trading. Os exemplos práticos e as explicações detalhadas apresentados ao longo do artigo devem fornecer uma compreensão clara de como criar uma solução semelhante para sua própria estratégia de negociação.
Na terceira parte desta série, adicionaremos um novo nível de integração com o Telegram. Dessa vez, trabalharemos no envio de capturas de tela dos gráficos diretamente para o Telegram. A capacidade de analisar o mercado visualmente, no contexto dos sinais de negociação, ajudará os traders a compreenderem melhor a situação. O texto combinado com dados visuais cria sinais ainda mais eficazes. Esse é exatamente o nosso objetivo: não apenas enviar sinais, mas também aperfeiçoar a negociação algorítmica e ampliar a percepção situacional através do canal de trading no Telegram. Fique ligado
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15495





- 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