
Desenvolvimento de um EA baseado na estratégia de rompimento do intervalo de consolidação em MQL5
Introdução
Neste artigo, exploraremos o desenvolvimento de um EA baseado na estratégia de rompimento do intervalo de consolidação (Consolidation Range Breakout) na linguagem de programação MetaQuotes Language 5 (MQL5) para MetaTrader 5. No acelerado mundo do trading financeiro, estratégias que aproveitam padrões e comportamentos de mercado são fundamentais para o sucesso. Uma dessas estratégias é o rompimento do intervalo de consolidação, que se concentra na identificação de períodos de consolidação do mercado e no aproveitamento de rompimentos consecutivos. Essa estratégia é particularmente eficaz para capturar movimentos significativos de preço que ocorrem após períodos de baixa volatilidade. O artigo abordará os seguintes tópicos:
- Visão geral da estratégia
- Planejamento da estratégia
- Implementação em MetaQuotes Language 5 (MQL5)
- Resultados de testes em dados históricos
- Considerações finais
Ao final deste artigo, você terá uma visão completa de como desenvolver e implementar um EA robusto baseado na estratégia de rompimento do intervalo de consolidação em MQL5, expandindo assim o seu conjunto de ferramentas de negociação. Usaremos ativamente a linguagem MetaQuotes Language 5 (MQL5) como nosso ambiente de desenvolvimento base (IDE) e executaremos os arquivos no terminal de trading MetaTrader 5. Vamos começar.
Visão geral da estratégia
Para facilitar a compreensão da estratégia de rompimento do intervalo de consolidação, vamos dividi-la em partes.
- Definição do intervalo de consolidação
O intervalo de consolidação, também conhecido como faixa de negociação, é um período em que o preço de um ativo financeiro oscila horizontalmente dentro de uma faixa específica, sem apresentar movimentos significativos de alta ou baixa. Caracteriza-se, portanto, por baixa volatilidade. O preço oscila entre um nível de suporte bem definido (limite inferior) e um nível de resistência (limite superior). Os traders frequentemente utilizam essa fase para prever possíveis pontos de rompimento, quando o preço pode se mover bruscamente em uma direção específica após o término do período.
- Como a estratégia funciona
A estratégia de rompimento do intervalo de consolidação utiliza o comportamento dos preços durante períodos de consolidação para identificar rompimentos e negociar com base neles. Veja como funciona:
O primeiro passo é identificar o intervalo de consolidação, analisando os movimentos recentes de preços. Isso envolve determinar os preços mais altos e mais baixos ao longo de um número específico de barras (velas), a fim de definir os limites superior e inferior do intervalo, que normalmente atuam como níveis de resistência e suporte. A escolha do período de tempo não é fixa, pois qualquer gráfico pode ser usado no processo de identificação. Assim, é necessário selecionar o período do gráfico que melhor se adapte ao seu estilo de negociação.
Monitoramento de rompimentos: após estabelecer o intervalo de consolidação, a estratégia monitora os movimentos de preços que ultrapassam os limites superior ou inferior do intervalo. Um rompimento ocorre quando o preço fecha acima do nível de resistência ou abaixo do nível de suporte. Alguns traders consideram fazer scalping na mesma vela que rompe o intervalo, ou seja, assim que o preço ultrapassa os limites do intervalo para baixo ou para cima, eles já consideram o evento como um rompimento.
Negociação no rompimento: quando identificado um rompimento, a estratégia inicia uma negociação na direção dele. Se o preço ultrapassar o nível de resistência, uma ordem de compra é colocada. Da mesma forma, se o preço cair abaixo do nível de suporte, uma ordem de venda será executada. Alguns traders esperam por uma correção após o rompimento para obter confirmação adicional. Nesse caso, eles aguardam que o preço volte ao intervalo e, quando ele sair novamente na mesma direção, entram no mercado. No entanto, este artigo não tratará da utilização da abordagem de correção.
- Implementação da estratégia
A implementação da estratégia de rompimento do intervalo de consolidação incluirá várias etapas:
Definição dos parâmetros do intervalo: determinamos o número de barras a serem analisadas para identificar o intervalo de consolidação e estabelecemos os critérios que caracterizam um rompimento. Especificamos e configuramos o intervalo de barras e o intervalo de preços-alvo. Por exemplo, para que o intervalo de consolidação seja válido, é necessário que haja pelo menos dez barras dentro de um intervalo de preços de 700 pontos ou 70 pips.
Desenvolvimento da lógica de detecção: escrevemos o código para escanear os dados históricos de preços, identificar o maior máximo e o menor mínimo no intervalo especificado e marcar esses níveis no gráfico. É necessário que o código especifique claramente as condições que devem ser atendidas para que o intervalo de consolidação seja considerado válido. As premissas utilizadas devem ser bem definidas para evitar ambiguidades.
Monitoramento de dados de preços em tempo real: monitoramos continuamente os dados de preços recebidos para detectar rompimentos o mais rápido possível. Esse monitoramento deve ser realizado a cada novo tick ou, caso não seja necessário em tempo real, a cada novo fechamento de vela.
Execução de negociações: implementamos a lógica de execução de negociações para inserir ordens de compra ou venda ao detectar um rompimento, incluindo a configuração de níveis apropriados de stop-loss e take-profit para o gerenciamento de riscos.
Otimização e teste: testamos a estratégia usando dados históricos para otimizar os parâmetros e garantir sua eficácia antes de aplicá-la ao mercado de negociação real. Isso nos ajudará a determinar os melhores parâmetros e identificar características-chave que precisam ser melhoradas ou filtradas para aprimorar o sistema.
Após concluir essas etapas, conseguiremos criar uma ferramenta poderosa baseada na estratégia de rompimento do intervalo de consolidação.
Planejamento da estratégia
Para facilitar a compreensão da ideia apresentada, visualizemos o plano na forma de um diagrama.
- Rompimento do limite superior do intervalo de consolidação:
- Rompimento do limite inferior do intervalo de consolidação:
Implementação em MetaQuotes Language 5 (MQL5)
Com a teoria concluída, vamos criar um EA no MQL5 para o MetaTrader 5.
No terminal MetaTrader 5, selecione "Serviço" > "Editor MetaQuotes Language" ou simplesmente pressione F4. Alternativamente, você pode clicar no ícone da IDE (ambiente de desenvolvimento integrado) na barra de ferramentas. Isso abrirá o ambiente de desenvolvimento MQL5, que permite criar robôs de trading, indicadores técnicos, scripts e bibliotecas de funções.
Na barra de ferramentas, escolha "Arquivo" - "Novo Arquivo" ou pressione CTRL + N para criar um novo documento. Você também pode clicar no ícone "Criar" na barra de ferramentas. Isso abrirá a janela do Assistente MQL.
Na janela do Assistente, escolha a opção EA (modelo) e clique em Avançar.
Nas propriedades gerais, insira o nome do arquivo do EA. Para especificar ou criar uma pasta, caso ela não exista, utilize uma barra invertida antes do nome do EA. Por padrão, a pasta Experts\. é definida, o que significa que nosso EA será criado dentro dela. As demais seções são bastante simples, mas você pode clicar no link na parte inferior da janela do Assistente para acessar mais detalhes.
Após especificar o nome do arquivo do EA, clique em "Avançar" > "Avançar" > "Concluir". Estamos prontos para implementar a estratégia em código.
Primeiramente, utilizaremos o comando #include no início do código-fonte. Isso nos dará acesso à classe CTrade, que será usada para criar um objeto de trading. Esse acesso é necessário para executar negociações.
#include <Trade/Trade.mqh> // Include the trade library CTrade obj_Trade; // Create an instance of the CTrade class
O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os colchetes angulares indicam que o arquivo será buscado no diretório padrão (geralmente localizado em caminho_de_instalação_do_terminal\MQL5\Include). O diretório atual não está incluso 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 fornecerá acesso direto aos métodos contidos nesta classe.
Como precisamos representar graficamente o intervalo no gráfico, será necessário um nome para identificá-lo. Usaremos um único objeto retangular para o intervalo. Assim, para visualização, utilizaremos este mesmo objeto. Após a criação do gráfico, simplesmente atualizaremos suas configurações sem precisar redesenhá-lo. Para obter um nome estático, fácil de lembrar e reutilizar, o definiremos da seguinte forma.
#define rangeNAME "CONSOLIDATION RANGE" // Define the name of the consolidation range
Usamos a palavra-chave #define para definir o macro rangeNAME com o valor CONSOLIDATION RANGE, simplificando a nomeação do intervalo de consolidação. Isso elimina a necessidade de inserir o nome repetidamente sempre que criamos um nível, economizando tempo e reduzindo a chance de erros de digitação. Em essência, os macros são utilizados para substituir texto durante a compilação.
Além disso, precisaremos armazenar as coordenadas do objeto retangular. Essas coordenadas são bidimensionais (2D), no formato (x, y), e são usadas para definir explicitamente as posições inicial e final, documentadas como x1, y1 e x2, y2, respectivamente. No gráfico de preços, o eixo X é representado pela escala de data e hora, enquanto o eixo Y representa a escala de preços. Para facilitar a compreensão, considere uma ilustração representativa.
Agora fica claro por que precisamos das coordenadas para construir o objeto retangular. A lógica abaixo é usada para garantir que as coordenadas do intervalo sejam armazenadas sem a necessidade de declará-las novamente sempre que houver uma atualização.
datetime TIME1_X1, TIME2_Y2; // Declare datetime variables to hold range start and end times double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices
Declaramos aqui duas variáveis do tipo datetime. O método utilizado é chamado de declaração de um único tipo de dado (single data-type declaration), no qual múltiplas variáveis do mesmo tipo são declaradas em uma única linha. Esse método é conciso, reduz o número de linhas de código e agrupa variáveis relacionadas, facilitando a compreensão de que pertencem ao mesmo tipo e são usadas para propósitos semelhantes, mantendo a consistência. Também podemos escrevê-las da seguinte forma:
datetime TIME1_X1; datetime TIME2_Y2;
A variável TIME1_X1 armazena o valor do tempo da primeira coordenada ao longo do eixo x, enquanto TIME2_Y2 armazena o valor do tempo da segunda coordenada, também ao longo do eixo x. Da mesma forma, declaramos as coordenadas de preços da seguinte maneira:
double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices
Será necessário monitorar constantemente o mercado a cada nova vela para avaliar se um intervalo de consolidação está se estabelecendo devido à baixa volatilidade. Assim, precisaremos de duas variáveis para armazenar indicadores que sinalizem se o intervalo existe e se o preço está dentro dele.
bool isRangeExist = false; // Flag to check if the range exists bool isInRange = false; // Flag to check if we are currently within the range
Definimos e inicializamos duas variáveis booleanas: isRangeExist e isInRange. A variável isRangeExist serve como um indicador para determinar se um intervalo de consolidação foi identificado e marcado no gráfico. Inicializamos seu valor como false, pois o intervalo não foi estabelecido inicialmente. Da mesma forma, a variável isInRange, também inicializada como false, é usada para determinar se o preço atual do mercado está dentro do intervalo de consolidação identificado. Esses indicadores são fundamentais para a lógica operacional do EA, pois ajudam a gerenciar o estado do processo de detecção do intervalo e monitoramento de rompimentos, garantindo que ações sejam realizadas apenas quando as condições apropriadas forem atendidas.
Em uma escala global, ainda precisamos definir a quantidade mínima de velas a serem analisadas, bem como o tamanho do intervalo em pontos. Como mencionado anteriormente, esses parâmetros são críticos para garantir a validade e a significância do intervalo de consolidação identificado.
int rangeBars = 10; // Number of bars to consider for the range int rangeSizePoints = 400; // Maximum range size in points
Declaramos e inicializamos duas variáveis inteiras: rangeBars e rangeSizePoints. A variável rangeBars é configurada para 10, indicando o número de velas que serão analisadas para determinar o intervalo de consolidação. Isso significa que verificaremos as últimas 10 velas para encontrar o maior máximo e o menor mínimo, e assim determinaremos nosso intervalo. A variável rangeSizePoints é definida como 400, o que determina o tamanho máximo permitido para o intervalo de consolidação em pontos. Se a diferença entre o maior e o menor preço dentro dessas 10 velas exceder 400 pontos, o intervalo não será considerado válido. Esses parâmetros são essenciais para estabelecer os critérios do intervalo e garantir a identificação de períodos significativos de consolidação nos dados de preços.
Finalmente, como abriremos posições, definiremos os pontos de stop-loss e take-profit.
double sl_points = 500.0; // Stop loss points double tp_points = 500.0; // Take profit points
Isso é tudo o que precisamos na área de visibilidade global. Talvez você se pergunte o que é essa área. É a área do programa em que variáveis, funções e outros elementos estão acessíveis em todo o código, fora de qualquer função ou bloco. Quando uma variável ou função é declarada nessa área, qualquer parte do programa pode acessá-la e alterá-la.
Todo o trabalho será realizado no manipulador de eventos OnTick. Essa função baseia-se puramente na ação do preço e será amplamente utilizada. Portanto, vamos examinar os parâmetros que essa função aceita, pois ela é a parte central de todo o código.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- }
Como já foi observado, trata-se de uma função simples, porém importante, que não aceita argumentos nem retorna valores. É apenas uma função do tipo void, ou seja, que não deve retornar nada. Essa função é usada em EAs e é executada sempre que ocorre um novo tick, ou seja, quando as cotações de um ativo específico são atualizadas.
Agora que vimos que a função OnTick é gerada a cada atualização de cotações, precisamos definir uma lógica de controle que nos permita executar o código apenas uma vez por vela, e não a cada tick. Isso é necessário para evitar execuções desnecessárias do código e economizar memória do dispositivo. Essa abordagem será útil ao procurar ajustes no intervalo de consolidação. Não é necessário buscar configurações a cada tick, pois sempre obteremos os mesmos resultados, desde que estejamos na mesma vela. A lógica está apresentada abaixo:
int currBars = iBars(_Symbol,_Period); // Get the current number of bars static int prevBars = currBars; // Static variable to store the previous number of bars static bool isNewBar = false; // Static flag to check if a new bar has appeared if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars
Primeiramente, declaramos uma variável inteira, currBars, que armazena a quantidade atual de velas no gráfico para o símbolo de negociação e o período de tempo especificados. Isso é feito usando a função iBars, que aceita dois argumentos: symbol e period. Em seguida, declaramos e inicializamos a variável inteira estática prevBars com o valor atual de velas. A palavra-chave static garante que a variável prevBars mantenha seu valor entre chamadas de função, lembrando efetivamente a quantidade de velas do tick anterior. Em terceiro lugar, declaramos uma variável booleana estática isNewBar e a inicializamos com o valor false. Essa variável nos ajudará a rastrear se uma nova vela foi formada. Em seguida, usamos uma estrutura condicional para verificar se a quantidade atual de velas é igual à quantidade anterior. Se forem iguais, significa que nenhuma nova vela foi formada, então configuramos o indicador de nova vela para false. Caso contrário, se a quantidade de velas anterior não for igual à atual, isso indica que uma nova vela foi formada, pois a quantidade de velas aumentou. Assim, configuramos o indicador de nova vela para true e atualizamos o valor de prevBars com o valor de currBars.
Agora, para cada nova vela gerada que não possui um intervalo de consolidação, precisamos escanear as velas predefinidas em busca de um potencial período de baixa volatilidade.
if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared ... }
Verificamos se a variável isRangeExist possui o valor false (indicando que o intervalo ainda não foi estabelecido) e se a variável isNewBar possui o valor true (indicando que uma nova vela foi formada). Isso garante que o processo continue apenas se o intervalo de consolidação ainda não tiver sido definido e uma nova vela tiver sido gerada.
Para determinar as coordenadas do primeiro ponto do objeto retangular a ser desenhado no gráfico, precisamos dos níveis extremos de resistência, ou seja, o horário da última vela no intervalo predefinido de análise e o preço mais alto dentro desse intervalo.
TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range
Primeiramente, definimos o horário de início do intervalo usando a função iTime, que retorna o horário de abertura de uma vela específica para o símbolo e o período especificados. A função aceita três parâmetros de entrada: _Symbol (o símbolo de negociação, como "AUDUSD"), _Period (o período de tempo, como PERIOD_M1 para velas de um minuto) e rangeBars (o índice da vela correspondente a um número específico de períodos atrás). O resultado é armazenado em TIME1_X1, marcando o início do intervalo de consolidação.
Em seguida, encontramos a vela com o maior preço máximo dentro do intervalo especificado usando a função iHighest, que retorna o índice da vela com o maior preço em um determinado número de velas. A função aceita cinco argumentos. Não é necessário explicar novamente os dois primeiros parâmetros, já descritos. O terceiro parâmetro (MODE_HIGH) indica que estamos procurando o preço mais alto. O quarto parâmetro (rangeBars) especifica a quantidade de velas a ser considerada na análise, e, por fim, o valor 1 indica que o início da busca será na vela anterior à vela atual que está se formando. Tecnicamente, a vela em formação tem o índice 0, enquanto a vela anterior tem o índice 1. O índice resultante é armazenado na variável inteira highestHigh_BarIndex.
Finalmente, extraímos o preço mais alto dessa vela usando a função iHigh, que retorna o preço máximo de uma vela específica. A função aceita três parâmetros de entrada, sendo os dois primeiros simples de entender. O terceiro argumento (highestHigh_BarIndex) é o índice da vela, determinado no passo anterior. O preço máximo é armazenado em PRICE1_Y1. Essas variáveis permitem definir o ponto inicial e o preço mais alto do intervalo de consolidação, que são cruciais para desenhar o gráfico do intervalo e detectar rompimentos subsequentes.
Para obter as segundas coordenadas, utilizamos uma abordagem semelhante à usada para determinar as primeiras.
TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range
A diferença no código está no fato de que, primeiro, nosso tempo é vinculado à vela atual, que tem índice 0. Em segundo lugar, para obter o índice da vela com o menor preço dentro do intervalo predefinido, usamos a função iLowest, juntamente com o parâmetro MODE_LOW, indicando que buscamos o menor preço. Por fim, a função iLow é utilizada para recuperar o preço da vela com o menor índice. A seguir, apresentamos uma visualização das coordenadas necessárias, considerando um gráfico arbitrário.
Agora que temos os pontos do intervalo de consolidação, precisamos validá-los para garantir que atendam aos critérios de um intervalo válido, conforme mencionado anteriormente na introdução, antes que sejam considerados e desenhados no gráfico.
isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points
Calculamos a diferença entre o preço mais alto (PRICE1_Y1) e o preço mais baixo (PRICE2_Y2) dentro do intervalo e, em seguida, convertemo-la para pontos, dividindo a diferença pelo tamanho de um ponto (_Point). Por exemplo, podemos ter 0,66777 como o valor mais alto e 0,66773 como o mais baixo. A diferença matemática seria 0,66777 - 0,66773 = 0,00004. O valor do ponto para o símbolo presumido seria 0,00001. Dividindo o resultado pelo tamanho do ponto, obtemos 4 pontos. Este valor é então comparado com rangeSizePoints, que representa o tamanho máximo permitido para o intervalo, definido em pontos.
Por fim, verificamos se um intervalo válido foi identificado e, caso afirmativo, o desenhamos no gráfico e notificamos o sucesso da criação.
if (isInRange){ // If the range size is valid plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range isRangeExist = true; // Set the range exist flag to true Print("RANGE PLOTTED"); // Print a message indicating the range is plotted }
Aqui, verificamos se o intervalo de consolidação identificado é válido avaliando a variável isInRange. Se o indicador da variável for true, indicando que o tamanho do intervalo está dentro dos limites permitidos, procedemos com a construção do gráfico do intervalo de consolidação. Para isso, chamamos a função plotConsolidationRange, com os parâmetros de entrada rangeNAME, TIME1_X1, PRICE1_Y1, TIME2_Y2 e PRICE2_Y2, que cria a representação visual do intervalo. Após a construção bem-sucedida do gráfico, definimos o indicador isRangeExist como true, indicando que um intervalo válido foi identificado e desenhado. Além disso, exibimos a mensagem "RANGE PLOTTED" (intervalo desenhado) no terminal, confirmando que o intervalo de consolidação foi visualizado com sucesso.
O trecho de código para a função responsável por desenhar ou atualizar o intervalo de consolidação é o seguinte:
//+------------------------------------------------------------------+ //| Function to plot the consolidation range | //| rangeName - name of the range object | //| time1_x1 - start time of the range | //| price1_y1 - high price of the range | //| time2_x2 - end time of the range | //| price2_y2 - low price of the range | //+------------------------------------------------------------------+ void plotConsolidationRange(string rangeName,datetime time1_x1,double price1_y1, datetime time2_x2,double price2_y2){ if (ObjectFind(0,rangeName) < 0){ // If the range object does not exist ObjectCreate(0,rangeName,OBJ_RECTANGLE,0,time1_x1,price1_y1,time2_x2,price2_y2); // Create the range object ObjectSetInteger(0,rangeName,OBJPROP_COLOR,clrBlue); // Set the color of the range ObjectSetInteger(0,rangeName,OBJPROP_FILL,true); // Enable fill for the range ObjectSetInteger(0,rangeName,OBJPROP_WIDTH,5); // Set the width of the range } else { // If the range object exists ObjectSetInteger(0,rangeName,OBJPROP_TIME,0,time1_x1); // Update the start time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,0,price1_y1); // Update the high price of the range ObjectSetInteger(0,rangeName,OBJPROP_TIME,1,time2_x2); // Update the end time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,1,price2_y2); // Update the low price of the range } ChartRedraw(0); // Redraw the chart to reflect changes }
Primeiro, definimos uma função do tipo void chamada plotConsolidationRange, que recebe cinco parâmetros: o nome do intervalo, duas coordenadas do primeiro ponto e duas coordenadas do segundo ponto. Em seguida, usamos uma estrutura condicional para verificar a existência do objeto utilizando a função ObjectFind, que retorna um número inteiro negativo caso o objeto não seja encontrado. Se isso acontecer, criamos um objeto identificado como OBJ_RECTANGLE, utilizando o tempo atual e as coordenadas fornecidas para o primeiro e o segundo pontos. Definimos então sua cor, preenchimento da área e largura. Se o objeto for encontrado, seus valores de tempo e preço são atualizados com os valores fornecidos e o gráfico é redesenhado para aplicar as alterações. O modificador 0 é utilizado para indicar a primeira coordenada, enquanto 1 indica a segunda.
Isso é tudo o que precisamos para exibir o intervalo de consolidação identificado no gráfico. O código completo responsável por isso é apresentado a seguir:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- int currBars = iBars(_Symbol,_Period); // Get the current number of bars static int prevBars = currBars; // Static variable to store the previous number of bars static bool isNewBar = false; // Static flag to check if a new bar has appeared if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points if (isInRange){ // If the range size is valid plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range isRangeExist = true; // Set the range exist flag to true Print("RANGE PLOTTED"); // Print a message indicating the range is plotted } } }
Após a compilação, obtemos os seguintes resultados:
Construímos o intervalo dentro das coordenadas predefinidas e registramos no log a ocorrência do intervalo desenhado. Se você não precisar preencher o intervalo, basta configurar o indicador da propriedade de preenchimento como false. Isso fará com que apenas as linhas do retângulo sejam desenhadas, enquanto a largura será ajustada conforme necessário. A lógica subjacente é descrita abaixo:
ObjectSetInteger(0,rangeName,OBJPROP_FILL,false); // Disable fill for the range
O resultado será o seguinte intervalo:
No artigo, utilizaremos o intervalo preenchido. Agora que temos a segurança de que podemos definir o intervalo, precisamos avançar e desenvolver a lógica que monitorará seu rompimento, abrirá posições conforme necessário ou acompanhará sua expansão e atualizará suas coordenadas para os novos valores.
m seguida, identificamos os casos de rompimento conforme descrito na parte teórica. Se houver rompimento, abrimos posições no mercado. Isso deve ser feito a cada tick, sem restrição a novas velas. Inicialmente, declaramos os preços Ask e Bid, que serão utilizados para abrir posições após o cumprimento das condições correspondentes. É importante realizar isso em cada tick para garantir que estamos utilizando as cotações mais recentes.
double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); // Get and normalize the current Ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); // Get and normalize the current Bid price
Aqui declaramos variáveis do tipo double para armazenar os preços mais recentes, normalizando-os de acordo com os dígitos do símbolo da moeda e arredondando os números de ponto flutuante para manter a precisão.
Como monitoramos rompimentos em cada tick, precisamos de uma lógica que garanta que a carga sobre o programa ocorra apenas quando o intervalo realmente existir e estivermos dentro dele. Na maior parte do tempo, o mercado estará em um estado de volatilidade média ou alta, o que significa que a frequência de aparecimento de intervalos de consolidação será limitada. Além disso, se um intervalo for definido e estiver visível no gráfico, o preço sairá desse intervalo. Se qualquer uma dessas condições for atendida, não há necessidade de verificar rompimentos ou atualizações adicionais do intervalo. Nesse cenário, basta aguardar até que outro intervalo seja identificado.
if (isRangeExist && isInRange){ // If the range exists and we are in range ... }
Aqui verificamos se o intervalo de consolidação existe (isRangeExist) e se o preço atual está dentro dele (isInRange). Se ambas as condições forem verdadeiras, iniciamos o cálculo dos preços potenciais de rompimento.
double R_HighBreak_Prc = (PRICE2_Y2+rangeSizePoints*_Point); // Calculate the high breakout price double R_LowBreak_Prc = (PRICE1_Y1-rangeSizePoints*_Point); // Calculate the low breakout price
Para determinar o preço de rompimento superior, somamos o tamanho máximo permitido do intervalo em pontos ao menor preço. Esse cálculo é feito multiplicando o tamanho do intervalo em pontos pelo tamanho de um ponto (_Point) e adicionando o resultado à variável PRICE2_Y2, armazenando o valor final em uma variável do tipo double chamada R_HighBreak_Prc. Por exemplo, se assumirmos que o preço mais baixo é 0,66773, o tamanho do intervalo em pontos é 400 e o tamanho do ponto é 0,00001, o cálculo seria 400 × 0,00001 = 0,00400. Ao somar esse valor ao preço mais baixo, obtemos 0,66773 + 0,00400 = 0,67173. Esse resultado final é armazenado como o preço de rompimento superior, que será usado para comparar com o preço de mercado e determinar um rompimento caso o preço de mercado ultrapasse esse valor. Da mesma forma, para determinar o preço de rompimento inferior, subtraímos o tamanho máximo permitido do intervalo em pontos do maior preço. Multiplicamos o tamanho do intervalo em pontos pelo tamanho de um ponto e subtraímos o resultado do maior preço, armazenando o valor final na variável R_LowBreak_Prc.
Em seguida, identificamos o rompimento e, caso ele ocorra, abrimos as posições correspondentes. Consideremos primeiro a situação em que o preço de mercado ultrapassa o preço de rompimento superior, sinalizando uma oportunidade de compra.
if (Ask > R_HighBreak_Prc){ // If the Ask price breaks the high breakout price Print("BUY NOW, ASK = ",Ask,", L = ",PRICE2_Y2,", H BREAK = ",R_HighBreak_Prc); // Print a message to buy isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Buy(0.01,_Symbol,Ask,Bid-sl_points*_Point,Bid+tp_points*_Point); return; // Exit the function }
Primeiro, verificamos se o preço atual Ask excede o preço de rompimento superior. Se essa condição for atendida, isso indica que o preço de mercado ultrapassou o limite superior do intervalo de consolidação. Então, exibimos uma mensagem no terminal registrando o evento e fornecendo o contexto do sinal de compra, incluindo o preço atual Ask, o preço mais baixo do intervalo e o preço de rompimento superior. Em seguida, redefinimos os indicadores isInRange e isRangeExist para false, indicando que o intervalo de consolidação atual não é mais válido e impedindo que novas decisões sejam tomadas com base nesse intervalo. Em seguida, verificamos se há posições existentes utilizando a função PositionsTotal. Caso existam, encerramos a função imediatamente para evitar a abertura de múltiplas posições simultaneamente. Se não houver posições existentes, colocamos uma ordem de compra utilizando o método obj_Trade do objeto CTrade, especificando o volume, o símbolo do ativo, o preço de abertura (Ask) e os valores de stop-loss e take-profit. Por fim, saímos da função para concluir o processo de início da negociação e garantir que nenhum código adicional seja executado durante este tick.
Para lidar com o cenário em que o preço de mercado cai abaixo do preço de rompimento inferior, sinalizando uma oportunidade de venda, aplicamos uma lógica de controle semelhante, conforme demonstrado no trecho de código a seguir.
else if (Bid < R_LowBreak_Prc){ // If the Bid price breaks the low breakout price Print("SELL NOW"); // Print a message to sell isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Sell(0.01,_Symbol,Bid,Ask+sl_points*_Point,Ask-tp_points*_Point); return; // Exit the function }
Após a compilação, obtemos o seguinte:
A partir da imagem acima, podemos observar que, assim que o preço ultrapassa os limites do intervalo, neste caso o superior, abrimos uma posição de compra com os respectivos valores de stop-loss e take-profit. As condições de entrada e os níveis de negociação podem ser totalmente configurados, podendo ser ajustados para atender ao estilo de negociação desejado. Por exemplo, os níveis podem estar baseados nos extremos do intervalo ou em uma relação risco-recompensa específica. Para confirmação, note que o preço Ask é 0,68313 e o menor preço do intervalo é 0,67911, o que torna o preço de rompimento superior (0,67911 + 0,00400) = 0,68311. Matematicamente, o preço atual Ask de 0,68313 é maior do que o preço de rompimento calculado de 0,68311, atendendo às condições para um rompimento superior e resultando na abertura de uma posição de compra ao preço solicitado.
Atualmente, o intervalo é estático, ou seja, o retângulo permanece fixo. Mesmo que o intervalo seja configurado corretamente, o objeto do intervalo não será atualizado automaticamente. Portanto, é necessário atualizar o intervalo sempre que o preço ultrapassar o limite atual do objeto do intervalo. Para dar "vida" ao retângulo, implementamos uma lógica que atualiza continuamente sua expansão à medida que novas velas são geradas. Consideremos primeiro o cenário em que o preço atual Ask excede o preço máximo previamente registrado dentro do intervalo de consolidação. Se essa condição for atendida, o limite superior do intervalo de consolidação deve ser atualizado para refletir o novo preço máximo. Isso é feito com o seguinte trecho de código:
if (Ask > PRICE1_Y1){ // If the Ask price is higher than the current high price PRICE1_Y1 = Ask; // Update the high price to the Ask price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE1_Y1 TO ASK, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range }
Se o preço solicitado (Ask) for maior que o preço máximo previamente registrado, definimos PRICE1_Y1 como o preço solicitado atual. Simultaneamente, atualizamos o horário final do intervalo (TIME2_Y2) para o horário atual, obtido por meio da função iTime, passando o índice da vela-alvo como a vela atual (0). Para rastrear essas alterações e garantir clareza, exibimos uma mensagem no terminal indicando que o intervalo foi atualizado e que um novo desenho é necessário. Em seguida, chamamos a função plotConsolidationRange com os parâmetros atualizados, incluindo o novo preço máximo e o horário atual, para que as alterações sejam refletidas visualmente no gráfico.
Para lidar com o cenário em que o preço atual Bid cai abaixo do preço mínimo previamente registrado no intervalo de consolidação, indicando a necessidade de atualizar o limite inferior do intervalo para refletir o novo preço mínimo, aplicamos uma abordagem semelhante.
else if (Bid < PRICE2_Y2){ // If the Bid price is lower than the current low price PRICE2_Y2 = Bid; // Update the low price to the Bid price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE2_Y2 TO BID, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range }
Para monitorar essas mudanças, consideremos os casos antes e depois das atualizações, possibilitando uma comparação visual, como seria observado em um GIF.
Antes da atualização:
Depois da atualização:
Por fim, também é possível um cenário em que nem o preço Ask supera o preço máximo nem o preço Bid cai abaixo do preço mínimo. Nesse caso, ainda precisamos expandir o intervalo de consolidação para incluir a última vela concluída. O trecho de código a seguir nos ajuda a realizar isso:
else{ if (isNewBar){ // If a new bar has appeared TIME2_Y2 = iTime(_Symbol,_Period,1); // Update the end time to the previous bar time Print("EXTEND THE RANGE TO PREV BAR TIME"); // Print a message indicating the range is extended plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } }
Verificamos se uma nova vela foi gerada usando o indicador isNewBar. Se uma nova vela tiver aparecido, atualizamos o horário de término do intervalo de consolidação (TIME2_Y2) para o horário da vela anterior, obtido por meio da função iTime, passando o índice da vela-alvo como 1 (a vela imediatamente anterior à atual). Para garantir clareza e rastrear essa modificação, exibimos uma mensagem no terminal indicando que o horário de término do intervalo foi estendido para o horário da vela anterior. Em seguida, chamamos a função plotConsolidationRange com os parâmetros atualizados, incluindo o novo horário de término, para refletir visualmente as alterações no gráfico.
Abaixo está uma ilustração do estágio de atualização no intervalo.
A seguir, é apresentado o código-fonte completo para a criação de um EA baseado na estratégia de rompimento do intervalo de consolidação em MQL5:
//+------------------------------------------------------------------+ //| CONSOLIDATION RANGE BREAKOUT.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> // Include the trade library CTrade obj_Trade; // Create an instance of the CTrade class #define rangeNAME "CONSOLIDATION RANGE" // Define the name of the consolidation range datetime TIME1_X1, TIME2_Y2; // Declare datetime variables to hold range start and end times double PRICE1_Y1, PRICE2_Y2; // Declare double variables to hold range high and low prices bool isRangeExist = false; // Flag to check if the range exists bool isInRange = false; // Flag to check if we are currently within the range int rangeBars = 10; // Number of bars to consider for the range int rangeSizePoints = 400; // Maximum range size in points double sl_points = 500.0; // Stop loss points double tp_points = 500.0; // Take profit points //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- // Initialization code here (we don't initialize anything) //--- return(INIT_SUCCEEDED); // Return initialization success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- // Deinitialization code here (we don't deinitialize anything) } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- int currBars = iBars(_Symbol,_Period); // Get the current number of bars static int prevBars = currBars; // Static variable to store the previous number of bars static bool isNewBar = false; // Static flag to check if a new bar has appeared if (prevBars == currBars){isNewBar = false;} // Check if the number of bars has not changed else {isNewBar = true; prevBars = currBars;} // If the number of bars has changed, set isNewBar to true and update prevBars if (isRangeExist == false && isNewBar){ // If no range exists and a new bar has appeared TIME1_X1 = iTime(_Symbol,_Period,rangeBars); // Get the start time of the range int highestHigh_BarIndex = iHighest(_Symbol,_Period,MODE_HIGH,rangeBars,1); // Get the bar index with the highest high in the range PRICE1_Y1 = iHigh(_Symbol,_Period,highestHigh_BarIndex); // Get the highest high price in the range TIME2_Y2 = iTime(_Symbol,_Period,0); // Get the current time int lowestLow_BarIndex = iLowest(_Symbol,_Period,MODE_LOW,rangeBars,1); // Get the bar index with the lowest low in the range PRICE2_Y2 = iLow(_Symbol,_Period,lowestLow_BarIndex); // Get the lowest low price in the range isInRange = (PRICE1_Y1 - PRICE2_Y2)/_Point <= rangeSizePoints; // Check if the range size is within the allowed points if (isInRange){ // If the range size is valid plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Plot the consolidation range isRangeExist = true; // Set the range exist flag to true Print("RANGE PLOTTED"); // Print a message indicating the range is plotted } } double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); // Get and normalize the current Ask price double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); // Get and normalize the current Bid price if (isRangeExist && isInRange){ // If the range exists and we are in range double R_HighBreak_Prc = (PRICE2_Y2+rangeSizePoints*_Point); // Calculate the high breakout price double R_LowBreak_Prc = (PRICE1_Y1-rangeSizePoints*_Point); // Calculate the low breakout price if (Ask > R_HighBreak_Prc){ // If the Ask price breaks the high breakout price Print("BUY NOW, ASK = ",Ask,", L = ",PRICE2_Y2,", H BREAK = ",R_HighBreak_Prc); // Print a message to buy isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Buy(0.01,_Symbol,Ask,Bid-sl_points*_Point,Bid+tp_points*_Point); return; // Exit the function } else if (Bid < R_LowBreak_Prc){ // If the Bid price breaks the low breakout price Print("SELL NOW"); // Print a message to sell isInRange = false; isRangeExist = false; // Reset range flags if (PositionsTotal() > 0){return;} // Exit the function obj_Trade.Sell(0.01,_Symbol,Bid,Ask+sl_points*_Point,Ask-tp_points*_Point); return; // Exit the function } if (Ask > PRICE1_Y1){ // If the Ask price is higher than the current high price PRICE1_Y1 = Ask; // Update the high price to the Ask price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE1_Y1 TO ASK, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } else if (Bid < PRICE2_Y2){ // If the Bid price is lower than the current low price PRICE2_Y2 = Bid; // Update the low price to the Bid price TIME2_Y2 = iTime(_Symbol,_Period,0); // Update the end time to the current time Print("UPDATED RANGE PRICE2_Y2 TO BID, NEEDS REPLOT"); // Print a message indicating the range needs to be replotted plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } else{ if (isNewBar){ // If a new bar has appeared TIME2_Y2 = iTime(_Symbol,_Period,1); // Update the end time to the previous bar time Print("EXTEND THE RANGE TO PREV BAR TIME"); // Print a message indicating the range is extended plotConsolidationRange(rangeNAME,TIME1_X1,PRICE1_Y1,TIME2_Y2,PRICE2_Y2); // Replot the consolidation range } } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Function to plot the consolidation range | //| rangeName - name of the range object | //| time1_x1 - start time of the range | //| price1_y1 - high price of the range | //| time2_x2 - end time of the range | //| price2_y2 - low price of the range | //+------------------------------------------------------------------+ void plotConsolidationRange(string rangeName,datetime time1_x1,double price1_y1, datetime time2_x2,double price2_y2){ if (ObjectFind(0,rangeName) < 0){ // If the range object does not exist ObjectCreate(0,rangeName,OBJ_RECTANGLE,0,time1_x1,price1_y1,time2_x2,price2_y2); // Create the range object ObjectSetInteger(0,rangeName,OBJPROP_COLOR,clrBlue); // Set the color of the range ObjectSetInteger(0,rangeName,OBJPROP_FILL,true); // Enable fill for the range ObjectSetInteger(0,rangeName,OBJPROP_WIDTH,5); // Set the width of the range } else { // If the range object exists ObjectSetInteger(0,rangeName,OBJPROP_TIME,0,time1_x1); // Update the start time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,0,price1_y1); // Update the high price of the range ObjectSetInteger(0,rangeName,OBJPROP_TIME,1,time2_x2); // Update the end time of the range ObjectSetDouble(0,rangeName,OBJPROP_PRICE,1,price2_y2); // Update the low price of the range } ChartRedraw(0); // Redraw the chart to reflect changes }
Resultados de testes em dados históricos
A seguir, os resultados dos testes realizados no testador de estratégias.
- Gráfico de saldo/patrimônio:
- Resultados dos testes em dados históricos:
- Entradas em negociações por períodos:
Considerações finais
Em conclusão, podemos afirmar com confiança que a automatização da estratégia de rompimento de consolidação não é tão complexa quanto parece. Como vimos, sua criação exige apenas uma compreensão clara da estratégia e dos objetivos a serem alcançados.
No geral, este artigo focou na parte teórica, que deve ser levada em consideração e compreendida com clareza para o desenvolvimento da estratégia. O código inclui os passos necessários para a análise de velas, a identificação de períodos de baixa volatilidade, a determinação de níveis de suporte e resistência nesses períodos, bem como o rastreamento de rompimentos, a visualização dos resultados e a abertura de posições com base nos sinais gerados. A longo prazo, isso permite automatizar a estratégia, tornando sua execução mais rápida e escalável.
Espero que este artigo tenha sido útil, interessante e fácil de entender, e que você possa aplicar o conhecimento apresentado para desenvolver seus próprios EAs.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15311







- 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