English Русский 中文 Español Deutsch 日本語
preview
Dominando a Dinâmica do Mercado: Criando um Expert Advisor (EA) para Estratégia de Suporte e Resistência

Dominando a Dinâmica do Mercado: Criando um Expert Advisor (EA) para Estratégia de Suporte e Resistência

MetaTrader 5Negociação | 4 novembro 2024, 10:39
333 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

Neste artigo, discutiremos a estratégia de negociação forex de Suporte e Resistência, no contexto da negociação puramente baseada em ação de preço, e a criação de um Expert Advisor (EA) com base nela. Vamos explorar a definição da estratégia, tipos, descrição e desenvolvimento em MetaQuotes Language 5 (MQL5) para MetaTrader 5 (MT5). Não discutiremos apenas a teoria por trás da estratégia, mas também seus respectivos conceitos, análise e identificação, e visualização no gráfico, o que a tornará uma ferramenta útil para os traders aprenderem a aumentar sua capacidade de prever movimentos do mercado, tomar melhores decisões e, eventualmente, se tornarem proficientes no gerenciamento de risco. Usando os seguintes tópicos, realizaremos o acima:

  1. Definição de Suporte e Resistência
  2. Descrição de Suporte e Resistência
  3. Tipos de Suportes e Resistências
  4. Descrição da estratégia de negociação
  5. Esboço da estratégia de negociação
  6. Implementação em MetaQuotes Language 5 (MQL5)
  7. Resultados do Strategy Tester
  8. Conclusão

Nesta jornada, usaremos extensivamente MetaQuotes Language 5 (MQL5) como nosso ambiente de codificação integrado (IDE), e executaremos os arquivos no terminal de negociação MetaTrader 5 (MT5). Portanto, ter as versões mencionadas será de suma importância. Vamos começar então.


Definição de Suporte e Resistência

A estratégia de negociação forex de suporte e resistência é uma ferramenta de análise fundamental usada por muitos traders forex para analisar e identificar níveis de preços em que o mercado provavelmente fará uma pausa ou reverterá. Tecnicamente, esses níveis tendem a ser rejeitados por preços históricos, o que os torna significativos ao longo do tempo, já que o preço faz pausas e reversões sobre eles, daí o nome suporte e resistência. Onde esses níveis são construídos, tipicamente com o preço ricocheteando nos níveis-chave várias vezes, indica um forte interesse de compra ou venda. 


Descrição de Suporte e Resistência

A descrição da estratégia de Suporte e Resistência gira em torno de sua aplicação em cenários de negociação. Os níveis de suporte tipicamente indicam uma barreira inferior que o preço luta para romper, sugerindo uma concentração de demanda, enquanto os níveis de resistência representam uma barreira superior indicativa de uma concentração de oferta. Os compradores geralmente entram no mercado nos níveis de suporte, e os preços tendem a subir, portanto, é um bom momento para os traders pensarem em comprar ou abrir posições longas. Por outro lado, os vendedores entram em ação nos níveis de resistência, e os preços podem cair, permitindo aos traders vender ou abrir posições curtas. Aqui está uma visualização do que queremos dizer.

S & R

A entrada no mercado é sempre dinâmica e depende do gosto e preferência de cada um, embora existam duas formas básicas de negociar os níveis. Alguns traders preferem negociar o rebote, comprando quando o preço cai em direção aos níveis de suporte e vendendo quando o preço sobe em direção aos níveis de resistência. Por outro lado, outros traders preferem negociar o rompimento, comprando quando o preço rompe os níveis de resistência e vendendo quando o preço rompe os níveis de suporte. Portanto, pode-se optar por "Fade the Break" ou "Trade the Break".


Tipos de Suportes e Resistências

Existem quatro tipos de níveis de suporte e resistência.

  • Níveis de suporte e resistência de números redondos: Esses níveis são formados pelo preço ricocheteando em um nível de preço semelhante, levando a um canal de preço horizontal. Por exemplo, as mínimas oscilantes de um mercado podem ter o mesmo nível de 0,65432, 0,65435 e 0,65437. Tipicamente, esses são os mesmos níveis, com um ângulo de declinação insignificante, indicando uma concentração de demanda de preço.
  • Níveis de suporte e demanda do canal de linha de tendência: Pontos de oscilação formados por linhas de tendência ascendentes ou descendentes criam zonas de oferta e demanda às quais os preços tendem a reagir.

TRENDLINE S&R

  • Níveis de suporte e resistência de Fibonacci: O Fibonacci é usado por traders para identificar zonas de reversão de preços, e essas zonas tendem a agir como zonas de oferta e demanda para os níveis de suporte e resistência.
  • Níveis de suporte e resistência do indicador: Indicadores técnicos como médias móveis fornecem zonas onde os preços tendem a reagir, criando pivôs para os níveis de suporte e resistência.

MA IND S&R


Descrição da estratégia de negociação

Como vimos, existem diferentes tipos de estratégias de suporte e resistência no universo forex. Para o artigo, vamos escolher e trabalhar com o tipo de números redondos horizontais, e então o mesmo conceito pode ser aplicado e adaptado para os outros tipos.

Primeiro, analisaremos o gráfico e obteremos as coordenadas de suporte e resistência. Depois de localizar as respectivas coordenadas, desenharemos os níveis no gráfico. Novamente, vimos que todo trader tem duas opções para negociar os níveis, ou seja, "fade" ou negociar o rompimento. No nosso caso, vamos optar por "fade the break". Abriremos posições de compra quando rompermos os suportes e abriremos posições de venda quando rompermos os níveis de resistência. Simples assim.


Esboço da estratégia de negociação

Para entender facilmente o conceito que transmitimos, vamos visualizá-lo em um esboço.

  • Nível de Resistência:

RESISTANCE BLUEPRINT

  • Nível de Suporte:

SUPPORT BLUEPRINT


Implementação em MetaQuotes Language 5 (MQL5)

Depois de aprender todas as teorias sobre a estratégia de negociação de Suporte e Resistência, vamos então automatizar a teoria e criar um Expert Advisor (EA) em MetaQuotes Language 5 (MQL5) para MetaTrader 5 (MT5).

Para criar um expert advisor (EA), no seu terminal MetaTrader 5, clique na guia Ferramentas e marque Editor de Linguagem MetaQuotes, ou simplesmente pressione F4 no seu teclado. Alternativamente, você pode clicar no ícone IDE (Ambiente de Desenvolvimento Integrado) na barra de ferramentas. Isso abrirá o ambiente MetaQuotes Language Editor, que permite escrever robôs de trading, indicadores técnicos, scripts e bibliotecas de funções.

OPEN METAEDITOR

Uma vez aberto o MetaEditor, na barra de ferramentas, navegue até a guia Arquivo e marque Novo Arquivo, ou simplesmente pressione CTRL + N, para criar um novo documento. Alternativamente, você pode clicar no ícone Novo na guia de ferramentas. Isso resultará em um pop-up do Assistente MQL.

NOVA EA

No Assistente que aparece, marque Expert Advisor (template) e clique em Próximo.

ASSISTENTE MQL

Nas propriedades gerais do Expert Advisor, na seção de nome, forneça o nome do arquivo do seu expert. Note que para especificar ou criar uma pasta, se ela não existir, use a barra invertida antes do nome do EA. Por exemplo, aqui temos "Experts" por padrão. Isso significa que nosso EA será criado na pasta Experts e podemos encontrá-lo lá. As outras seções são bastante diretas, mas você pode seguir o link na parte inferior do Assistente para saber como realizar o processo com precisão.

NOME DA EA

Depois de fornecer o nome do arquivo desejado do Expert Advisor, clique em Próximo, clique em Próximo novamente e, em seguida, clique em Concluir. Depois de fazer tudo isso, estamos prontos para codificar e programar nossa estratégia.

Primeiro, incluímos uma instância de trade usando #include no início do código-fonte. Isso nos dá acesso à classe CTrade, que usaremos para criar um objeto de trade. Isso é crucial, pois precisamos dele para abrir negociações.

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

O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os colchetes angulares indicam que o arquivo Trade.mqh será retirado do diretório padrão (geralmente é terminal_installation_directory\MQL5\Include). O diretório atual não está incluído na busca. A linha pode ser colocada em qualquer lugar do programa, mas geralmente todas as inclusões são colocadas no início do código-fonte, para uma melhor estrutura do código e referência mais fácil. A declaração do objeto obj_Trade da classe CTrade nos dará acesso aos métodos contidos nessa classe com facilidade, graças aos desenvolvedores do MQL5.

CTRADE CLASS

No escopo global, precisamos definir arrays que irão armazenar nossos dados de preços mais altos e mais baixos, que mais tarde manipularemos e analisaremos para encontrar os níveis de suporte e resistência. Depois que os níveis forem encontrados, precisaremos também armazená-los em um array, já que, obviamente, haverá mais de um.

double pricesHighest[], pricesLowest[];

double resistanceLevels[2], supportLevels[2];

Aqui, declaramos dois arrays do tipo double que irão armazenar os preços mais altos e mais baixos para uma quantidade estipulada de dados, e outros dois que irão armazenar os dados de níveis de suporte e resistência identificados e ordenados. Normalmente, são as duas coordenadas de cada nível. Note que as variáveis de preço estão vazias, o que as torna arrays dinâmicos sem um tamanho predefinido, o que significa que podem conter um número arbitrário de elementos com base nos dados fornecidos. Por outro lado, as variáveis de nível têm um tamanho fixo de dois, o que as torna arrays estáticos, significando que podem conter exatamente dois elementos cada. Se você pretende usar mais coordenadas, pode aumentar o tamanho delas para o número específico de pontos que considerar apropriado.

Finalmente, uma vez que identificarmos os níveis, precisaremos plotá-los no gráfico para fins de visualização. Portanto, temos que definir os nomes das linhas, suas respectivas cores atribuídas e seus respectivos prefixos para facilitar a identificação e garantir a exclusividade, caso haja vários experts na mesma conta de negociação. Isso permite que o EA seja compatível com outros EAs, pois identificará seus níveis e trabalhará com eles de forma eficaz e independente.

#define resLine "RESISTANCE LEVEL"
#define colorRes clrRed
#define resline_prefix "R"

#define supLine "SUPPORT LEVEL"
#define colorSup clrBlue
#define supline_prefix "S"

Usamos a palavra-chave #define para definir uma macro chamada "resLine" com o valor "RESISTANCE LEVEL" para facilmente armazenar o nome do nível de resistência, em vez de ter que reescrever repetidamente o nome em cada instância em que criarmos o nível, economizando significativamente tempo e reduzindo as chances de fornecer o nome incorretamente. Basicamente, macros são usadas para substituição de texto durante a compilação.

Da mesma forma, definimos a cor do nível de resistência como vermelho e, finalmente, definimos o prefixo "R" para os níveis de resistência, que usaremos para rotular e identificar as linhas de resistência no gráfico. Similarmente aos níveis de resistência, definimos os níveis de suporte, seguindo os mesmos critérios.

Uma vez que inicializamos o EA, precisamos definir nossos dados em uma série temporal, de modo que trabalharemos com os dados mais recentes primeiro e prepararemos nossos arrays de armazenamento para conter nossos dados. Isso é feito no manipulador de evento OnInit.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   ArraySetAsSeries(pricesHighest,true);
   ArraySetAsSeries(pricesLowest,true);
   // define the size of the arrays
   ArrayResize(pricesHighest,50);
   ArrayResize(pricesLowest,50);
//---
   return(INIT_SUCCEEDED);
}

Duas coisas distintas ocorrem. Primeiro, configuramos nossos arrays de armazenamento de preços como séries temporais usando a função embutida ArraySetAsSeries do MQL5, que leva dois argumentos: o array alvo e o sinalizador booleano, verdadeiro neste caso, para aceitar a conversão. Isso significa que os arrays serão indexados com os dados mais antigos no índice mais alto e os dados mais recentes no índice 0. Aqui está um exemplo. Digamos que recuperamos dados de 2020 a 2024. Aqui está o formato em que recebemos os dados.

Ano Data
2020 0
2021 1
2022 2
2023 3
2024 4

Os dados no formato acima não são convenientes de usar, pois estão organizados em ordem cronológica, onde os dados mais antigos estão indexados no primeiro índice, significando que são usados primeiro. É mais conveniente usar os dados mais recentes para análise primeiro e, portanto, precisamos organizar os dados em ordem cronológica inversa para obter resultados como abaixo.

Ano Data
2024 4
2023 3
2022 2
2021 1
2020 0

Para alcançar o formato acima programaticamente, utilizamos a função ArraySetAsSeries, como explicado anteriormente. Em segundo lugar, definimos o tamanho dos arrays usando a função ArrayResize e especificamos que cada um contenha cinquenta elementos. Isso poderia ser qualquer valor, apenas um valor arbitrário que adotamos, e você pode optar por ignorá-lo. No entanto, para formalidade, não precisamos de muitos dados em nossos arrays, pois planejamos ordenar os dados de preços recebidos para ter apenas os dez primeiros dados mais significativos, e o tamanho extra será reservado. Então, você pode ver por que não faz sentido ter um tamanho maior nos nossos arrays.

No manipulador de evento OnDeinit, precisamos eliminar os dados de armazenamento que estavam em uso da memória do computador. Isso ajudará a economizar recursos.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   //ArrayFree(resistanceLevels); // cannot be used for static allocated array
   //ArrayFree(supportLevels); // cannot be used for static allocated array
   
   ArrayRemove(resistanceLevels,0,WHOLE_ARRAY);
   ArrayRemove(supportLevels,0,WHOLE_ARRAY);
}

Usamos a função ArrayFree para eliminar os dados que ocupam a memória do computador, já que o EA não estará mais em uso e os dados se tornarão inúteis. A função é do tipo de dados void, que recebe apenas um único parâmetro ou argumento, o array dinâmico, e libera o seu buffer, definindo o tamanho da dimensão zero para 0. No entanto, para os arrays estáticos, onde armazenamos os preços de suporte e resistência, a função não pode ser usada. Isso não significa que não possamos eliminar os dados. Chamamos outra função, ArrayRemove, para remover os dados que desejamos descartar. A função é do tipo de dados booleano e leva três argumentos para remover um número especificado de elementos de um array. Especificamos a variável do array alvo e fornecemos o índice a partir do qual a operação de remoção começará. Neste caso, é zero, já que queremos remover tudo, e finalmente o número de elementos a serem removidos, neste caso, o array inteiro para eliminar tudo.

A maioria de nossas atividades será executada no manipulador de eventos OnTick. Isso será puramente ação de preço, e vamos depender fortemente deste manipulador de eventos. Portanto, vamos analisar os parâmetros que a função recebe além dela, já que é o coração deste código.

void OnTick(){
//---

}

Como já foi visto, esta é uma função simples, mas crucial, que não recebe argumentos nem retorna nada. É apenas uma função void, o que significa que não precisa retornar nada. Esta função é usada em Expert Advisors e é executada quando há um novo tick, ou seja, uma mudança nas cotações de preços para o ativo específico.

Agora que vimos que a função OnTick é gerada a cada mudança nas cotações de preços, precisamos definir alguma lógica de controle que nos permita executar o código uma vez por barra e não a cada tick, pelo menos para evitar execuções desnecessárias de código, economizando assim a memória do dispositivo. Isso será necessário ao buscar níveis de suporte e resistência. Não precisamos procurar os níveis em cada tick, pois sempre obteremos os mesmos resultados, desde que ainda estejamos na mesma vela. Aqui está a lógica:

   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;

Primeiro, declaramos uma variável inteira "currBars" que armazena o número calculado de barras atuais no gráfico para o símbolo de negociação especificado e período ou intervalo de tempo. Isso é obtido usando a função iBars, que leva apenas dois argumentos, ou seja, símbolo e período. 

Em seguida, declaramos outra variável inteira estática "prevBars" para armazenar o número total de barras anteriores no gráfico quando uma nova barra é gerada e inicializamos com o valor das barras atuais no gráfico para a primeira execução da função. Usaremos essa variável para comparar o número atual de barras com o número anterior de barras, para determinar a ocorrência de uma nova geração de barra no gráfico.

Finalmente, usamos uma instrução condicional para verificar se o número atual de barras é igual ao número anterior de barras. Se forem iguais, significa que nenhuma nova barra foi formada, então terminamos a execução e retornamos. Caso contrário, se as contagens de barras atuais e anteriores não forem iguais, isso indica que uma nova barra foi formada. Nesse caso, prosseguimos para atualizar a variável de barras anteriores para as barras atuais, para que no próximo tick seja igual ao número de barras no gráfico, a menos que avancemos para uma nova barra.

As barras a serem consideradas para análise são apenas as barras visíveis no gráfico. Isso porque não precisamos considerar os dados mais antigos, como dez milhões de barras, já que seriam inúteis. Imagine a situação em que você tem um nível de suporte que remonta ao ano anterior. Não faz sentido, certo? Portanto, agora, consideramos apenas as barras que são visíveis no gráfico, já que são os dados mais recentes viáveis para as condições atuais do mercado. Para fazer isso, usamos a lógica abaixo.

   int visible_bars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);

Declaramos uma variável inteira "visible_bars" e usamos a função ChartGetInteger para obter o total de barras visíveis no gráfico. É uma função do tipo de dados long e, portanto, fazemos a conversão para inteiro adicionando (int) antes da função. Claro que poderíamos definir nossa variável alvo como long, mas não precisamos alocar tantos bytes de memória. 

Para procurar os níveis, teremos que fazer um loop por cada barra. Um loop for é essencial para isso.

   for (int i=1; i<=visible_bars-1; i++){
   ...
   
   }

O loop é feito primeiro inicializando a variável inteira do contador do loop "i" para um, para designar o ponto de partida do loop. Um significa que começamos na barra anterior à barra atual, já que a barra atual está em estágio de formação e, portanto, ainda está indefinida. Pode resultar em qualquer coisa. Em seguida, a condição que deve ser verdadeira para que o loop continue executando. Enquanto "i" for menor ou igual ao total de barras sendo consideradas menos um, o loop continuará executando. Por fim, incrementamos o contador do loop "i" em um cada vez que o loop é executado. Basicamente, "i++" é o mesmo que "i=i+1". Também poderíamos usar um loop decremental que teria o operador de decremento "--", levando a uma análise que começa com a barra mais antiga até a barra atual, mas optamos por um loop incremental para que a análise das barras comece pela barra mais recente até a mais antiga.

Em cada loop, selecionamos uma barra ou vela e, portanto, precisamos obter as propriedades da barra. Neste caso, apenas as propriedades de abertura, máxima, mínima, fechamento e tempo são importantes para nós.

      double open = iOpen(_Symbol,_Period,i);
      double high = iHigh(_Symbol,_Period,i);
      double low = iLow(_Symbol,_Period,i);
      double close = iClose(_Symbol,_Period,i);
      datetime time = iTime(_Symbol,_Period,i);

Aqui, declaramos as variáveis de tipo de dados correspondentes e as inicializamos com os dados correspondentes. Por exemplo, usamos a função iOpen para obter o preço de abertura da barra, fornecendo o nome do símbolo do instrumento financeiro, seu período e índice da barra alvo.

Depois de obter os dados da barra, precisaremos verificar os dados em relação ao restante das barras anteriores para encontrar uma barra que tenha os mesmos dados que a selecionada, o que indicará um nível ao qual o preço reagiu várias vezes no passado. No entanto, não faz sentido ter um nível de suporte ou resistência que compreenda dois níveis consecutivos. Os níveis devem estar, pelo menos, distantes um do outro. Vamos primeiro definir isso.

      int diff_i_j = 10;

Definimos uma variável inteira que irá armazenar a diferença em barras entre a barra atual e a barra que deve ser considerada para a verificação de uma correspondência no mesmo nível. No nosso caso, essa diferença é 10. Visualmente representado, aqui está o que queremos dizer.

DIFERENÇA DE BARRAS

Agora podemos iniciar um loop que incorpora essa lógica.

      for (int j=i+diff_i_j; j<=visible_bars-1; j++){
      ...
      
      }

Para o loop interno, usamos a variável inteira "j", que começa em dez barras a partir da barra atual e vai até a penúltima barra. Para entender facilmente isso e ficar confortável com os resultados antes de prosseguir, vamos visualizá-lo imprimindo a saída.

//Print in the outer loop
      Print(":: BAR NO: ",i);

      //Print in the inner loop
         Print("BAR CHECK NO: ",j);

BARRAS SELECIONADAS NO LOOP

Você pode ver que, por exemplo, para uma barra selecionada no índice 15, inicializamos o loop 10 barras a partir da barra atualmente selecionada. Matematicamente, isso seria 15+10=25. Então, a partir da 25ª barra, o loop executa até a penúltima barra, que neste caso é 33.

Agora que podemos selecionar corretamente o intervalo das barras conforme necessário, também podemos obter as propriedades das barras selecionadas.

         double open_j = iOpen(_Symbol,_Period,j);
         double high_j = iHigh(_Symbol,_Period,j);
         double low_j = iLow(_Symbol,_Period,j);
         double close_j = iClose(_Symbol,_Period,j);
         datetime time_j = iTime(_Symbol,_Period,j);

A mesma lógica da recuperação de propriedades do loop externo é considerada. A única diferença é que definimos nossas variáveis com um sublinhado extra "j" para indicar que as propriedades são do loop interno e o índice alvo da barra muda para "j".

Como agora temos todos os dados de preços necessários, podemos prosseguir para verificar os níveis de suporte e resistência.

         // CHECK FOR RESISTANCE
         double high_diff = NormalizeDouble((MathAbs(high-high_j)/_Point),0);
         bool is_resistance = high_diff <= 10;
         
         // CHECK FOR SUPPORT
         double low_diff = NormalizeDouble((MathAbs(low-low_j)/_Point),0);
         bool is_support = low_diff <= 10;

Para verificar os níveis de resistência, definimos uma variável do tipo double chamada "high_diff", que irá armazenar os dados para a diferença entre a máxima da barra atualmente selecionada no loop externo e a barra atualmente selecionada no loop interno. A função MathAbs é usada para garantir que o resultado seja um número positivo, independentemente de qual preço seja maior, retornando o valor absoluto ou módulo da entrada. Por exemplo, poderíamos ter 0,65432 - 0,05456 = -0,00024. Nossa resposta contém um número negativo, mas a função irá ignorar o sinal negativo e fornecer 0,00024. Em seguida, dividimos o resultado pelo ponto, a menor variação de preço possível de um instrumento, para obter a forma pontual da diferença. Usando nosso exemplo novamente, isso seria 0,00024/0,00001 = 24,0. Por fim, apenas para ser preciso, formatamos o número de ponto flutuante para um número especificado de dígitos usando a função NormalizeDouble. Neste caso, temos zero, o que significa que nossa saída será um número inteiro. Usando nosso exemplo novamente, teríamos 24 sem o ponto decimal.

Em seguida, verificamos se a diferença é menor ou igual a dez, um intervalo predefinido no qual a diferença é aceitável, e armazenamos o resultado em uma variável booleana "is_resistance". A mesma lógica se aplica à verificação do nível de suporte.

Se as condições dos nossos níveis forem atendidas, nossos níveis de suporte e resistência serão verdadeiros, caso contrário, serão falsos. Para ver se podemos identificar os níveis, vamos imprimi-los no diário. Para verificar tudo, imprimimos os preços das coordenadas dos níveis de resistência, juntamente com suas diferenças de preço para confirmar que atendem às nossas condições.

         if (is_resistance){
            Print("RESISTANCE AT BAR ",i," (",high,") & ",j," (",high_j,"), Pts = ",high_diff);
          ...  
         }

Aqui está o resultado que obtemos.

IMPRESSÕES DE RESISTÊNCIA

Podemos identificar os níveis, mas eles são apenas quaisquer níveis. Como queremos níveis que estejam nos pontos mais altos ou mais baixos, precisaremos de alguma lógica de controle extra para garantir que consideramos apenas os níveis mais significativos. Para conseguir isso, precisaremos copiar os preços máximos e mínimos das barras em consideração, ordená-los em ordem crescente e decrescente, respectivamente, e então pegar a primeira e a última quantidade de barras necessárias, respectivamente. Fazemos isso antes dos loops "for".

   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   int copiedBarsHighs = CopyHigh(_Symbol,_Period,1,visible_bars,pricesHighest);
   int copiedBarsLows = CopyLow(_Symbol,_Period,1,visible_bars,pricesLowest);

Antes do armazenamento, liberamos nossos arrays de quaisquer dados. Em seguida, copiamos as máximas das barras para o array de destino. Isso é realizado através do uso da função do tipo de dados inteiro CopyHigh, fornecendo o símbolo, período, índice inicial da barra a ser copiada, o número de barras e o array de armazenamento de destino. O resultado, que é a quantidade de barras copiadas, é atribuído à variável inteira "copiedBarsHighs". O mesmo é feito para os preços mínimos. Para garantir que obtivemos os dados, imprimimos os arrays no diário usando a função ArrayPrint.

   ArrayPrint(pricesHighest);
   ArrayPrint(pricesLowest);

Estes são os resultados que obtemos.

DADOS NÃO ORDENADOS

Em seguida, ordenamos os dados em ordem crescente usando a função ArraySort e imprimimos os resultados novamente.

         // sort the array in ascending order
   ArraySort(pricesHighest);
   ArraySort(pricesLowest);

   ArrayPrint(pricesHighest);
   ArrayPrint(pricesLowest);

Aqui está o que obtemos.

DADOS ORDENADOS EM ORDEM CRESCENTE

Finalmente, precisamos obter os dez primeiros preços máximos e os dez últimos preços mínimos, dados que formarão nossos pontos extremos.

   ArrayRemove(pricesHighest,10,WHOLE_ARRAY);

Para obter os dez primeiros preços máximos das barras mais altas, usamos a função ArrayRemove e fornecemos o array de destino, o índice onde a remoção começa, no nosso caso dez, e finalmente o número de elementos a serem removidos, que no nosso caso é o restante dos dados.

Para obter os dez últimos preços mínimos das barras mais baixas, uma operação semelhante é realizada, mas com um método mais complexo e menos direto.

   ArrayRemove(pricesLowest,0,visible_bars-10);

Usamos a mesma função, mas nosso índice inicial é zero, já que não estamos interessados nos primeiros valores, e a contagem é o número total de barras em consideração menos dez. Quando imprimimos os dados, obtemos a seguinte saída.

DADOS FIXOS FINAIS NECESSÁRIOS

Agora que temos os dados das barras mais altas, podemos continuar a verificar os níveis e determinar as configurações válidas. Iniciamos outro loop "for" para realizar a operação.

            for (int k=0; k<ArraySize(pricesHighest); k++){
            ...
            }

Desta vez, nossa variável de contagem "k" começa do zero, já que queremos considerar todos os preços no array. 

Como queremos encontrar correspondências de preços, declaramos variáveis de armazenamento booleanas que irão armazenar as bandeiras dos resultados da correspondência, fora dos loops "for", e as inicializamos como falsas.

   bool matchFound_high1 = false, matchFound_low1 = false;
   bool matchFound_high2 = false, matchFound_low2 = false;

Se o preço armazenado selecionado for igual à máxima da barra no primeiro loop, definimos a bandeira para a primeira máxima encontrada como verdadeira e informamos sobre a ocorrência. Da mesma forma, se o preço armazenado eleito for igual à máxima da barra no segundo loop, definimos a bandeira para a segunda máxima encontrada como verdadeira e informamos sobre a ocorrência.

               if (pricesHighest[k]==high){
                  matchFound_high1 = true;
                  Print("> RES H1(",high,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (pricesHighest[k]==high_j){
                  matchFound_high2 = true;
                  Print("> RES H2(",high_j,") FOUND @ ",k," (",pricesHighest[k],")");
               }

Se a correspondência para as duas coordenadas for encontrada, mas os níveis de resistência atuais forem iguais aos preços, isso significa que já temos os níveis. Portanto, não precisamos continuar criando mais níveis de resistência. Informamos sobre a ocorrência, definimos a bandeira "stop_processing" como verdadeira e encerramos o loop prematuramente.

               if (matchFound_high1 && matchFound_high2){
                  if (resistanceLevels[0]==high || resistanceLevels[1]==high_j){
                     Print("CONFIRMED BUT This is the same resistance level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  ...
                  
               }

A bandeira "stop_processing" é definida fora dos loops "for" na parte superior e incorporada à lógica de execução do primeiro loop "for" para garantir que economizamos recursos.

   bool stop_processing = false; // Flag to control outer loop

//...
   for (int i=1; i<=visible_bars-1 && !stop_processing; i++){
      ...
   
   }

Caso contrário, se a correspondência para as duas coordenadas for encontrada, mas elas não forem iguais aos preços atuais, isso significa que temos outro novo nível de resistência e podemos atualizá-lo para os dados mais recentes. 

                  else {
                     Print(" ++++++++++ RESISTANCE LEVELS CONFIRMED @ BARS ",i,
                     "(",high,") & ",j,"(",high_j,")");
                     resistanceLevels[0] = high;
                     resistanceLevels[1] = high_j;
                     ArrayPrint(resistanceLevels);
                     
                     ...
                  }

Aqui está a visualização dos resultados que obtemos.

NÍVEIS DE RESISTÊNCIA CONFIRMADOS

Para visualizar os níveis, vamos mapeá-los no gráfico.

                     draw_S_R_Level(resLine,high,colorRes,5);
                     draw_S_R_Level_Point(resline_prefix,high,time,218,-1,colorRes,90);
                     draw_S_R_Level_Point(resline_prefix,high,time_j,218,-1,colorRes,90);

                     stop_processing = true; // Set the flag to stop processing
                     break;

Utilizamos duas funções para o trabalho. A primeira função, draw_S_R_Level, recebe o nome da linha a ser desenhada, o preço, a cor e a largura da linha.

void draw_S_R_Level(string levelName,double price,color clr,int width){
   if (ObjectFind(0,levelName) < 0){
      ObjectCreate(0,levelName,OBJ_HLINE,0,TimeCurrent(),price);
      ObjectSetInteger(0,levelName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,levelName,OBJPROP_WIDTH,width);
   }
   else {
      ObjectSetDouble(0,levelName,OBJPROP_PRICE,price);
   }
   ChartRedraw(0);
}

A função é do tipo de dados void, o que significa que ela não precisa retornar nada. Em seguida, utilizamos uma instrução condicional para verificar se o objeto existe, usando a função ObjectFind, que retorna um número inteiro negativo caso o objeto não seja encontrado. Se esse for o caso, prosseguimos para criar o objeto identificado como OBJ_HLINE, no tempo atual e no preço especificado, uma vez que requer apenas uma única coordenada. Então, definimos sua cor e largura. Se o objeto for encontrado, apenas atualizamos seu preço para o preço especificado e redesenhamos o gráfico para aplicar as alterações atuais. Essa função desenha apenas uma linha simples no gráfico. Aqui está o que obtemos.

LINHA DE RESISTÊNCIA SIMPLES

A segunda função, draw_S_R_Level_Point, recebe o nome da linha a ser desenhada, o preço, o tempo, o código da seta, a direção, a cor e o ângulo da etiqueta de descrição. Essa função desenha os pontos de nível para que eles fiquem mais definidos na linha de resistência que foi desenhada.
void draw_S_R_Level_Point(string objName,double price,datetime time,
      int arrowcode,int direction,color clr,double angle){
   //objName = " ";
   StringConcatenate(objName,objName," @ \nTime: ",time,"\nPrice: ",DoubleToString(price,_Digits));
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)) {
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,10);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
   }
   string prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }
   ChartRedraw(0);
}

A função personalizada "draw_S_R_Level_Point" recebe sete parâmetros para facilitar sua reutilização. As funções dos parâmetros são as seguintes:

  • objName: Uma string que representando o nome do objeto gráfico a ser criado.
  • price: Um valor duplo representando a coordenada de preço onde o objeto deve ser colocado.
  • time: Um valor de data e hora indicando a coordenada de tempo onde o objeto deve ser colocado.
  • arrowCode: Um número inteiro especificando o código da seta para o objeto seta.
  • direction: Um número inteiro indicando a direção (para cima ou para baixo) para posicionar a etiqueta de texto.
  • clr: Um valor de cor (por exemplo, clrBlue, clrRed) para os objetos gráficos.
  • angle: Ângulo de orientação da etiqueta de descrição.

A função primeiro concatena o nome do objeto com o tempo e o preço para a distinção dos pontos de nível. Isso garante que, ao passar o cursor sobre a etiqueta, apareça um pop-up da descrição com o tempo e preço específicos e únicos da coordenada.

A função então verifica se um objeto com o objName especificado já existe no gráfico. Caso contrário, ela prossegue para criar os objetos. A criação do objeto é realizada com o uso da função interna "ObjectCreate", que requer a especificação do objeto a ser desenhado, neste caso, o objeto seta identificado como "OBJ_ARROW", bem como o tempo e o preço, que formam as coordenadas do ponto de criação do objeto. Depois, configuramos as propriedades do objeto, como código da seta, cor, tamanho da fonte e ponto de ancoragem. Para o código da seta, o MQL5 já possui alguns caracteres predefinidos da fonte wingdings que podem ser usados diretamente. Aqui está uma tabela especificando os caracteres:

WINGDINGS

Até este ponto, apenas desenhamos a seta especificada no gráfico conforme abaixo:

RESISTÊNCIA + SETA

Podemos ver que conseguimos desenhar os pontos de resistência com o código de seta especificado; neste caso, usamos o código de seta 218, mas não há descrição deles. Portanto, para adicionar a respectiva descrição, prosseguimos para concatenar a seta com um texto. Criamos outro objeto de texto especificado como "OBJ_TEXT" e configuramos suas respectivas propriedades. A etiqueta de texto serve como uma anotação descritiva associada aos pontos de resistência, proporcionando contexto adicional ou informações sobre os pontos de resistência, tornando-os mais informativos para traders e analistas. Escolhemos o valor do texto para ser um preço especificado, significando que é um ponto de resistência.

A variável "objNameDescription" é então criada concatenando o "objName" original com o texto descritivo. Este nome combinado garante que a seta e seu rótulo de texto associado estejam vinculados. Este trecho de código específico é usado para conseguir isso.

   string prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }

Este é o resultado da concatenação dos pontos de resistência com suas descrições.

RESISTÊNCIA + SETA + DESCRIÇÃO

Concomitantemente, para mapear os níveis de suporte, aplica-se a mesma lógica, mas com condições inversas.

         else if (is_support){
            //Print("SUPPORT AT BAR ",i," (",low,") & ",j," (",low_j,"), Pts = ",low_diff);
            
            for (int k=0; k<ArraySize(pricesLowest); k++){
               if (pricesLowest[k]==low){
                  matchFound_low1 = true;
                  //Print("> SUP L1(",low,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (pricesLowest[k]==low_j){
                  matchFound_low2 = true;
                  //Print("> SUP L2(",low_j,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (matchFound_low1 && matchFound_low2){
                  if (supportLevels[0]==low || supportLevels[1]==low_j){
                     Print("CONFIRMED BUT This is the same support level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  else {
                     Print(" ++++++++++ SUPPORT LEVELS CONFIRMED @ BARS ",i,
                     "(",low,") & ",j,"(",low_j,")");
                     supportLevels[0] = low;
                     supportLevels[1] = low_j;
                     ArrayPrint(supportLevels);
                     
                     draw_S_R_Level(supLine,low,colorSup,5);
                     draw_S_R_Level_Point(supline_prefix,low,time,217,1,colorSup,-90);
                     draw_S_R_Level_Point(supline_prefix,low,time_j,217,1,colorSup,-90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }

O resultado final que obtemos devido à identificação dos níveis e seu respectivo mapeamento no marco do gráfico é como abaixo.

NÍVEIS DE SUPORTE E RESISTÊNCIA

Em seguida, procedemos para monitorar os níveis e, caso os níveis saiam da proximidade das barras visíveis, consideramos o nível de resistência inválido e o excluímos. Para alcançar isso, precisamos encontrar as linhas de nível de objeto e, quando encontradas, verificamos as condições para sua validade.

   if (ObjectFind(0,resLine) >= 0){
      double objPrice = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      double visibleHighs[];
      ArraySetAsSeries(visibleHighs,true);
      CopyHigh(_Symbol,_Period,1,visible_bars,visibleHighs);
      //Print("Object Found & visible bars is: ",ArraySize(visibleHighs));
      //ArrayPrint(visibleHighs);
      bool matchHighFound = false;

      ...
   }

Aqui, verificamos se o objeto de linha de resistência é encontrado e, se sim, obtemos seu preço. Copiamos novamente os altos das barras visíveis no gráfico e os armazenamos na variável de array double visibleHighs.

Depois, percorremos os preços altos e tentamos encontrar se há uma correspondência entre o preço da barra atualmente selecionada e o preço da linha de resistência. Se houver correspondência, definimos a bandeira matchHighFound como verdadeira e encerramos o loop.

      for (int i=0; i<ArraySize(visibleHighs); i++){
         if (visibleHighs[i] == objPrice){
            Print("> Match price for resistance found at bar # ",i+1," (",objPrice,")");
            matchHighFound = true;
            break;
         }
      }

Caso não haja correspondência, isso significa que o nível de resistência está fora da proximidade. Informamos sobre a instância e usamos uma função personalizada para excluir o objeto.

      if (!matchHighFound){
         Print("(",objPrice,") > Match price for the resistance line not found. Delete!");
         deleteLevel(resLine);
      }

A função personalizada deleteLevel aceita apenas um argumento, o nome do nível a ser excluído, e usa a função ObjectDelete para excluir o objeto definido.

void deleteLevel(string levelName){
   ObjectDelete(0,levelName);
   ChartRedraw(0);
}

A mesma lógica se aplica à linha de nível de suporte, mas prevalecem as condições inversas.

   if (ObjectFind(0,supLine) >= 0){
      double objPrice = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      double visibleLows[];
      ArraySetAsSeries(visibleLows,true);
      CopyLow(_Symbol,_Period,1,visible_bars,visibleLows);
      //Print("Object Found & visible bars is: ",ArraySize(visibleLows));
      //ArrayPrint(visibleLows);
      bool matchLowFound = false;
      
      for (int i=0; i<ArraySize(visibleLows); i++){
         if (visibleLows[i] == objPrice){
            Print("> Match price for support found at bar # ",i+1," (",objPrice,")");
            matchLowFound = true;
            break;
         }
      }
      if (!matchLowFound){
         Print("(",objPrice,") > Match price for the support line not found. Delete!");
         deleteLevel(supLine);
      }
   }

Finalmente, se até este ponto os níveis de resistência e suporte ainda estiverem dentro do gráfico, significa que são válidos e, portanto, podemos continuar a criar uma lógica que determinará se eles foram rompidos e abrirá uma posição de mercado. Vamos considerar primeiro a quebra do nível de resistência.

Como o preço pode romper acima do nível de resistência várias vezes, levando a gerações múltiplas de sinal, precisamos de uma lógica para garantir que, uma vez que rompemos um nível de resistência e geramos um sinal, não acionaremos o sinal novamente ao rompê-lo posteriormente se for o mesmo nível. Para isso, declaramos uma variável double estática que armazenará nosso preço para o sinal, que manterá seu valor até que tenhamos um sinal diferente.

   static double ResistancePriceTrade = 0;
   if (ObjectFind(0,resLine) >= 0){
      double ResistancePriceLevel = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      if (ResistancePriceTrade != ResistancePriceLevel){
      ...

   }

Em seguida, verificamos a existência da linha de resistência e, se ela existir, obtemos seu preço. Usando uma instrução condicional, verificamos se o sinal não é igual ao preço da linha de resistência, o que significa que ainda não temos um sinal gerado para aquele nível específico e que podemos prosseguir para verificar o sinal de rompimento. Para realizar a verificação, precisaremos dos dados da barra anterior, bem como das cotações de preços atualizadas.

         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

Em seguida, usamos instruções condicionais para verificar se o preço rompe acima do nível de resistência. Se sim, informamos sobre o sinal de venda via impressão no diário. Depois, usamos nosso objeto de negociação e operador de ponto para acessar o método de entrada de venda e fornecer os parâmetros necessários. Finalmente, atualizamos o valor da variável de sinal para o nível de resistência atual para não gerar outro sinal baseado no mesmo nível de resistência.

         if (open1 > close1 && open1 < ResistancePriceLevel
            && high1 > ResistancePriceLevel && Bid < ResistancePriceLevel){
            Print("$$$$$$$$$$$$ SELL NOW SIGNAL!");
            obj_Trade.Sell(0.01,_Symbol,Bid,Bid+350*5*_Point,Bid-350*_Point);
            ResistancePriceTrade = ResistancePriceLevel;
         }

A mesma lógica se aplica à lógica de rompimento de suporte, mas prevalecem as condições inversas.

   static double SupportPriceTrade = 0;
   if (ObjectFind(0,supLine) >= 0){
      double SupportPriceLevel = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      if (SupportPriceTrade != SupportPriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 < close1 && open1 > SupportPriceLevel
            && low1 < SupportPriceLevel && Ask > SupportPriceLevel){
            Print("$$$$$$$$$$$$ BUY NOW SIGNAL!");
            obj_Trade.Buy(0.01,_Symbol,Ask,Ask-350*5*_Point,Ask+350*_Point);
            SupportPriceTrade = SupportPriceLevel;
         }
         
      }
   }

Aqui está a representação do marco.

COMÉRCIOS COMPLETOS DE SUPORTE E RESISTÊNCIA

A seguir está o código completo necessário para criar uma estratégia de negociação forex de Suporte e Resistência em MQL5, que identifica os níveis, mapeia-os no gráfico e abre posições de mercado, respectivamente.

//+------------------------------------------------------------------+
//|                                       RESISTANCE AND SUPPORT.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

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

//bool stop_processing = false;

double pricesHighest[], pricesLowest[];

double resistanceLevels[2], supportLevels[2];


#define resLine "RESISTANCE LEVEL"
#define colorRes clrRed
#define resline_prefix "R"

#define supLine "SUPPORT LEVEL"
#define colorSup clrBlue
#define supline_prefix "S"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   ArraySetAsSeries(pricesHighest,true);
   ArraySetAsSeries(pricesLowest,true);
   // define the size of the arrays
   ArrayResize(pricesHighest,50);
   ArrayResize(pricesLowest,50);
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   //ArrayFree(resistanceLevels); // cannot be used for static allocated array
   //ArrayFree(supportLevels); // cannot be used for static allocated array
   
   ArrayRemove(resistanceLevels,0,WHOLE_ARRAY);
   ArrayRemove(supportLevels,0,WHOLE_ARRAY);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   
   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;
   
   int visible_bars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   bool stop_processing = false; // Flag to control outer loop
   bool matchFound_high1 = false, matchFound_low1 = false;
   bool matchFound_high2 = false, matchFound_low2 = false;
   
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   int copiedBarsHighs = CopyHigh(_Symbol,_Period,1,visible_bars,pricesHighest);
   int copiedBarsLows = CopyLow(_Symbol,_Period,1,visible_bars,pricesLowest);
   
   //ArrayPrint(pricesHighest);
   //ArrayPrint(pricesLowest);
         // sort the array in ascending order
   ArraySort(pricesHighest);
   ArraySort(pricesLowest);
   //ArrayPrint(pricesHighest);
   //ArrayPrint(pricesLowest);
   ArrayRemove(pricesHighest,10,WHOLE_ARRAY);
   ArrayRemove(pricesLowest,0,visible_bars-10);
   //Print("FIRST 10 HIGHEST PRICES:");
   //ArrayPrint(pricesHighest);
   //Print("LAST 10 LOWEST PRICES:");
   //ArrayPrint(pricesLowest);
   
   for (int i=1; i<=visible_bars-1 && !stop_processing; i++){
      //Print(":: BAR NO: ",i);
      double open = iOpen(_Symbol,_Period,i);
      double high = iHigh(_Symbol,_Period,i);
      double low = iLow(_Symbol,_Period,i);
      double close = iClose(_Symbol,_Period,i);
      datetime time = iTime(_Symbol,_Period,i);
      
      int diff_i_j = 10;
      
      for (int j=i+diff_i_j; j<=visible_bars-1; j++){
         //Print("BAR CHECK NO: ",j);
         double open_j = iOpen(_Symbol,_Period,j);
         double high_j = iHigh(_Symbol,_Period,j);
         double low_j = iLow(_Symbol,_Period,j);
         double close_j = iClose(_Symbol,_Period,j);
         datetime time_j = iTime(_Symbol,_Period,j);
         
         // CHECK FOR RESISTANCE
         double high_diff = NormalizeDouble((MathAbs(high-high_j)/_Point),0);
         bool is_resistance = high_diff <= 10;
         
         // CHECK FOR SUPPORT
         double low_diff = NormalizeDouble((MathAbs(low-low_j)/_Point),0);
         bool is_support = low_diff <= 10;
         
         if (is_resistance){
            //Print("RESISTANCE AT BAR ",i," (",high,") & ",j," (",high_j,"), Pts = ",high_diff);
            
            for (int k=0; k<ArraySize(pricesHighest); k++){
               if (pricesHighest[k]==high){
                  matchFound_high1 = true;
                  //Print("> RES H1(",high,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (pricesHighest[k]==high_j){
                  matchFound_high2 = true;
                  //Print("> RES H2(",high_j,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (matchFound_high1 && matchFound_high2){
                  if (resistanceLevels[0]==high || resistanceLevels[1]==high_j){
                     Print("CONFIRMED BUT This is the same resistance level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurily
                  }
                  else {
                     Print(" ++++++++++ RESISTANCE LEVELS CONFIRMED @ BARS ",i,
                     "(",high,") & ",j,"(",high_j,")");
                     resistanceLevels[0] = high;
                     resistanceLevels[1] = high_j;
                     ArrayPrint(resistanceLevels);
                     
                     draw_S_R_Level(resLine,high,colorRes,5);
                     draw_S_R_Level_Point(resline_prefix,high,time,218,-1,colorRes,90);
                     draw_S_R_Level_Point(resline_prefix,high,time_j,218,-1,colorRes,90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }
         
         else if (is_support){
            //Print("SUPPORT AT BAR ",i," (",low,") & ",j," (",low_j,"), Pts = ",low_diff);
            
            for (int k=0; k<ArraySize(pricesLowest); k++){
               if (pricesLowest[k]==low){
                  matchFound_low1 = true;
                  //Print("> SUP L1(",low,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (pricesLowest[k]==low_j){
                  matchFound_low2 = true;
                  //Print("> SUP L2(",low_j,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (matchFound_low1 && matchFound_low2){
                  if (supportLevels[0]==low || supportLevels[1]==low_j){
                     Print("CONFIRMED BUT This is the same support level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  else {
                     Print(" ++++++++++ SUPPORT LEVELS CONFIRMED @ BARS ",i,
                     "(",low,") & ",j,"(",low_j,")");
                     supportLevels[0] = low;
                     supportLevels[1] = low_j;
                     ArrayPrint(supportLevels);
                     
                     draw_S_R_Level(supLine,low,colorSup,5);
                     draw_S_R_Level_Point(supline_prefix,low,time,217,1,colorSup,-90);
                     draw_S_R_Level_Point(supline_prefix,low,time_j,217,1,colorSup,-90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }
         
         
         
         if (stop_processing){break;}
      }
      if (stop_processing){break;}
   }
   
   if (ObjectFind(0,resLine) >= 0){
      double objPrice = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      double visibleHighs[];
      ArraySetAsSeries(visibleHighs,true);
      CopyHigh(_Symbol,_Period,1,visible_bars,visibleHighs);
      //Print("Object Found & visible bars is: ",ArraySize(visibleHighs));
      //ArrayPrint(visibleHighs);
      bool matchHighFound = false;
      
      for (int i=0; i<ArraySize(visibleHighs); i++){
         if (visibleHighs[i] == objPrice){
            Print("> Match price for resistance found at bar # ",i+1," (",objPrice,")");
            matchHighFound = true;
            break;
         }
      }
      if (!matchHighFound){
         Print("(",objPrice,") > Match price for the resistance line not found. Delete!");
         deleteLevel(resLine);
      }
   }
   
   if (ObjectFind(0,supLine) >= 0){
      double objPrice = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      double visibleLows[];
      ArraySetAsSeries(visibleLows,true);
      CopyLow(_Symbol,_Period,1,visible_bars,visibleLows);
      //Print("Object Found & visible bars is: ",ArraySize(visibleLows));
      //ArrayPrint(visibleLows);
      bool matchLowFound = false;
      
      for (int i=0; i<ArraySize(visibleLows); i++){
         if (visibleLows[i] == objPrice){
            Print("> Match price for support found at bar # ",i+1," (",objPrice,")");
            matchLowFound = true;
            break;
         }
      }
      if (!matchLowFound){
         Print("(",objPrice,") > Match price for the support line not found. Delete!");
         deleteLevel(supLine);
      }
   }
   
   static double ResistancePriceTrade = 0;
   if (ObjectFind(0,resLine) >= 0){
      double ResistancePriceLevel = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      if (ResistancePriceTrade != ResistancePriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 > close1 && open1 < ResistancePriceLevel
            && high1 > ResistancePriceLevel && Bid < ResistancePriceLevel){
            Print("$$$$$$$$$$$$ SELL NOW SIGNAL!");
            obj_Trade.Sell(0.01,_Symbol,Bid,Bid+350*5*_Point,Bid-350*_Point);
            ResistancePriceTrade = ResistancePriceLevel;
         }
         
      }
   }
   
   static double SupportPriceTrade = 0;
   if (ObjectFind(0,supLine) >= 0){
      double SupportPriceLevel = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      if (SupportPriceTrade != SupportPriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 < close1 && open1 > SupportPriceLevel
            && low1 < SupportPriceLevel && Ask > SupportPriceLevel){
            Print("$$$$$$$$$$$$ BUY NOW SIGNAL!");
            obj_Trade.Buy(0.01,_Symbol,Ask,Ask-350*5*_Point,Ask+350*_Point);
            SupportPriceTrade = SupportPriceLevel;
         }
         
      }
   }
   
}
//+------------------------------------------------------------------+

void draw_S_R_Level(string levelName,double price,color clr,int width){
   if (ObjectFind(0,levelName) < 0){
      ObjectCreate(0,levelName,OBJ_HLINE,0,TimeCurrent(),price);
      ObjectSetInteger(0,levelName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,levelName,OBJPROP_WIDTH,width);
   }
   else {
      ObjectSetDouble(0,levelName,OBJPROP_PRICE,price);
   }
   ChartRedraw(0);
}

void deleteLevel(string levelName){
   ObjectDelete(0,levelName);
   ChartRedraw(0);
}

void draw_S_R_Level_Point(string objName,double price,datetime time,
      int arrowcode,int direction,color clr,double angle){
   //objName = " ";
   StringConcatenate(objName,objName," @ \nTime: ",time,"\nPrice: ",DoubleToString(price,_Digits));
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)) {
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,10);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
   }
   string prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }
   ChartRedraw(0);
}

Saúde para nós! Agora, criamos um sistema de negociação puramente baseado em ação de preço com a estratégia de negociação forex de Suporte e Resistência para não apenas gerar sinais de negociação, mas também abrir posições de mercado com base nos sinais gerados.


Resultados do Strategy Tester

Após testar no testador de estratégia, aqui estão os resultados.

  • Gráfico de Saldo/Equidade:

GRÁFICO

  • Resultados do Backtest:

RESULTADOS


Conclusão

Em conclusão, a automação da estratégia de negociação forex de suporte e resistência é possível e fácil, como vimos. Tudo o que é necessário é ter um entendimento claro da estratégia, bem como seu plano, e então usar esse conhecimento para um avanço. Aproveitamos com confiança os recursos poderosos da linguagem MQL5 para criar uma estratégia de negociação precisa e eficiente. A análise e a criação do EA mostraram que a automação não apenas economiza tempo valioso, mas também aumenta a eficácia da negociação ao reduzir erros humanos e interferência emocional.

Aviso: A informação ilustrada neste artigo é apenas para fins educacionais. Destina-se apenas a mostrar insights sobre como criar um Expert Advisor (EA) de Suporte e Resistência baseado em uma abordagem puramente de preço e, portanto, deve ser usado como base para a criação de um EA melhor com mais otimização e extração de dados em consideração. As informações apresentadas não garantem quaisquer resultados de negociação.

Esperamos sinceramente que o artigo tenha sido instrutivo e útil para você na automação do EA de suporte e resistência. Essa integração de sistemas automatizados certamente aumentará em frequência à medida que os mercados financeiros se desenvolverem, proporcionando aos traders instrumentos de ponta para lidar com todos os aspectos da dinâmica do mercado. Com tecnologias como o MQL5 continuando a avançar e abrir portas para soluções de negociação mais complexas e inteligentes, o futuro da negociação parece promissor.


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

Visualizações de negociações no gráfico (Parte 2): Desenho gráfico de informações Visualizações de negociações no gráfico (Parte 2): Desenho gráfico de informações
Escreveremos do zero um script para facilitar a captura de capturas de tela (print-screens) de negociações, visando a análise de entradas. Em um único gráfico, será conveniente exibir todas as informações necessárias sobre uma negociação específica, com a possibilidade de desenhar diferentes timeframes.
Redes neurais de maneira fácil (Parte 94): Otimização da sequência de dados iniciais Redes neurais de maneira fácil (Parte 94): Otimização da sequência de dados iniciais
Ao trabalhar com séries temporais, geralmente usamos os dados na sequência histórica. Mas isso é realmente o mais eficiente? Há quem acredite que modificar a sequência dos dados iniciais pode aumentar a eficácia dos modelos de aprendizado. Neste artigo, vou apresentar um desses métodos.
Construindo um Modelo de Restrição de Tendência de Candlestick (Parte 5): Sistema de Notificação (Parte II) Construindo um Modelo de Restrição de Tendência de Candlestick (Parte 5): Sistema de Notificação (Parte II)
Hoje, estamos discutindo uma integração funcional do Telegram para notificações do Indicador MetaTrader 5 usando o poder do MQL5, em parceria com Python e a API do Bot do Telegram. Explicaremos tudo em detalhes para que ninguém perca nenhum ponto. Ao final deste projeto, você terá adquirido conhecimentos valiosos para aplicar em seus projetos.
Do básico ao intermediário: Array (IV) Do básico ao intermediário: Array (IV)
Neste artigo iremos ver como podemos fazer algo muito parecido com o encontrado em linguagens como C, C++ e Java. Onde podemos enviar um número quase infinito de parâmetros para dentro de uma função ou procedimento. Apesar de aparentemente ser um tópico avançado. Na minha visão, o que será visto aqui, pode muito bem ser implementado por qualquer iniciante. Desde que ele tenha compreendido os conceitos vistos arteriormente. O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como sendo, uma aplicação cuja finalidade não venha a ser o aprendizado e estudo dos conceitos mostrados.
OSZAR »