
Desenvolvendo Sistemas de Trading ICT Avançados: Implementando Order Blocks em um Indicador
- 1.1: Fundamentos da Criação de Order Blocks
- 1.1.1: Lógica Baseada em Ação do Preço (Nível Básico)
- 1.1.2: Lógica Baseada em Ação do Preço e Indicadores (Nível Intermediário)
- 2.0: Desenvolvendo o Indicador de Order Blocks
- 2.1: Configurando Entradas e Parâmetros do Indicador
- 2.2: Criando Estruturas e Funções-Chave
- 2.3: Lógica de detecção de blocos de ordens
- 2.4: Visualização: Cores e Verificação da Mitigação de Order Blocks
- 2.5: Implementando Alertas para Mitigação de Blocos de Ordem e Remoção de Objetos
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:
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. |
- 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.
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:
- Início do movimento: Começa por um candlestick de baixo volume que marca o início da acumulação.
- 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.
- 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:
Exemplo de Baixa:
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:
Exemplo de Baixa:
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:
- Verifica se uma nova vela se formou.
- 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:
-
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.
-
Desenho de Retângulos: Implementamos uma função especial para desenhar retângulos e representar visualmente os blocos de ordem no gráfico.
-
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.
-
Mitigação de Bloco de Ordem: Criaremos uma função que verifica se um Bloco de Ordem foi mitigado.
-
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:
Exemplo 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:
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:
- Detectar blocos de ordem usando a lógica descrita acima.
- Atribuir valores às estruturas.
- Adicionar a estrutura ao array que armazenará os blocos de ordem.
- Verificar mitigação dos blocos de ordem.
- Desenhar blocos de ordem.
- Alertar.
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:
- Inicializamos a variável 'mitigated' como falsa.
- Definimos um nome para um bloco de ordem com base em seu tipo e definimos o horário de acordo com one_sail.
- 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:
- 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.
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:
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
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
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
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.
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.
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]);
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).