Desenvolvimento de sistemas de trading avançados ICT: Implementação de sinais no indicador Order Blocks
- Detecção de blocos de ordens (Order Blocks) com base na profundidade de mercado
- Inicialização e finalização do evento "livro de preços" e criação de arrays
- Coleta de dados da profundidade de mercado para determinar volumes
- Estratégia de busca de Order Blocks usando profundidade de mercado
- Criação de buffers para o indicador
- Modificação da função OnInit para configurar os buffers
- Implementação dos buffers no indicador (2)
- Atualização dos parâmetros de entrada (Inputs)
- Lógica de formação dos sinais do indicador
- Implementação da estratégia de trading
- Configuração dos níveis de Take Profit (TP) e Stop Loss (SL)
- Exibição dos níveis de TP e SL no gráfico
- Adição de buffers para os níveis de TP e SL (4)
- Encerramento do código principal e limpeza
Introdução
Bem-vindo ao mundo do MQL5! Neste artigo vamos nos concentrar na adição de buffers e sinais de entrada ao nosso indicador, concluindo assim a funcionalidade necessária para seu uso em estratégias de trading automatizadas.
Se você é iniciante nesta série, recomendamos revisar a primeira parte, na qual são explicados os conceitos principais e o processo de criação de um indicador do zero.
Detecção de blocos de ordens (Order Blocks) com base na profundidade de mercado
Nossa lógica de definição de blocos de ordens usando profundidade de mercado será a seguinte:
- Array Creation: criamos dois arrays para armazenar o volume de cada vela no livro de preços, o que nos permitirá organizar e analisar de forma eficiente os dados de volume.
- Market Depth Data Collection, no evento.
void OnBookEvent( )
Vamos registrar todas as mudanças no livro de ofertas, salvando o novo volume para manter os dados atualizados em tempo real.
3. Rules to validate Order Blocks: depois que o volume é salvo nos arrays, vamos usar esses dados junto com regras de price action para validar o bloco de ordens (Order Block).
Regras para Order Block com base na profundidade de mercado:
Inicialmente, ao criar nosso indicador, buscávamos blocos de ordens (Order Blocks) em um intervalo específico de velas. No entanto, no caso da profundidade de mercado, não faremos a busca dentro de um intervalo "x". Em vez disso, vamos concentrar a pesquisa especificamente na terceira vela, considerando que a vela 0 é a vela atual.
| Regras | Order Block altista | Order Block baixista |
|---|---|---|
| Pico de volume na vela 3: | O volume de compras na vela 3 deve ser maior, em determinada proporção, do que os volumes de compras e vendas nas velas 2 e 4. | Vamos buscar que o volume de vendas na vela 3 seja um pouco maior do que o volume de compras e vendas nas velas 2 e 4. |
| 3 velas consecutivas: | Devem aparecer 3 velas consecutivas altistas (velas 1, 2 e 3) | Devem aparecer 3 velas consecutivas baixistas (velas 1, 2 e 3) |
| Corpo da vela 3: | A mínima da vela 2 deve estar acima da metade do corpo da vela 3. | A máxima da vela 2 deve estar abaixo da metade do corpo da vela 3. |
| Máxima ou mínima da vela 3: | A máxima da vela 3 deve estar abaixo do fechamento da vela 2. | A mínima da vela 3 deve estar acima do fechamento da vela 2. |
Com essas regras garantimos o seguinte:
- Imbalance de compras ou vendas: verificamos a presença de um desequilíbrio significativo nas compras ou vendas em uma vela específica, na qual as ordens de compra ou venda foram maiores, em determinada proporção, do que na vela anterior e na vela seguinte.
- Controle do corpo da vela no imbalance: garantimos que as ordens não executadas, surgidas devido ao excesso de demanda ou oferta, não tenham sido absorvidas pela vela seguinte, confirmando assim a solidez do bloco de ordens.
- Movimento fortemente altista ou baixista: confirmamos que o padrão representa um movimento intenso, seja ele altista ou baixista, refletindo a força do desequilíbrio na dinâmica do preço.
Agora, tendo tudo isso em mente, vamos transferir o que estudamos para o código.
Inicialização e finalização do evento "livro de preços" e criação de arrays
Criação de arrays
Antes de usar o livro de preços, é necessário criar arrays dinâmicos que vão armazenar os dados de volume. Esses arrays terão o tipo:
long E serão usados para armazenar o volume de compras e o volume de vendas, respectivamente.
- Vamos ao bloco global do programa e declaramos os arrays dinâmicos:
long buy_volume[]; long sell_volume[];2. Dentro do evento OnInit vamos alterar o tamanho dos arrays para que seu tamanho inicial seja igual a 1. Além disso, vamos atribuir o valor 0 ao índice 0 de cada array.
ArrayResize(buy_volume,1); ArrayResize(sell_volume,1); buy_volume[0] = 0.0; sell_volume[0] = 0.0;
Inicialização e finalização do evento "livro de preços":
Antes de iniciar o livro de preços, criamos uma variável global que vai indicar se essa função está disponível. Isso vai nos permitir evitar o uso de:
INIT_FAILEDComo nem todos os símbolos em algumas corretoras fornecem volume de negociação no livro de preços, o indicador não dependerá da corretora que obrigatoriamente oferece essa funcionalidade. - Para determinar se o símbolo com o qual você deseja operar possui livro de preços e volume de negociação, é possível realizar os seguintes passos:
1. Clique no canto superior esquerdo do gráfico nesta imagem: ![]()
2. Verifique se o seu símbolo possui volume disponível para o livro de preços. Algo semelhante ao exemplo abaixo será exibido.
Símbolo com volume no livro de preços:
Símbolo sem volume no livro de preços:
Como já mencionei, o volume no livro de preços não está disponível para todos os símbolos; isso também vai depender da corretora com a qual você trabalha.
Vamos agora para a inicialização e finalização do livro de preços.
1. Variável global de controle
Definimos uma variável booleana global para indicar o status de disponibilidade do livro de preços:
bool use_market_book = true; //true by default
Essa variável é inicializada com o valor true, mas poderemos alterá-la caso a abertura do livro de preços falhe.
2. Inicialização do livro de preços
Para inicializar o livro de preços, é utilizada a função:
MarketBookAdd() Ela abre o livro de preços (Depth of Market) para o símbolo especificado. Como argumento, a função recebe o símbolo atual:
_Symbol No evento OnInit verificamos se a inicialização foi bem-sucedida.
if(!MarketBookAdd(_Symbol)) //Verify initialization of the order book for the current symbol { Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Print error in case of failure use_market_book = false; //Mark use_market_book as false if initialization fails }
3. Finalização do trabalho com o livro de preços
No evento OnDeinit liberamos o livro de preços, usando:
MarketBookRelease() Verificamos o encerramento e exibimos uma mensagem dependendo do resultado:
//--- if(MarketBookRelease(_Symbol)) //Verify if closure was successful Print("Order book successfully closed for: " , _Symbol); //Print success message if so else Print("Order book closed with errors for: " , _Symbol , " Last error: " , GetLastError()); //Print error message with code if not
Coleta de dados da profundidade de mercado para determinar os volumes dos arrays
Após inicializar o livro de preços, podemos começar a coletar os dados correspondentes. Para isso, criamos o evento OnBookEvent, que será acionado sempre que ocorrer uma alteração na profundidade de mercado.
- Criação do evento OnBookEvent:
void OnBookEvent(const string& symbol)2. Verificação do símbolo e da disponibilidade da profundidade de mercado:
if(symbol !=_Symbol || use_market_book == false) return; // Exit the event if conditions are not met
Após concluir essa verificação, podemos apresentar o código completo do evento OnBookEvent:
void OnBookEvent(const string& symbol) { if(symbol !=_Symbol || use_market_book == false) return; // Define array to store Market Book data MqlBookInfo book_info[]; // Retrieve Market Book data bool book_count = MarketBookGet(_Symbol,book_info); // Verify if data was successfully obtained if(book_count == true) { // Iterate through Market Book data for(int i = 0; i < ArraySize(book_info); i++) { // Check if the record is a buy order (BID) if(book_info[i].type == BOOK_TYPE_BUY || book_info[i].type == BOOK_TYPE_BUY_MARKET) { buy_volume[0] += book_info[i].volume; } // Check if the record is a sell order (ASK) if(book_info[i].type == BOOK_TYPE_SELL || book_info[i].type == BOOK_TYPE_SELL_MARKET) { sell_volume[0] += book_info[i].volume; } } } else { Print("No Market Book data retrieved."); } }
Esse código realiza o seguinte:
- Obtenção do volume: sempre que ocorre uma alteração no livro de preços, OnBookEvent coleta o volume da última ordem registrada.
- Atualização dos arrays: soma o volume de compra e de venda no índice 0 dos arrays buy_volume e sell_volume, respectivamente.
Para que o array acumule a profundidade do volume do mercado em cada nova vela e mantenha o histórico como uma série temporal, o código deve ajustar o volume atual no índice 0 e deslocar os demais dados para frente, garantindo que os dados não se acumulem de forma excessiva e que o tamanho do array permaneça constante (por exemplo, 30 elementos).
1. Verificação de nova vela e validação do contador de abertura de velas (maior que 1)
Para evitar acionamentos incorretos na inicialização do programa e garantir que os arrays sejam atualizados somente quando uma nova vela se abre (e após pelo menos uma abertura), implementamos a verificação da variável counter junto com new_vela. Isso garante que a atualização dos arrays ocorrerá exclusivamente quando realmente surgir informação nova.
Declaração de variáveis estáticasDeclaramos counter como uma variável estática para que ela seja preservada entre chamadas de OnCalculate. A variável new_vela deve indicar se uma nova vela foi aberta.
static int counter = 0;
Condição de verificação de nova vela e contador
Verificamos que counter é maior que 1, new_vela tem valor true e o uso do mercado é permitido. Somente se essas condições forem satisfeitas, vamos alterar o tamanho do array e deslocar os elementos. Esse controle evita uma mudança prematura de tamanho e garante que o array seja atualizado apenas quando houver dados válidos e quando o market book fornecer volume de negociação para o símbolo atual.
if(counter > 1 && new_vela == true && use_market_book == true)
Atualização do contador
Aumentamos o contador em 1 cada vez que uma nova vela é detectada.
counter++;
2. Controle do tamanho do array
Verificamos que o array não exceda o tamanho máximo de 30 elementos. Se isso ocorrer, alteramos seu tamanho para 30, removendo o elemento mais antigo:
if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Keep buy_volume size at 30 ArrayResize(sell_volume, 30); // Keep sell_volume size at 30 }
3. Alteração de tamanho para inserir novos valores
Adicionamos espaço adicional ao array para armazenar o novo volume na posição inicial.
ArrayResize(buy_volume, ArraySize(buy_volume) + 1); ArrayResize(sell_volume, ArraySize(sell_volume) + 1);
4. Deslocamento dos elementos
Movemos todos os elementos do array uma posição à frente. Isso garante que os novos dados sejam sempre armazenados no índice 0, enquanto os antigos são deslocados para índices de valores maiores.
for(int i = ArraySize(buy_volume) - 1; i > 0; i--) { buy_volume[i] = buy_volume[i - 1]; sell_volume[i] = sell_volume[i - 1]; }
5. Verificação dos volumes
Exibimos o volume de compra e de venda na posição 1 do array para verificar o volume da última vela.
Print("Buy volume of the last candle: ", buy_volume[1]); Print("Sell volume of the last candle: ", sell_volume[1]);
6. Reinicialização dos volumes
Zeramos o índice 0 de ambos os arrays, para que comece a ser acumulado o volume da nova vela.
buy_volume[0] = 0; sell_volume[0] = 0;
7. Condição que permite evitar erros caso haja dados conflitantes no market book
Adicionei essa condição para desativar automaticamente use_market_book se os valores buy_volume e sell_volume nas últimas posições (índices 3, 2 e 1) forem iguais a zero. Essa configuração é necessária porque, mesmo que um símbolo possua market book no mercado real, ao iniciar no testador de estratégias ele também é identificado como tendo market book. No entanto, o array pode ser preenchido de modo incorreto devido à ausência de alterações no livro de preços no modo de teste, o que gera valores zerados no array e pode fazer o indicador armazenar informações incorretas.
Essa verificação impede que o indicador processe dados sem sentido e garante que use_market_book seja utilizado apenas quando o market book contiver valores válidos.
if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4) { if(buy_volume[3] == 0 && sell_volume[3] == 0 && buy_volume[2] == 0 && sell_volume[2] == 0 && buy_volume[1] == 0 && sell_volume[1] == 0) use_market_book = false; }
De forma integrada, o código ficará assim:
if(counter > 1 && new_vela == true && use_market_book == true) { if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4) { if(buy_volume[3] == 0 && sell_volume[3] == 0 && buy_volume[2] == 0 && sell_volume[2] == 0 && buy_volume[1] == 0 && sell_volume[1] == 0) use_market_book = false; } // If array size is greater than or equal to 30, resize to maintain a fixed length if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Ensure buy_volume does not exceed 30 elements ArrayResize(sell_volume, 30); // Ensure sell_volume does not exceed 30 elements } ArrayResize(buy_volume,ArraySize(buy_volume)+1); ArrayResize(sell_volume,ArraySize(sell_volume)+1); for(int i = ArraySize(buy_volume) - 1; i > 0; i--) { buy_volume[i] = buy_volume[i - 1]; sell_volume[i] = sell_volume[i - 1]; } // Reset volumes at index 0 to begin accumulating for the new candlestick buy_volume[0] = 0; sell_volume[0] = 0; }
Estratégia de busca de Order Blocks usando o livro de preços
A estratégia seguirá a mesma lógica que utilizamos anteriormente, porém com uma diferença importante: em vez de usar laços, vamos realizar as verificações diretamente na vela 3. A lógica geral permanece a mesma, pois verificamos determinadas condições, identificamos a vela mais próxima (com base no tipo de bloco de ordens), depois atribuímos os valores correspondentes à estrutura e adicionamos o bloco de ordens ao array. Aqui aplicaremos o mesmo processo, só que de forma mais simples.
Vamos começar criando as estruturas que armazenarão as informações dos blocos de ordens:
OrderBlocks newVela_Order_block_Book_bajista; OrderBlocks newVela_Order_block_Book;
1. Condições iniciais
Primeiro verificamos que o tamanho dos arrays buy_volume e sell_volume seja de pelo menos 5 elementos. Isso garante que haja histórico suficiente para análise. Também asseguramos que use_market_book esteja ativo para o processamento do livro de preços.
if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
2. Definição das variáveis de controle
Definimos a variável case_book para indicar se determinada condição de volume está sendo atendida. Estabelecemos ratio no nível de (1,4), que servirá como fator comparativo para detectar aumento significativo no volume de compras.
bool case_book = false; double ratio = 1.4;
3. Estado do volume de compras (Case Book)
Aqui verificamos se o volume de compras no índice 3 ultrapassa significativamente o volume nos índices 2 e 4, tanto do lado da compra quanto da venda, usando ratio como multiplicador. Se essa condição for atendida, case_book é ativado.
Cenário altista:
if(buy_volume[3] > buy_volume[4] * ratio && buy_volume[3] > buy_volume[2] * ratio && buy_volume[3] > sell_volume[4] * ratio && buy_volume[3] > sell_volume[2] * ratio) { case_book = true; }Cenário baixista:
if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio && sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio) { case_book = true; }
4. Cálculo do corpo da vela
Calculamos o tamanho do corpo da vela (body_tree) no índice 3, subtraindo o preço de abertura do preço de fechamento.
double body_tree = closeArray[3] - openArray[3];
double body_tree = openArray[3] - closeArray[3]; 5. Verificação das condições de preço para o setup altista
Avaliamos as condições mencionadas no início (veja a tabela acima).
Cenário altista:
if(lowArray[2] > ((body_tree * 0.5) + openArray[3]) && highArray[3] < closeArray[2] && closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
Cenário baixista:
if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] && closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
6. Identificação das velas altistas anteriores
Chamamos a função FindFurthestBullish, que busca a vela altista mais distante dentro de um intervalo de 20 velas a partir do índice 3. Isso nos ajuda a encontrar uma vela de referência para um setup altista confiável. Se uma vela altista for encontrada, o índice será maior que 0, o que nos permite continuar.
Cenário altista:
int furthestAlcista = FindFurthestAlcista(Time[3], 20); if(furthestAlcista > 0)
7. Atribuição de valores ao bloco de ordens
Se todas as condições forem atendidas, definimos o bloco de ordens (newVela_Order_block_Book ou newVela_Order_block_Book_bajista) com os valores da vela encontrada.
Cenário altista:
Print("Case Book Found"); datetime time1 = Time[furthestAlcista]; double price2 = openArray[furthestAlcista]; double price1 = lowArray[furthestAlcista]; //Assign the above variables to the structure newVela_Order_block_Book.price1 = price1; newVela_Order_block_Book.time1 = time1; newVela_Order_block_Book.price2 = price2; newVela_Order_block_Book.mitigated = false; newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book);
Cenário baixista:
Print("Case Book Found"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //Assign the above variables to the structure newVela_Order_block_Book_bajista.price1 = price1; newVela_Order_block_Book_bajista.time1 = time1; newVela_Order_block_Book_bajista.price2 = price2; newVela_Order_block_Book_bajista.mitigated = false; newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1); AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);
Código completo:
if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true) { bool case_book = false; double ratio = 1.4; if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio && sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio) { case_book = true; } double body_tree = openArray[3] - closeArray[3]; if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] && closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1]) { int furthestBajista = FindFurthestBajista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle" if(furthestBajista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle". { Print("Case Book Found"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //Assign the above variables to the structure newVela_Order_block_Book_bajista.price1 = price1; newVela_Order_block_Book_bajista.time1 = time1; newVela_Order_block_Book_bajista.price2 = price2; newVela_Order_block_Book_bajista.mitigated = false; newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1); AddIndexToArray_bajistas(newVela_Order_block_Book_bajista); } } } //-------------------- Bullish -------------------- if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true) { bool case_book = false; double ratio = 1.4; if(buy_volume[3] > buy_volume[4]*ratio && buy_volume[3] > buy_volume[2]*ratio && buy_volume[3] > sell_volume[4]*ratio && buy_volume[3] > sell_volume[2]*ratio) { case_book = true; } double body_tree = closeArray[3] - openArray[3]; if(lowArray[2] > ((body_tree * 0.5)+openArray[3]) && highArray[3] < closeArray[2] && closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1]) { int furthestAlcista = FindFurthestAlcista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle" if(furthestAlcista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle". { Print("Case Book Found"); datetime time1 = Time[furthestAlcista]; //assign the index time of furthestAlcista to the variable time1 double price2 = openArray[furthestAlcista]; //assign the open of furthestAlcista as price 2 (remember that we draw it on a bearish candlestick most of the time) double price1 = lowArray[furthestAlcista]; //assign the low of furthestAlcista as price 1 //Assign the above variables to the structure newVela_Order_block_Book.price1 = price1; newVela_Order_block_Book.time1 = time1; newVela_Order_block_Book.price2 = price2; newVela_Order_block_Book.mitigated = false; newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book); } } }
Criação de buffers para o indicador
Para criar e configurar os buffers do nosso indicador de blocos de ordens (order blocks) em MQL5, começaremos definindo dois buffers e dois elementos gráficos globais (plots) para armazenar e exibir os níveis de preço dos blocos altistas e baixistas.
1. Declaração dos buffers e plots
Vamos declarar dois buffers na parte global do programa, para que possam armazenar os dados de preço dos blocos de ordens. Além disso, adicionaremos dois plots que servirão para visualizar os blocos de ordens no gráfico.
#property indicator_buffers 2 #property indicator_plots 2 #property indicator_label1 "Bullish Order Block" #property indicator_label2 "Bearish Order Block"
2. Criação de arrays dinâmicos para os buffers
Declaramos dois arrays dinâmicos, buyOrderBlockBuffer e sellOrderBlockBuffer, para armazenar os níveis de preço correspondentes aos blocos de ordens de compra (altistas) e de venda (baixistas), respectivamente. Esses arrays serão atribuídos aos buffers, o que permitirá exibir os dados dos blocos de ordens no gráfico.
//--- Define the buffers double buyOrderBlockBuffer[]; // Buffer for bullish order blocks double sellOrderBlockBuffer[]; // Buffer for bearish order blocks
Descrição:
- buyOrderBlockBuffer: armazena os níveis de preço dos blocos de ordens altistas (bullish order blocks) e é destinado aos pontos altistas nos quais o preço pode encontrar suporte.
- sellOrderBlockBuffer: armazena os níveis de preço dos blocos de ordens baixistas (bearish order blocks) e exibe pontos baixistas onde o preço pode encontrar resistência.
Modificação da função OnInit para configurar os buffers
Nesta parte vamos aprimorar a função OnInit para configurar os buffers do indicador, atribuindo os arrays dos blocos de ordens altistas e baixistas como buffers do indicador. Isso permitirá que o indicador armazene e exiba corretamente os dados no gráfico.
Passo a passo:
1. Atribuição dos buffers de dados com SetIndexBuffer
Primeiro, em OnInit, atribuimos os arrays buyOrderBlockBuffer e sellOrderBlockBuffer aos buffers do indicador usando SetIndexBuffer. Isso garante que esses arrays possam armazenar e apresentar dados no gráfico.
//--- Assign data buffers to the indicator SetIndexBuffer(0, buyOrderBlockBuffer, INDICATOR_DATA); SetIndexBuffer(1, sellOrderBlockBuffer, INDICATOR_DATA)
2. Configuração dos buffers como séries e preenchimento com valores vazios
Para que os dados sejam exibidos em ordem inversa (como uma série temporal), definimos modo de série para os arrays. Além disso, inicializamos ambos os buffers com o valor EMPTY_VALUE, para evitar que dados incorretos sejam exibidos antes que os valores reais sejam calculados.
ArraySetAsSeries(buyOrderBlockBuffer, true); ArraySetAsSeries(sellOrderBlockBuffer, true); ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
Implementação dos buffers no indicador (2)
Nesta parte vamos atribuir os preços dos blocos de ordens altistas e baixistas aos buffers do indicador. Esses buffers tornam os dados acessíveis em cada índice que corresponde ao tempo (time1) de cada bloco de ordens.1. Atribuição de preços para os blocos de ordens altistas
Dentro do laço no qual avaliamos cada bloco altista no array ob_alcistas, adicionamos a seguinte linha de código para armazenar o preço price2 no buffer buyOrderBlockBuffer. Usamos a função iBarShift para obter o índice exato no gráfico em que time1 coincide com o tempo do bloco de ordens.
buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;
Aqui o valor price2 do bloco altista é atribuído ao índice correspondente no buyOrderBlockBuffer, para que o buffer reflita o nível de preço do bloco no gráfico.
2. Atribuição de preços para os blocos de ordens baixistas
De forma análoga, atribuímos o preço price2 de cada bloco baixista ao buffer sellOrderBlockBuffer. Isso é feito percorrendo o array ob_bajistas e definindo o valor do preço no índice correspondente.
sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;No resultado final:
- iBarShift é utilizado para determinar o índice exato em que o tempo do bloco coincide com a posição no gráfico.
- buyOrderBlockBuffer e sellOrderBlockBuffer recebem os valores de price2, o que permite registrar os preços no momento correto e utilizá-los no gráfico e em cálculos adicionais do indicador.
Atualização dos parâmetros de entrada (Inputs)
Nesta parte vamos configurar os parâmetros de entrada (inputs) para que o usuário possa personalizar o estilo de cálculo do take profit (TP) e do stop loss (SL) conforme suas preferências. Para isso, criaremos um enum que permitirá escolher entre duas opções:ATR (Average True Range) ou POINT (pontos fixos).
Enumeração ENUM_TP_SL_STYLE
A enumeração ENUM_TP_SL_STYLE permite ao usuário escolher um dos dois modos de cálculo do TP e SL.
- ATR: define os valores de TP e SL com base no intervalo médio de movimento do preço, ajustando-os automaticamente de acordo com a volatilidade atual do mercado.
- POINT: define TP e SL em pontos fixos com base no valor especificado pelo usuário.
enum ENUM_TP_SL_STYLE
{
ATR,
POINT
};
Explicação:
-
ATR: ao escolher essa opção, o usuário define um multiplicador que determinará a distância de TP e SL em relação ao ATR. Um valor maior do multiplicador aumentará a distância entre TP e SL com base na volatilidade atual.
-
POINT: nesta opção, o usuário define manualmente TP e SL em pontos fixos. Isso permite estabelecer níveis estáticos de TP e SL independentemente da volatilidade.
Agora, continuando o trabalho com os parâmetros do indicador, estruturamos os parâmetros de entrada usando sinput e agrupamos as configurações em seções. Isso garante uma apresentação mais organizada e visualmente clara no painel do indicador, facilitando o processo de configuração para o usuário.
1. Seção de estratégia
Primeiro criamos a seção de estratégia, que inclui a opção de estilos para o cálculo do take profit (TP) e do stop loss (SL):
sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT; // TP and SL style: ATR or fixed points
Nesta seção, tp_sl_style permitirá ao usuário escolher se deseja calcular TP e SL com base no ATR (Average True Range) ou com pontos fixos.
2. Configuração de TP e SL de acordo com o método selecionado
Para contemplar as particularidades de cada método, adicionamos dois grupos adicionais, um para o método ATR e outro para pontos fixos.
Grupo ATR: aqui incluímos duas variáveis de entrada input do tipo double, que permitem ao usuário definir os multiplicadores do ATR, ajustando assim o alcance de TP e SL com base na volatilidade.
sinput group " ATR " input double Atr_Multiplier_1 = 1.5; // Multiplier for TP input double Atr_Multiplier_2 = 2.0; // Multiplier for SL
Grupo POINT: neste grupo adicionamos duas variáveis de entrada input do tipo int para definir os pontos fixos de TP e SL, o que permite controlar manualmente e com precisão essas distâncias.
sinput group " POINT " input int TP_POINT = 500; // Fixed points for TP input int SL_POINT = 275; // Fixed points for SL
Graças a essa organização, os parâmetros ficam devidamente ordenados e categorizados, o que facilita seu uso e melhora a clareza na configuração do indicador. O usuário poderá ajustar intuitivamente o estilo de TP e SL, escolhendo entre configurações automáticas baseadas em ATR ou ajustes manuais em pontos.
Código completo dos parâmetros:
sinput group "--- Order Block Indicator settings ---" sinput group "-- Order Block --" input int Rango_universal_busqueda = 500; input int Witdth_order_block = 1; input bool Back_order_block = true; input bool Fill_order_block = true; input color Color_Order_Block_Bajista = clrRed; input color Color_Order_Block_Alcista = clrGreen; sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT; sinput group " ATR " input double Atr_Multiplier_1 = 1.5; input double Atr_Multiplier_2 = 2.0; sinput group " POINT " input int TP_POINT = 500; input int SL_POINT = 275;
Lógica de formação dos sinais do indicador
Para gerar sinais de compra ou venda, utilizam-se duas variáveis estáticas:
| Variável | Descrição |
|---|---|
| time_ e time_b | Armazenam o tempo de mitigação do order block e adicionam 5 velas (em segundos) ao tempo de expiração. |
| buscar_oba e buscar_obb | Controlam a busca por novos order blocks mitigados (mitigated order block). São ativadas ou desativadas dependendo das condições. |
Processo de geração dos sinais
Detecção do Order Block mitigado:- Quando ocorre a mitigação de um bloco de ordens, a variável time_ recebe o tempo atual mais a margem de 5 velas.
- A variável de busca recebe o valor false, para impedir a continuidade da busca enquanto as condições do sinal são avaliadas.
- As condições de compra ou venda são avaliadas com base na média móvel exponencial (EMA) e no tempo de mitigação (time_).
| Tipo de sinal | Condições da EMA | Condições do tempo |
|---|---|---|
| Compra | A EMA de período 30 deve estar abaixo do fechamento da vela 1 | time_ deve ser maior que o tempo atual |
| Venda | A EMA de período 30 deve estar acima do fechamento da vela 1 | time_b deve ser maior que o tempo atual |
Observação: essas condições garantem que o sinal seja gerado dentro de 5 velas após a mitigação do bloco de ordens (order block).
Ações no caso de cumprimento ou descumprimento:
| Estado | Ação |
|---|---|
| Cumprimento | Os buffers de take profit (TP) e stop loss (SL) são preenchidos para executar a operação correspondente |
| Descumprimento | A variável de busca é redefinida para true e time_ e time_b são zeradas, permitindo retomar a busca por novos blocos de ordens (caso o tempo máximo tenha sido ultrapassado). |
Fluxograma:
Compras

Vendas

Implementação da estratégia de trading
Antes de começar, vamos criar um identificador da média móvel exponencial.
Criamos variáveis globais (array e identificador):
int hanlde_ma; double ma[];
Em OnInit inicializamos o identificador e verificamos se ele recebeu um valor.
hanlde_ma = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE); if(hanlde_ma == INVALID_HANDLE) { Print("The EMA indicator is not available. Failure: ", _LastError); return INIT_FAILED; }Declaramos variáveis estáticas para gerenciar o estado da busca e o tempo de ativação do OB, diferenciando os cenários de compra e venda.
//Variables for buy static bool buscar_oba = true; static datetime time_ = 0; //Variables for sell static bool buscar_obb = true; static datetime time_b = 0;
Em seguida executamos um laço para buscar order blocks mitigados, de forma análoga ao que fizemos no artigo anterior para os alertas.
Começamos adicionando as condições:
//Bullish case if(buscar_oba == true) //Bearish case if(buscar_obb == true)
// Bearish case for(int i = 0; i < ArraySize(ob_bajistas); i++) { if(ob_bajistas[i].mitigated == true && !Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) && ObjectFind(ChartID(), ob_bajistas[i].name) >= 0) { Alert("The bearishorder block is being mitigated: ", TimeToString(ob_bajistas[i].time1)); buscar_obb = false; // Pause search time_b = iTime(_Symbol,_Period,1); // Record the mitigation time Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name); break; } } // Bullish case for(int i = 0; i < ArraySize(ob_alcistas); i++) { if(ob_alcistas[i].mitigated == true && !Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) && ObjectFind(ChartID(), ob_alcistas[i].name) >= 0) { Alert("The bullish order block is mitigated: ", TimeToString(ob_alcistas[i].time1)); time_ = iTime(_Symbol,_Period,0); Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name); buscar_oba = false; // Pause search break; } }
O passo seguinte é determinar se um OB foi mitigado, isto é, se houve interação do preço com ele. Se for encontrado um OB mitigado, seu tempo é registrado e a busca é pausada. Isso é executado para ambos os cenários, altista e baixista.
// Buy if(buscar_oba == false && time_ > 0 && new_vela) { /* Code for Buy */ } // Sell if(buscar_obb == false && time_b > 0 && new_vela) { /* Code for Sell */ }
Esta parte garante que o sistema interrompa a busca após detectar a mitigação, evitando duplicação de sinais.
Condição inicial para execução da operação
A estratégia utiliza certas condições para ativar a busca por sinais de compra ou venda após a mitigação de um OB, desde que o tempo máximo de espera ainda não tenha sido excedido.
// Buy double close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits); datetime max_time_espera = time_ + (PeriodSeconds() * 5); if(close_ > ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) { // Code for Buy... } // Sell close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits); max_time_espera = time_b + (PeriodSeconds() * 5); if(close_ < ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) { // Code for Sell... }
Nessas condições:
- buscar_oba ou buscar_obb devem ser iguais a false, confirmando a mitigação anterior,
- time_ ou time_b devem ter valor maior que 0, indicando que o tempo foi registrado,
- new_vela confirma que estamos em uma nova vela, ajudando a evitar decisões repetidas.
Validação das condições de compra ou venda
Para estabelecer as condições necessárias, primeiro precisamos de uma variável que armazene o tempo máximo de espera. Depois, precisamos do valor de fechamento da vela 1 e de sua EMA (média móvel exponencial). Para obter o fechamento usamos a função iClose e os valores da EMA são armazenados no array que contém toda a série histórica da média móvel.
// Reset for Buy if(iTime(_Symbol,_Period,0) > max_time_espera) { time_ = 0; buscar_oba = true; } // Reset for Sell if(iTime(_Symbol,_Period,0) > max_time_espera) { time_b = 0; buscar_obb = true; }
Reinício da busca por Order Blocks
Por fim, se o tempo máximo de espera for excedido sem que as condições sejam atendidas, o código reinicia a busca, para garantir que novos OBs possam ser detectados:
void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)
Agora falta a função responsável por desenhar o tp e o sl, além de adicioná-los aos buffers. Vamos fazer isso agora e, em seguida, finalizar o código atual.
Vamos analisar isso nas próximas partes.
Configuração dos níveis de Take Profit (TP) e Stop Loss (SL)
Nesta parte vamos desenvolver a função GetTP_SL, que calculará os valores de TP e SL com base em dois métodos: usando ATR (Average True Range) ou pontos fixos, como mencionamos anteriormente ao configurar os inputs.
1: Definição da função
A função GetTP_SL receberá como parâmetros o preço de abertura da posição, o tipo da posição (ENUM_POSITION_TYPE) e referências para os níveis TP e SL (tp1, tp2, sl1 e sl2), nos quais serão armazenados os valores calculados.
double atr[]; ArraySetAsSeries(atr, true); CopyBuffer(atr_i, 0, 0, 1, atr);
2: Obtenção do ATR
Para calcular os níveis com base em ATR, primeiro precisamos de um array que armazene o valor do ATR da última vela. Usamos CopyBuffer para preencher o array atr com o valor atual.
if (type == POSITION_TYPE_BUY) { sl1 = price_open_position - (atr[0] * Atr_Multiplier_1); sl2 = price_open_position - (atr[0] * Atr_Multiplier_2); tp1 = price_open_position + (atr[0] * Atr_Multiplier_1); tp2 = price_open_position + (atr[0] * Atr_Multiplier_2); } if (type == POSITION_TYPE_SELL) { sl1 = price_open_position + (atr[0] * Atr_Multiplier_1); sl2 = price_open_position + (atr[0] * Atr_Multiplier_2); tp1 = price_open_position - (atr[0] * Atr_Multiplier_1); tp2 = price_open_position - (atr[0] * Atr_Multiplier_2); }
3: Cálculo de TP e SL com base no ATR
Quando tp_sl_style está configurado como ATR, calculamos os níveis de TP e SL multiplicando o valor do ATR pelos multiplicadores definidos (Atr_Multiplier_1 e Atr_Multiplier_2). Em seguida somamos ou subtraímos esses valores do preço de abertura, dependendo do tipo de posição.
if (type == POSITION_TYPE_BUY) { sl1 = price_open_position - (SL_POINT * _Point); sl2 = price_open_position - (SL_POINT * _Point * 2); tp1 = price_open_position + (TP_POINT * _Point); tp2 = price_open_position + (TP_POINT * _Point * 2); } if (type == POSITION_TYPE_SELL) { sl1 = price_open_position + (SL_POINT * _Point); sl2 = price_open_position + (SL_POINT * _Point * 2); tp1 = price_open_position - (TP_POINT * _Point); tp2 = price_open_position - (TP_POINT * _Point * 2); }
4: Cálculo de TP e SL com base em pontos
Quando tp_sl_style está configurado como POINT, somamos ou subtraímos os pontos especificados (TP_POINT e SL_POINT), multiplicados pelo valor de um ponto do símbolo atual (_Point), ao preço de abertura. Essa é uma alternativa simples ao cálculo baseado em ATR.
bool TrendCreate(long chart_ID, // Chart ID string name, // Line name int sub_window, // Subwindow index datetime time1, // Time of the first point double price1, // Price of the first point datetime time2, // Time of the second point double price2, // Price of the second point color clr, // Line color ENUM_LINE_STYLE style, // Line style int width, // Line width bool back, // in the background bool selection // Selectable form moving ) { ResetLastError(); if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { Print(__FUNCTION__, ": ¡Failed to create trend line! Error code = ",GetLastError()); return(false); } ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style); ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width); ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); ChartRedraw(chart_ID); return(true); }
Exibição dos níveis de TP e SL no gráfico
Criamos uma função que exibirá os valores de tp e sl no gráfico. Para isso precisaremos criar linhas e textos.
Para as linhas:
bool TextCreate(long chart_ID, // Chart ID string name, // Object name int sub_window, // Subwindow index datetime time, // Anchor time double price, // Anchor price string text, // the text string font, // Font int font_size, // Font size color clr, // color double angle, // Text angle ENUM_ANCHOR_POINT anchor, // Anchor point bool back=false, // font bool selection=false) // Selectable for moving { //--- reset error value ResetLastError(); //--- create "Text" object if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price)) { Print(__FUNCTION__, ": ¡Failed to create object \"Text\"! Error code = ",GetLastError()); return(false); } ObjectSetString(chart_ID,name,OBJPROP_TEXT,text); ObjectSetString(chart_ID,name,OBJPROP_FONT,font); ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size); ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle); ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor); ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); ChartRedraw(chart_ID); return(true); }
Para os textos:
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)
Agora passamos à criação da função.
Passo 1. Parâmetros de entrada
A função recebe os seguintes parâmetros:
- tp1 e tp2 — valores de dois níveis de Take Profit,
- sl1 e sl2 — valores de dois níveis de Stop Loss.
string curr_time = TimeToString(iTime(_Symbol, _Period, 0)); datetime extension_time = iTime(_Symbol, _Period, 0) + (PeriodSeconds(PERIOD_CURRENT) * 15); datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);
Passo 2: Preparação do tempo
Primeiro é criada a string curr_time, que armazena a data e a hora atuais da vela no gráfico. Em seguida calculamos extension_time, que é estendido em 15 períodos à frente do tempo atual para projetar as linhas de TP e SL à direita do gráfico. text_time é utilizado para ajustar a posição das etiquetas de texto, expandindo-a levemente além de extension_time.
TrendCreate(ChartID(), curr_time + " TP1", 0, iTime(_Symbol, _Period, 0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " TP1 - Text", 0, text_time, tp1, "TP1", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);
Passo 3: Renderização das linhas e etiquetas de TP e SL
- Take Profit 1 (tp1):
- Uma linha verde pontilhada (STYLE_DOT) é criada no nível de tp1 usando a função TrendCreate.
- Depois, TextCreate adiciona a etiqueta com o texto "TP1" na posição correspondente a tp1.
TrendCreate(ChartID(), curr_time + " TP2", 0, iTime(_Symbol, _Period, 0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " TP2 - Text", 0, text_time, tp2, "TP2", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);2. Take Profit 2 (tp2):
- Outra linha verde pontilhada é desenhada no nível de tp2 e a etiqueta "TP2" é adicionada.
TrendCreate(ChartID(), curr_time + " SL1", 0, iTime(_Symbol, _Period, 0), sl1, extension_time, sl1, clrRed, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " SL1 - Text", 0, text_time, sl1, "SL1", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);3. Stop Loss 1 (sl1):
- No nível de sl1 é desenhada uma linha vermelha pontilhada junto com a etiqueta "SL1".
TrendCreate(ChartID(), curr_time + " SL2", 0, iTime(_Symbol, _Period, 0), sl2, extension_time, sl2, clrRed, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " SL2 - Text", 0, text_time, sl2, "SL2", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);4. Stop Loss 2 (sl2):
- Da mesma forma é desenhada outra linha vermelha no nível de sl2 e adicionada a etiqueta "SL2".
void DrawTP_SL(double tp1, double tp2, double sl1, double sl2) { string curr_time = TimeToString(iTime(_Symbol,_Period,0)); datetime extension_time = iTime(_Symbol,_Period,0) + (PeriodSeconds(PERIOD_CURRENT) * 15); datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2); TrendCreate(ChartID(),curr_time+" TP1",0,iTime(_Symbol,_Period,0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" TP1 - Text",0,text_time,tp1,"TP1","Arial",8,clrGreen,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" TP2",0,iTime(_Symbol,_Period,0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" TP2 - Text",0,text_time,tp2,"TP2","Arial",8,clrGreen,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" SL1",0,iTime(_Symbol,_Period,0),sl1,extension_time,sl1,clrRed,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" SL1 - Text",0,text_time,sl1,"SL1","Arial",8,clrRed,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" SL2",0,iTime(_Symbol,_Period,0),sl2,extension_time,sl2,clrRed,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" SL2 - Text",0,text_time,sl2,"SL2","Arial",8,clrRed,0.0,ANCHOR_CENTER); }
Código completo:
#property indicator_label3 "Take Profit 1" #property indicator_label4 "Take Profit 2" #property indicator_label5 "Stop Loss 1" #property indicator_label6 "Stop Loss 2"
Adição de buffers para os níveis de TP e SL (4)
Assim como criamos anteriormente dois buffers que armazenam price2, voltamos agora à parte global do programa, onde escrevemos:
#property indicator_buffers 6 #property indicator_plots 6
Além disso, aumentamos a quantidade de plots e buffers de 2 para 6.
double tp1_buffer[]; double tp2_buffer[]; double sl1_buffer[]; double sl2_buffer[];
Criamos o array de buffers:
SetIndexBuffer(2, tp1_buffer, INDICATOR_DATA); SetIndexBuffer(3, tp2_buffer, INDICATOR_DATA); SetIndexBuffer(4, sl1_buffer, INDICATOR_DATA); SetIndexBuffer(5, sl2_buffer, INDICATOR_DATA); ArraySetAsSeries(buyOrderBlockBuffer, true); ArraySetAsSeries(sellOrderBlockBuffer, true); ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArraySetAsSeries(tp1_buffer, true); ArraySetAsSeries(tp2_buffer, true); ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArraySetAsSeries(sl1_buffer, true); ArraySetAsSeries(sl2_buffer, true); ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
Inicializamos os buffers usando fill e configuramos o modo de série:
datetime tiempo_ultima_vela_1; Assim, neste estágio, já teremos os buffers disponíveis.
Encerramento do código principal e limpeza
Para finalizar o desenvolvimento do indicador, é necessário implementar o código de limpeza e otimização. Isso permitirá que o indicador funcione mais rapidamente nos backtests e liberará recursos dos arrays, como OrderBlocks, quando não forem mais necessários.
1. Limpeza de arrays
Dentro de OnCalculate vamos controlar a criação de uma nova vela, só que desta vez no time frame diário. Para isso usamos uma variável global, que armazenará o horário da última vela:
if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1, 0)) { Eliminar_Objetos(); ArrayFree(ob_bajistas); ArrayFree(ob_alcistas); ArrayFree(pricetwo_eliminados_oba); ArrayFree(pricetwo_eliminados_obb); tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1, 0); }Cada vez que uma nova vela diária se abre, liberamos a memória dos arrays, evitando o acúmulo de dados antigos e otimizando o desempenho.
void OnDeinit(const int reason) { Eliminar_Objetos(); ArrayFree(ob_bajistas); ArrayFree(ob_alcistas); ArrayFree(pricetwo_eliminados_oba); ArrayFree(pricetwo_eliminados_obb); if(atr_i != INVALID_HANDLE) IndicatorRelease(atr_i); if(hanlde_ma != INVALID_HANDLE) //EMA IndicatorRelease(hanlde_ma); ResetLastError(); if(MarketBookRelease(_Symbol)) //Verify if closure was successful Print("Order book successfully closed for: " , _Symbol); //Print success message if so else Print("Order book closed with errors for: " , _Symbol , " Last error: " , GetLastError()); //Print error message with code if not }
2. Modificação de OnDeinit
Em OnDeinit liberamos o identificador do indicador EMA e limpamos os arrays. Isso garante que, ao desativar o indicador, nenhum recurso permaneça alocado na memória.
void Eliminar_Objetos() { for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iterate through the array of bullish order blocks { ObjectDelete(ChartID(),ob_alcistas[i].name); // delete the object using the order block's name } for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iterate through the array of bearish order blocks { ObjectDelete(ChartID(),ob_bajistas[n].name); // delete the object using the order block's name } //Delete all TP and SL lines ObjectsDeleteAll(0," TP",-1,-1); ObjectsDeleteAll(0," SL",-1,-1); }
3. Função de remoção de objetos:
A função Remove_Objects foi otimizada para remover também as linhas de Take Profit (TP) e Stop Loss (SL) junto com os retângulos dos blocos de ordens. Assim garantimos que o gráfico permaneça limpo.string short_name = "Order Block Indicator"; IndicatorSetString(INDICATOR_SHORTNAME,short_name); // Set data precision for digits // Assign labels for each plot PlotIndexSetString(0, PLOT_LABEL, "Bullish Order Block"); PlotIndexSetString(1, PLOT_LABEL, "Bearish Order Block"); PlotIndexSetString(2, PLOT_LABEL, "Take Profit 1"); PlotIndexSetString(3, PLOT_LABEL, "Take Profit 2"); PlotIndexSetString(4, PLOT_LABEL, "Stop Loss 1"); PlotIndexSetString(5, PLOT_LABEL, "Stop Loss 2");
4. Configuração inicial em OnInit:
Em OnInit configuramos o nome curto do indicador e os rótulos dos elementos gráficos (plots). Dessa forma, os elementos serão identificados corretamente na janela de dados.
//Buy double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); double tp1; double tp2; double sl1; double sl2; GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2); DrawTP_SL(tp1,tp2,sl1,sl2); tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1; tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2; sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1; sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2; time_ = 0; buscar_oba = true; //Sell double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); double tp1; double tp2; double sl1; double sl2; GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2); DrawTP_SL(tp1,tp2,sl1,sl2); tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1; tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2; sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1; sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2; time_b = 0; buscar_obb = true;
5. Configuração dos níveis de TP e SL ao abrir operações:
Por fim, configuramos os níveis de Take Profit e Stop Loss para operações de compra e venda. Para compras utilizamos o preço ask e calculamos os níveis correspondentes, para vendas utilizamos o preço bid. Em seguida exibimos no gráfico para que os níveis possam ser acompanhados.
undefined
| Passo | Compras | Vendas |
|---|---|---|
| Preço: | Obtém e normaliza o preço ask. | Obtém e normaliza o preço bid. |
| Variáveis: | São inicializadas as variáveis que armazenarão os valores de Take Profit e Stop Loss. (tp1, tp2, sl1 e sl2). | As mesmas variáveis são utilizadas para armazenar os níveis de Take Profit e Stop Loss. (tp1, tp2, sl1 e sl2). |
| Cálculo: | GetTP_SL calcula os níveis de TP e SL com base no preço ask para a operação de compra. | GetTP_SL calcula os níveis de TP e SL com base no preço bid para a operação de venda. |
| Renderização: | DrawTP_SL exibe visualmente no gráfico os níveis de TP e SL para a operação de compra. | DrawTP_SL exibe visualmente no gráfico os níveis de TP e SL para a operação de venda. |
| Buffer: | É utilizado iBarShift para encontrar o índice do candle atual e salvar TP e SL nos buffers. (tp1_buffer, tp2_buffer, sl1_buffer e sl2_buffer). | É utilizado iBarShift para salvar TP e SL nos mesmos buffers. (tp1_buffer, tp2_buffer, sl1_buffer e sl2_buffer). |
| Variáveis estáticas: | As variáveis estáticas são redefinidas para buscar novos blocos de ordens altistas na próxima iteração. (variáveis: "time_" e "buscar_oba"). | As variáveis estáticas são redefinidas para buscar novos blocos de ordens baixistas na próxima iteração. (variáveis: "time_b" e "search_obb"). |
Considerações finais
Nesta parte do artigo abordamos como criar um indicador Order Blocks baseado no volume da profundidade de mercado e como otimizar sua funcionalidade adicionando buffers adicionais ao indicador original.
Nosso resultado final:

Assim concluímos o desenvolvimento de nosso indicador Order Blocks. Nos futuros materiais, abordaremos a criação de uma classe de gerenciamento de riscos do zero e o desenvolvimento de um robô de negociação que integra esse gerenciamento de riscos, utilizando os buffers de sinal do nosso indicador para tomar decisões de forma mais precisa e automatizada.
Traduzido do espanhol pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/es/articles/16268
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.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 51): Aprendizado por Reforço com SAC
Gerenciamento de riscos (Parte 1): Fundamentos da construção de uma classe de gerenciamento de riscos
Desenvolvimento de um Kit de Ferramentas para Análise da Ação do Preço (Parte 6): Mean Reversion Signal Reaper
MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição
- 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
https://www.mql5.com/pt/articles/16268
5. Definição dos níveis de TP e SL ao abrir as negociações
Por fim, definimos os níveis de Take Profit e Stop Loss para as negociações de compra e venda. Para as negociações de compra, use o preço Ask; para as negociações de venda, use o preço Bid. Em seguida, desenhe as linhas TP e SL no gráfico para monitoramento.
Parece queisso poderia ser um pouco simplificado.