English Русский 中文 Español Deutsch 日本語
preview
Criando um EA em MQL5 com base na estratégia de Rompimento do Intervalo Diário (Daily Range Breakout)

Criando um EA em MQL5 com base na estratégia de Rompimento do Intervalo Diário (Daily Range Breakout)

MetaTrader 5Negociação |
258 3
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

Neste artigo, analisaremos como criar um Expert Advisor (EA) na linguagem MetaQuotes Language 5 (MQL5) baseado na estratégia de Rompimento do Intervalo Diário. Como os traders estão sempre em busca de soluções eficientes para a automação do trading, a estratégia de rompimento de intervalo diário oferece uma abordagem sistemática que permite obter lucro com os movimentos de preço que ultrapassam um determinado intervalo, o que a torna atraente para os traders de Forex no MetaTrader 5.

Iniciamos apresentando os princípios fundamentais da estratégia de Rompimento do Intervalo Diário, fornecendo uma base sólida para sua implementação no trading automatizado. Em seguida, examinamos detalhadamente como definir as condições de rompimento e estabelecer os pontos de entrada e saída. Depois, abordaremos o processo de escrita do código em MQL5, destacando suas funções principais e a lógica por trás da estratégia. Além disso, discutimos a importância do backtest e da otimização do programa para garantir sua eficácia em condições reais de mercado. Os temas analisados neste artigo são:

  1. Compreensão da estratégia de Rompimento do Intervalo Diário
  2. Esquema do EA
  3. Implementação da estratégia de Rompimento do Intervalo Diário em MQL5
  4. Backtest e otimização
  5. Considerações finais

Ao final deste artigo, você estará munido dos conhecimentos necessários para desenvolver um EA em MQL5 que utilize com eficácia a estratégia de Rompimento do Intervalo Diário, aprimorando sua abordagem no trading. Então, vamos começar.


Compreensão da estratégia de Rompimento do Intervalo Diário

A estratégia de rompimento do intervalo diário é um método de trading bastante conhecido entre os traders de Forex. Ela permite que os traders tirem proveito das grandes variações de preço que ocorrem após a formação do intervalo diário pelo mercado. Essa estratégia usa o movimento de preço do mercado para identificar os principais níveis de suporte e resistência. Assim que esses níveis são identificados, os traders operam os rompimentos, esperando por possíveis movimentos fortes, que geralmente acontecem após o mercado ultrapassar um desses níveis.

Essa estratégia é baseada no intervalo diário, definido como a diferença entre o preço mais alto e o mais baixo de um par de moedas durante o dia de negociação. Os pontos de rompimento são determinados com base no intervalo do dia de negociação anterior. O rompimento ocorre quando o preço ultrapassa o nível de resistência estabelecido ou cai abaixo do nível de suporte. Olhando para trás, é possível perceber que os preços do dia anterior geralmente definem níveis muito bem delimitados, que podem ser usados como potenciais pontos de rompimento. Quando o preço rompe para cima o nível de resistência, uma posição comprada é aberta. Quando o preço rompe para baixo o nível de suporte, uma posição vendida é aberta. Uma ilustração visual é apresentada abaixo.

DAILY RANGE BREAKOUT ILLUSTRATION

Para alcançar a máxima eficiência, essa estratégia é utilizada em gráficos de uma hora ou quatro horas. Quando os traders utilizam esse padrão nesses timeframes, frequentemente conseguem capturar oscilações de preço maiores e mais relevantes. Isso ocorre porque a estratégia é, em grande parte, livre dos ruídos presentes em intervalos de tempo mais baixos. Antes de realizar operações durante as sessões de Londres e Nova York, a estratégia de rompimento geralmente usa o movimento de preço da sessão asiática para definir o intervalo diário. Estratégias de rompimento costumam gerar sinais falsos, e o Rompimento do Intervalo Diário não é exceção. Por isso, assim como qualquer estratégia de negociação, é fundamental gerenciar os riscos ao utilizá-la. Para manter o risco sob controle, posicione o stop-loss logo abaixo da última oscilação mínima para posições compradas e acima da última oscilação máxima para posições vendidas. Essa será a nossa estratégia. Ela gerencia o risco usando um stop-loss posicionado acima ou abaixo da última oscilação máxima ou mínima, dependendo da situação. Abaixo, há outra ilustração da lógica do stop-loss.

STOP LOSS ENTRY

A estratégia de Rompimento do Intervalo Diário é vantajosa em diversos aspectos. Primeiro, sua simplicidade a torna uma boa escolha tanto para traders iniciantes quanto para os mais experientes. Em segundo lugar, ela se baseia em níveis definidos, o que impede que os traders tomem muitas decisões arbitrárias. A forma como esse método de trading atua no mercado permite obter uma visão clara do cenário antes e depois de cada sessão diária. Pela manhã, a atividade de mercado pode se limitar a um certo "intervalo". Ao final da sessão matinal, o rompimento da linha superior ou inferior desse intervalo pode se tornar um possível sinal de entrada para uma operação no dia seguinte. Na próxima seção, descreveremos com mais detalhes os nossos parâmetros de negociação, apresentando um esquema claro com todas as informações específicas.


Esquema do EA

Rompimento do nível superior do intervalo: Condição de compra

Quando o preço rompe acima do intervalo superior estabelecido no dia anterior, isso indica um rompimento de alta e sugere que o mercado pode continuar subindo. Esse rompimento sinaliza forte interesse por parte dos compradores e potencial para um movimento ascendente adicional. Abrimos uma posição de compra quando o preço de fechamento da barra atual está acima do nível superior do intervalo, buscando lucrar com o impulso que normalmente segue esse tipo de rompimento.

ROMPIMENTO DO NÍVEL SUPERIOR DO INTERVALO

Rompimento do nível inferior do intervalo: Condição de venda

Por outro lado, quando o preço rompe abaixo do intervalo inferior estabelecido no dia anterior, isso indica um rompimento de baixa e sugere que o mercado pode continuar caindo. Esse rompimento indica forte pressão de venda e potencial para mais movimento descendente. Abrimos uma posição de venda quando o preço de fechamento da barra atual está abaixo do nível inferior do intervalo, esperando uma continuação da queda após o rompimento.

ROMPIMENTO DO NÍVEL INFERIOR DO INTERVALO

Essas imagens ilustrativas do esquema da estratégia serão úteis quando formos implementar essas condições de negociação em MQL5 e servirão como guia para escrever regras precisas de entrada e saída.


Implementação da estratégia de Rompimento do Intervalo Diário em MQL5

Depois de entender toda a teoria sobre a estratégia de Rompimento do Intervalo Diário, vamos automatizar a teoria e criar um EA na linguagem MetaQuotes Language 5 (MQL5) para o MetaTrader 5.

Para criar o EA, no seu terminal MetaTrader 5, vá até a aba "Ferramentas" e selecione o editor de linguagem MetaQuotes, ou simplesmente pressione a tecla F4 no seu teclado. Alternativamente, você pode clicar no ícone IDE (ambiente de desenvolvimento integrado) na barra de ferramentas. Isso abrirá o ambiente de desenvolvimento no MetaQuotes Language Editor, onde é possível programar robôs de trading, indicadores técnicos, scripts e bibliotecas de funções.

Abrir o MetaEditor

Depois de abrir o MetaEditor, na barra de ferramentas selecione "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.

CREATE NEW EA

Na janela do Assistente que se abrir, selecione o template "EA" e clique em Avançar.

Assistente MQL

Nas propriedades gerais, especifique o nome do arquivo do seu EA. Para indicar ou criar uma pasta, caso ela ainda não exista, use a barra invertida antes do nome do EA. Por exemplo, por padrão está definida a pasta "Experts\". Isso significa que nosso EA será criado dentro da pasta Experts. As outras seções são bastante simples, mas você pode clicar no link na parte inferior do Assistente para obter mais detalhes.

NOME DO NOVO EA

Depois de definir o nome do arquivo do EA, clique em "Avançar" > "Avançar" > "Concluir". Agora estamos prontos para transformar a estratégia em código.

Primeiramente, começamos definindo alguns metadados sobre o EA. Isso inclui: o nome do EA, informações de direitos autorais e o link para o site da MetaQuotes. Também especificamos a versão do EA, que está definida como "1.00".

//+------------------------------------------------------------------+
//|                          Daily Range Breakout Expert Advisor.mq5 |
//|      Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                                     https://forexalg0-trader.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://forexalg0-trader.com"
#property description "Daily Range Breakout Expert Advisor"
#property version   "1.00"

Quando o programa é carregado, são exibidas as informações conforme ilustrado abaixo.

METADATA INFORMATION

Primeiro, incluímos o módulo de negociação, usando a diretiva #include no início do código-fonte. Ela nos dará acesso à classe CTrade, que será usada para criar um objeto de negociação. Isso é muito importante, pois será necessário para abrir ordens.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os sinais de menor e maior indicam que o arquivo Trade.mqh será buscado no diretório padrão (geralmente terminal_installation_directory\MQL5\Include). O diretório atual não é incluído na busca. A linha pode ser colocada em qualquer parte do código, mas normalmente todos os includes são colocados no início do código-fonte para melhor organização e facilidade de manutenção. Graças aos desenvolvedores do MQL5, a declaração do objeto obj_Trade da classe CTrade nos dará acesso direto aos métodos contidos nessa classe.

CLASSE CTRADE

Em seguida, precisamos declarar algumas variáveis importantes para armazenar e acompanhar os dados de rompimento do intervalo.

double maximum_price = -DBL_MAX;      //--- Initialize the maximum price with the smallest possible value
double minimum_price = DBL_MAX;       //--- Initialize the minimum price with the largest possible value
datetime maximum_time, minimum_time;  //--- Declare variables to store the time of the highest and lowest prices
bool isHaveDailyRange_Prices = false; //--- Boolean flag to check if daily range prices are extracted
bool isHaveRangeBreak = false;        //--- Boolean flag to check if a range breakout has occurred

Aqui declaramos algumas variáveis importantes para monitorar dados-chave de preços e lidar com rompimentos de intervalo na lógica de negociação. Primeiro, inicializamos duas variáveis double, "maximum_price" e "minimum_price", que irão armazenar os preços mais altos e mais baixos encontrados durante um determinado período. A variável "maximum_price" é inicializada com -DBL_MAX, o menor valor possível do tipo double, garantindo que qualquer preço encontrado será maior e substituirá esse valor inicial. Da mesma forma, atribuímos à variável "minimum_price" o valor DBL_MAX, o maior valor possível do tipo double, garantindo que qualquer preço mais baixo irá substituí-lo como o novo valor mínimo.

Também declaramos duas variáveis datetime, "maximum_time" e "minimum_time", para armazenar os momentos exatos em que ocorrem os preços máximos e mínimos. Isso será útil mais adiante, caso precisemos indicar os pontos exatos em que esses níveis de preço foram atingidos.

Além disso, são declaradas duas variáveis bool para lidar com a lógica relacionada aos intervalos de preço e rompimentos. A primeira, "isHaveDailyRange_Prices", é inicializada como “false” e serve como uma flag que indica se os preços do intervalo diário (ou seja, o máximo e o mínimo) foram identificados com sucesso. A segunda, "isHaveRangeBreak", também inicializada como “false”, atua como uma flag que sinaliza se houve um rompimento, ou seja, se o preço ultrapassou os limites do intervalo diário. Além disso, vamos exibir visualmente os intervalos no gráfico. Para isso, precisaremos de nomes para esses intervalos, que também já podemos declarar aqui.

#define RECTANGLE_PREFIX "RANGE RECTANGLE " //--- Prefix for naming range rectangles
#define UPPER_LINE_PREFIX "UPPER LINE "     //--- Prefix for naming upper range line
#define LOWER_LINE_PREFIX "LOWER LINE "     //--- Prefix for naming lower range line

Aqui definimos três diretivas de pré-processador, que criam prefixos para nomear diversos objetos gráficos associados ao intervalo de negociação. Utilizamos a diretiva #define RECTANGLE_PREFIX "RANGE RECTANGLE " para estabelecer um padrão de nomenclatura consistente para os retângulos que representam o intervalo de negociação, facilitando a identificação e o gerenciamento desses objetos no gráfico. Da mesma forma, #define UPPER_LINE_PREFIX "UPPER LINE " cria um prefixo específico para a linha superior do intervalo, enquanto LOWER_LINE_PREFIX "LOWER LINE " serve ao mesmo propósito para a linha inferior. Com esses prefixos, garantimos que todos os objetos gráficos relacionados ao intervalo tenham nomes sistematizados, o que ajuda a manter clareza e organização no código, especialmente quando há vários objetos simultaneamente no gráfico.

Com isso em mente, agora podemos passar à lógica real do processamento no código. Executaremos nossa lógica com base nos processos de tick e, portanto, iremos diretamente ao manipulador de eventos OnTick, que é chamado e executado a cada tick processado no gráfico.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---

}

Este é apenas o manipulador de eventos padrão de tick, que servirá como base para nossa lógica de controle. Em seguida, precisamos declarar algumas variáveis para manter nossa lógica de intervalo temporal.

   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

Declaramos três estáticas variáveis para controlar funcionalidades relacionadas ao tempo. A primeira variável, "midnight", recebe o valor retornado pela função iTime, que obtém o horário da meia-noite no gráfico diário do símbolo atual, representado por _Symbol, com o período definido como PERIOD_D1 para indicar que estamos lidando com candles diários. O valor 0 indica o candle atual. Isso estabelece um ponto de referência básico para os cálculos diários.

Em seguida, calculamos o horário para "sixAM", adicionando seis horas, representadas como 6 * 3600 (onde 3600 é o número de segundos em uma hora, ou seja, 1 hora multiplicada por 60 minutos multiplicada por 60 segundos), à variável "midnight". Isso nos permite definir o horário para a análise diária após a abertura do mercado, facilitando nossa análise do movimento de preços a partir das primeiras horas do dia de negociação.

Por fim, definimos a variável "scanBarTime" para indicar o horário de varredura da próxima barra após as 6h. Fazemos isso adicionando dinamicamente uma barra adicional ao horário atual de varredura das 6h, para que o candle das 6h também seja considerado no processo. O número 1 representa a quantidade de barras a serem escaneadas, e a função PeriodSeconds converte automaticamente o período atual do gráfico em segundos. Por exemplo, em um gráfico de 1 hora, isso significa que convertendo 1 hora em segundos e multiplicando por 1 barra, geralmente obtemos 3600 segundos, que são então adicionados às 6h, resultando no candle das 7h. No geral, essas variáveis estáticas são cruciais para a implementação da lógica baseada em tempo da nossa estratégia de negociação.

A seguir, também podemos declarar variáveis que definem os intervalos de tempo válidos para rompimentos, se o rompimento ocorrer após 7 horas ou em um horário como 13:00, não consideramos esse sinal válido e, assim, esperamos pela configuração do próximo dia.

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+5) * 3600; //--- Set the end of valid breakout time to 11 AM

Aqui declaramos duas estáticas variáveis adicionais para definir o intervalo de tempo em que as condições de rompimento serão consideradas válidas dentro da nossa estratégia de negociação. A primeira variável, "validBreakTime_start", é inicializada com o valor de "scanBarTime", que definimos anteriormente. Isso marca o início do período válido para rompimentos, permitindo que concentremos nossa atenção no movimento de preços a partir da barra seguinte às 6h da manhã.

A segunda variável, "validBreakTime_end", é calculada adicionando (6 + 5) * 3600 à variável "midnight". Essa expressão define o fim do nosso período válido de rompimento, que corresponde às 11h da manhã. Ao definir esse intervalo de tempo, criamos uma janela clara durante a qual avaliaremos as condições de rompimento, garantindo que nossas decisões de negociação sejam baseadas em movimentos de preço que ocorram dentro desse intervalo específico. Com tudo isso pronto, estamos preparados para iniciar a implementação da nossa lógica. A primeira coisa que precisamos considerar é que queremos verificar as configurações a cada novo dia, portanto, precisaremos de uma lógica que identifique quando um novo dia começou.

   if (isNewDay()){
        //---

   }

Utilizamos a instrução if para verificar se há um novo dia e, caso isso seja verdade, executamos o bloco de código dentro dela. Para verificar a existência de um novo dia, utilizamos uma função lógica personalizada chamada "isNewDay". A lógica dela está descrita abaixo:

bool isNewDay() {
   //--- Flag to indicate if a new day has started
   bool newDay = false;
   
   //--- Structure to hold the current date and time
   MqlDateTime Str_DateTime;
   
   //--- Convert the current time to a structured format
   TimeToStruct(TimeCurrent(), Str_DateTime);
   
   //--- Static variable to store the previous day
   static int prevDay = 0;
   
   //--- Get the current day from the structured time
   int currDay = Str_DateTime.day;
   
   //--- If the previous day is the same as the current day, we're still on the same day
   if (prevDay == currDay) {
      newDay = false;
   }
   //--- If the current day differs from the previous one, we have a new day
   else if (prevDay != currDay) {
      //--- Print a message indicating the new day
      Print("WE HAVE A NEW DAY WITH DATE ", currDay);
      
      //--- Update the previous day to the current day
      prevDay = currDay;
      
      //--- Set the flag to true, indicating a new day has started
      newDay = true;
   }
   
   //--- Return whether a new day has started
   return (newDay);
}

Aqui definimos a função lógica "isNewDay", que tem como objetivo determinar se um novo dia começou em nossa estratégia de negociação. Inicializamos uma variável booleana chamada "newDay" com o valor "false", que serve como uma flag para indicar se um novo dia teve início. Para acompanhar a data e hora atuais, criamos uma estrutura do tipo MqlDateTime chamada "Str_DateTime". Utilizamos a função TimeToStruct para converter o tempo atual, obtido da hora corrente, para um formato estruturado, preenchendo "Str_DateTime" com as informações correspondentes de data e hora.

Em seguida, declaramos uma variável estática do tipo inteiro chamada "prevDay", inicializada com zero, que irá armazenar o dia da última data registrada. Depois, extraímos o dia atual da estrutura "Str_DateTime" e o atribuimos à variável inteira "currDay".

Comparamos "prevDay" com "currDay". Se forem iguais, isso significa que ainda estamos no mesmo dia, e então mantemos o valor "false" para "newDay". Por outro lado, se "prevDay" for diferente de "currDay", entendemos que um novo dia começou. Nesse caso, exibimos uma mensagem indicando a transição para o novo dia com a função Print, atualizamos a variável "prevDay" com o valor de "currDay". E então definimos a flag "newDay" como "true", confirmando que um novo dia teve início. Por fim, a função retorna o valor da flag "newDay", permitindo que usemos essa informação na lógica de negociação para decidir se alguma ação deve ser tomada em decorrência do início de um novo dia.

É justamente dentro dessa função que reiniciamos todas as configurações quando um novo dia começa, para realizar os cálculos diários e gerenciar a exibição lógica da seguinte forma.

      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+5) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day

Nesta função, reiniciamos várias variáveis e parâmetros no começo de um novo dia de negociação, a fim de nos preparar para novos cálculos e para o monitoramento dos dados. Começamos obtendo o novo horário da meia-noite para o dia atual, utilizando a função iTime, que fornece o timestamp de abertura da barra diária atual. Em seguida, atualizamos a variável "midnight" com esse novo valor.

Em seguida, recalculamos o horário das 6 da manhã, adicionando 6 horas, representadas como "6 * 3600", à variável "midnight" recém-definida. Isso nos fornece um ponto de referência para o início da sessão de negociação pela manhã. Depois disso, definimos o valor de "scanBarTime" como sendo um candle após as 6h, adicionando a duração de um período, obtida com a função PeriodSeconds, para alinhar nossos cálculos ao período atual do gráfico.

Depois passamos para a atualização dos intervalos de tempo válidos para rompimento, definindo "validBreakTime_start" com base no novo valor calculado de "scanBarTime". Esse ajuste indica o ponto inicial a partir do qual começaremos a considerar possíveis rompimentos durante o dia de negociação. Também definimos "validBreakTime_end" como sendo 11h da manhã, calculando esse horário como "midnight + (6 + 5) * 3600", o que nos dá um ponto final claro para avaliação de rompimentos. Além disso, reinicializamos os valores de "maximum_price" e "minimum_price" para acompanhar o movimento de preços do novo dia, atribuindo a "maximum_price" o valor -DBL_MAX (o menor valor possível) e a "minimum_price" o valor DBL_MAX, o maior valor possível. Esse reset permite que os preços máximos e mínimos do dia sejam identificados com precisão.

Por fim, definimos os flags booleanos "isHaveDailyRange_Prices" e "isHaveRangeBreak" como "false", indicando que ainda não definimos o intervalo diário nem detectamos um rompimento no novo dia. Essa reinicialização completa prepara nosso sistema para novos cálculos, garantindo que o monitoramento da movimentação de preços ao longo do dia seja feito com precisão. Agora podemos seguir para a lógica de escaneamento do candle. Não é necessário realizar o escaneamento a cada tick, mas sim somente quando um novo candle é gerado. Para isso, precisaremos de uma outra lógica de controle para lidar com a identificação de novos candles. 

   if (isNewBar()){
        //---

   }

Aqui, continuamos utilizando a instrução if em conjunto com a função "isNewBar" para implementar a lógica de detecção de novo candle. O código funcional adaptado é mostrado abaixo como um trecho de código.

bool isNewBar() {
   //--- Static variable to hold the previous number of bars
   static int prevBars = 0;
   
   //--- Get the current number of bars on the chart
   int currBars = iBars(_Symbol, _Period);
   
   //--- If the number of bars hasn't changed, return false
   if (prevBars == currBars) return (false);
   
   //--- Update the previous bar count with the current one
   prevBars = currBars;
   
   //--- Return true if a new bar has been formed
   return (true);
}

Começamos declarando uma variável estática chamada "prevBars", que armazena a quantidade anterior de candles exibidos no gráfico. A palavra-chave static garante que a variável mantenha seu valor entre as chamadas de função, o que nos permite acompanhar eficientemente as mudanças na contagem de candles. Em seguida, obtemos o número atual de candles no gráfico usando a função iBars, onde _Symbol representa o ativo negociado e _Period se refere ao timeframe do gráfico. Essa função retorna o número total de candles disponíveis no momento para o instrumento e período especificados.

Em seguida, comparamos a quantidade atual de candles, armazenada na variável "currBars", com a quantidade anterior, "prevBars". Se esses dois valores forem iguais, isso significa que nenhum novo candle foi formado desde a última verificação, portanto retornamos "false", indicando que ainda estamos no mesmo candle. Caso os valores sejam diferentes, significa que um novo candle foi criado, e isso nos leva a atualizar "prevBars" com o valor de "currBars". Por fim, retornamos o valor "true", sinalizando que um novo candle foi realmente formado. A seguir, dentro da função, precisamos processar os dados assim que um novo candle for formado, dando atenção especial a uma determinada condição de tempo para extração dos dados de preço.

      //--- If a new bar has been formed, process the data
      datetime currentBarTime = iTime(_Symbol,_Period,0); //--- Get the time of the current bar
      
      if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){
         //--- If it's time to scan and the daily range is not yet extracted
         Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); //--- Log the extraction process
         int total_bars = int((sixAM - midnight)/PeriodSeconds(_Period)) + 1; //--- Calculate total bars between midnight and 6 AM
         Print("Total Bars for scan = ",total_bars); //--- Log the total number of bars for scanning
         int highest_price_bar_index = -1;   //--- Variable to store the bar index of the highest price
         int lowest_price_bar_index = -1;    //--- Variable to store the bar index of the lowest price

         //--- 

      }

Primeiro, declaramos a variável "currentBarTime", usando a função iTime, que extrai o tempo do candle atual no gráfico. Isso nos ajuda a determinar se estamos em um momento específico do dia em que precisamos processar certos dados de preço. Em seguida, verificamos duas condições com a instrução "if". Primeiro, checamos se o horário do candle atual é igual ao horário de escaneamento do candle, que é o horário programado para a análise (neste caso, definido para 6 da manhã). Em segundo lugar, verificamos se os preços do intervalo diário ainda não foram extraídos, confirmando que o flag "isHaveDailyRange_Prices" está com valor false. Se ambas as condições forem verdadeiras, isso indica que estamos no momento correto e que devemos iniciar a extração dos dados de intervalo de preço.

Registramos uma mensagem usando a função Print, para indicar que há dados suficientes de candles disponíveis e que o processo de extração vai começar. Isso ajuda a rastrear quando e por que o processo é executado em tempo real. Passamos então ao cálculo do número total de candles entre a meia-noite e as 6 da manhã, o que é essencial para determinar o intervalo de preços nesse período. A função PeriodSeconds define a duração de cada candle, e dividimos a diferença entre os horários "sixAM" e "midnight" por essa duração para calcular o total de candles. Para garantir que todos os candles do intervalo estejam incluídos, adicionamos 1.

Por fim, exibimos o número total de candles a serem escaneados com outra função Print, e depois declaramos duas variáveis: "highest_price_bar_index" e "lowest_price_bar_index". Inicializamos essas variáveis com o valor -1 e as usaremos para armazenar os índices dos candles que contêm os preços mais altos e mais baixos, respectivamente, dentro do intervalo observado. Essa configuração nos prepara para extrair os dados de preço desses candles específicos. Após a execução do programa, obtivemos os seguintes resultados.

BARS SCAN CONFIRMATION

Podemos ver que, assim que a quantidade necessária de candles para analisar o intervalo é determinada, informamos o status de conclusão e o número de candles dentro do intervalo a ser considerado. Neste ponto, podemos prosseguir com a extração dos dados do intervalo diário identificado e com o estabelecimento dos limites do intervalo.

         for (int i=1; i<=total_bars ; i++){ //--- Loop through all bars within the defined time range
            double open_i = open(i);         //--- Get the opening price of the i-th bar
            double close_i = close(i);       //--- Get the closing price of the i-th bar
            
            double highest_price_i = (open_i > close_i) ? open_i : close_i; //--- Determine the highest price between open and close
            double lowest_price_i = (open_i < close_i) ? open_i : close_i;  //--- Determine the lowest price between open and close
            
            if (highest_price_i > maximum_price){
               //--- If the current highest price is greater than the recorded maximum price
               maximum_price = highest_price_i; //--- Update the maximum price
               highest_price_bar_index = i;     //--- Update the index of the highest price bar
               maximum_time = time(i);          //--- Update the time of the highest price
            }
            if (lowest_price_i < minimum_price){
               //--- If the current lowest price is lower than the recorded minimum price
               minimum_price = lowest_price_i;  //--- Update the minimum price
               lowest_price_bar_index = i;      //--- Update the index of the lowest price bar
               minimum_time = time(i);          //--- Update the time of the lowest price
            }
         }

Para extrair os dados, percorremos todos os candles dentro de um período específico (da meia-noite até as 6 da manhã) para determinar os preços mais altos e mais baixos. O objetivo é encontrar os preços máximos e mínimos que ocorreram dentro desse intervalo e registrar o momento em que aconteceram. Começamos configurando um laço for com a instrução "for (int i=1; i<=total_bars ; i++)". Essa instrução significa que o laço percorrerá cada candle, começando do primeiro (índice 1) até "total_bars", que foi previamente calculado para representar a quantidade de candles entre meia-noite e 6 da manhã. A variável "i" representa o índice de cada candle dentro do laço.

Dentro do laço, extraímos os preços de abertura e fechamento de cada candle usando funções personalizadas chamadas "abrir" e "fechar", respectivamente. Essas duas variáveis — "open_i" para o preço de abertura e "close_i" para o preço de fechamento — nos ajudam a analisar o movimento de preço em cada candle. Antes de continuar, vale destacar que essas funções personalizadas são funções utilitárias que definimos em qualquer lugar da área global e utilizamos diretamente, e o trecho de código correspondente será mostrado adiante.

//--- Utility functions to retrieve price and time data for a given bar index
double open(int index){return (iOpen(_Symbol,_Period,index));}   //--- Get the opening price
double high(int index){return (iHigh(_Symbol,_Period,index));}   //--- Get the highest price
double low(int index){return (iLow(_Symbol,_Period,index));}     //--- Get the lowest price
double close(int index){return (iClose(_Symbol,_Period,index));} //--- Get the closing price
datetime time(int index){return (iTime(_Symbol,_Period,index));} //--- Get the time of the bar

A seguir, utilizamos uma operação ternária, para determinar os preços mais altos e mais baixos de cada candle. A instrução "double highest_price_i = (open_i > close_i) ? open_i : close_i;" verifica se o preço de abertura é maior que o preço de fechamento. Se for, o preço de abertura é definido como o mais alto do candle. Caso contrário, o preço de fechamento é considerado o mais alto. De forma semelhante, "double lowest_price_i = (open_i < close_i) ? open_i : close_i;" compara os preços de abertura e fechamento para determinar o menor preço do candle.

Depois de calcular os preços mais altos e mais baixos do candle atual, comparamos esses valores com os preços máximos e mínimos gerais identificados até o momento:

  • Se o preço máximo para esse candle selecionado for maior que o valor máximo registrado até então, atualizamos a variável "maximum_price" com esse novo valor. Também salvamos o índice desse candle em "highest_price_bar_index" e registramos o horário do candle utilizando a função "time", que extrai o timestamp associado ao candle de índice i. Isso nos permite rastrear o momento exato em que o preço mais alto foi atingido.
  • Se "lowest_price_i" for menor do que o valor atual de "minimum_price", atualizamos "minimum_price" com esse novo valor. Também salvamos o índice desse candle em "highest_price_bar_index" e registramos o horário em "minimum_time" com a função "time".

Esse processo garante que, ao final do laço, tenhamos identificado corretamente os preços mais altos e mais baixos no intervalo entre a meia-noite e as 6h da manhã, além dos horários em que esses valores ocorreram. Mais adiante, utilizaremos essas informações para definir níveis-chave de preços para análise de rompimento. Para confirmar que estamos capturando corretamente esses níveis, podemos exibi-los no log como forma de validação.

         //--- Log the maximum and minimum prices, along with their respective bar indices and times
         Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time);
         Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);

Aqui exibimos os preços máximos e mínimos detectados, junto com seus respectivos índices de candle e horários, para fins de verificação. Após executar o programa, obtivemos os seguintes dados:

PRICE LEVELS

Na imagem, vemos que nosso preço máximo ocorre no sétimo candle, cujo dado no log é 0,6548, valor que corresponde ao preço de abertura visível na janela de dados. O horário é meia-noite, como mostrado na régua de tempo e data do cruzamento no eixo x. Assim, podemos ter certeza de que os preços do dia foram obtidos corretamente e que podemos utilizá-los para análises posteriores. No entanto, não há mais necessidade de continuar analisando durante o restante do dia, pois já coletamos os dados necessários. Portanto, podemos definir nosso flag lógico para a variável de rastreamento de preços como true e aguardar o próximo dia para coletar os dados novamente.

         isHaveDailyRange_Prices = true; //--- Set the flag indicating daily range prices have been extracted

Após configurar o flag, estamos totalmente prontos. Contudo, ainda não visualizamos o intervalo desenhado no gráfico. Por isso, podemos desenvolver algum mecanismo que nos permita desenhar os intervalos diretamente no gráfico. Para isso, precisaremos criar funções que possam ser reutilizadas. A primeira função será responsável por criar os retângulos.

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE A RECTANGLE                             |
//+------------------------------------------------------------------+
void create_Rectangle(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   //--- Check if the object already exists by finding it on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create a rectangle object using the defined parameters: name, type, and coordinates
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the rectangle (start point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the rectangle (start point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the rectangle (end point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the rectangle (end point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Enable the fill property for the rectangle, making it filled
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);
      
      //--- Set the color for the rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the rectangle to not appear behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);

      //--- Redraw the chart to reflect the new changes
      ChartRedraw(0);
   }
}

Aqui criamos uma função do tipo void chamada "create_Rectangle", que será responsável por desenhar um objeto retangular no gráfico do MetaTrader. A função recebe seis parâmetros: "objName" (nome do objeto), "time1" e "price1" (coordenadas de um dos cantos do retângulo), "time2" e "price2" (coordenadas do canto oposto), e "clr" (cor do retângulo). Dentro da função, primeiro verificamos se já existe um objeto com esse nome no gráfico, utilizando a função ObjectFind. Se o objeto não for encontrado (ou seja, se retornar um valor menor que 0), então prosseguimos com a criação do retângulo.

Em seguida, chamamos a função ObjectCreate para criar o objeto retangular, fornecendo os parâmetros necessários: o identificador do gráfico (definido como 0 para o gráfico atual), o nome do objeto, o tipo de objeto (OBJ_RECTANGLE) e as coordenadas (definidas como "time1, price1" e "time2, price2").

Depois, usamos as funções ObjectSetInteger e ObjectSetDouble para definir propriedades específicas do retângulo:

  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1)" define o horário do primeiro canto (ponto inicial) do retângulo.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1)" define o preço do primeiro canto (ponto inicial) do retângulo.
  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2)" define o horário do segundo canto (ponto final) do retângulo.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2)" define o preço do segundo canto (ponto final) do retângulo.

Também ativamos a propriedade de preenchimento (fill) do retângulo usando o atributo OBJPROP_FILL, que faz com que o retângulo apareça preenchido visualmente no gráfico, e não apenas como um contorno. Depois, definimos a cor do retângulo utilizando o atributo OBJPROP_COLOR, aplicando a cor especificada ("clr") que foi passada como parâmetro para a função. Em seguida, o retângulo é configurado para ser exibido à frente de outros objetos, desativando a propriedade OBJPROP_BACK. Por fim, chamamos a função ChartRedraw para atualizar o gráfico, garantindo que o retângulo recém-criado apareça imediatamente no gráfico. A próxima função que precisamos definir será responsável pela criação de linhas no gráfico, para que possamos usá-las na representação do intervalo de tempo de início e fim.

//+------------------------------------------------------------------+
//|      FUNCTION TO CREATE A TREND LINE                             |
//+------------------------------------------------------------------+
void create_Line(string objName, datetime time1, double price1, datetime time2, double price2, int width, color clr, string text) {
   //--- Check if the line object already exists by its name
   if (ObjectFind(0, objName) < 0) {
      //--- Create a trendline object with the specified parameters
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Set the width for the line
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, width);
      
      //--- Set the color of the trendline
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the trendline to not be behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      //--- Retrieve the current chart scale
      long scale = 0;
      if(!ChartGetInteger(0, CHART_SCALE, 0, scale)) {
         //--- Print an error message if unable to retrieve the chart scale
         Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED");
      }
      //--- Set a default font size based on the chart scale
      int fontsize = 11;
      if (scale == 0) { fontsize = 5; }
      else if (scale == 1) { fontsize = 6; }
      else if (scale == 2) { fontsize = 7; }
      else if (scale == 3) { fontsize = 9; }
      else if (scale == 4) { fontsize = 11; }
      else if (scale == 5) { fontsize = 13; }
      
      //--- Define the description text to appear near the right price
      string txt = " Right Price";
      string objNameDescr = objName + txt;
      
      //--- Create a text object next to the line to display the description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time2, price2);
      
      //--- Set the color for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize);
      
      //--- Anchor the text to the left of the line
      ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT);
      
      //--- Set the text content to display the specified string
      ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + text);
      
      //--- Set the font of the text to "Calibri"
      ObjectSetString(0, objNameDescr, OBJPROP_FONT, "Calibri");
      
      //--- Redraw the chart to reflect the changes
      ChartRedraw(0);
   }
}

Aqui criamos outra função do tipo void chamada "create_Line" e também passamos os parâmetros necessários. A função recebe oito parâmetros: "objName" (nome do objeto de linha), "time1" e "price1" (coordenadas do ponto inicial), "time2" e "price2" (coordenadas do ponto final), "width" (espessura da linha), "clr" (cor da linha) e "text" (descrição que será exibida ao lado da linha de tendência). Começamos verificando se já existe uma linha de tendência com esse nome no gráfico, usando a função ObjectFind. Se o objeto trendline com o nome especificado não existir (retornar valor menor que 0), então seguimos para a criação da linha.

Para criar a linha de tendência, utilizamos a função ObjectCreate, que define o tipo de objeto como OBJ_TREND e atribui as coordenadas iniciais ("time1, price1") e finais ("time2, price2") da linha de tendência.

Em seguida, usamos as funções ObjectSetInteger e ObjectSetDouble para atribuir as propriedades dos pontos inicial e final da linha:

  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1)" define o horário do primeiro ponto.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1)" define o preço do primeiro ponto.
  • "ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2)" define o horário do segundo ponto.
  • "ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2)" define o preço do segundo ponto.

Depois, definimos a espessura da linha usando a propriedade OBJPROP_WIDTH, que determina a grossura da linha, e então aplicamos a cor desejada. Em seguida, garantimos que a linha seja exibida à frente de outros objetos no gráfico, definindo a propriedade OBJPROP_BACK como false, o que significa que a linha de tendência não ficará atrás de outros elementos visuais.

Para melhorar a exibição da linha de tendência, obtemos o zoom atual do gráfico utilizando ChartGetInteger. Se conseguirmos obter o zoom com sucesso, usamos esse valor para ajustar o tamanho da fonte do texto descritivo que será exibido ao lado da linha. Com base no zoom do gráfico, ajustamos o tamanho da fonte adequadamente, sendo o valor padrão igual a 11. Em seguida, definimos o rótulo descritivo como "Right Price" (Preço Correto), que será posicionado próximo à linha de tendência, e geramos o nome do objeto para esse rótulo adicionando "txt" ao nome original do objeto, formando assim "objNameDescr".

Então, criamos um objeto de texto com a função ObjectCreate, posicionando-o no final da linha ("time2, price2") e configuramos diversas propriedades:

  • "ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr)" define a cor do texto de acordo com a cor da linha de tendência.
  • "ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize)" define o tamanho da fonte com base no valor previamente calculado.
  • "ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT)" ancora o texto à esquerda da linha.
  • "ObjectSetString(0, objNameDescr, OBJPROP_TEXT, ' ' + text)" define o conteúdo do texto com o valor do parâmetro "text" passado à função.
  • "ObjectSetString(0, objNameDescr, OBJPROP_FONT, 'Calibri')" define a fonte do texto como "Calibri", para melhor legibilidade.

Por fim, atualizamos o gráfico chamando ChartRedraw para garantir que a linha de tendência recém-criada e o texto associado sejam exibidos corretamente no gráfico. Em seguida, podemos chamar essas funções e utilizá-las para exibir as informações sobre o intervalo.

         //--- Create visual elements to represent the daily range
         create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); //--- Create a rectangle for the daily range
         create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,3,clrBlack,DoubleToString(maximum_price,_Digits)); //--- Draw upper range line
         create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,3,clrRed,DoubleToString(minimum_price,_Digits));   //--- Draw lower range line

Após a compilação do código e execução do programa, obtemos as seguintes informações.

FIRST VISUAL PLOT

Agora podemos ver que estamos exibindo visualmente e representando graficamente os detalhes do intervalo, o que é visualmente mais atrativo e facilita a verificação e confirmação dos preços. O próximo passo é verificar a ocorrência de rompimentos. Este é o momento em que precisamos checar a cada tick se houve rompimento de algum nível e, caso as condições sejam atendidas, acionar a lógica de negociação correspondente. No que diz respeito ao rompimento do nível superior, temos a seguinte lógica.

   //--- Get the close price and time of the previous bar
   double barClose = close(1); 
   datetime barTime = time(1);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
   }

Aqui verificamos se o preço de fechamento do candle anterior ultrapassa o valor máximo do intervalo diário, o que indicaria um rompimento da parte superior do intervalo. Primeiro, extraímos o preço de fechamento e o horário do candle anterior utilizando as funções personalizadas "close" e "time", armazenando esses valores nas variáveis "barClose" e "barTime", respectivamente. Isso nos permite trabalhar com o preço de fechamento e o momento do candle em análise.

Em seguida, realizamos uma série de verificações para confirmar se o rompimento realmente ocorreu. Verificamos se o valor de "barClose" é maior que "maximum_price", confirmando que o preço de fechamento superou o preço mais alto registrado no dia. Também checamos se os preços do intervalo diário já foram identificados, usando o flag "isHaveDailyRange_Prices", e garantimos que nenhum rompimento anterior tenha sido detectado, com a verificação do flag "!isHaveRangeBreak". Além disso, garantimos que o rompimento esteja ocorrendo dentro da janela de tempo permitida, verificando se "barTime" está entre "validBreakTime_start" e "validBreakTime_end".

Se todas essas condições forem satisfeitas, registramos o evento de rompimento com uma mensagem indicando que o preço de fechamento ultrapassou o limite superior do intervalo. Em seguida, atualizamos a variável "isHaveRangeBreak" para true, indicando que um rompimento foi detectado. Por fim, chamamos a função "drawBreakPoint" para marcar visualmente esse rompimento no gráfico. A função utiliza o horário do candle, o preço de fechamento, o tamanho do marcador, a cor e o nível de prioridade para exibir graficamente o ponto de rompimento. A lógica da função segue a mesma estrutura das funções anteriores.

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE AN ARROW                                |
//+------------------------------------------------------------------+
void drawBreakPoint(string objName, datetime time, double price, int arrCode, color clr, int direction) {
   //--- Check if the arrow object already exists on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create an arrow object with the specified time, price, and arrow code
      ObjectCreate(0, objName, OBJ_ARROW, 0, time, price);
      
      //--- Set the arrow's code (symbol)
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      
      //--- Set the color for the arrow
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the arrow
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 12);
      
      //--- Set the anchor position for the arrow based on the direction
      if (direction > 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      
      //--- Define a text label for the break point
      string txt = " Break";
      string objNameDescr = objName + txt;
      
      //--- Create a text object for the break point description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time, price);
      
      //--- Set the color for the text description
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, 12);
      
      //--- Adjust the text anchor based on the direction of the arrow
      if (direction > 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
   }
   //--- Redraw the chart to reflect the new objects
   ChartRedraw(0);
}

Para verificar rompimentos do nível inferior, utilizamos a mesma lógica usada para detectar os rompimentos do nível superior.

   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
   }

Após a compilação, obtemos o seguinte resultado.

Rompimento do nível inferior

A operação foi bem-sucedida. Podemos ver que, assim que o rompimento do nível inferior é atingido, uma seta aparece no gráfico marcando o ponto do rompimento, indicando visualmente o candle em que ocorreu o breakout. Vamos executar o programa e observar também o rompimento no sentido oposto.

Rompimento do nível superior

A operação foi bem-sucedida. Também podemos ver que houve, de fato, um rompimento no nível superior, como esperado. O próximo passo agora é abrir posições imediatamente após a ocorrência desses rompimentos — e é isso.

   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*2);
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*2);
   }

Seguindo essa lógica, agora podemos abrir posições. Após a execução do programa, obtemos o seguinte resultado.

SELL BREAK TRADE

Com base na imagem, podemos confirmar que as posições estão sendo abertas corretamente, por exemplo, posicionando o stop-loss da operação de venda no nível superior e o take-profit a uma distância duas vezes maior que o tamanho do intervalo, abaixo do ponto de entrada. Na próxima seção, vamos nos concentrar no teste da aplicação para avaliar seu desempenho e ajustar com precisão os parâmetros para alcançar os melhores resultados possíveis.


Backtest e otimização

Após concluir a implementação, o próximo passo importante é testar o EA de forma minuciosa para avaliar seu desempenho e otimizar seus parâmetros. Um teste eficaz garante que a estratégia funcione corretamente em diferentes condições de mercado, reduzindo ao mínimo o risco de problemas inesperados durante a operação. Aqui utilizaremos o Testador de Estratégia do MetaTrader 5 para realizar o backtest e a otimização, com o objetivo de encontrar os melhores valores de entrada possíveis para a nossa estratégia.

Para executar a otimização, precisaremos de parâmetros de entrada configuráveis na seção de propriedades. A otimização que realizaremos levará em conta a relação risco-retorno, o horário em que o rompimento é válido (em horas) e a direção da negociação após o rompimento. Ou seja, poderíamos considerar, por exemplo, que ao ocorrer um rompimento no nível inferior, em vez de abrir uma posição de venda, abriremos uma de compra. Simples assim. Abaixo está a lógica que adotamos.

enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions};

input int r2r = 2;
input int hoursValidity = 5;
input trade_direction direction_of_trade = Default_Trade_Directions;

Aqui definimos um enum e inicializamos algumas variáveis de entrada, que serão usadas para controlar o comportamento da negociação e os parâmetros da estratégia. Primeiro, declaramos um enum chamado "trade_direction", que define dois valores possíveis: "Default_Trade_Directions" e "Invert_Trade_Directions". Enum (enumerador) é um tipo de dado definido pelo usuário no MQL5, que permite atribuir nomes a constantes inteiras, tornando o código mais legível e fácil de administrar. Neste caso, "trade_direction" nos ajudará a controlar se a operação seguirá a direção padrão ou será invertida dependendo das condições específicas.

Em seguida, definimos três variáveis de entrada que permitem ao usuário alterar seus valores diretamente pelas configurações do EA, sem precisar editar o código. No entanto, elas serão ainda mais úteis durante a otimização do programa. A primeira variável é "r2r", que tem o valor padrão 2 e será usada para controlar a relação risco-retorno da estratégia. A palavra-chave input indica que essa variável pode ser modificada externamente pelo usuário. O segundo parâmetro de entrada é "hoursValidity", inicializado com o valor padrão de 5. Essa variável definirá por quantas horas as condições ou os sinais de rompimento permanecem válidos.

Por fim, o terceiro parâmetro de entrada é "direction_of_trade", que é do tipo "trade_direction" (o enumerador que definimos anteriormente). Por padrão, está definido como "Default_Trade_Directions", mas o usuário pode alterá-lo para "Invert_Trade_Directions" caso deseje que a operação seja executada na direção oposta. Esses parâmetros de entrada fornecem flexibilidade na escolha da direção da operação sem alterar a lógica principal do EA. Com isso em mente, só precisamos substituir os parâmetros estáticos correspondentes no código e incorporar esse aspecto dinâmico.

   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Set the end of valid breakout time to 11 AM
   
   if (isNewDay()){
      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day
   }

   //---

   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
      }
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
   }

Agora adicionamos as alterações e destacamos seções específicas para maior clareza. Ao compilar, já podemos encontrar esses parâmetros disponíveis na seção de entrada de dados, conforme mostrado abaixo. Também é possível selecionar quais desses parâmetros usar para otimização, a fim de encontrar os melhores ajustes de negociação para o programa.

DADOS DE ENTRADA PARA OTIMIZAÇÃO

Na imagem, vemos que temos os dados de entrada prontos para otimização, e para iniciar o processo basta clicar no botão "Iniciar". Neste caso, selecionamos apenas um mês de dados para evitar sobrecarregar a otimização do programa. Após a finalização, aplicamos as configurações otimizadas ao programa e as usamos para o backtest. Abaixo estão os resultados obtidos.

BACKTEST GIF

A operação foi bem-sucedida! Podemos concluir que o programa funcionou conforme o esperado. O trecho final do código-fonte responsável pela criação e execução da estratégia de Rompimento do Intervalo Diário é o seguinte:
//+------------------------------------------------------------------+
//|                          Daily Range Breakout Expert Advisor.mq5 |
//|      Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                                     https://forexalg0-trader.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://forexalg0-trader.com"
#property description "Daily Range Breakout Expert Advisor"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

enum trade_direction {Default_Trade_Directions,Invert_Trade_Directions};

input int r2r = 2;
input int hoursValidity = 5;
input trade_direction direction_of_trade = Default_Trade_Directions;

double maximum_price = -DBL_MAX;  //--- Initialize the maximum price with the smallest possible value
double minimum_price = DBL_MAX;   //--- Initialize the minimum price with the largest possible value
datetime maximum_time, minimum_time; //--- Declare variables to store the time of the highest and lowest prices
bool isHaveDailyRange_Prices = false; //--- Boolean flag to check if daily range prices are extracted
bool isHaveRangeBreak = false;        //--- Boolean flag to check if a range breakout has occurred

#define RECTANGLE_PREFIX "RANGE RECTANGLE " //--- Prefix for naming range rectangles
#define UPPER_LINE_PREFIX "UPPER LINE "     //--- Prefix for naming upper range line
#define LOWER_LINE_PREFIX "LOWER LINE "     //--- Prefix for naming lower range line

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   //--- Initialization code can be placed here if needed
   
   //---
   return(INIT_SUCCEEDED); //--- Return successful initialization
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   //--- Deinitialization code can be placed here if needed
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   //--- 
   static datetime midnight = iTime(_Symbol,PERIOD_D1,0);  //--- Get the time of midnight (start of the day) for daily chart
   static datetime sixAM = midnight + 6 * 3600;            //--- Calculate 6 AM based on midnight time
   static datetime scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Set scan time for the next bar after 6 AM

   static datetime validBreakTime_start = scanBarTime;     //--- Set the start of valid breakout time
   static datetime validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Set the end of valid breakout time to 11 AM
   
   if (isNewDay()){
      //--- Reset values for the new day
      midnight = iTime(_Symbol,PERIOD_D1,0);    //--- Get the new midnight time
      sixAM = midnight + 6 * 3600;              //--- Recalculate 6 AM
      scanBarTime = sixAM + 1 * PeriodSeconds(_Period); //--- Recalculate the scan bar time

      validBreakTime_start = scanBarTime;       //--- Update valid breakout start time
      validBreakTime_end = midnight + (6+hoursValidity) * 3600; //--- Update valid breakout end time to 11 AM

      maximum_price = -DBL_MAX;                 //--- Reset the maximum price for the new day
      minimum_price = DBL_MAX;                  //--- Reset the minimum price for the new day
      
      isHaveDailyRange_Prices = false;          //--- Reset the daily range flag for the new day
      isHaveRangeBreak = false;                 //--- Reset the breakout flag for the new day
   }
   
   if (isNewBar()){
      //--- If a new bar has been formed, process the data
      datetime currentBarTime = iTime(_Symbol,_Period,0); //--- Get the time of the current bar
      
      if (currentBarTime == scanBarTime && !isHaveDailyRange_Prices){
         //--- If it's time to scan and the daily range is not yet extracted
         Print("WE HAVE ENOUGH BARS DATA FOR DOCUMENTATION. MAKE THE EXTRACTION"); //--- Log the extraction process
         int total_bars = int((sixAM - midnight)/PeriodSeconds(_Period)) + 1; //--- Calculate total bars between midnight and 6 AM
         Print("Total Bars for scan = ",total_bars); //--- Log the total number of bars for scanning
         int highest_price_bar_index = -1;   //--- Variable to store the bar index of the highest price
         int lowest_price_bar_index = -1;    //--- Variable to store the bar index of the lowest price
   
         for (int i=1; i<=total_bars ; i++){ //--- Loop through all bars within the defined time range
            double open_i = open(i);         //--- Get the opening price of the i-th bar
            double close_i = close(i);       //--- Get the closing price of the i-th bar
            
            double highest_price_i = (open_i > close_i) ? open_i : close_i; //--- Determine the highest price between open and close
            double lowest_price_i = (open_i < close_i) ? open_i : close_i;  //--- Determine the lowest price between open and close
            
            if (highest_price_i > maximum_price){
               //--- If the current highest price is greater than the recorded maximum price
               maximum_price = highest_price_i; //--- Update the maximum price
               highest_price_bar_index = i;     //--- Update the index of the highest price bar
               maximum_time = time(i);          //--- Update the time of the highest price
            }
            if (lowest_price_i < minimum_price){
               //--- If the current lowest price is lower than the recorded minimum price
               minimum_price = lowest_price_i;  //--- Update the minimum price
               lowest_price_bar_index = i;      //--- Update the index of the lowest price bar
               minimum_time = time(i);          //--- Update the time of the lowest price
            }
         }
         //--- Log the maximum and minimum prices, along with their respective bar indices and times
         Print("Maximum Price = ",maximum_price,", Bar index = ",highest_price_bar_index,", Time = ",maximum_time);
         Print("Minimum Price = ",minimum_price,", Bar index = ",lowest_price_bar_index,", Time = ",minimum_time);
         
         //--- Create visual elements to represent the daily range
         create_Rectangle(RECTANGLE_PREFIX+TimeToString(maximum_time),maximum_time,maximum_price,minimum_time,minimum_price,clrBlue); //--- Create a rectangle for the daily range
         create_Line(UPPER_LINE_PREFIX+TimeToString(midnight),midnight,maximum_price,sixAM,maximum_price,3,clrBlack,DoubleToString(maximum_price,_Digits)); //--- Draw upper range line
         create_Line(LOWER_LINE_PREFIX+TimeToString(midnight),midnight,minimum_price,sixAM,minimum_price,3,clrRed,DoubleToString(minimum_price,_Digits));   //--- Draw lower range line
         
         isHaveDailyRange_Prices = true; //--- Set the flag indicating daily range prices have been extracted
      }
   }
   
   //--- Get the close price and time of the previous bar
   double barClose = close(1); 
   datetime barTime = time(1);
   
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   //--- Check for upper range breakout condition
   if (barClose > maximum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
       && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the HIGH range. ",barClose," > ",maximum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,234,clrBlack,-1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,minimum_price,Bid+(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,Ask+(maximum_price-minimum_price),Ask-(maximum_price-minimum_price)*r2r);
      }
   }
   //--- Check for lower range breakout condition
   else if (barClose < minimum_price && isHaveDailyRange_Prices && !isHaveRangeBreak
            && barTime >= validBreakTime_start && barTime <= validBreakTime_end){
      Print("CLOSE Price broke the LOW range. ",barClose," < ",minimum_price); //--- Log the breakout event
      isHaveRangeBreak = true; //--- Set the flag indicating a breakout occurred
      drawBreakPoint(TimeToString(barTime),barTime,barClose,233,clrBlue,1); //--- Draw a point to mark the breakout
      
      if (direction_of_trade == Default_Trade_Directions){
         obj_Trade.Sell(0.01,_Symbol,Bid,maximum_price,Ask-(maximum_price-minimum_price)*r2r);
      }
      else if (direction_of_trade == Invert_Trade_Directions){
         obj_Trade.Buy(0.01,_Symbol,Ask,Bid-(maximum_price-minimum_price),Bid+(maximum_price-minimum_price)*r2r);
      }
   }
}

//--- Utility functions to retrieve price and time data for a given bar index
double open(int index){return (iOpen(_Symbol,_Period,index));}   //--- Get the opening price
double high(int index){return (iHigh(_Symbol,_Period,index));}   //--- Get the highest price
double low(int index){return (iLow(_Symbol,_Period,index));}     //--- Get the lowest price
double close(int index){return (iClose(_Symbol,_Period,index));} //--- Get the closing price
datetime time(int index){return (iTime(_Symbol,_Period,index));} //--- Get the time of the bar

//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE A RECTANGLE                             |
//+------------------------------------------------------------------+
void create_Rectangle(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   //--- Check if the object already exists by finding it on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create a rectangle object using the defined parameters: name, type, and coordinates
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the rectangle (start point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the rectangle (start point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the rectangle (end point)
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the rectangle (end point)
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Enable the fill property for the rectangle, making it filled
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);
      
      //--- Set the color for the rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the rectangle to not appear behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);

      //--- Redraw the chart to reflect the new changes
      ChartRedraw(0);
   }
}
//+------------------------------------------------------------------+
//|      FUNCTION TO CREATE A TREND LINE                             |
//+------------------------------------------------------------------+
void create_Line(string objName, datetime time1, double price1, datetime time2, double price2, int width, color clr, string text) {
   //--- Check if the line object already exists by its name
   if (ObjectFind(0, objName) < 0) {
      //--- Create a trendline object with the specified parameters
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price1, time2, price2);
      
      //--- Set the time for the first point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);
      
      //--- Set the price for the first point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);
      
      //--- Set the time for the second point of the trendline
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);
      
      //--- Set the price for the second point of the trendline
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);
      
      //--- Set the width for the line
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, width);
      
      //--- Set the color of the trendline
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the trendline to not be behind other objects
      ObjectSetInteger(0, objName, OBJPROP_BACK, false);
      
      //--- Retrieve the current chart scale
      long scale = 0;
      if(!ChartGetInteger(0, CHART_SCALE, 0, scale)) {
         //--- Print an error message if unable to retrieve the chart scale
         Print("UNABLE TO GET THE CHART SCALE. DEFAULT OF ", scale, " IS CONSIDERED");
      }
      //--- Set a default font size based on the chart scale
      int fontsize = 11;
      if (scale == 0) { fontsize = 5; }
      else if (scale == 1) { fontsize = 6; }
      else if (scale == 2) { fontsize = 7; }
      else if (scale == 3) { fontsize = 9; }
      else if (scale == 4) { fontsize = 11; }
      else if (scale == 5) { fontsize = 13; }
      
      //--- Define the description text to appear near the right price
      string txt = " Right Price";
      string objNameDescr = objName + txt;
      
      //--- Create a text object next to the line to display the description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time2, price2);
      
      //--- Set the color for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, fontsize);
      
      //--- Anchor the text to the left of the line
      ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT);
      
      //--- Set the text content to display the specified string
      ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + text);
      
      //--- Set the font of the text to "Calibri"
      ObjectSetString(0, objNameDescr, OBJPROP_FONT, "Calibri");
      
      //--- Redraw the chart to reflect the changes
      ChartRedraw(0);
   }
}

bool isNewBar() {
   //--- Static variable to hold the previous number of bars
   static int prevBars = 0;
   
   //--- Get the current number of bars on the chart
   int currBars = iBars(_Symbol, _Period);
   
   //--- If the number of bars hasn't changed, return false
   if (prevBars == currBars) return (false);
   
   //--- Update the previous bar count with the current one
   prevBars = currBars;
   
   //--- Return true if a new bar has been formed
   return (true);
}

bool isNewDay() {
   //--- Flag to indicate if a new day has started
   bool newDay = false;
   
   //--- Structure to hold the current date and time
   MqlDateTime Str_DateTime;
   
   //--- Convert the current time to a structured format
   TimeToStruct(TimeCurrent(), Str_DateTime);
   
   //--- Static variable to store the previous day
   static int prevDay = 0;
   
   //--- Get the current day from the structured time
   int currDay = Str_DateTime.day;
   
   //--- If the previous day is the same as the current day, we're still on the same day
   if (prevDay == currDay) {
      newDay = false;
   }
   //--- If the current day differs from the previous one, we have a new day
   else if (prevDay != currDay) {
      //--- Print a message indicating the new day
      Print("WE HAVE A NEW DAY WITH DATE ", currDay);
      
      //--- Update the previous day to the current day
      prevDay = currDay;
      
      //--- Set the flag to true, indicating a new day has started
      newDay = true;
   }
   
   //--- Return whether a new day has started
   return (newDay);
}
//+------------------------------------------------------------------+
//|       FUNCTION TO CREATE AN ARROW                                |
//+------------------------------------------------------------------+
void drawBreakPoint(string objName, datetime time, double price, int arrCode, color clr, int direction) {
   //--- Check if the arrow object already exists on the chart
   if (ObjectFind(0, objName) < 0) {
      //--- Create an arrow object with the specified time, price, and arrow code
      ObjectCreate(0, objName, OBJ_ARROW, 0, time, price);
      
      //--- Set the arrow's code (symbol)
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrCode);
      
      //--- Set the color for the arrow
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the arrow
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 12);
      
      //--- Set the anchor position for the arrow based on the direction
      if (direction > 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
      
      //--- Define a text label for the break point
      string txt = " Break";
      string objNameDescr = objName + txt;
      
      //--- Create a text object for the break point description
      ObjectCreate(0, objNameDescr, OBJ_TEXT, 0, time, price);
      
      //--- Set the color for the text description
      ObjectSetInteger(0, objNameDescr, OBJPROP_COLOR, clr);
      
      //--- Set the font size for the text
      ObjectSetInteger(0, objNameDescr, OBJPROP_FONTSIZE, 12);
      
      //--- Adjust the text anchor based on the direction of the arrow
      if (direction > 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0, objNameDescr, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
         ObjectSetString(0, objNameDescr, OBJPROP_TEXT, " " + txt);
      }
   }
   //--- Redraw the chart to reflect the new objects
   ChartRedraw(0);
}

Resultados do backtest:

RESULTADOS DO TESTE COM DADOS HISTÓRICOS

Gráfico do backtest:

GRÁFICO DO BACKTEST

Nesta etapa do teste, otimizamos os parâmetros de entrada e verificamos a eficácia da estratégia por meio do testador de estratégias. As alterações nos parâmetros de entrada proporcionaram mais flexibilidade à estratégia de negociação. Confirmamos que ela funciona conforme o planejado e apresenta resultados positivos após o backtest e a otimização.


Considerações finais

Em resumo, este artigo apresentou uma abordagem aprofundada e passo a passo para a criação de um EA em MQL5 que opera com base no rompimento do intervalo diário. Começamos pelas partes fundamentais que permitem ao EA calcular os intervalos de preço diários e definir os níveis de rompimento. Esses elementos são essenciais para determinar o momento exato antes e logo após o qual se pode esperar que o preço ultrapasse os limites do intervalo diário.

Além disso, mostramos como implementar diversas funções em MQL5 que monitoram as condições de mercado, realizam as comparações de preço necessárias e então tomam as ações adequadas para executar uma ordem no instante do rompimento. Para ajudar os traders a visualizarem rapidamente os níveis-chave necessários à estratégia, adicionamos ao gráfico vários elementos visuais — como retângulos e linhas de tendência. Cuidamos para que esses elementos fossem programados com flexibilidade suficiente, já que a estratégia exige parâmetros de entrada ajustáveis.

Aviso legal: As informações contidas neste artigo são fornecidas exclusivamente para fins educacionais. O objetivo é apresentar uma visão geral de como desenvolver um EA baseado na estratégia de Rompimento do Intervalo Diário, servindo como base para o desenvolvimento de sistemas mais avançados, com posterior otimização e testes. As estratégias e métodos descritos não garantem quaisquer resultados de negociação. Você utiliza este conteúdo por sua conta e risco. Antes de aplicar qualquer solução de trading automatizado, realize testes rigorosos e leve em consideração as condições de mercado potenciais.

A estratégia foi testada com o Testador de Estratégias do MetaTrader 5, o que nos permitiu avaliar sua eficácia e fazer os ajustes necessários para que ela funcione ainda melhor dentro das nossas condições de operação. Bons códigos e bons trades!

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16135

Últimos Comentários | Ir para discussão (3)
Stanislav Korotky
Stanislav Korotky | 21 out. 2024 em 14:59

Parece um conhecido "Morning Flat Breakout", não um "Daily Range Breakout" (que é diferente), portanto, o título é enganoso.

Além disso, não vi um código para lidar com o fuso horário, pois, dependendo do deslocamento do GMT do seu servidor, o "morning flat", que define o intervalo para o breakout, deve ser rastreado a partir não necessariamente da meia-noite 00:00, mas com o deslocamento.

Allan Munene Mutiiria
Allan Munene Mutiiria | 22 out. 2024 em 11:03
Stanislav Korotky #:

Parece um conhecido "Morning Flat Breakout", não um "Daily Range Breakout" (que é diferente), portanto, o título é enganoso.

Além disso, não vi um código para lidar com o fuso horário, pois, dependendo do deslocamento do GMT do seu servidor, o "morning flat", que define o intervalo para o breakout, deve ser rastreado a partir não necessariamente da meia-noite 00:00, mas com o deslocamento.

Obrigado pela resposta. Isso depende da estratégia usada. No nosso caso, é o Daily Range Breakout, que vai essencialmente da meia-noite às 6h, que pode, é claro, ser ajustado. Muito obrigado.

Aa123456789
Aa123456789 | 13 jun. 2025 em 17:16
Você pode modificar o código para que o EA possa diversificar e ajustar o take profit e o stop loss, o tempo de negociação e a porcentagem de risco?
Assistente Connexus (Parte 5): Métodos HTTP e códigos de status Assistente Connexus (Parte 5): Métodos HTTP e códigos de status
Neste artigo, vamos entender os métodos HTTP e os códigos de status, dois elementos muito importantes para a interação entre cliente e servidor na internet. Compreender o que cada método faz de fato permite criar requisições mais precisas, informando ao servidor qual ação deve ser executada e tornando a comunicação mais eficiente.
Reimaginando Estratégias Clássicas (Parte IX): Análise de Múltiplos Time-Frames (II) Reimaginando Estratégias Clássicas (Parte IX): Análise de Múltiplos Time-Frames (II)
Na discussão de hoje, examinamos a estratégia de análise de múltiplos time-frames para descobrir em qual time-frame nosso modelo de IA apresenta melhor desempenho. Nossa análise nos levou a concluir que os time-frames Mensal e de 1 Hora produzem modelos com taxas de erro relativamente baixas no par EURUSD. Usamos isso a nosso favor e criamos um algoritmo de negociação que faz previsões de IA no time-frame Mensal e executa suas negociações no time-frame de 1 Hora.
Do básico ao intermediário: Acesso aleatório (II) Do básico ao intermediário: Acesso aleatório (II)
Neste artigo iremos ver como duas abordagens ligeiramente diferentes podem ter um impacto muito grande em todo uma metodologia de implementação. Tanto pelo ponto de vista de performance, quanto pelo ponto de vista de como acessos ao disco devem ser pensados a fim de evitar problemas de compatibilidade entre diferentes aplicações.
Consultor Especialista Auto-Otimizável com MQL5 e Python (Parte V): Modelos de Markov Profundos Consultor Especialista Auto-Otimizável com MQL5 e Python (Parte V): Modelos de Markov Profundos
Nesta discussão, aplicaremos uma Cadeia de Markov simples sobre um indicador RSI, para observar como o preço se comporta após o indicador atravessar níveis-chave. Concluímos que os sinais de compra e venda mais fortes no par NZDJPY são gerados quando o RSI está nas faixas de 11-20 e 71-80, respectivamente. Vamos demonstrar como você pode manipular seus dados para criar estratégias de trading ideais aprendidas diretamente a partir dos dados que possui. Além disso, mostraremos como treinar uma rede neural profunda para aprender a utilizar a matriz de transição de forma otimizada.