English Русский Español Deutsch 日本語
preview
Desenvolvendo Sistemas de Trading ICT Avançados: Implementando Order Blocks em um Indicador

Desenvolvendo Sistemas de Trading ICT Avançados: Implementando Order Blocks em um Indicador

MetaTrader 5Exemplos |
25 18
Niquel Mendoza
Niquel Mendoza


1.0: Introdução 

Bem-vindo(a) e obrigado(a) por ler este artigo. Hoje você aprenderá a desenvolver um indicador baseado na teoria de order blocks dos Smart Money Concepts e do Inner Circle Trader.  

1.1: Fundamentos da Criação de Order Blocks 

Blocos de Ordem são zonas no gráfico onde ordens pendentes provavelmente estão aguardando para serem executadas.

Isso normalmente ocorre quando um grande participante do mercado, como uma instituição financeira, deseja entrar em uma posição significativa, mas não possui liquidez suficiente para executar toda a ordem de uma vez sem impactar o mercado. De acordo com as leis básicas de oferta e demanda, a execução de uma parte da ordem faz o preço subir (no caso de uma compra) em uma busca agressiva por vendedores que possam fornecer a liquidez necessária para completar a negociação.

Como o participante institucional não pode executar a ordem completa de uma só vez sem causar uma alteração substancial no preço, ele divide a ordem em partes menores. Isso permite que concluam a operação sem que o preço se mova significativamente antes de entrarem totalmente em sua posição.

A partir desse conceito, podemos identificar essas zonas em um gráfico de preços como áreas de forte desequilíbrio entre oferta e demanda (seja para compra ou para venda). A seguir, exploraremos três maneiras de identificar essas zonas e como implementá-las em código.

1.1.1: Lógica Baseada em Ação do Preço (Nível Básico) 


Antes de mergulharmos na lógica dos order blocks, vamos revisar rapidamente os componentes de um candlestick, o que é essencial para entender a mecânica.

Um candlestick é composto por quatro pontos de preço:                    

Price (Preço) Descrição
  High (Máxima) O preço máximo atingido durante o período do candlestick
  Low (Mínima) O preço mínimo atingido durante o período do candlestick
  Open (Abertura) O preço no qual o candlestick se abre
  Close (Fechamento) O preço no qual o candlestick se fecha   

Vamos ver um exemplo no gráfico para entender melhor:

          OHCL

Para começar, de acordo com a teoria dos order blocks, o primeiro elemento importante é identificar um desequilíbrio de mercado. Esse desequilíbrio muitas vezes é visível no gráfico como uma sequência de múltiplos candlesticks consecutivos na mesma direção, indicando uma tendência clara.

Exemplo: Tendência de alta com 4 candlesticks consecutivos

Neste caso, vamos focar em uma tendência de alta formada por quatro candlesticks de alta consecutivos, seguindo estas regras:      

Candlestick Descrição
Previous candlestick (Candlestick anterior) Este candlestick precede o movimento de alta de 4 candlesticks consecutivos. Ele normalmente fecha abaixo do nível inicial do movimento de alta.
First candlestick (Primeiro candlestick) Marca o início do movimento de alta. Seu fechamento está acima da abertura do candlestick anterior.
Segundo, Terceiro e Quarto candlesticks
Esses candlesticks continuam o impulso de alta, cada um fechando acima do fechamento do candlestick anterior.

Regras:
  • Condição de Movimento de Alta: Para qualificar como um movimento válido, os quatro candlesticks devem ser consecutivamente de alta. O primeiro candlestick inicia o desequilíbrio, e os seguintes o confirmam.
  • Identificação do Order Block: O order block é identificado na zona que abrange o candle anterior e o primeiro candle de alta, representando a área onde os compradores assumiram o controle.

Abaixo, podemos ver um exemplo gráfico mostrando quatro velas de alta consecutivas, indicando claramente um desequilíbrio de preço.

Exemplo Simples de OB

Regras para identificar Order Blocks baseados em candlesticks consecutivos:Aspecto

Aspecto Order Block de Alta Order Block de Baixa
Condição dos Candlesticks Os candlesticks 1, 2, 3 e 4 devem ser todos de alta. Cada um fecha acima da sua abertura. Os candlesticks 1, 2, 3 e 4 devem ser todos de baixa. Cada um fecha abaixo da sua abertura.
Validação do Extremo do Candlestick 2 A mínima do candlestick 2 deve estar acima do ponto médio do corpo do candlestick 1.
(Exceção: Martelo)
A máxima do candlestick 2 deve estar abaixo do ponto médio do corpo do candlestick 1.
(Exceção: Martelo invertido)
Condição do Corpo do Candlestick 2 Pelo menos 40% do corpo do candlestick 2 deve ultrapassar a máxima do candlestick 1. Pelo menos 40% do corpo do candlestick 2 deve estar abaixo da mínima do candlestick 1.
Validação do Extremo do Candlestick 3  A mínima do candlestick 3 deve estar acima de 25% do corpo do 2º candlestick. A máxima do candlestick 3 deve estar abaixo de 25% do corpo do 2º candlestick.
Condição do Corpo do Candlestick 3 Metade do corpo do 3º candlestick deve ultrapassar a máxima do candlestick 2. Metade do corpo do 3º candlestick deve estar abaixo da mínima do candlestick 2.

Propósito das regras:

Esses critérios são projetados para garantir que o padrão de 4 candlesticks seja forte o suficiente para validar um Order Block e confirmar que as ordens pendentes dentro da zona ainda não foram preenchidas.

1.1.2: Lógica Baseada em Ação do Preço e Indicadores (Nível Intermediário)

Nesta abordagem mais avançada, não apenas confiamos na ação do preço, mas também aplicamos indicadores para validar a força do movimento, especificamente, o indicador de volume.

Princípios da Estratégia

Como discutido anteriormente, movimentos significativos do mercado frequentemente começam com volume relativamente baixo, seguido por um aumento acentuado no volume uma vez que grandes ordens são executadas. Esse aumento de volume geralmente se estende por 2 ou 3 candlesticks, sinalizando a formação de um Order Block.

Podemos decompor essa lógica em dois cenários principais:

Caso 1: Order Block com Aumento de Volume

Nesse cenário, o Order Block é formado quando o volume começa a subir significativamente. As condições são as seguintes:

  1. Início do movimento: Começa por um candlestick de baixo volume que marca o início da acumulação.
  2. Aumento de volume: No candlestick seguinte, o volume aumenta significativamente, sinalizando a execução de ordens. Esse aumento pode persistir por 2 a 3 candlesticks consecutivos.
  3. Confirmação do Order Block: O order block é identificado na zona onde o volume começa a aumentar e presume-se que as ordens pendentes tenham sido preenchidas.

Exemplo de Alta: 

 Bullish Increasing Volume

Exemplo de Baixa:         

Bearish Increasing Volume

Caso 2: Order Block com um único pico de volume

Nesse caso, o Order Block é identificado quando se observa um pico significativo de volume em um candlestick chave, denominado primeiro candlestick. O padrão de order block é validado tanto pela ação do preço quanto pela análise de volume e consiste em 3 candlesticks consecutivos — de alta ou de baixa.

Regras:

Aspecto Order Block de Alta Order Block de Baixa
Pico de volume no candlestick 1
O candlestick 1 deve ter o maior volume entre os três e seu volume deve exceder tanto o do candlestick anterior quanto o do candlestick 2.
Candlestick 1 deve ter o maior volume entre os três e seu volume deve exceder tanto o do candlestick anterior quanto o do candlestick 2.
Verificando o Extremo do Candlestick 2
A mínima do candlestick 2 deve estar acima do ponto médio do corpo do candlestick 1, indicando que a zona de acumulação do Order Block permanece intocada.
(Exceção: se o candlestick 1 for um martelo)
A máxima do candlestick 2 deve estar abaixo do ponto médio do corpo do candlestick 1, indicando que a zona de acumulação do Order Block permanece intocada.
(Exceção: se o candlestick 1 for um martelo invertido)
Condição 60% do corpo do candlestick 2 60% do corpo do 2º candlestick deve se estender acima da máxima do candlestick 1, indicando continuidade do momentum de alta. 60% do corpo do 2º candlestick deve estar abaixo da mínima do candlestick 1, indicando continuidade do momentum de baixa.
Verificando o Extremo do Candlestick 3 A máxima do candlestick 3 deve ser maior que o preço de abertura do candlestick 2, confirmando continuidade do momentum de baixa. A mínima do candlestick 3 deve ser menor que o preço de abertura do candlestick 2, confirmando continuidade do momentum de alta.

Exemplo de Alta: 

Bullish Volume Peak

 Exemplo de Baixa:

Bearish Volume Peak


2.0: Desenvolvendo o Indicador de Order Blocks

2.1: Configurando Entradas e Parâmetros do Indicador


Finalmente, após cobrir uma quantidade substancial de teoria, vamos para a parte que muitos provavelmente aguardavam — codificar tudo o que aprendemos até aqui. 

1. Começamos criando um novo programa do tipo “Custom Indicator”:

2. Em seguida, escrevemos o nome do indicador e o nome do autor.

3. Depois selecionamos “OnCalculate()” para os cálculos subsequentes.

 4. Pressione Done para finalizar.

Nesta etapa, ainda não definiremos nenhum plot.

indicator_buffers 
indicator_plots 

Para evitar o erro: “no indicator plot defined for indicator00”

adicionamos o seguinte código placeholder no topo:

#property  indicator_buffers 1
#property  indicator_plots 1

Isso removerá o aviso, permitindo prosseguirmos com a configuração e a implementação da lógica.

Vamos configurar primeiro as entradas:

  • Cores para order blocks de alta e de baixa:

    Essas configurações permitem ao usuário escolher as cores que representarão visualmente os Order Blocks de alta e de baixa, facilitando distingui-los rapidamente.

  • Opções de personalização dos retângulos:

    Essas opções controlam como os retângulos usados para marcar Order Blocks são exibidos:

    • Espessura da borda: Define a espessura das bordas do retângulo.
    • Em segundo plano: Define se o retângulo aparece atrás ou sobre os candlesticks.
    • Selecionável: Permite habilitar ou desabilitar a capacidade de clicar e mover os retângulos no gráfico.
  • Intervalo de busca de Order Block:

    Esse parâmetro define quantos candles para trás a partir do candlestick atual o indicador buscará por Order Blocks válidos. Ajustá-lo permite adaptar o indicador a diferentes estratégias ou timeframes.

  • Organizando Entradas com Agrupamento:

    Inputs são parâmetros ajustáveis que o usuário pode modificar fora do programa. Para melhorar a legibilidade e a organização, usamos o seguinte conceito:     

    sinput

    Usar a palavra-chave acima nos permite organizar melhor os parâmetros, agrupando-os em categorias por meio da propriedade:

    group
  • Isso não apenas melhora a estrutura do código, mas também facilita ao usuário reconhecer os diferentes conjuntos de parâmetros relacionados a aspectos específicos do indicador, como configuração de retângulos ou análise de order blocks.

    sinput group "--- Order Block Indicator settings ---"
    input          int  Rango_universal_busqueda = 500; // Universal range for searching order blocks
    input          int  Witdth_order_block = 1;         // Width of the order block lines
    
    input          bool Back_order_block = true; // Enable object to be drawn in the background
    input          bool Fill_order_block = true; // Enable fill for the order block rectangle
    
    input          color Color_Order_Block_Bajista = clrRed;   // Assign red color for bearish order block
    input          color Color_Order_Block_Alcista = clrGreen; // Assign green color for bullish order block
    

    2.2: Criando Estruturas e Funções-Chave

    Nesta seção, definiremos as principais estruturas e funções para gerenciar order blocks no nosso indicador. Isso permitirá armazenar e organizar informações-chave de cada order block e gerenciar os dados de forma eficiente usando arranjos dinâmicos.

    1. Variável para armazenar o tempo do último candlestick

    Primeiro criaremos uma variável que armazenará o tempo do último candle processado. Isso é necessário para evitar duplicação de order blocks no mesmo candlestick e garantir o acompanhamento correto ao longo do tempo.

    datetime tiempo_ultima_vela;
    

    2. Handler para o indicador ATR:

    O segundo passo é criar um handler de indicador:

    ATR (Average True Range)
    

    Isso nos ajudará a medir a volatilidade do mercado e a complementar a lógica do indicador. Esse handler é inicializado logo no começo para que possa ser usado no cálculo dos order blocks.

    int atr_i;
    

    3. Criando uma estrutura para armazenar dados do order block

    Agora criaremos uma estrutura que armazenará os dados relevantes de cada order block. Essa estrutura é muito importante, pois contém informações sobre o tempo, os preços, o nome do bloco e se ele foi mitigado ou não. Além disso, criaremos um arranjo dinâmico que armazenará todos os order blocks detectados.

    struct OrderBlock
    {
       datetime time1;      // Time of the candle prior to the first candle of the order block
       double price1;       // Upper price level of the order block (level 1)
       double price2;       // Lower price level of the order block (level 2)
       string name;         // Name of the order block
       bool mitigated;      // Status of the order block (true if mitigated, false if not)
    };
    

    Descrição dos Campos da Estrutura

    A estrutura OrderBlock consiste nos seguintes campos:

    • time1: Armazena o tempo do candlestick que precede o primeiro candlestick do order block. É útil saber quando um bloco foi formado e comparar o tempo.

    • price1: Representa o preço de máxima ou o primeiro preço chave do order block. Será a máxima no caso de um order block de alta.

    • price2: Representa o preço de mínima ou o segundo preço chave do order block. Será a mínima no caso de um order block de alta.

    • name: Armazena um nome único para identificar order blocks no gráfico. Esse nome será usado para rotular claramente o bloco e torná-lo visualmente reconhecível.

    • mitigated: Indica se o order block foi mitigado ou não. Se o order block foi mitigado (isto é, o preço tocou ou excedeu os níveis do bloco), esse valor será verdadeiro; caso contrário, falso.

    4. Arranjo dinâmico para armazenar order blocks

    Por fim, criaremos um arranjo dinâmico contendo todos os order blocks identificados. Esses arranjos permitirão armazenar múltiplos blocos em memória e gerenciá-los dinamicamente, ativando ou desativando blocks conforme necessário ao longo do tempo.

    OrderBlocks ob_bajistas[];
    OrderBlocks ob_alcistas[];
    

    A Função OnInit()

    A função OnInit() é responsável por inicializar todos os elementos do indicador e realizar verificações para garantir que tudo esteja ok antes que o indicador comece a funcionar. Abaixo explicamos passo a passo o que está acontecendo no código.

    1. Inicializar Variáveis

    No início, define-se o valor inicial da variável:

    "tiempo_ultima_vela"

    Em 0. Essa variável é importante porque armazenará o tempo do último candlestick processado, o que evitará duplicações e permitirá gerenciar corretamente o fluxo do indicador.

    tiempo_ultima_vela = 0;

    Em seguida, o handler do indicador é iniciado:

    "ATR" (Average True Range)

    O período do ATR é de 14 candlesticks. O ATR será então usado para medir a volatilidade do mercado e contribuirá para a lógica dos order blocks.

    atr_i = iATR(_Symbol, PERIOD_CURRENT, 14);
    
    

    2. Verificando Parâmetros de Entrada

    Após a inicialização, o código verifica o valor da seguinte variável:

    Rango_universal_busqueda

    É menor que 40. Essa variável define o intervalo no qual o indicador buscará order blocks. Se esse intervalo for muito pequeno, pode afetar a precisão e a efetividade do indicador; portanto, uma mensagem de aviso é exibida e o indicador é interrompido, retornando o valor INIT_PARAMETERS_INCORRECT.

    if (Rango_universal_busqueda < 40)
    {
       Print("Search range too small");
       return (INIT_PARAMETERS_INCORRECT);
    }
    

    Essa verificação permite garantir que o intervalo de busca tenha um tamanho apropriado para o funcionamento correto do indicador e evitar configurações incorretas que possam afetar sua operação.

    3. Verificando a Inicialização do Indicador ATR

    O próximo passo é verificar se a inicialização do handler do indicador ATR está correta.

    Se: 

    "atr_i"

    For igual a:

    INVALID_HANDLE

    Isso significa que ocorreu um erro ao tentar criar o indicador; portanto, uma mensagem de erro é exibida e o seguinte é retornado:

    INIT_FAILED

    Isso encerra o indicador.

    if (atr_i == INVALID_HANDLE)
    {
       Print("Error copying data for indicators");
       return (INIT_FAILED);
    }
    

    4. Redimensionamento de Arrays Dinâmicos

    Os arrays dinâmicos que armazenam blocos de ordem de baixa e de alta são redimensionados conforme necessário, enquanto seu tamanho inicial é definido como 0. Isso garante que ambos os arrays estejam vazios no início do programa, prontos para armazenar novos blocos de ordem.

    ArrayResize(ob_bajistas, 0);
    ArrayResize(ob_alcistas, 0);
    

    O uso de arrays dinâmicos é muito importante para controlar o número de blocos de ordem detectados, pois permite que esses arrays cresçam ou diminuam conforme necessário.

    Inicialização Concluída com Sucesso

    Se todas as verificações e inicializações forem concluídas sem erros, então a função:

    OnInit()

    Retorna:

    INIT_SUCCEEDED

    Isso significa que o indicador está devidamente inicializado e pronto para começar a rodar no gráfico.

    return (INIT_SUCCEEDED);

    Código Completo

    int OnInit()
      {
    //--- indicator buffers mapping
       tiempo_ultima_vela = 0;
       atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
    
       if(Rango_universal_busqueda < 40)
         {
          Print("Search range too small");
          return (INIT_PARAMETERS_INCORRECT);
         }
    
       if(atr_i== INVALID_HANDLE)
         {
          Print("Error copying data of indicators");
          return(INIT_FAILED);
         }
    
       ArrayResize(ob_bajistas,0);
       ArrayResize(ob_alcistas,0);
    
    
    //---
       return(INIT_SUCCEEDED);
      }
    

    Agora vamos adicionar código ao evento de desinicialização do indicador para liberar memória:

    void OnDeinit(const int reason)
      {
    //---
      Eliminar_Objetos();
    
      ArrayFree(ob_bajistas);
      ArrayFree(ob_alcistas);
       
      }
    
    Eliminar_Objetos();

    Essa será a função que usaremos depois para remover quaisquer retângulos que criarmos.

    Verificação do Surgimento de uma Nova Vela

    O objetivo desse código é otimizar o indicador, garantindo que ele só seja executado quando uma nova vela se abrir, em vez de toda vez que o preço mudar. Rodar um indicador a cada mudança de preço desperdiçaria recursos do computador, especialmente se estivermos analisando vários ativos ou usando múltiplos indicadores.

    Para isso, é verificado o horário da última vela processada. Se uma nova vela for detectada, o processamento do indicador é ativado. Abaixo está uma descrição de cada parte do código.

    1. Inicialização</b0> 

    Temos uma variável do tipo bool: 

    "new_vela"

    Ela atua como um gatilho. O valor padrão é false, o que significa que nenhuma nova vela foi aberta.

    bool new_vela = false; // We assign the trigger that tells us whether a new candle has opened to false

    2. Verificação do surgimento de uma nova vela

    O próximo passo é verificar o horário da última vela processada:

    tiempo_ultima_vela

    Verifique se ele difere do horário da vela atual no gráfico. Função:

    iTime()

    Retorna o horário de abertura de uma vela específica, neste caso a vela mais recente (que é indexada como 0). Se o horário não corresponder, significa que uma nova vela surgiu.

    if(tiempo_ultima_vela != iTime(_Symbol, PERIOD_CURRENT, 0)) // Check if the current time is different from the stored time
    {
        new_vela = true;                                        // If it doesn't match, set the new candle indicator to true
        tiempo_ultima_vela = iTime(_Symbol, PERIOD_CURRENT, 0); // Update the last processed candle time
    }
    

    Esse bloco de código executa duas tarefas:

    1. Verifica se uma nova vela se formou.
    2. Atualiza a variável last_sail_time com o horário da nova vela para uso futuro.

    Execução do código principal

    Se a variável: 

    new_vela

    Se for verdade, isso significa que uma nova vela foi aberta. Neste caso, podemos executar o código do indicador principal que manipula blocos de ordens, ou qualquer outra lógica apropriada. Ao realizar essa verificação, evitamos executar o código em cada tick de preço, mas apenas quando uma nova vela aparece no gráfico.

    if(new_vela == true)
    {
       // Here we will place the main code of the indicator that will run only when a new candlestick opens
    }
    

    Criação de Arrays para Armazenar Dados de Velas, Volume e ATR

    Este bloco inclui arrays que armazenam informações-chave sobre velas, volume em ticks e ATR. Esses dados são necessários para análise do comportamento do preço e identificação de blocos de ordem.

    1. Declaração de Arrays

    Declaramos arrays dos tipos double, datetime e long para armazenar os valores correspondentes:

    double openArray[];  // Stores Open price of candlesticks
    double closeArray[]; // Stores Close price of candlesticks
    double highArray[];  // Stores High price of candlesticks
    double lowArray[];   // Stores Low price of candlesticks
    datetime Time[];     // Stores the time of each candlestick
    double atr[];        // Stores ATR values, which indicator market volatility
    long Volumen[];      // Stores tick volume, representing the number of transactions in each candlestick
    

    2. Configuração dos Arrays como Séries Temporais

    Usamos a função ArraySetAsSeries() para fazer os arrays atuarem como séries temporais. Isso significa que o índice 0 representará a vela mais recente, facilitando o acesso aos dados atuais:

    ArraySetAsSeries(openArray, true);
    ArraySetAsSeries(closeArray, true);
    ArraySetAsSeries(highArray, true);
    ArraySetAsSeries(lowArray, true);
    ArraySetAsSeries(Time, true);
    ArraySetAsSeries(Volumen, true);
    ArraySetAsSeries(atr, true);
    

    3. Copiando Dados de Velas e ATR

    Em seguida, usamos as funções CopyOpen, CopyClose, CopyHigh, CopyLow, CopyTime e CopyTickVolume para copiar os dados de velas e volume em ticks para os arrays desejados. Também usamos CopyBuffer para obter valores de ATR:

    int copiedBars = CopyOpen(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), openArray);
    if(copiedBars < 0)
    {
        Print("Error copying data from Open: ", GetLastError());
    }
    CopyClose(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), closeArray);
    CopyHigh(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), highArray);
    CopyLow(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), lowArray);
    CopyTime(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Time);
    CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Volumen);
    CopyBuffer(atr_i, 0, 0, (Rango_universal_busqueda * 2), atr);
    

    4. Tratamento de Erros:

    Ao copiar os dados de abertura, verificamos se o número de barras copiadas é negativo, o que indica um erro. Nesse caso, para auxiliar no debug, uma mensagem de erro é exibida usando a função GetLastError().

    Preparação para Codificação da Lógica de Detecção de Blocos de Ordem

    Antes de implementar a lógica de detecção de blocos de ordem, implementaremos algumas preparações necessárias:

    1. Detecção de Velas de Alta Anteriores: Criaremos uma função que identifica se existem velas de alta anteriores à primeira vela do padrão. Se encontradas, atribuiremos o valor dessa primeira vela à mais próxima, permitindo desenhar o Bloco de Ordem desde o início do movimento.

    2. Desenho de Retângulos: Implementamos uma função especial para desenhar retângulos e representar visualmente os blocos de ordem no gráfico.

    3. Gerenciamento de Arrays: Desenvolveremos funções para adicionar os blocos de ordem detectados nos arrays correspondentes. Isso inclui:

      • Verificação de Duplicação: Uma função que garante que o bloco de ordem que estamos tentando adicionar não tenha sido registrado antes. Assim, apenas novos blocos de ordem serão adicionados.
    4. Mitigação de Bloco de Ordem: Criaremos uma função que verifica se um Bloco de Ordem foi mitigado.

    5. Remoção de Bloco de Ordem: Adicionaremos uma função para marcar blocos de ordem como removidos, mantendo nossas notificações organizadas e limpas.

    Com essas funções, podemos começar a adicionar blocos de ordem nos arrays e garantir que apenas novos blocos sejam registrados. A partir deste ponto, não forneceremos explicações linha por linha devido à quantidade de código, mas sim uma breve descrição de cada seção relevante.

    1. Função:

    //+------------------------------------------------------------------+
    //|     Functions to Manage and Add Values to the Arrays             |
    //+------------------------------------------------------------------+
    void AddIndexToArray_alcistas(OrderBlocks &newVela_Order_block_alcista)
      {
       if(!IsDuplicateOrderBlock_alcista(newVela_Order_block_alcista))  // Here we check if the structure we are about to add already exists in the array
         {
          int num_orderblocks_alcista = ArraySize(ob_alcistas);         // We assign the variable "num_orderblocks_alcista" the current size of the ob_alcistas array
          ArrayResize(ob_alcistas, num_orderblocks_alcista + 1);        // Resize the array by increasing its size by 1 to make space for a new order block
          ob_alcistas[num_orderblocks_alcista] = newVela_Order_block_alcista; // Assign the new order block to the new index (last position) in the array
         }
      }
    
    bool IsDuplicateOrderBlock_alcista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_alcistas); i++) //Start a loop to go through all positions of the ob_alcistas array
         {
          if(ob_alcistas[i].time1 == newBlock.time1 &&
             ob_alcistas[i].name == newBlock.name
            ) // Check if both time1 and name of the order block already exist in the array
            {
             return true; // If they do, return true (i.e., it is a duplicate)
             break        // Exit the loop
            }
         }
       return false; // If no duplicate is found, return false
      }
    
    // This would be the same logic but for bearish order blocks
    void AddIndexToArray_bajistas(OrderBlocks &newVela_Order_block_bajista)
      {
       if(!IsDuplicateOrderBlock_bajista(newVela_Order_block_bajista))
         {
          int num_orderblocks_bajistas = ArraySize(ob_bajistas);
          ArrayResize(ob_bajistas, num_orderblocks_bajistas + 1);
          ob_bajistas[num_orderblocks_bajistas] = newVela_Order_block_bajista;
         }
      }
    
    bool IsDuplicateOrderBlock_bajista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_bajistas); i++)
         {
          if(ob_bajistas[i].time1 == newBlock.time1 &&
             ob_bajistas[i].name == newBlock.name
            )
            {
             return true; // Duplicate found
             break;
            }
         }
       return false;      // No duplicate found
      }
    

    Agora que implementamos as funções que nos ajudam a verificar se o bloco de ordem não é duplicado e adicioná-lo ao array dinâmico, é hora de explicar como desenharemos esses blocos de ordem no gráfico.

    Para isso, usaremos dois pontos de preços principais:

    • Preço 1: No caso de um bloco de ordem de alta, esse preço representará a base do retângulo. Para um bloco de ordem de baixa, representará o lado superior paralelo.

    • Preço 2: Em um bloco de ordem de alta, esse preço corresponderá ao lado superior paralelo do retângulo. No bloco de ordem de baixa, representará a base do retângulo.

    Exemplo de Alta:

         Bullish OB

     Exemplo de Baixa:

          Order Block de Baixa

    Agora vamos passar a demonstrar funções de mitigação em blocos de ordem.

    //+------------------------------------------------------------------+
    //|             Functions for Order Blocks                           |
    //+------------------------------------------------------------------+
    datetime  mitigados_alcsitas(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start); // Using iBarShift we find the index of the candle by passing the "start" time
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);   // Using iBarShift we find the index of the candle by passing the "end" time
       NormalizeDouble(price,_Digits); // Normalize the price we will work with
    
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--) // Start a loop from start (time1 of the order block) to end (time[1])
         {
          //terminated by endIndex which will be time[0] + 1 = time[1]  --> We are searching for mitigation from past to present (backward)
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          //Normalizamos todas laas variable
    
          if(price > lowArray[i] || price > openArray[i] || price > closeArray[i] || price > highArray[i]) // Check if OHLC closed below price
            {
             return Time[i]; //If mitigation is found, return the time of the candle where it happened
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
            }
         }
    
       return 0; //If no mitigation was found, return 0
      }
    
    // the same in the bearish case but changing something
    // instead of the price closing below the price
    datetime  mitigado_bajista(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
    
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start);
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(price,_Digits);
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--)
         {
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          if(highArray[i] > price || closeArray[i] > price || openArray[i] > price || lowArray[i] > price)
            {
    
             return Time[i]; // returns the time of the candle found
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
    
            }
         }
    
       return 0; // not mitigated so far
      }
    
    datetime esOb_mitigado_array_alcista(OrderBlocks &newblock, datetime end)
      {
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i <  endIndex -2  ; i++)
         {
    
          double low = NormalizeDouble(iLow(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
          if((newblock.price2 >= low || newblock.price2 >= open) || newblock.price2 >= close)
            {
             newblock.mitigated = true;
    
    
             return iTime(_Symbol,PERIOD_CURRENT,i); // returns the time of the found candle
            }
         }
    
       return 0; // not mitigated so far
      }
    
    datetime esOb_mitigado_array_bajista(OrderBlocks &newblock, datetime end)
      {
    
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i<  endIndex -2  ; i++)
         {
    
          double high = NormalizeDouble(iHigh(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
          if((high >= newblock.price2 || close >= newblock.price2) || open >= newblock.price2)
            {
             newblock.mitigated = true;
             // returns the time of the found candlestick
             return iTime(_Symbol,PERIOD_CURRENT,i);
            }
         }
    
       return 0; // not mitigated so far
      }
    
    

    Essas funções são responsáveis por verificar o estado dos blocos de ordem:

    • Função de Verificação de Mitigação: Essa função verifica a mitigação de blocos de ordem e é usada para avaliar a estrutura adicionada.

    • Função de Ativação de Mitigação: A segunda função, que inclui a palavra-chave "array", ativa o status de mitigação para os blocos de ordem.

    Em seguida, veremos funções para desenhar retângulos e encontrar a vela de alta ou de baixa mais próxima.

    A função de determinar a vela de alta ou de baixa mais próxima é muito importante. Seu objetivo é garantir a detecção correta da vela correspondente ao detectar blocos de ordem, especialmente em situações onde o bloco de ordem é formado no início de um movimento forte. Isso evitará detecções falsas no meio ou no final de um movimento, o que pode reduzir a eficácia da análise.

    Exemplo de Alta:

    Função de Exemplo OB+

    Aqui estão as funções:

    //+------------------------------------------------------------------+
    //|  Functions to find the nearest bullish or bearish candle         |
    //+------------------------------------------------------------------+
    int FindFurthestAlcista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Function to find the furthest bullish candle in a consecutive sequence
    // Initialize variables
       int furthestVela = 0;
       int counter_seguidas = 0;
    
       for(int i = startVela  + 1; i <= startVela + numVelas ; i++)  // Since the candle at "start" is already known to be bullish, we skip it (+1)
         {
          //then it is obvious that the candle at time 1 is bullish (in this function), that's why we increase +1, then we check that i is less than or equal to the index of startVela + num candles
          //here num candles would be the number of candles to search for
          double Close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits); //we get the open by index
          double Open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);  //we get the close by index
    
          if(Close > Open || Close == Open)  // we check if it's a bullish candle (close > open), that is, the close must be greater than the open
            {
             counter_seguidas++; // if this is true, we increase a variable by 1, which we will use later
            }
          else
             if(Open > Close)
               {
                furthestVela = startVela  + 1 + counter_seguidas; //if the found candle is not bullish, it is obviously bearish, therefore we assign the index of the previous candle to the one that started the bullish move
                // startVela: is the candle we passed, then we add 1 because as we said before, we will draw the order block one candle before the bullish move (i.e., bearish candle)
                // to this we add the counter of consecutive candles
    
                break; // exit the loop
               }
         }
    
    //we check if the body of the candle before the move is more than 30% larger than the candle that starts the move; if it is, we revert to the normal value
       double body1 = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits) - NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits);;
       double body_furtles =  NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,furthestVela),_Digits) -  NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,furthestVela),_Digits);
    
       if(body_furtles > (1.3 * body1))
          furthestVela--;
    
       return furthestVela; // return the index of the found candle
      }
    
    // Function to search for the furthest bearish candle with consecutive bearish candles
    int FindFurthestBajista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Index of the initial candle
       int furthestVela = 0;     // Initialize variable
       int counter_seguidas = 0; // Counter of consecutive bearish candles
    
       for(int i = startVela + 1; i <= startVela + numVelas; i++)
         {
          double Close = NormalizeDouble(iClose(_Symbol, PERIOD_CURRENT, i), _Digits);
          double Open = NormalizeDouble(iOpen(_Symbol, PERIOD_CURRENT, i), _Digits);
    
          // If the candle is bearish
          if(Close < Open || Close == Open)
            {
             counter_seguidas++; // Increase the counter of consecutive bearish candles
            }
          // If the candle is bullish, we stop
          else
             if(Close > Open)
               {
                // Return the candle where the bearish sequence is interrupted by a bullish one
                furthestVela = startVela + 1 + counter_seguidas;
                break;
               }
         }
    
       return furthestVela;
      }
    

    Agora só precisamos criar uma função para desenhar retângulos:

     void RectangleCreate(long chart_ID, string name, const int sub_window, datetime time1, double price1, datetime time2, double price2, color clr, int width, bool fill, bool back , ENUM_LINE_STYLE style , bool select = false)
      {
       ResetLastError(); // reset the error
    
     // check and create rectangles
       if(!ObjectCreate(chart_ID, name, OBJ_RECTANGLE, sub_window, time1, price1, time2, price2))
         {
          Print(__FUNCTION__, ": Failed to create a rectangle! Error code = ", GetLastError()  , "The name of the rectangle is: " , name); //if creation fails, print the function + error code and rectangle name
         }
    
      // set the properties of the rectangles
       ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
       ObjectSetInteger(chart_ID, name, OBJPROP_STYLE, STYLE_SOLID);
       ObjectSetInteger(chart_ID, name, OBJPROP_WIDTH, width);
       ObjectSetInteger(chart_ID, name, OBJPROP_FILL, fill);
       ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, select);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, select);
       ObjectSetInteger(Chart_ID, name, OBJPROP_STYLE ,style);  
      }
    

    Uma vez que todas essas funções estejam prontas, passaremos para a próxima parte.

    2.3: Lógica de detecção de blocos de ordens


    A lógica que desenvolvemos para este sistema é a seguinte:

    1. Detectar blocos de ordem usando a lógica descrita acima.
    2. Atribuir valores às estruturas. 
    3. Adicionar a estrutura ao array que armazenará os blocos de ordem.
    4. Verificar mitigação dos blocos de ordem.
    5. Desenhar blocos de ordem.
    6. Alertar. 
    Com base nisso, podemos começar a programar o indicador.

    Primeiro, criamos estruturas para armazenar os valores dos blocos de ordem:

    • Criamos 4 variáveis que terão a forma da estrutura OrderBlocks.
    • 2 para salvar blocos de ordem de alta (blocos de ordem do indicador e de Price Action).
    • 2 para salvar blocos de ordem de baixa (blocos de ordem do indicador e de Price Action).

    OrderBlocks  newVela_Order_block_alcista;
    OrderBlocks  newVela_Order_block_volumen;
    OrderBlocks newVela_Order_Block_bajista;
    OrderBlocks newVela_Order_Block_bajista_2;
    

    Depois de adicionar essas estruturas, já temos variáveis nas quais os valores dos blocos de ordem serão armazenados.

    Agora só precisamos usar a lógica para detectar blocos de ordem. Então, vamos começar.

    Começaremos com a lógica:

    • Precisamos procurar blocos de ordem no intervalo de velas e atribuir-lhes um índice, o que é semelhante a procurar um padrão de velas com as condições que definimos na lógica.
    • Para isso, usaremos o loop 'for'.

    Aqui está o código:

    for(int i = Rango_universal_busqueda  ; i  > 5  ; i--)
       {
    
    //checking errors
    if(i + 3> ArraySize(highArray)  || i + 3 > ArraySize(atr))
    continue;
    if(i < 0)
    continue;
    
    //--------Variable Declaration--------------------------------------------//
    
    // Update candle indices
    int one_vela = i ; // central candlestick
    int  vela_atras_two = i +2;
    int vela_atras_one = one_vela +1;
    int two_vela = one_vela - 1;
    int tree_vela = one_vela - 2;
    int four_vela = one_vela -3;
    
    NormalizeDouble(highArray[vela_atras_one],_Digits);
    NormalizeDouble(lowArray[vela_atras_one ], _Digits);
    NormalizeDouble(closeArray[vela_atras_one ],_Digits);
    NormalizeDouble(openArray[vela_atras_one ],_Digits);
    
    NormalizeDouble(highArray[two_vela],_Digits);
    NormalizeDouble(lowArray[two_vela], _Digits);
    NormalizeDouble(closeArray[two_vela],_Digits);
    NormalizeDouble(openArray[two_vela],_Digits);
    
    NormalizeDouble(highArray[tree_vela],_Digits);
    NormalizeDouble(lowArray[tree_vela], _Digits);
    NormalizeDouble(closeArray[tree_vela],_Digits);
    NormalizeDouble(openArray[tree_vela],_Digits);
    
    NormalizeDouble(highArray[one_vela],_Digits);
    NormalizeDouble(lowArray[one_vela], _Digits);
    NormalizeDouble(closeArray[one_vela],_Digits);
    NormalizeDouble(openArray[one_vela],_Digits);
    
    // Calculate average body size of previous candles
    double body1 = closeArray[one_vela] - openArray[one_vela];
    double body2 = closeArray[two_vela] - openArray[two_vela];
    double body3 = closeArray[tree_vela] - openArray[two_vela];
    
    // Volume condition
    long Volumen_one_vela = Volumen[one_vela];
    long Volumen_two_vela = Volumen[two_vela];
    long volumen_vela_atras_one = Volumen[vela_atras_one];
    

    • Basicamente, neste código estamos criando um loop que se moverá do valor máximo da vela, que será:

    (Rango_universal_busqueda)

    e termina no índice 6:

    i  > 5

    No caso i > 5 

    Subtraímos 1 do valor de i.

    • Normalizamos massivamente o OHCL das velas com as quais trabalharemos.
    • Atribuímos o corpo da vela como: close - open.
    • Obtemos o valor do tick, apenas para o caso de pico:

    //Volume 
    long Volumen_one_vela = Volumen[one_vela];
    long Volumen_two_vela = Volumen[two_vela];
    long volumen_vela_atras_one = Volumen[vela_atras_one];
    

    Agora adicionaremos mais um caso, que é o atr, o qual basicamente analisamos para ver movimentos fortes de preço em uma direção.  

    • Nesse trecho de código, mostrado abaixo, é possível ver as condições lógicas básicas e o indicador atr:
      //Boolean variables to detect if the case is met (only Price Action)
      bool esVelaCorrecta_case_normal =false;
      bool  esVela_Martillo = false;
      
      //Here we check that 4 consecutive bullish candles have formed with close > open
      if(
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
      
         esVelaCorrecta_case_normal =true; // if true, assign true to "esVelaCorrecta_case_normal"
        }
      else
         esVelaCorrecta_case_normal =false; // otherwise assign false
      
      bool fuerte_movimiento_alcista =false; // create a variable that activates only if a strong bullish movement occurs
      
      // Check if a movement of 6 consecutive bullish candles was created
      if(
         closeArray[one_vela + 2] > openArray[one_vela + 2] &&
         closeArray[one_vela + 1] > openArray[one_vela +1] &&
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
         fuerte_movimiento_alcista = true; // if true assign true to "fuerte_movimiento_alcista"
        }
      
      //verificamos si es vela martillo
      if(openArray[one_vela] - lowArray[one_vela] > closeArray[one_vela] - openArray[one_vela]) // check if lower wick is larger than the candle body
        {
         esVela_Martillo = true; // if so set "esVela_Martillo" to true
        }
      
      bool atr_case = false;
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && closeArray[one_vela] > openArray[one_vela]
         && closeArray[four_vela] > openArray[four_vela] && closeArray[tree_vela] > openArray[tree_vela])
         atr_case = true;  // in this code we look for ATR to first fall in one candle
      //then rise, and candles 1, 3, 4 must be bullish; second candle not necessary for this case
      
      //Verification for normal case
      if((esVelaCorrecta_case_normal == true && ((lowArray[two_vela] > ((body1 *0.5)+openArray[one_vela]) && ((body2 * 0.4)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && lowArray[tree_vela] > ((body2 * 0.25) +openArray[two_vela])) || fuerte_movimiento_alcista == true || atr_case == true)  
        {
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20); // call function to find previous bullish candles before "one_vela"
         if(furthestAlcista > 0) // whether or not found, will be > 0 since it returns previous candle index if none found
           {
      
            datetime time1 = Time[furthestAlcista];     //assign time of furthestAlcista candle to time1
            double price2 = openArray[furthestAlcista]; //assign open of furthestAlcista as price2 (usually drawn on a bearish candle)
            double price1 = lowArray[furthestAlcista];  //assign low of furthestAlcista as price1
      
            //assign mentioned variables to the structure
            newVela_Order_block_alcista.price1 = price1;
            newVela_Order_block_alcista.time1 = time1;
            newVela_Order_block_alcista.price2 = price2;
      
            case_OrderBlockAlcista_normal = true; //if all true, activate normal bullish case
           }
         else
            case_OrderBlockAlcista_normal =false;
      
        }
      //versión bajista
      
      bool case_OrderBlockBajista_normal = false;
      bool case_OrderBlockBajista_volumen = false;
      
      //---------------Conditions for Order Blocks--------------------//
      //+------------------------------------------------------------------+
      //| Conditions For Bearish Order Block case_normal                   |
      //+------------------------------------------------------------------+
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela]  &&
         closeArray[tree_vela] < openArray[tree_vela]  &&
         closeArray[one_vela-3]< openArray[one_vela-3]
        )
        {
         esVelaCorrecta_case_normal =true;
        }
      else
         esVelaCorrecta_case_normal =false;
      
      bool a = false;
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && esVelaCorrecta_case_normal)
         a= true;
      
      bool fuerte_movimiento_bajista =false;
      
      if(
         closeArray[one_vela + 2] < openArray[one_vela + 2] &&
         closeArray[one_vela + 1] < openArray[one_vela +1] &&
         closeArray[one_vela] < openArray[one_vela] &&
         closeArray[two_vela] < openArray[two_vela] &&
         closeArray[tree_vela] < openArray[tree_vela] &&
         closeArray[one_vela - 3] <= openArray[one_vela - 3]
      )
        {
         fuerte_movimiento_bajista = true;
        }
      
      // Verification for normal bearish case
      if((esVelaCorrecta_case_normal == true && highArray[two_vela] < ((body1 *0.70)+closeArray[one_vela]) && ((body2 * 0.4)+closeArray[two_vela]) < lowArray[one_vela] && highArray[tree_vela] < highArray[two_vela])
         || a == true || fuerte_movimiento_bajista == true
        )
        {
         int furthestBajista = FindFurthestBajista(Time[one_vela], 20);
         if(furthestBajista != -1)
           {
      
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista.price1 = price1;
            newVela_Order_Block_bajista.time1 = time1;
            newVela_Order_Block_bajista.price2 = price2 ;
      
           }
         else
            case_OrderBlockBajista_normal =false;
        }
      //+------------------------------------------------------------------+
      

      Vamos falar sobre cada função com os comentários adequados. Portanto, buscamos identificar certos padrões e, quando os encontramos, ativamos as variáveis Booleanas.

      Em seguida, verificamos a existência de velas de alta antes de one_candle, que é a vela na qual o movimento começa.

      Por fim, atribuímos valores de preço e tempo aos blocos de ordem.

      Agora passamos para o caso do volume, onde analisaremos tanto picos de volume quanto aumento de volume.

      //condition orderblock volume --------------------------------//
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true; //here we check the volume peak
        }
      else
         VolumenCorrecto = false;
      
      //so that the bullish candle behind is bearish and 2 bullish
      if(closeArray[one_vela] > openArray[one_vela]  &&
         closeArray[two_vela] > openArray[two_vela])
        {
         VelaCorrecta_casevolumen = true;
        }
      
      //consecutive case
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] < closeArray[tree_vela] && openArray[four_vela] < closeArray[four_vela])
         case_vol_2 = true;
      
      //here we verify that the highlights do not mitigate the order block
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true
          && ((lowArray[two_vela] > ((body1 * 0.5)+openArray[one_vela]) && ((body2 *0.6)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && highArray[tree_vela] > openArray[two_vela]) || case_vol_2 == true)
        {
      //I already explained all this above, it is literally the same, we look for the closest bullish trend and assign a value to the one before it
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20);
         if(furthestAlcista > 0)
           {
      
            datetime time1 = Time[furthestAlcista];
            double price2 = openArray[furthestAlcista];
            double price1 = lowArray[furthestAlcista];
      
            newVela_Order_block_volumen.price1 = price1;
            newVela_Order_block_volumen.time1 = time1;
            newVela_Order_block_volumen.price2 = price2;
      
            case_orderblock_vol= true;
           }
         else
            case_orderblock_vol =false;
      
        }
      //Bearish version
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //| Condition for Bullish Order Block Case case_Volumen              |
      //+------------------------------------------------------------------+
      
      bool VelaCorrecta_casevolumen = false;
      bool VolumenCorrecto;
      //condition orderblock volume --------------------------------//
      //by peak volume
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true;
        }
      else
         VolumenCorrecto = false;
      //we look here for 2 consecutive bearish candles
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela])
        {
      
         VelaCorrecta_casevolumen = true; //we set the variable "VelaCorrecta_casevolumen" to true
        }
      //we look for an increasing volume in addition to the 3rd candle and 4th candle being bearish
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] > closeArray[tree_vela] && openArray[four_vela] > closeArray[four_vela])
         case_vol_2 = true;
      
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true && highArray[two_vela] < ((body1 * 0.5)+closeArray[one_vela]) && ((body2 *0.5)+closeArray[two_vela]) < lowArray[one_vela]) || case_vol_2 == true)   // verificamos si se cumple
        {
      // the peak volume case or increasing volume case
         int furthestBajista = FindFurthestBajista(Time[one_vela],20); //we look for the bearish candle closest to the 1st candle
      
         if(furthestBajista > 0)
           {
            //if this is true, which as I said before it will always be, we assign the candle values
            //to the structure variables to draw the rectangles
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista_2.price1 = price1;
            newVela_Order_Block_bajista_2.time1 = time1;
            newVela_Order_Block_bajista_2.price2 = price2 ;
      
            case_OrderBlockBajista_volumen = true;
           }
         else
            case_OrderBlockBajista_volumen = false;
        }
      //+------------------------------------------------------------------+
      
      

      Agora que implementamos a detecção de blocos de ordem, precisamos adicioná-los ao array para que mais tarde possam ser exibidos no gráfico.

      Neste código, realizaremos as seguintes ações:

      1. Inicializamos a variável 'mitigated' como falsa.
      2. Definimos um nome para um bloco de ordem com base em seu tipo e definimos o horário de acordo com one_sail.
      3. Finalmente, adicionamos o bloco de ordem a um array dinâmico.
      if(case_OrderBlockAlcista_normal == true
         && mitigados_alcsitas(newVela_Order_block_alcista.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_alcista.time1,Time[0]) == 0)  //we verify that the order block has not been mitigated
        {
         newVela_Order_block_alcista.mitigated = false; //we activate the order block status as unmitigated = false
         newVela_Order_block_alcista.name =  "Order Block Alcista normal" + TimeToString(newVela_Order_block_alcista.time1) ;  //we assign the name "Normal Bullish Order Block" + the time of one_Vela
         AddIndexToArray_alcistas(newVela_Order_block_alcista); //we add to the array to then check if they are being mitigated and draw them
      
        }
      //the same would be for the volume case
      if(case_orderblock_vol == true
         && mitigados_alcsitas(newVela_Order_block_volumen.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_volumen.time1,Time[0]) == 0)
        {
         newVela_Order_block_volumen.mitigated = false;
         newVela_Order_block_volumen.name =  "Order Block Alcista vol" + TimeToString(newVela_Order_block_volumen.time1) ;
         AddIndexToArray_alcistas(newVela_Order_block_volumen);
      
        }
      
        } 
      
      //--- Bearish version
      
      if(case_OrderBlockBajista_normal == true  && mitigado_bajista(newVela_Order_Block_bajista.price2,openArray, closeArray, highArray, lowArray, Time, Time[0],newVela_Order_Block_bajista.time1) == 0
        ) //we check if the bearish order block was not mitigated and the normal case is true
        {
         newVela_Order_Block_bajista.mitigated = false; //we initialize the state of the order block as unmitigated = false
         newVela_Order_Block_bajista.name = ("Order Block Bajista ")+ TimeToString(newVela_Order_Block_bajista.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
         AddIndexToArray_bajistas(newVela_Order_Block_bajista); //we add the structure to the array
        }
      
      if(case_OrderBlockBajista_volumen == true   && mitigado_bajista(newVela_Order_Block_bajista_2.price2, openArray,closeArray,highArray,lowArray,Time,Time[0],newVela_Order_Block_bajista_2.time1)== 0
        )//we check if the bearish order block was not mitigated and the volume case is true
        {
         newVela_Order_Block_bajista_2.mitigated = false; //we initialize the state of the order block as unmitigated = false
         newVela_Order_Block_bajista_2.name = ("Order Block Bajista ") + TimeToString(newVela_Order_Block_bajista_2.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
         AddIndexToArray_bajistas(newVela_Order_Block_bajista_2); //we add the structure to the array
      
        }
        } 
      //+------------------------------------------------------------------+
      

      Agora podemos passar para o desenho e a verificação da mitigação.

      2.4: Visualização: Cores e Verificação da Mitigação de Blocos de Ordem


      Nesta seção, veremos como atualizar blocos de ordem, desenhá-los e ativar seu estado mitigado.

      •  Nós os desenharemos iterando sobre os arrays "ob_bullish" e "ob_bearish", como já mencionamos, que armazenam informações sobre blocos de ordem.
      •  Moveremos os objetos usando "ObjectMove", pois não queremos redesenhar tudo de novo, já que isso reduz a eficiência do programa e também consome mais recursos do computador.

      Agora que fizemos tudo isso, vejamos o código que preparei para atender a esses requisitos:

      for(int i = 0; i < ArraySize(ob_alcistas); i++) //We iterate through all the indexes of the array where the order blocks information is stored
        {
         datetime mitigadoTime = esOb_mitigado_array_alcista(ob_alcistas[i],ob_alcistas[i].time1); //we call the function that will tell us if index i has been mitigated or not. If it is, we activate its state to true
      
         if(ob_alcistas[i].mitigated == false)  //we verify that it has not been mitigated
           {
            if(mitigadoTime == 0) //We condition that the order block has not been touched by the price
              {
      
               if(ObjectFind(ChartID(),ob_alcistas[i].name) < 0) //we check if the object exists in the graph with ObjectFind
                 {
                  RectangleCreate(ChartID(), ob_alcistas[i].name, 0, ob_alcistas[i].time1, ob_alcistas[i].price1,
                                  Time[0], ob_alcistas[i].price2,Color_Order_Block_Alcista, Witdth_order_block, Fill_order_block, Back_order_block,STYLE_SOLID); //  we create the rectangle with the data
      
                 }
               else
                  ObjectMove(ChartID(),ob_alcistas[i].name,1,Time[0],ob_alcistas[i].price2);     //on the contrary, if the object exists, the only thing we will do is update it to the current time using anchor point 1
              }
           }
        }
      
      // Draw all order blocks from the orderBlocks array
      for(int i = 0; i < ArraySize(ob_bajistas); i++)
        {
         datetime mitigadoTime = esOb_mitigado_array_bajista(ob_bajistas[i],ob_bajistas[i].time1);
      
         if(ob_bajistas[i].mitigated == false)
           {
      
            if(mitigadoTime == 0)
              {
      
               if(ObjectFind(ChartID(),ob_bajistas[i].name) < 0)
                 {
                  RectangleCreate(ChartID(), ob_bajistas[i].name,0, ob_bajistas[i].time1, ob_bajistas[i].price1,
                                  Time[0], ob_bajistas[i].price2,Color_Order_Block_Bajista,Witdth_order_block,Fill_order_block,Back_order_block,STYLE_SOLID);
      
                 }
               else
                  ObjectMove(ChartID(),ob_bajistas[i].name,1,Time[0],ob_bajistas[i].price2);
      
              }
           }
      
        }
      //+------------------------------------------------------------------+
      
      • A detecção de blocos de ordem é realizada dentro do loop for que, por sua vez, é executado com a seguinte condição:
      new_vela == true
      • Um retângulo é desenhado fora do loop, mas também com a condição:
      new_vela == true

      2.5: Implementando Alertas para Mitigação de Blocos de Ordem e Remoção de Objetos


      Nesta seção, veremos como implementar alertas quando blocos de ordem são mitigados e criar a função que mencionamos no início:

      Eliminar_Objetos()

      Vamos começar pela definição da lógica:

      1. Precisaremos chamar as seguintes funções:

      esOb_mitigado_array_bajista  

      esOb_mitigado_array_alcista

      Quando detectamos que o bloco de ordem foi mitigado, retornamos o horário da vela de mitigação e também definimos o status do bloco de ordem como true, o que equivale a dizer que o bloco foi mitigado.

      Portanto, para saber se um bloco de ordem foi mitigado ou não, usaremos seu estado:

      mitigated

      Agora, olhando para a estrutura do bloco de ordem, vemos que ele possui preço, tempo, estado e nome:

      struct OrderBlocks
      {
       datetime time1;
       double price1;
       double price2;
       string name;
       bool mitigated;
       
      };
      

       Dessa estrutura, estamos particularmente interessados em 2 variáveis para os alertas:

       string name;
       bool mitigated;
      

      • mitigated: Essa variável booleana nos permite saber se o bloco de ordem foi mitigado.
      • name: Usando esse parâmetro, verificamos se o bloco de ordem mitigado já havia sido mitigado anteriormente.
      Lembre-se de que, uma vez que "mitigated" é ativado, o bloco de ordem será sempre considerado mitigado. Por isso precisamos de um filtro para evitar que o indicador exiba alertas repetidamente: esse filtro será o nome do bloco de ordem.

      Ainda precisamos de:

      • Dois arrays; esses arrays serão do tipo string e também dinâmicos, já que atualizaremos seu tamanho conforme mais blocos de ordem forem mitigados.
      • Uma função para adicionar entradas aos arrays de string.
      • Uma função para verificar se a string que passamos como nome do bloco de ordem já existe no array.

      Certo, já integrei as funções e arrays que estavam faltando:

      • Vamos para a seção global do programa, onde escrevemos:

      string pricetwo_eliminados_oba[];
      string pricetwo_eliminados_obb[];
      

      Estes serão os arrays de que precisamos.

      Depois criaremos as seguintes funções:

        bool Es_Eliminado_PriceTwo(string pName_ob , string &pArray_price_two_eliminados[])
        {
         bool a = false; // we create the variable "a" and initialize it to false
           for(int i = 0 ; i < ArraySize(pArray_price_two_eliminados) ; i++) // we traverse all the indices of the array passed as a reference
           {
            if(pName_ob == pArray_price_two_eliminados[i]) // we will compare all the positions in the array with the variable "pName_ob"
            { // if the comparison is identical the variable "a" becomes true
             a = true; 
              break; // we exit the loop
            } 
           } 
        return a; //we return the value of "a"
       }   
       
       //function to add values and assign a new size to the array passed by reference 
       void Agregar_Index_Array_1(string &array[], string pValor_Aagregar) {
          int num_array = ArraySize(array);
          if (ArrayResize(array, num_array + 1) == num_array + 1) {
              array[num_array] = pValor_Aagregar;
          } else {
            Print("Error resizing array");
          }
      }
      
      • Essas funções nos ajudarão a garantir que, quando um bloco de ordem for mitigado, ele não seja considerado novamente, evitando um spam massivo de alertas.

      Agora passamos para a parte dentro de OnCalculate() para finalizar a programação dos alertas:

      • Começamos criando um loop por todos os índices do array de blocos de ordem.
      • Verificamos com um if() o status do bloco de ordem e também se o nome do bloco não está no array de strings onde armazenamos os nomes dos blocos já mitigados.
      • Se tudo isso for verdadeiro, notificamos o usuário com um alerta de que um bloco de ordem foi mitigado.
      • Adicionamos o nome do bloco de ordem ao array de strings para evitar repetições.
      • Finalizamos saindo do loop com um break.
      // Loop through the order blocks
      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) == false)
           {
            Alert("El order block alcista esta siendo mitigado: ", TimeToString(ob_alcistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
      
            break;
           }
        }
      
      // Loop through the order blocks
      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) == false)
           {
      
            Alert("El order block bajista esta siendo mitigado: ", TimeToString(ob_bajistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
      
            break;
           }
        }
      //+------------------------------------------------------------------+
      

      Agora que terminamos com os alertas, vamos passar para a exclusão de objetos:

      bool  ObjectDelete(
         long    chart_id,     // chart identifier
         string  name          // object name
         );
      

      Em seguida, precisamos do ID do gráfico atual:

      ChartID()

      Nome do bloco de ordem:

      name

      Com tudo isso em mente, precisamos percorrer todas as nossas posições de blocos de ordem e então chamar:

      ObjectDelete() 

      Para excluir todos os objetos que criamos:

        void Eliminar_Objetos()
        {
        
        for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // we iterate through the array of bullish order blocks 
        {
         ObjectDelete(ChartID(),ob_alcistas[i].name); // we delete the object using the name of the order block
        }
        for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // we iterate through the array of bearish order blocks 
        {
        ObjectDelete(ChartID(),ob_bajistas[n].name);  // we delete the object using the name of the order block
        }
        
       }
      

      Neste ponto, terminamos de trabalhar no indicador, mas ainda precisamos alterar as funções:

      OnInit() 

      Precisamos garantir que ele trate corretamente as novas variáveis e arrays que adicionamos.

      Portanto, alteramos para: 

      OnDeinit()

      Também garantimos liberar os recursos usados pelo indicador excluindo os objetos gráficos e liberando a memória dos arrays dinâmicos onde os dados dos blocos de ordem são armazenados.

      Também é importante garantir que o handler do ATR seja devidamente liberado, caso tenhamos inicializado corretamente, para evitar vazamentos de memória ou erros ao fechar o indicador. Fazemos isso da seguinte forma:

      if(atr_i != INVALID_HANDLE) 
          IndicatorRelease(atr_i);
      

      A implementação final de

      OnDeinit()

      é a seguinte:

       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 );
      
        }
      
      //---
      
      int OnInit()
        {
      //--- indicator buffers mapping
        tiempo_ultima_vela = 0;
           atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
           
           if(Rango_universal_busqueda < 40)
           {
            Print("Search range too small");
            return (INIT_PARAMETERS_INCORRECT);
           }  
           
            if( atr_i== INVALID_HANDLE)
           {
            Print("Error copying data for indicators");  
            return(INIT_FAILED);
           }
        
        ArrayResize(ob_bajistas,0);
        ArrayResize(ob_alcistas,0);
        ArrayResize(pricetwo_eliminados_oba,0);
        ArrayResize(pricetwo_eliminados_obb,0);
         
      //---
         return(INIT_SUCCEEDED);
        }
      


      3.0: Conclusão 

      Neste artigo, você aprendeu a:

      • Criar um indicador baseado nos conceitos de Smart Money e Inner Circle Trader.
      • Configurar alertas.
      • Desenhar retângulos no gráfico.

      Nosso resultado final:

                 Order-block-example-GIF

      Se você chegou até aqui, agradeço sinceramente pelo entusiasmo e paciência em aprender conceitos de trading mais avançados. A programação oferece uma ampla gama de possibilidades, desde conceitos básicos como máximas e mínimas de um período até a criação de robôs de negociação inteligentes. Convido você a continuar explorando mais artigos para avançar neste fascinante mundo da programação.

      Ficarei muito feliz se você compartilhar este artigo com alguém que possa precisar.

      Como agradecimento pela sua leitura, preparei um arquivo que inclui todo o código do indicador que discutimos aqui. Além disso, quero mencionar que esta série não termina aqui e planejo desenvolver mais partes. Abaixo está um possível esboço de artigos futuros:

      Parte Dois:

      • Integrar buffers e plots para este indicador (buffer de sinal de compra e venda).
      • Implementar níveis de take profit (TP) e stop loss (SL) uma vez que um sinal seja disparado (duas linhas para TP e duas para SL).
      • Introduzir um novo método avançado para detecção de Blocos de Ordem (baseado no order book).

      Se eu receber apoio suficiente, pretendo criar uma terceira parte onde construiremos um Expert Advisor (EA) usando os buffers do indicador que desenvolvemos.

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

      Arquivos anexados |
      Últimos Comentários | Ir para discussão (18)
      CapeCoddah
      CapeCoddah | 14 jul. 2025 em 10:56

      Olá,

      Eu estava testando meu EA no Strategy Tester, EURUSD H4 1/1/2025-2//1/2025, e observei que, no final da execução, havia dois problemas com o indicador Block Order.

      Primeiro, ele selecionou uma ordem de bloqueio em 2/3/2025, que está fora da janela de teste, e, segundo, colocou o texto do bloco na área Chart Shift.


      Curtir


      CapeCoddah

      CapeCoddah
      CapeCoddah | 16 jul. 2025 em 10:34

      Aqui está uma versão traduzida para o inglês do seu primeiro indicador. Decidi que precisava entender seus vários comentários de código em inglês e reconsiderei o Google Translate, pois o DeepL não me impressionou. Primeiro, alterei todos os comentários // para #/# para permitir que o Google traduzisse os comentários de linha //... e, em seguida, converti o arquivo de texto em um documento do MS Word para inserir no Translate.Após a tradução, abri o novo documento e o salvei como um arquivo de texto, renomeei-o e comecei a sintaxe do novo código-fonte. Calculo que o Translate tenha feito 90% do trabalho, mas adicionou espaços e caracteres que exigiram conversão manual. Depois de um dia de trabalho, ele foi compilado sem erros. Surpreendentemente, funcionou na primeira tentativa! Comparei-o com seu indicador original para 1000 barras e eles eram idênticos.

      Niquel Mendoza
      Niquel Mendoza | 16 jul. 2025 em 15:07
      CapeCoddah # :

      Aqui está uma versão traduzida para o inglês do seu primeiro indicador. Decidi que precisava entender seus vários comentários de código em inglês e reconsiderei o Google Translate, pois o DeepL não me impressionou. Primeiro, alterei todos os comentários // para #/# para permitir que o Google traduzisse os comentários de linha //... e, em seguida, converti o arquivo de texto em um documento do MS Word para inserir no Translate.Após a tradução, abri o novo documento e o salvei como um arquivo de texto, renomeei-o e comecei a sintaxe do novo código-fonte. Calculo que o Translate tenha feito 90% do trabalho, mas adicionou espaços e caracteres que exigiram conversão manual. Depois de um dia de trabalho, ele foi compilado sem erros. Surpreendentemente, funcionou na primeira tentativa! Comparei-o com seu indicador original para 1000 barras e eles eram idênticos.

      Olá, CapeCoddah, achei o código que você criou excelente. Desculpe-me por não ter respondido antes; estou envolvido em vários projetos, o que limitou minha disponibilidade para ajudá-lo. Entretanto, hoje posso dedicar algum tempo para trabalhar em uma versão aprimorada do indicador. Estou compartilhando o código abaixo.
      CapeCoddah
      CapeCoddah | 18 jul. 2025 em 12:48

      Infelizmente, parece que seu indicador é estruturalmente falho e inútil para negociação, pois está realizando seus cálculos em variáveis futuras que são desconhecidas no momento do cálculo, conforme destacado no código abaixo em negrito.

      for( int i = Universal_search_range ; i > 5 ; i--) {
      //verificação de erros
      if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
      continue ;
      if( i < 0) continue;

      // Atualizar índices de candlestick
      one_candle = i ; //vela central
      candle_behind_two = i +2;
      candle_behind_one = one_candle +1;
      two_candle = one_candle - 1;
      three_candle = one_candle - 2;
      four_candle = one_candle -3
      ;

      // Calcular o volume médio dos candles anteriores
      body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
      body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
      body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

      Niquel Mendoza
      Niquel Mendoza | 27 jul. 2025 em 13:23
      CapeCoddah #:

      Infelizmente, parece que seu indicador é estruturalmente falho e inútil para negociação, pois está realizando seus cálculos em variáveis futuras que são desconhecidas no momento do cálculo, conforme destacado no código abaixo em negrito.

      for( int i = Universal_search_range ; i > 5 ; i--) {
      //verificação de erros
      if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
      continue ;
      if( i < 0) continue;

      // Atualizar índices de candlestick
      one_candle = i ; //vela central
      candle_behind_two = i +2;
      candle_behind_one = one_candle +1;
      two_candle = one_candle - 1;
      three_candle = one_candle - 2;
      four_candle = one_candle -3
      ;

      // Calcular o volume médio dos candles anteriores
      body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
      body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
      body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

      Olá CapeCoddah, acho que não é esse o caso, pois, por exemplo, o indicador faz todos os cálculos usando matrizes em série (embora isso não seja comum, eles normalmente são feitos sem série) no contexto do primeiro loop mostrado, esse loop é usado para detectar bloqueios de ordem, O que é feito é ir do candle "Universal_search_range" (lembre-se de que, na série, o candle 0 é o mais recente) até o candle 6, de modo que em nenhum momento vejo os candles futuros sendo usados e, se esse fosse o caso, o two_candle ou outro índice resultaria em um valor menor que 0, o que causaria estar fora do intervalo. Assim, o candle four_candle = one_candle - 3 seria o mais próximo de 0 no caso de o loop terminar onde i = 6 e four_candle = 3, portanto, levando em conta que o candle atual é 0, posso dizer que em nenhum momento estou usando candles futuros. O nome pode parecer confuso, eu sei, mas fiz dessa forma porque era mais fácil para mim entender, já que, quando se tratava de obter os blocos de ordem, one_vela era como o candle central. Portanto, se eu estivesse procurando um movimento forte, avaliaria os candles que o seguiram (em série, isso seria subtração).

      Técnicas do MQL5 Wizard que você deve conhecer (Parte 45): Aprendizado por Reforço com Monte-Carlo Técnicas do MQL5 Wizard que você deve conhecer (Parte 45): Aprendizado por Reforço com Monte-Carlo
      Monte-Carlo é o quarto algoritmo diferente em aprendizado por reforço que estamos considerando com o objetivo de explorar sua implementação em Expert Advisors montados pelo wizard. Embora ancorado em amostragem aleatória, ele apresenta vastas formas de simulação que podemos explorar.
      Redes neurais em trading:  Modelos híbridos de sequências de grafos (Conclusão) Redes neurais em trading: Modelos híbridos de sequências de grafos (Conclusão)
      Seguimos o estudo de modelos híbridos de sequências de grafos (GSM++), que integram as vantagens de diferentes arquiteturas e garantem alta precisão na análise, além de uso eficiente dos recursos computacionais. Esses modelos identificam, de maneira eficaz, padrões ocultos, reduzindo o impacto do ruído de mercado e elevando a qualidade das previsões.
      Do básico ao intermediário: Como bolhas de sabão Do básico ao intermediário: Como bolhas de sabão
      Neste artigo, será explicado um mecanismo muito simples e fácil de entender, cujo proposito seria o de gerar a ordenação de uma array, qualquer. Nele veremos que nem sempre o resultado apresentado é aquele que realmente esperamos obter. Sendo assim necessário adaptar a própria implementação a fim de conseguir obter os resultados adequado.
      Indicador de avaliação da força e da fraqueza dos pares de moedas em MQL5 puro Indicador de avaliação da força e da fraqueza dos pares de moedas em MQL5 puro
      Estamos criando um indicador profissional para análise da força das moedas em MQL5. Neste guia passo a passo, você aprenderá a desenvolver uma poderosa ferramenta de trading com painel visual para o MetaTrader 5, a calcular a força das moedas em múltiplos timeframes (H1, H4 e D1), a implementar a atualização dinâmica de dados e a criar uma interface amigável para o usuário.