English Русский 中文 Español Deutsch 日本語
preview
Desenvolvimento de sistemas de trading avançados ICT: Implementação de sinais no indicador Order Blocks

Desenvolvimento de sistemas de trading avançados ICT: Implementação de sinais no indicador Order Blocks

MetaTrader 5Exemplos |
141 1
Niquel Mendoza
Niquel Mendoza



      Introdução

      Bem-vindo ao mundo do MQL5! Neste artigo vamos nos concentrar na adição de buffers e sinais de entrada ao nosso indicador, concluindo assim a funcionalidade necessária para seu uso em estratégias de trading automatizadas.

      Se você é iniciante nesta série, recomendamos revisar a primeira parte, na qual são explicados os conceitos principais e o processo de criação de um indicador do zero.


      Detecção de blocos de ordens (Order Blocks) com base na profundidade de mercado

      Nossa lógica de definição de blocos de ordens usando profundidade de mercado será a seguinte:

       Algoritmo            

      1. Array Creation: criamos dois arrays para armazenar o volume de cada vela no livro de preços, o que nos permitirá organizar e analisar de forma eficiente os dados de volume.
      2. Market Depth Data Collection, no evento.                                
      void  OnBookEvent( )       

      Vamos registrar todas as mudanças no livro de ofertas, salvando o novo volume para manter os dados atualizados em tempo real.

            3. Rules to validate Order Blocks: depois que o volume é salvo nos arrays, vamos usar esses dados junto com regras de price action para validar o bloco de ordens (Order Block).

      Regras para Order Block com base na profundidade de mercado:

      Inicialmente, ao criar nosso indicador, buscávamos blocos de ordens (Order Blocks) em um intervalo específico de velas. No entanto, no caso da profundidade de mercado, não faremos a busca dentro de um intervalo "x". Em vez disso, vamos concentrar a pesquisa especificamente na terceira vela, considerando que a vela 0 é a vela atual.

      Regras Order Block altista Order Block baixista 
      Pico de volume na vela 3: O volume de compras na vela 3 deve ser maior, em determinada proporção, do que os volumes de compras e vendas nas velas 2 e 4. Vamos buscar que o volume de vendas na vela 3 seja um pouco maior do que o volume de compras e vendas nas velas 2 e 4.
      3 velas consecutivas: Devem aparecer 3 velas consecutivas altistas
      (velas 1, 2 e 3)
      Devem aparecer 3 velas consecutivas baixistas
      (velas 1, 2 e 3)
      Corpo da vela 3: A mínima da vela 2 deve estar acima da metade do corpo da vela 3. A máxima da vela 2 deve estar abaixo da metade do corpo da vela 3.
      Máxima ou mínima da vela 3: A máxima da vela 3 deve estar abaixo do fechamento da vela 2. A mínima da vela 3 deve estar acima do fechamento da vela 2.

      Com essas regras garantimos o seguinte:

      • Imbalance de compras ou vendas: verificamos a presença de um desequilíbrio significativo nas compras ou vendas em uma vela específica, na qual as ordens de compra ou venda foram maiores, em determinada proporção, do que na vela anterior e na vela seguinte.
      • Controle do corpo da vela no imbalance: garantimos que as ordens não executadas, surgidas devido ao excesso de demanda ou oferta, não tenham sido absorvidas pela vela seguinte, confirmando assim a solidez do bloco de ordens.
      • Movimento fortemente altista ou baixista: confirmamos que o padrão representa um movimento intenso, seja ele altista ou baixista, refletindo a força do desequilíbrio na dinâmica do preço.

      Agora, tendo tudo isso em mente, vamos transferir o que estudamos para o código.


      Inicialização e finalização do evento "livro de preços" e criação de arrays

      Criação de arrays

      Antes de usar o livro de preços, é necessário criar arrays dinâmicos que vão armazenar os dados de volume. Esses arrays terão o tipo:

      long

      E serão usados para armazenar o volume de compras e o volume de vendas, respectivamente.

      1. Vamos ao bloco global do programa e declaramos os arrays dinâmicos:

      long buy_volume[];
      long sell_volume[];
      
      

            2. Dentro do evento OnInit vamos alterar o tamanho dos arrays para que seu tamanho inicial seja igual a 1. Além disso, vamos atribuir o valor 0 ao índice 0 de cada array.

        ArrayResize(buy_volume,1);
        ArrayResize(sell_volume,1);
        buy_volume[0] = 0.0;
        sell_volume[0] = 0.0;
      

      Inicialização e finalização do evento "livro de preços":

      Antes de iniciar o livro de preços, criamos uma variável global que vai indicar se essa função está disponível. Isso vai nos permitir evitar o uso de:

      INIT_FAILED
      Como nem todos os símbolos em algumas corretoras fornecem volume de negociação no livro de preços, o indicador não dependerá da corretora que obrigatoriamente oferece essa funcionalidade.

      • Para determinar se o símbolo com o qual você deseja operar possui livro de preços e volume de negociação, é possível realizar os seguintes passos:

      1. Clique no canto superior esquerdo do gráfico nesta imagem:

      2. Verifique se o seu símbolo possui volume disponível para o livro de preços. Algo semelhante ao exemplo abaixo será exibido.

      Símbolo com volume no livro de preços:

      Depth of Market 2

      Símbolo sem volume no livro de preços:

       ETHUSD Depth of Market

      Como já mencionei, o volume no livro de preços não está disponível para todos os símbolos; isso também vai depender da corretora com a qual você trabalha.

      Vamos agora para a inicialização e finalização do livro de preços.

      1. Variável global de controle

      Definimos uma variável booleana global para indicar o status de disponibilidade do livro de preços:

      bool use_market_book = true; //true by default

      Essa variável é inicializada com o valor true, mas poderemos alterá-la caso a abertura do livro de preços falhe.

      2. Inicialização do livro de preços

      Para inicializar o livro de preços, é utilizada a função: 

      MarketBookAdd()

      Ela abre o livro de preços (Depth of Market) para o símbolo especificado. Como argumento, a função recebe o símbolo atual:

      _Symbol

      No evento OnInit verificamos se a inicialização foi bem-sucedida.

       if(!MarketBookAdd(_Symbol)) //Verify initialization of the order book for the current symbol
           {
            Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Print error in case of failure
            use_market_book = false; //Mark use_market_book as false if initialization fails
           }
      

      3. Finalização do trabalho com o livro de preços

      No evento OnDeinit liberamos o livro de preços, usando:

       MarketBookRelease()

      Verificamos o encerramento e exibimos uma mensagem dependendo do resultado:

      //---
         if(MarketBookRelease(_Symbol)) //Verify if closure was successful
           Print("Order book successfully closed for: " , _Symbol); //Print success message if so
         else
           Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not
      


      Coleta de dados da profundidade de mercado para determinar os volumes dos arrays

      Após inicializar o livro de preços, podemos começar a coletar os dados correspondentes. Para isso, criamos o evento OnBookEvent, que será acionado sempre que ocorrer uma alteração na profundidade de mercado.

      1. Criação do evento OnBookEvent:

      void OnBookEvent(const string& symbol)
           2. Verificação do símbolo e da disponibilidade da profundidade de mercado:
       if(symbol !=_Symbol || use_market_book == false)
            return; 
      // Exit the event if conditions are not met
      
      

      Após concluir essa verificação, podemos apresentar o código completo do evento OnBookEvent:    

      void OnBookEvent(const string& symbol)
        {
         if(symbol !=_Symbol || use_market_book == false)
            return;
      // Define array to store Market Book data
         MqlBookInfo book_info[];
      
      // Retrieve Market Book data
         bool book_count = MarketBookGet(_Symbol,book_info);
      
      // Verify if data was successfully obtained
         if(book_count == true)
           {
            // Iterate through Market Book data
            for(int i = 0; i < ArraySize(book_info); i++)
              {
               // Check if the record is a buy order (BID)
               if(book_info[i].type == BOOK_TYPE_BUY  || book_info[i].type ==  BOOK_TYPE_BUY_MARKET)
                 {
                 
                  buy_volume[0] += book_info[i].volume;
                 }
               // Check if the record is a sell order (ASK)
               if(book_info[i].type == BOOK_TYPE_SELL || book_info[i].type == BOOK_TYPE_SELL_MARKET)
                 {
                  sell_volume[0] += book_info[i].volume;
                 }
              }
           }
         else
           {
            Print("No Market Book data retrieved.");
           }
        }
      

      Esse código realiza o seguinte:

      • Obtenção do volume: sempre que ocorre uma alteração no livro de preços, OnBookEvent coleta o volume da última ordem registrada.
      • Atualização dos arrays: soma o volume de compra e de venda no índice 0 dos arrays buy_volume e sell_volume, respectivamente.

      Para que o array acumule a profundidade do volume do mercado em cada nova vela e mantenha o histórico como uma série temporal, o código deve ajustar o volume atual no índice 0 e deslocar os demais dados para frente, garantindo que os dados não se acumulem de forma excessiva e que o tamanho do array permaneça constante (por exemplo, 30 elementos).

      1. Verificação de nova vela e validação do contador de abertura de velas (maior que 1)

      Para evitar acionamentos incorretos na inicialização do programa e garantir que os arrays sejam atualizados somente quando uma nova vela se abre (e após pelo menos uma abertura), implementamos a verificação da variável counter junto com new_vela. Isso garante que a atualização dos arrays ocorrerá exclusivamente quando realmente surgir informação nova.

      Declaração de variáveis estáticas
      Declaramos counter como uma variável estática para que ela seja preservada entre chamadas de OnCalculate. A variável new_vela deve indicar se uma nova vela foi aberta.
      static int counter = 0;

      Condição de verificação de nova vela e contador
      Verificamos que counter é maior que 1, new_vela tem valor true e o uso do mercado é permitido. Somente se essas condições forem satisfeitas, vamos alterar o tamanho do array e deslocar os elementos. Esse controle evita uma mudança prematura de tamanho e garante que o array seja atualizado apenas quando houver dados válidos e quando o market book fornecer volume de negociação para o símbolo atual.

      if(counter > 1 && new_vela == true && use_market_book == true)

      Atualização do contador
      Aumentamos o contador em 1 cada vez que uma nova vela é detectada.

      counter++;

      2. Controle do tamanho do array

      Verificamos que o array não exceda o tamanho máximo de 30 elementos. Se isso ocorrer, alteramos seu tamanho para 30, removendo o elemento mais antigo:

      if(ArraySize(buy_volume) >= 30)
      {
         ArrayResize(buy_volume, 30);  // Keep buy_volume size at 30
         ArrayResize(sell_volume, 30); // Keep sell_volume size at 30
      }
      

      3. Alteração de tamanho para inserir novos valores

      Adicionamos espaço adicional ao array para armazenar o novo volume na posição inicial.

      ArrayResize(buy_volume, ArraySize(buy_volume) + 1);
      ArrayResize(sell_volume, ArraySize(sell_volume) + 1);
      

      4. Deslocamento dos elementos

      Movemos todos os elementos do array uma posição à frente. Isso garante que os novos dados sejam sempre armazenados no índice 0, enquanto os antigos são deslocados para índices de valores maiores.

      for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
      {
         buy_volume[i] = buy_volume[i - 1];
         sell_volume[i] = sell_volume[i - 1];
      }
      

      5. Verificação dos volumes

      Exibimos o volume de compra e de venda na posição 1 do array para verificar o volume da última vela.

      Print("Buy volume of the last candle: ", buy_volume[1]);
      Print("Sell volume of the last candle: ", sell_volume[1]);
      

      6. Reinicialização dos volumes

      Zeramos o índice 0 de ambos os arrays, para que comece a ser acumulado o volume da nova vela.

      buy_volume[0] = 0;
      sell_volume[0] = 0;
      

      7. Condição que permite evitar erros caso haja dados conflitantes no market book

      Adicionei essa condição para desativar automaticamente use_market_book se os valores buy_volume e sell_volume nas últimas posições (índices 3, 2 e 1) forem iguais a zero. Essa configuração é necessária porque, mesmo que um símbolo possua market book no mercado real, ao iniciar no testador de estratégias ele também é identificado como tendo market book. No entanto, o array pode ser preenchido de modo incorreto devido à ausência de alterações no livro de preços no modo de teste, o que gera valores zerados no array e pode fazer o indicador armazenar informações incorretas.

      Essa verificação impede que o indicador processe dados sem sentido e garante que use_market_book seja utilizado apenas quando o market book contiver valores válidos.

      if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
              {
               if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
              }     
      

      De forma integrada, o código ficará assim:

      if(counter > 1 && new_vela == true && use_market_book == true)
           {
            if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
              {
               if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
              }
            
             // If array size is greater than or equal to 30, resize to maintain a fixed length
           if(ArraySize(buy_volume) >= 30)
            {
            ArrayResize(buy_volume, 30);  // Ensure buy_volume does not exceed 30 elements
            ArrayResize(sell_volume, 30); // Ensure sell_volume does not exceed 30 elements
            }   
        
            ArrayResize(buy_volume,ArraySize(buy_volume)+1);
            ArrayResize(sell_volume,ArraySize(sell_volume)+1);
      
            for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
              {
               buy_volume[i] = buy_volume[i - 1];
               sell_volume[i] = sell_volume[i - 1];
              }
      
            // Reset volumes at index 0 to begin accumulating for the new candlestick 
            buy_volume[0] = 0;
            sell_volume[0] = 0;
           }
      


      Estratégia de busca de Order Blocks usando o livro de preços

      A estratégia seguirá a mesma lógica que utilizamos anteriormente, porém com uma diferença importante: em vez de usar laços, vamos realizar as verificações diretamente na vela 3. A lógica geral permanece a mesma, pois verificamos determinadas condições, identificamos a vela mais próxima (com base no tipo de bloco de ordens), depois atribuímos os valores correspondentes à estrutura e adicionamos o bloco de ordens ao array. Aqui aplicaremos o mesmo processo, só que de forma mais simples.

      Vamos começar criando as estruturas que armazenarão as informações dos blocos de ordens:

      OrderBlocks newVela_Order_block_Book_bajista;
      OrderBlocks newVela_Order_block_Book;
      

      1. Condições iniciais

      Primeiro verificamos que o tamanho dos arrays buy_volume e sell_volume seja de pelo menos 5 elementos. Isso garante que haja histórico suficiente para análise. Também asseguramos que use_market_book esteja ativo para o processamento do livro de preços.

      if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)

      2. Definição das variáveis de controle

      Definimos a variável case_book para indicar se determinada condição de volume está sendo atendida. Estabelecemos ratio no nível de (1,4), que servirá como fator comparativo para detectar aumento significativo no volume de compras.

      bool case_book = false;
      double ratio = 1.4;
      

      3. Estado do volume de compras (Case Book)

      Aqui verificamos se o volume de compras no índice 3 ultrapassa significativamente o volume nos índices 2 e 4, tanto do lado da compra quanto da venda, usando ratio como multiplicador. Se essa condição for atendida, case_book é ativado.

      Cenário altista:

      if(buy_volume[3] > buy_volume[4] * ratio && buy_volume[3] > buy_volume[2] * ratio &&
         buy_volume[3] > sell_volume[4] * ratio && buy_volume[3] > sell_volume[2] * ratio)
      {
          case_book = true;
      }
      
      Cenário baixista:
      if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
      sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
      {
      case_book = true;
      }
      

      4. Cálculo do corpo da vela

      Calculamos o tamanho do corpo da vela (body_tree) no índice 3, subtraindo o preço de abertura do preço de fechamento.

      double body_tree = closeArray[3] - openArray[3]; 
      double body_tree = openArray[3] - closeArray[3];

      5. Verificação das condições de preço para o setup altista

      Avaliamos as condições mencionadas no início (veja a tabela acima).

      Cenário altista:

      if(lowArray[2] > ((body_tree * 0.5) + openArray[3]) && highArray[3] < closeArray[2] &&
         closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
      
      

      Cenário baixista:

      if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
                  closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
      

      6. Identificação das velas altistas anteriores

      Chamamos a função FindFurthestBullish, que busca a vela altista mais distante dentro de um intervalo de 20 velas a partir do índice 3. Isso nos ajuda a encontrar uma vela de referência para um setup altista confiável. Se uma vela altista for encontrada, o índice será maior que 0, o que nos permite continuar.

      Cenário altista:

      int furthestAlcista = FindFurthestAlcista(Time[3], 20);
      if(furthestAlcista > 0)
      

      7. Atribuição de valores ao bloco de ordens

      Se todas as condições forem atendidas, definimos o bloco de ordens (newVela_Order_block_Book ou newVela_Order_block_Book_bajista) com os valores da vela encontrada.

      Cenário altista:

      Print("Case Book Found");
      datetime time1 = Time[furthestAlcista]; 
      double price2 = openArray[furthestAlcista];
      double price1 = lowArray[furthestAlcista]; 
      
      //Assign the above variables to the structure
      newVela_Order_block_Book.price1 = price1;
      newVela_Order_block_Book.time1 = time1;
      newVela_Order_block_Book.price2 = price2;
      newVela_Order_block_Book.mitigated = false;
      newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);
      AddIndexToArray_alcistas(newVela_Order_block_Book);
      

      Cenário baixista:

      Print("Case Book Found");
      datetime time1 = Time[furthestBajista];
      double price1 = closeArray[furthestBajista];
      double price2 = lowArray[furthestBajista];
      
      //Assign the above variables to the structure
      newVela_Order_block_Book_bajista.price1 = price1;
      newVela_Order_block_Book_bajista.time1 = time1;
      newVela_Order_block_Book_bajista.price2 = price2;
      newVela_Order_block_Book_bajista.mitigated = false;
      newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);
      AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);
      

      Código completo:

      if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
        {
      
         bool case_book = false;
         double ratio = 1.4;
      
         if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
            sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
           {
            case_book = true;
           }
         double body_tree =   openArray[3] - closeArray[3];
      
         if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
            closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
           {
            int furthestBajista = FindFurthestBajista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle"
            if(furthestBajista  > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
              {
               Print("Case Book Found");
               datetime time1 = Time[furthestBajista];
               double price1 = closeArray[furthestBajista];
               double price2 = lowArray[furthestBajista];
      
               //Assign the above variables to the structure
               newVela_Order_block_Book_bajista.price1 = price1;
               newVela_Order_block_Book_bajista.time1 = time1;
               newVela_Order_block_Book_bajista.price2 = price2;
               newVela_Order_block_Book_bajista.mitigated = false;
               newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);
      
               AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);
      
              }
           }
        }
      //--------------------    Bullish   -------------------- 
      
      if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
        {
      
         bool case_book = false;
         double ratio = 1.4;
      
      
         if(buy_volume[3] > buy_volume[4]*ratio && buy_volume[3] > buy_volume[2]*ratio &&
            buy_volume[3] > sell_volume[4]*ratio && buy_volume[3] > sell_volume[2]*ratio)
           {
            case_book = true;
           }
         double body_tree =  closeArray[3] - openArray[3];
      
         if(lowArray[2] > ((body_tree * 0.5)+openArray[3]) && highArray[3] < closeArray[2] &&
            closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
           {
            int furthestAlcista = FindFurthestAlcista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle"
            if(furthestAlcista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
              {
               Print("Case Book Found");
               datetime time1 = Time[furthestAlcista];     //assign the index time of furthestAlcista to the variable time1
               double price2 = openArray[furthestAlcista]; //assign the open of furthestAlcista as price 2 (remember that we draw it on a bearish candlestick most of the time)
               double price1 = lowArray[furthestAlcista];  //assign the low of furthestAlcista as price 1
      
               //Assign the above variables to the structure
               newVela_Order_block_Book.price1 = price1;
               newVela_Order_block_Book.time1 = time1;
               newVela_Order_block_Book.price2 = price2;
               newVela_Order_block_Book.mitigated = false;
               newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);
      
               AddIndexToArray_alcistas(newVela_Order_block_Book);
      
              }
           }
        }
      
      


      Criação de buffers para o indicador

      Para criar e configurar os buffers do nosso indicador de blocos de ordens (order blocks) em MQL5, começaremos definindo dois buffers e dois elementos gráficos globais (plots) para armazenar e exibir os níveis de preço dos blocos altistas e baixistas.

      1. Declaração dos buffers e plots

      Vamos declarar dois buffers na parte global do programa, para que possam armazenar os dados de preço dos blocos de ordens. Além disso, adicionaremos dois plots que servirão para visualizar os blocos de ordens no gráfico.

      #property  indicator_buffers 2
      #property  indicator_plots 2
      #property indicator_label1 "Bullish Order Block"
      #property indicator_label2 "Bearish Order Block"
      

      2. Criação de arrays dinâmicos para os buffers

      Declaramos dois arrays dinâmicos, buyOrderBlockBuffer e sellOrderBlockBuffer, para armazenar os níveis de preço correspondentes aos blocos de ordens de compra (altistas) e de venda (baixistas), respectivamente. Esses arrays serão atribuídos aos buffers, o que permitirá exibir os dados dos blocos de ordens no gráfico.

      //--- Define the buffers
      double buyOrderBlockBuffer[];   // Buffer for bullish order blocks
      double sellOrderBlockBuffer[];  // Buffer for bearish order blocks
      

      Descrição:

      • buyOrderBlockBuffer: armazena os níveis de preço dos blocos de ordens altistas (bullish order blocks) e é destinado aos pontos altistas nos quais o preço pode encontrar suporte.
      • sellOrderBlockBuffer: armazena os níveis de preço dos blocos de ordens baixistas (bearish order blocks) e exibe pontos baixistas onde o preço pode encontrar resistência.


      Modificação da função OnInit para configurar os buffers

      Nesta parte vamos aprimorar a função OnInit para configurar os buffers do indicador, atribuindo os arrays dos blocos de ordens altistas e baixistas como buffers do indicador. Isso permitirá que o indicador armazene e exiba corretamente os dados no gráfico.

      Passo a passo:

      1. Atribuição dos buffers de dados com SetIndexBuffer

      Primeiro, em OnInit, atribuimos os arrays buyOrderBlockBuffer e sellOrderBlockBuffer aos buffers do indicador usando SetIndexBuffer. Isso garante que esses arrays possam armazenar e apresentar dados no gráfico.

      //--- Assign data buffers to the indicator
         SetIndexBuffer(0, buyOrderBlockBuffer, INDICATOR_DATA);
         SetIndexBuffer(1, sellOrderBlockBuffer, INDICATOR_DATA)
      

      2. Configuração dos buffers como séries e preenchimento com valores vazios

      Para que os dados sejam exibidos em ordem inversa (como uma série temporal), definimos modo de série para os arrays. Além disso, inicializamos ambos os buffers com o valor EMPTY_VALUE, para evitar que dados incorretos sejam exibidos antes que os valores reais sejam calculados.

        ArraySetAsSeries(buyOrderBlockBuffer, true);
        ArraySetAsSeries(sellOrderBlockBuffer, true);
        ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE);  // Initialize to EMPTY_VALUE
        ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
      


      Implementação dos buffers no indicador (2)

      Nesta parte vamos atribuir os preços dos blocos de ordens altistas e baixistas aos buffers do indicador. Esses buffers tornam os dados acessíveis em cada índice que corresponde ao tempo (time1) de cada bloco de ordens.

      1. Atribuição de preços para os blocos de ordens altistas

      Dentro do laço no qual avaliamos cada bloco altista no array ob_alcistas, adicionamos a seguinte linha de código para armazenar o preço price2 no buffer buyOrderBlockBuffer. Usamos a função iBarShift para obter o índice exato no gráfico em que time1 coincide com o tempo do bloco de ordens.

      buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;
      
      

      Aqui o valor price2 do bloco altista é atribuído ao índice correspondente no buyOrderBlockBuffer, para que o buffer reflita o nível de preço do bloco no gráfico.

      2. Atribuição de preços para os blocos de ordens baixistas

      De forma análoga, atribuímos o preço price2 de cada bloco baixista ao buffer sellOrderBlockBuffer. Isso é feito percorrendo o array ob_bajistas e definindo o valor do preço no índice correspondente.

      sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;
      
      
      No resultado final:
      • iBarShift é utilizado para determinar o índice exato em que o tempo do bloco coincide com a posição no gráfico.
      • buyOrderBlockBuffer e sellOrderBlockBuffer recebem os valores de price2, o que permite registrar os preços no momento correto e utilizá-los no gráfico e em cálculos adicionais do indicador.


      Atualização dos parâmetros de entrada (Inputs)

      Nesta parte vamos configurar os parâmetros de entrada (inputs) para que o usuário possa personalizar o estilo de cálculo do take profit (TP) e do stop loss (SL) conforme suas preferências. Para isso, criaremos um enum que permitirá escolher entre duas opções:ATR (Average True Range) ou POINT (pontos fixos).

      Enumeração ENUM_TP_SL_STYLE

      A enumeração ENUM_TP_SL_STYLE permite ao usuário escolher um dos dois modos de cálculo do TP e SL.

      • ATR: define os valores de TP e SL com base no intervalo médio de movimento do preço, ajustando-os automaticamente de acordo com a volatilidade atual do mercado.
      • POINT: define TP e SL em pontos fixos com base no valor especificado pelo usuário.

      enum ENUM_TP_SL_STYLE
        {
         ATR,
         POINT
        };
      

      Explicação:

      • ATR: ao escolher essa opção, o usuário define um multiplicador que determinará a distância de TP e SL em relação ao ATR. Um valor maior do multiplicador aumentará a distância entre TP e SL com base na volatilidade atual.

      • POINT: nesta opção, o usuário define manualmente TP e SL em pontos fixos. Isso permite estabelecer níveis estáticos de TP e SL independentemente da volatilidade.

      Agora, continuando o trabalho com os parâmetros do indicador, estruturamos os parâmetros de entrada usando sinput e agrupamos as configurações em seções. Isso garante uma apresentação mais organizada e visualmente clara no painel do indicador, facilitando o processo de configuração para o usuário.

      1. Seção de estratégia

      Primeiro criamos a seção de estratégia, que inclui a opção de estilos para o cálculo do take profit (TP) e do stop loss (SL):

      sinput group "-- Strategy --"
      input ENUM_TP_SL_STYLE tp_sl_style = POINT; // TP and SL style: ATR or fixed points
      
      

      Nesta seção, tp_sl_style permitirá ao usuário escolher se deseja calcular TP e SL com base no ATR (Average True Range) ou com pontos fixos.

      2. Configuração de TP e SL de acordo com o método selecionado

      Para contemplar as particularidades de cada método, adicionamos dois grupos adicionais, um para o método ATR e outro para pontos fixos.

      Grupo ATR: aqui incluímos duas variáveis de entrada input do tipo double, que permitem ao usuário definir os multiplicadores do ATR, ajustando assim o alcance de TP e SL com base na volatilidade.

      sinput group " ATR "
      input double Atr_Multiplier_1 = 1.5; // Multiplier for TP
      input double Atr_Multiplier_2 = 2.0; // Multiplier for SL
      
      

      Grupo POINT: neste grupo adicionamos duas variáveis de entrada input do tipo int para definir os pontos fixos de TP e SL, o que permite controlar manualmente e com precisão essas distâncias.

      sinput group " POINT "
      input int TP_POINT = 500; // Fixed points for TP
      input int SL_POINT = 275; // Fixed points for SL
      

      Graças a essa organização, os parâmetros ficam devidamente ordenados e categorizados, o que facilita seu uso e melhora a clareza na configuração do indicador. O usuário poderá ajustar intuitivamente o estilo de TP e SL, escolhendo entre configurações automáticas baseadas em ATR ou ajustes manuais em pontos.

      Código completo dos parâmetros:

      sinput group "--- Order Block Indicator settings ---"
      sinput group "-- Order Block --"
      input          int  Rango_universal_busqueda = 500;
      input          int  Witdth_order_block = 1;
      
      input          bool Back_order_block = true;
      input          bool Fill_order_block = true;
      
      input          color Color_Order_Block_Bajista = clrRed;
      input          color Color_Order_Block_Alcista = clrGreen;
      
      sinput group "-- Strategy --"
      input          ENUM_TP_SL_STYLE tp_sl_style = POINT;
      
      sinput group " ATR "
      input          double Atr_Multiplier_1 = 1.5;
      input          double Atr_Multiplier_2 = 2.0;
      sinput group " POINT "
      input          int TP_POINT = 500;
      input          int SL_POINT = 275;
      
      


      Lógica de formação dos sinais do indicador

      Para gerar sinais de compra ou venda, utilizam-se duas variáveis estáticas:

      Variável Descrição
      time_ e time_b Armazenam o tempo de mitigação do order block e adicionam 5 velas (em segundos) ao tempo de expiração.
      buscar_oba e buscar_obb Controlam a busca por novos order blocks mitigados (mitigated order block). São ativadas ou desativadas dependendo das condições.

      Processo de geração dos sinais

      Detecção do Order Block mitigado:
      • Quando ocorre a mitigação de um bloco de ordens, a variável time_ recebe o tempo atual mais a margem de 5 velas.
      • A variável de busca recebe o valor false, para impedir a continuidade da busca enquanto as condições do sinal são avaliadas.
      Condições dos sinais de compra e venda:
      • As condições de compra ou venda são avaliadas com base na média móvel exponencial (EMA) e no tempo de mitigação (time_). 
      Na tabela seguinte, estão resumidas as condições específicas:
      Tipo de sinal   Condições da EMA
      Condições do tempo
       Compra  A EMA de período 30 deve estar abaixo do fechamento da vela 1   time_ deve ser maior que o tempo atual
       Venda  A EMA de período 30 deve estar acima do fechamento da vela 1   time_b deve ser maior que o tempo atual

      Observação: essas condições garantem que o sinal seja gerado dentro de 5 velas após a mitigação do bloco de ordens (order block).

      Ações no caso de cumprimento ou descumprimento:

      Estado   Ação
       Cumprimento Os buffers de take profit (TP) e stop loss (SL) são preenchidos para executar a operação correspondente
       Descumprimento A variável de busca é redefinida para true e time_ e time_b são zeradas, permitindo retomar a busca por novos blocos de ordens (caso o tempo máximo tenha sido ultrapassado).

      Fluxograma:

      Compras

       Logic to Open Buy Position

      Vendas

      Logic to Open Sell Position


      Implementação da estratégia de trading

      Antes de começar, vamos criar um identificador da média móvel exponencial.

      Criamos variáveis globais (array e identificador):

      int hanlde_ma;
      double ma[]; 
      

      Em OnInit inicializamos o identificador e verificamos se ele recebeu um valor.

      hanlde_ma = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE);
      
      if(hanlde_ma == INVALID_HANDLE)
      {
      Print("The EMA indicator is not available. Failure: ", _LastError); 
      return INIT_FAILED;
      }
      
      Declaramos variáveis estáticas para gerenciar o estado da busca e o tempo de ativação do OB, diferenciando os cenários de compra e venda.
      //Variables for buy
      static bool buscar_oba = true;
      static datetime time_ = 0;
      
      //Variables for sell
      static bool buscar_obb = true;
      static datetime time_b = 0;
      

      Em seguida executamos um laço para buscar order blocks mitigados, de forma análoga ao que fizemos no artigo anterior para os alertas.

      Começamos adicionando as condições:

      //Bullish case
       if(buscar_oba == true)
      //Bearish case
       if(buscar_obb == true)
      
      // Bearish case
      for(int i = 0; i < ArraySize(ob_bajistas); i++) {
          if(ob_bajistas[i].mitigated == true &&
             !Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) &&
             ObjectFind(ChartID(), ob_bajistas[i].name) >= 0) {
              
              Alert("The bearishorder block is being mitigated: ", TimeToString(ob_bajistas[i].time1));
              buscar_obb = false;  // Pause search
              time_b = iTime(_Symbol,_Period,1);  //  Record the mitigation time
              Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
              break;
          }
      }
      
      // Bullish case
      for(int i = 0; i < ArraySize(ob_alcistas); i++) {
          if(ob_alcistas[i].mitigated == true &&
             !Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) &&
             ObjectFind(ChartID(), ob_alcistas[i].name) >= 0) {
              
              Alert("The bullish order block is mitigated: ", TimeToString(ob_alcistas[i].time1));
              time_ = iTime(_Symbol,_Period,0);
              Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
              buscar_oba = false;  // Pause search
              break;
          }
      }
      
      

      O passo seguinte é determinar se um OB foi mitigado, isto é, se houve interação do preço com ele. Se for encontrado um OB mitigado, seu tempo é registrado e a busca é pausada. Isso é executado para ambos os cenários, altista e baixista.

      // Buy
      if(buscar_oba == false && time_ > 0 && new_vela) { /* Code for Buy */ }
      
      // Sell
      if(buscar_obb == false && time_b > 0 && new_vela) { /* Code for Sell */ }
      
      

      Esta parte garante que o sistema interrompa a busca após detectar a mitigação, evitando duplicação de sinais.

      Condição inicial para execução da operação

      A estratégia utiliza certas condições para ativar a busca por sinais de compra ou venda após a mitigação de um OB, desde que o tempo máximo de espera ainda não tenha sido excedido.

      // Buy
      double close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
      datetime max_time_espera = time_ + (PeriodSeconds() * 5);
      
      if(close_ > ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
          // Code for Buy...
      }
      
      // Sell
      close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
      max_time_espera = time_b + (PeriodSeconds() * 5);
      
      if(close_ < ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
          // Code for Sell...
      }
      

      Nessas condições:

      1. buscar_oba ou buscar_obb devem ser iguais a false, confirmando a mitigação anterior,
      2. time_ ou time_b devem ter valor maior que 0, indicando que o tempo foi registrado,
      3. new_vela confirma que estamos em uma nova vela, ajudando a evitar decisões repetidas.

      Validação das condições de compra ou venda

      Para estabelecer as condições necessárias, primeiro precisamos de uma variável que armazene o tempo máximo de espera. Depois, precisamos do valor de fechamento da vela 1 e de sua EMA (média móvel exponencial). Para obter o fechamento usamos a função iClose e os valores da EMA são armazenados no array que contém toda a série histórica da média móvel.

      // Reset for Buy
      if(iTime(_Symbol,_Period,0) > max_time_espera) {
          time_ = 0;
          buscar_oba = true;
      }
      
      // Reset for Sell
      if(iTime(_Symbol,_Period,0) > max_time_espera) {
          time_b = 0;
          buscar_obb = true;
      }
      

      Reinício da busca por Order Blocks

      Por fim, se o tempo máximo de espera for excedido sem que as condições sejam atendidas, o código reinicia a busca, para garantir que novos OBs possam ser detectados:

      void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)
      
      

      Agora falta a função responsável por desenhar o tp e o sl, além de adicioná-los aos buffers. Vamos fazer isso agora e, em seguida, finalizar o código atual.

      Vamos analisar isso nas próximas partes.


      Configuração dos níveis de Take Profit (TP) e Stop Loss (SL)

      Nesta parte vamos desenvolver a função GetTP_SL, que calculará os valores de TP e SL com base em dois métodos: usando ATR (Average True Range) ou pontos fixos, como mencionamos anteriormente ao configurar os inputs.

      1: Definição da função

      A função GetTP_SL receberá como parâmetros o preço de abertura da posição, o tipo da posição (ENUM_POSITION_TYPE) e referências para os níveis TP e SL (tp1, tp2, sl1 e sl2), nos quais serão armazenados os valores calculados.

      double atr[];
      ArraySetAsSeries(atr, true);
      CopyBuffer(atr_i, 0, 0, 1, atr);
      
      

      2: Obtenção do ATR

      Para calcular os níveis com base em ATR, primeiro precisamos de um array que armazene o valor do ATR da última vela. Usamos CopyBuffer para preencher o array atr com o valor atual.

      if (type == POSITION_TYPE_BUY) {
          sl1 = price_open_position - (atr[0] * Atr_Multiplier_1);
          sl2 = price_open_position - (atr[0] * Atr_Multiplier_2);
          tp1 = price_open_position + (atr[0] * Atr_Multiplier_1);
          tp2 = price_open_position + (atr[0] * Atr_Multiplier_2);
      }
      
      if (type == POSITION_TYPE_SELL) {
          sl1 = price_open_position + (atr[0] * Atr_Multiplier_1);
          sl2 = price_open_position + (atr[0] * Atr_Multiplier_2);
          tp1 = price_open_position - (atr[0] * Atr_Multiplier_1);
          tp2 = price_open_position - (atr[0] * Atr_Multiplier_2);
      }
      
      

      3: Cálculo de TP e SL com base no ATR

      Quando tp_sl_style está configurado como ATR, calculamos os níveis de TP e SL multiplicando o valor do ATR pelos multiplicadores definidos (Atr_Multiplier_1 e Atr_Multiplier_2). Em seguida somamos ou subtraímos esses valores do preço de abertura, dependendo do tipo de posição.

      if (type == POSITION_TYPE_BUY) {
          sl1 = price_open_position - (SL_POINT * _Point);
          sl2 = price_open_position - (SL_POINT * _Point * 2);
          tp1 = price_open_position + (TP_POINT * _Point);
          tp2 = price_open_position + (TP_POINT * _Point * 2);
      }
      
      if (type == POSITION_TYPE_SELL) {
          sl1 = price_open_position + (SL_POINT * _Point);
          sl2 = price_open_position + (SL_POINT * _Point * 2);
          tp1 = price_open_position - (TP_POINT * _Point);
          tp2 = price_open_position - (TP_POINT * _Point * 2);
      }
      
      

      4: Cálculo de TP e SL com base em pontos

      Quando tp_sl_style está configurado como POINT, somamos ou subtraímos os pontos especificados (TP_POINT e SL_POINT), multiplicados pelo valor de um ponto do símbolo atual (_Point), ao preço de abertura. Essa é uma alternativa simples ao cálculo baseado em ATR.

      bool TrendCreate(long            chart_ID,        // Chart ID
                       string          name,            // Line name
                       int             sub_window,      // Subwindow index
                       datetime              time1,           // Time of the first point
                       double                price1,          // Price of the first point
                       datetime              time2,           // Time of the second point
                       double                price2,          // Price of the second point
                       color           clr,         // Line color
                       ENUM_LINE_STYLE style,       // Line style
                       int             width,       // Line width
                       bool            back,        // in the background
                       bool            selection    // Selectable form moving
                      )
        {
         ResetLastError();
         if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2))
           {
            Print(__FUNCTION__,
                  ": ¡Failed to create trend line! Error code = ",GetLastError());
            return(false);
           }
      
         ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
         ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);
         ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width);
         ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
         ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
         ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
         ChartRedraw(chart_ID);
         return(true);
        }
      
      


      Exibição dos níveis de TP e SL no gráfico

      Criamos uma função que exibirá os valores de tp e sl no gráfico. Para isso precisaremos criar linhas e textos.

      Para as linhas:

      bool TextCreate(long              chart_ID,                // Chart ID
                      string            name,                    // Object name
                      int               sub_window,              // Subwindow index
                      datetime                time,                   // Anchor time
                      double                  price,                  // Anchor price
                      string            text,              // the text
                      string            font,              // Font
                      int               font_size,         // Font size
                      color             clr,               // color
                      double            angle,             // Text angle
                      ENUM_ANCHOR_POINT anchor,            // Anchor point
                      bool              back=false,               // font
                      bool              selection=false)          // Selectable for moving
      
        {
      
      //--- reset error value
         ResetLastError();
      //--- create "Text" object
         if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price))
           {
            Print(__FUNCTION__,
                  ": ¡Failed to create object \"Text\"! Error code = ",GetLastError());
            return(false);
           }
         ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
         ObjectSetString(chart_ID,name,OBJPROP_FONT,font);
         ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size);
         ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle);
         ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor);
         ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
         ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
         ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
         ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
      
         ChartRedraw(chart_ID);
         return(true);
        }
      
      

      Para os textos:

      void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)

      Agora passamos à criação da função.

      Passo 1. Parâmetros de entrada

      A função recebe os seguintes parâmetros:

      • tp1 e tp2 — valores de dois níveis de Take Profit,
      • sl1 e sl2 — valores de dois níveis de Stop Loss.
      string curr_time = TimeToString(iTime(_Symbol, _Period, 0));
      datetime extension_time = iTime(_Symbol, _Period, 0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
      datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);
      
      

      Passo 2: Preparação do tempo

      Primeiro é criada a string curr_time, que armazena a data e a hora atuais da vela no gráfico. Em seguida calculamos extension_time, que é estendido em 15 períodos à frente do tempo atual para projetar as linhas de TP e SL à direita do gráfico. text_time é utilizado para ajustar a posição das etiquetas de texto, expandindo-a levemente além de extension_time.

      TrendCreate(ChartID(), curr_time + " TP1", 0, iTime(_Symbol, _Period, 0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, 1, true, false);
      TextCreate(ChartID(), curr_time + " TP1 - Text", 0, text_time, tp1, "TP1", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);
      
      

      Passo 3: Renderização das linhas e etiquetas de TP e SL

      1. Take Profit 1 (tp1):
      • Uma linha verde pontilhada (STYLE_DOT) é criada no nível de tp1 usando a função TrendCreate.
      • Depois, TextCreate adiciona a etiqueta com o texto "TP1" na posição correspondente a tp1.
      TrendCreate(ChartID(), curr_time + " TP2", 0, iTime(_Symbol, _Period, 0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, 1, true, false);
      TextCreate(ChartID(), curr_time + " TP2 - Text", 0, text_time, tp2, "TP2", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);
      
      
      2. Take Profit 2 (tp2):
      • Outra linha verde pontilhada é desenhada no nível de tp2 e a etiqueta "TP2" é adicionada.
      TrendCreate(ChartID(), curr_time + " SL1", 0, iTime(_Symbol, _Period, 0), sl1, extension_time, sl1, clrRed, STYLE_DOT, 1, true, false);
      TextCreate(ChartID(), curr_time + " SL1 - Text", 0, text_time, sl1, "SL1", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);
      
      
      3. Stop Loss 1 (sl1):
      • No nível de sl1 é desenhada uma linha vermelha pontilhada junto com a etiqueta "SL1".
      TrendCreate(ChartID(), curr_time + " SL2", 0, iTime(_Symbol, _Period, 0), sl2, extension_time, sl2, clrRed, STYLE_DOT, 1, true, false);
      TextCreate(ChartID(), curr_time + " SL2 - Text", 0, text_time, sl2, "SL2", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);
      
      
      4. Stop Loss 2 (sl2):
      • Da mesma forma é desenhada outra linha vermelha no nível de sl2 e adicionada a etiqueta "SL2".
      void DrawTP_SL(double tp1, double tp2, double sl1, double sl2)
        {
      
      
         string  curr_time = TimeToString(iTime(_Symbol,_Period,0));
         datetime extension_time = iTime(_Symbol,_Period,0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
         datetime   text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);
      
      
         TrendCreate(ChartID(),curr_time+" TP1",0,iTime(_Symbol,_Period,0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,1,true,false);
         TextCreate(ChartID(),curr_time+" TP1 - Text",0,text_time,tp1,"TP1","Arial",8,clrGreen,0.0,ANCHOR_CENTER);
      
         TrendCreate(ChartID(),curr_time+" TP2",0,iTime(_Symbol,_Period,0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,1,true,false);
         TextCreate(ChartID(),curr_time+" TP2 - Text",0,text_time,tp2,"TP2","Arial",8,clrGreen,0.0,ANCHOR_CENTER);
      
         TrendCreate(ChartID(),curr_time+" SL1",0,iTime(_Symbol,_Period,0),sl1,extension_time,sl1,clrRed,STYLE_DOT,1,true,false);
         TextCreate(ChartID(),curr_time+" SL1 - Text",0,text_time,sl1,"SL1","Arial",8,clrRed,0.0,ANCHOR_CENTER);
      
         TrendCreate(ChartID(),curr_time+" SL2",0,iTime(_Symbol,_Period,0),sl2,extension_time,sl2,clrRed,STYLE_DOT,1,true,false);
         TextCreate(ChartID(),curr_time+" SL2 - Text",0,text_time,sl2,"SL2","Arial",8,clrRed,0.0,ANCHOR_CENTER);
      
        }
      
      

      Código completo:

      #property indicator_label3 "Take Profit 1"
      #property indicator_label4 "Take Profit 2"
      #property indicator_label5 "Stop Loss 1"
      #property indicator_label6 "Stop Loss 2"
      
      


      Adição de buffers para os níveis de TP e SL (4)

      Assim como criamos anteriormente dois buffers que armazenam price2, voltamos agora à parte global do programa, onde escrevemos:

      #property  indicator_buffers 6
      #property  indicator_plots 6
      

      Além disso, aumentamos a quantidade de plots e buffers de 2 para 6.

      double tp1_buffer[];
      double tp2_buffer[];
      double sl1_buffer[];
      double sl2_buffer[];
      

      Criamos o array de buffers:

      SetIndexBuffer(2, tp1_buffer, INDICATOR_DATA);
      SetIndexBuffer(3, tp2_buffer, INDICATOR_DATA);
      
      SetIndexBuffer(4, sl1_buffer, INDICATOR_DATA);
      SetIndexBuffer(5, sl2_buffer, INDICATOR_DATA);
      
      
      ArraySetAsSeries(buyOrderBlockBuffer, true);
      ArraySetAsSeries(sellOrderBlockBuffer, true);
      ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
      ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
      
      ArraySetAsSeries(tp1_buffer, true);
      ArraySetAsSeries(tp2_buffer, true);
      ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
      ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
      
      ArraySetAsSeries(sl1_buffer, true);
      ArraySetAsSeries(sl2_buffer, true);
      ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
      ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
      

      Inicializamos os buffers usando fill e configuramos o modo de série:

      datetime    tiempo_ultima_vela_1;

      Assim, neste estágio, já teremos os buffers disponíveis.


      Encerramento do código principal e limpeza

      Para finalizar o desenvolvimento do indicador, é necessário implementar o código de limpeza e otimização. Isso permitirá que o indicador funcione mais rapidamente nos backtests e liberará recursos dos arrays, como OrderBlocks, quando não forem mais necessários.

      1. Limpeza de arrays

      Dentro de OnCalculate vamos controlar a criação de uma nova vela, só que desta vez no time frame diário. Para isso usamos uma variável global, que armazenará o horário da última vela:

       if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1,  0))
           {
            Eliminar_Objetos();
      
            ArrayFree(ob_bajistas);
            ArrayFree(ob_alcistas);
            ArrayFree(pricetwo_eliminados_oba);
            ArrayFree(pricetwo_eliminados_obb);
      
            tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1,  0);
           }
      
      Cada vez que uma nova vela diária se abre, liberamos a memória dos arrays, evitando o acúmulo de dados antigos e otimizando o desempenho.
      void OnDeinit(const int reason)
        {
         Eliminar_Objetos();
      
         ArrayFree(ob_bajistas);
         ArrayFree(ob_alcistas);
         ArrayFree(pricetwo_eliminados_oba);
         ArrayFree(pricetwo_eliminados_obb);
      
         if(atr_i  != INVALID_HANDLE)
            IndicatorRelease(atr_i);
         if(hanlde_ma != INVALID_HANDLE) //EMA
            IndicatorRelease(hanlde_ma);
      
         ResetLastError();
      
          if(MarketBookRelease(_Symbol)) //Verify if closure was successful
           Print("Order book successfully closed for: " , _Symbol); //Print success message if so
         else
           Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not
        }
      

      2. Modificação de OnDeinit

      Em OnDeinit liberamos o identificador do indicador EMA e limpamos os arrays. Isso garante que, ao desativar o indicador, nenhum recurso permaneça alocado na memória.

      void Eliminar_Objetos()
        {
      
         for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iterate through the array of bullish order blocks
           {
            ObjectDelete(ChartID(),ob_alcistas[i].name);   // delete the object using the order block's name
           }
         for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iterate through the array of bearish order blocks
           {
            ObjectDelete(ChartID(),ob_bajistas[n].name);   // delete the object using the order block's name
           }
       //Delete all TP and SL lines
         ObjectsDeleteAll(0," TP",-1,-1);
         ObjectsDeleteAll(0," SL",-1,-1);
        }
      

      3. Função de remoção de objetos:

      A função Remove_Objects foi otimizada para remover também as linhas de Take Profit (TP) e Stop Loss (SL) junto com os retângulos dos blocos de ordens. Assim garantimos que o gráfico permaneça limpo.
         string short_name = "Order Block Indicator";
         IndicatorSetString(INDICATOR_SHORTNAME,short_name);
      
      // Set data precision for digits
      
      // Assign labels for each plot
         PlotIndexSetString(0, PLOT_LABEL, "Bullish Order Block");
         PlotIndexSetString(1, PLOT_LABEL, "Bearish Order Block");
         PlotIndexSetString(2, PLOT_LABEL, "Take Profit 1");
         PlotIndexSetString(3, PLOT_LABEL, "Take Profit 2");
         PlotIndexSetString(4, PLOT_LABEL, "Stop Loss 1");
         PlotIndexSetString(5, PLOT_LABEL, "Stop Loss 2");
      

      4. Configuração inicial em OnInit:

      Em OnInit configuramos o nome curto do indicador e os rótulos dos elementos gráficos (plots). Dessa forma, os elementos serão identificados corretamente na janela de dados.

      //Buy
      double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      
      double tp1;
      double tp2;
      double sl1;
      double sl2;
      GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2);
      
      DrawTP_SL(tp1,tp2,sl1,sl2);
      
      tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
      tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
      sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
      sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;
      
      time_ = 0;
      buscar_oba = true;
      
      //Sell
      
      double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      double tp1;
      double tp2;
      double sl1;
      double sl2;
      GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2);
      
      DrawTP_SL(tp1,tp2,sl1,sl2);
      
      tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
      tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
      sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
      sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;
      
      time_b = 0;
      buscar_obb = true;
      

      5. Configuração dos níveis de TP e SL ao abrir operações:

      Por fim, configuramos os níveis de Take Profit e Stop Loss para operações de compra e venda. Para compras utilizamos o preço ask e calculamos os níveis correspondentes, para vendas utilizamos o preço bid. Em seguida exibimos no gráfico para que os níveis possam ser acompanhados.

      undefined
      Passo   Compras
      Vendas
       Preço: Obtém e normaliza o preço ask.
      Obtém e normaliza o preço bid.
      Variáveis: São inicializadas as variáveis que armazenarão os valores de Take Profit e Stop Loss.

      (tp1, tp2, sl1 e sl2). 
      As mesmas variáveis são utilizadas para armazenar os níveis de Take Profit e Stop Loss.

      (tp1, tp2, sl1 e sl2). 
      Cálculo: GetTP_SL calcula os níveis de TP e SL com base no preço ask para a operação de compra. GetTP_SL calcula os níveis de TP e SL com base no preço bid para a operação de venda.  
      Renderização: DrawTP_SL exibe visualmente no gráfico os níveis de TP e SL para a operação de compra. DrawTP_SL exibe visualmente no gráfico os níveis de TP e SL para a operação de venda.
      Buffer: É utilizado iBarShift para encontrar o índice do candle atual e salvar TP e SL nos buffers.

       (tp1_buffer, tp2_buffer, sl1_buffer e sl2_buffer).    
      É utilizado iBarShift para salvar TP e SL nos mesmos buffers.

       (tp1_buffer, tp2_buffer, sl1_buffer e sl2_buffer).   
      Variáveis estáticas:  As variáveis estáticas são redefinidas para buscar novos blocos de ordens altistas na próxima iteração.

      (variáveis: "time_" e "buscar_oba").
      As variáveis estáticas são redefinidas para buscar novos blocos de ordens baixistas na próxima iteração.

      (variáveis: "time_b" e "search_obb").


      Considerações finais

      Nesta parte do artigo abordamos como criar um indicador Order Blocks baseado no volume da profundidade de mercado e como otimizar sua funcionalidade adicionando buffers adicionais ao indicador original.

      Nosso resultado final:

      Final Example GIF

      Assim concluímos o desenvolvimento de nosso indicador Order Blocks. Nos futuros materiais, abordaremos a criação de uma classe de gerenciamento de riscos do zero e o desenvolvimento de um robô de negociação que integra esse gerenciamento de riscos, utilizando os buffers de sinal do nosso indicador para tomar decisões de forma mais precisa e automatizada.

      Traduzido do espanhol pela MetaQuotes Ltd.
      Artigo original: https://www.mql5.com/es/articles/16268

      Últimos Comentários | Ir para discussão (1)
      Vladislav Boyko
      Vladislav Boyko | 4 out. 2025 em 19:24

      https://www.mql5.com/pt/articles/16268

      5. Definição dos níveis de TP e SL ao abrir as negociações

      Por fim, definimos os níveis de Take Profit e Stop Loss para as negociações de compra e venda. Para as negociações de compra, use o preço Ask; para as negociações de venda, use o preço Bid. Em seguida, desenhe as linhas TP e SL no gráfico para monitoramento.

      tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
      tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
      sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
      sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

      Parece queisso poderia ser um pouco simplificado.

      Técnicas do MQL5 Wizard que você deve conhecer (Parte 51): Aprendizado por Reforço com SAC Técnicas do MQL5 Wizard que você deve conhecer (Parte 51): Aprendizado por Reforço com SAC
      Soft Actor Critic é um algoritmo de Aprendizado por Reforço que utiliza 3 redes neurais. Uma rede ator e 2 redes críticas. Esses modelos de aprendizado de máquina são combinados em uma parceria mestre-escravo onde as redes críticas são modeladas para melhorar a precisão de previsão da rede ator. Ao mesmo tempo em que introduzimos ONNX nesta série, exploramos como essas ideias podem ser colocadas à prova como um sinal personalizado de um Expert Advisor montado pelo wizard.
      Gerenciamento de riscos (Parte 1): Fundamentos da construção de uma classe de gerenciamento de riscos Gerenciamento de riscos (Parte 1): Fundamentos da construção de uma classe de gerenciamento de riscos
      Neste artigo, analisaremos os fundamentos do gerenciamento de riscos no trading e veremos como criar nossas primeiras funções para calcular o lote adequado para uma operação, assim como o stop loss. Além disso, examinaremos em detalhes como essas funções funcionam, explicando cada etapa. Nosso objetivo é fornecer uma compreensão clara de como aplicar esses conceitos na negociação automática. No final, aplicaremos tudo na prática, criando um script simples com o arquivo incluível que desenvolveremos.
      Desenvolvimento de um Kit de Ferramentas para Análise da Ação do Preço (Parte 6): Mean Reversion Signal Reaper Desenvolvimento de um Kit de Ferramentas para Análise da Ação do Preço (Parte 6): Mean Reversion Signal Reaper
      Embora alguns conceitos possam parecer simples à primeira vista, trazê-los à prática pode ser bastante desafiador. No artigo abaixo, levaremos você a uma jornada pela nossa abordagem inovadora para automatizar um Expert Advisor (EA) que analisa o mercado de forma eficiente utilizando uma estratégia de reversão à média. Junte-se a nós enquanto desvendamos as complexidades desse empolgante processo de automação.
      MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição
      Descubra como criar funções exportáveis em EX5 para consultar e salvar de forma eficiente dados históricos de posições. Neste guia passo a passo, ampliaremos a biblioteca EX5 de gerenciamento de histórico desenvolvendo módulos que recuperam propriedades-chave da posição fechada mais recentemente. Isso inclui lucro líquido, duração da negociação, stop loss em pips, take profit, valores de lucro e vários outros detalhes importantes.