English Русский 中文 Español Deutsch 日本語
preview
Do iniciante ao especialista: Sistema de análise autogeométrica

Do iniciante ao especialista: Sistema de análise autogeométrica

MetaTrader 5Exemplos |
25 0
Clemence Benjamin
Clemence Benjamin

Sumário:

  1. Introdução
  2. Contextualização
  3. Visão geral
  4. Implementação
  5. Resultados e testes
  6. Conclusão

Introdução

Esta discussão tem como foco tratar a análise de padrões de candles por meio de abordagens geométricas. Em nosso artigo recente "Do iniciante ao especialista: Programação de candles japoneses", focamos na identificação de padrões simples de candles, que normalmente são compostos por apenas alguns candles. No entanto, ao lidar com sequências maiores de candles japoneses, o reconhecimento de padrões se torna mais complexo, pois a consistência em séries longas tende a diminuir.

Ainda assim, há um ponto claro: ainda conseguimos identificar os principais máximos e mínimos nos dados. Ligar esses pontos pode nos ajudar a avaliar tendências de forma mais eficiente.

Quando tivemos nosso primeiro contato com o trading no mercado Forex, muitos materiais de estudo nos apresentaram a ideia de formações triangulares e outros padrões geométricos no movimento dos preços de mercado. E, de fato, a geometria está presente nos mercados: ela pode oferecer uma representação simplificada e visual do que está acontecendo no mercado.

A maioria dos traders está acostumada a identificar essas figuras manualmente, desenhando linhas de tendência ou inserindo objetos geométricos nos gráficos. Hoje, buscamos dar mais um passo adiante, usando MQL5 para automatizar esse processo, eliminando a necessidade de intervenção manual e proporcionando uma análise mais rápida e consistente.


Contextualização

A vantagem da programação orientada a objetos (POO) está em sua grande capacidade de modelar e resolver com eficiência problemas computacionais reais. MQL5, uma linguagem derivada de C++, herdou esse ponto forte e é uma ferramenta valiosa voltada ao desenvolvimento de algoritmos de trading. Diante da tarefa proposta, já contamos com uma vantagem significativa graças aos benefícios estruturais e modulares oferecidos pela OOP.

Nesta seção, antes de examinar os métodos tradicionais usados para identificar figuras geométricas no trading, descreveremos brevemente os principais termos relacionados ao nosso tema. Embora existam muitas figuras na análise técnica, vamos nos concentrar em dois exemplos comuns: retângulos e triângulos.

O que é geometria?

A geometria é o ramo da matemática que estuda as propriedades e relações espaciais. Isso inclui o estudo de distâncias, formas, tamanhos, ângulos e do posicionamento relativo das figuras. A geometria oferece uma base sólida para interpretar o mundo físico, permitindo modelar e analisar relações espaciais.

Por que a geometria é importante no contexto do trading?

A geometria oferece uma base visual e estrutural para compreender e interpretar o comportamento do mercado. Os traders frequentemente se apoiam nos padrões e formas que decorrem do movimento dos preços, e esses padrões são, por natureza, geométricos. Isso facilita o reconhecimento de padrões. Veja uma lista de padrões comuns, conhecidos pela maioria dos traders:

  • Triângulo (ascendente, descendente, simétrico)
  • Retângulos (zonas de consolidação)
  • Canais (linhas de tendência paralelas)
  • "Ombro-Cabeça-Ombro", "Topo duplo/Fundo duplo": todos se baseiam na simetria geométrica

Esses padrões ajudam os traders a:

  • Identificar possíveis rompimentos ou reversões
  • Medir a força da tendência
  • Definir pontos de entrada/saída

Nesta tabela, reuni as principais considerações sobre a aplicação da análise geométrica aos dados de preços de mercado.

Parâmetro Papel da geometria
Clareza visual Ajuda a simplificar a transformação de dados de mercado complexos em formas reconhecíveis
Apoio à tomada de decisão As figuras orientam entradas, saídas e setups de trading
Objetividade Reduz as suposições por meio de uma lógica espacial precisa
Automação Permite detectar algoritmicamente padrões e níveis-chave
Psicologia do mercado As figuras frequentemente refletem o comportamento coletivo dos traders (por exemplo, os triângulos indicam compressão).

Vejamos como a geometria do triângulo e do retângulo é tradicionalmente aplicada no trading. Darei uma visão geral do que os diferentes padrões triangulares costumam indicar sobre o movimento dos preços, mas não entrarei em estratégias de trading específicas. Faço essa ressalva porque observei que muitas regras amplamente aceitas acabam se mostrando inválidas em condições reais de mercado. Embora esses padrões realmente funcionem até certo ponto, é prudente prestar bastante atenção ao comportamento do preço antes que as estruturas geométricas se formem.

Por exemplo, embora normalmente se espere que um triângulo ascendente leve a um rompimento de alta, já vi muitos casos em que ocorreu o oposto. Essas formas frequentemente representam pontos de decisão no mercado, não garantias. Portanto, embora o padrão possa indicar determinado movimento direcional, é essencial alinhar sua interpretação ao contexto da ação do preço antes do surgimento do padrão. Apoiar-se exclusivamente em expectativas de manual, sem considerar o contexto de mercado, pode levar a conclusões equivocadas.

Triângulo

Um triângulo é uma figura geométrica fundamental composta por três lados e três ângulos (vértices). No trading de Forex, os padrões triangulares indicam uma possível continuação ou reversão da tendência. Os principais tipos de padrões triangulares são:

1. Triângulo simétrico:

Indica um período de consolidação com volatilidade decrescente. Costuma anteceder um rompimento em qualquer direção.

Triângulo simétrico

Triângulo simétrico

2. Triângulo ascendente:

Caracteriza-se por um nível de resistência plano ou levemente inclinado e por uma linha de suporte ascendente. Geralmente é um padrão de alta que sinaliza um possível rompimento para cima.

Triângulo ascendente

Triângulo ascendente

3. Triângulo descendente:

Possui uma linha de suporte plana ou levemente inclinada e uma resistência descendente; geralmente é baixista e sugere um rompimento para baixo.

Triângulo descendente

Triângulo descendente

Retângulo

Um retângulo é uma figura geométrica de quatro lados, com lados opostos iguais e paralelos. Ele possui ângulos retos, cada um com 90 graus, em todos os vértices. No mercado Forex e na análise técnica, um retângulo (também chamado de intervalo de negociação ou zona de consolidação) é um padrão gráfico no qual o preço se move lateralmente entre níveis horizontais de suporte e resistência. Ele indica um período de indecisão antes que o mercado possa continuar a tendência anterior ou reverter. Esse objeto está disponível entre as ferramentas de análise de mercado no terminal MetaTrader 5.

Estas são as principais características:

  • O preço oscila entre uma linha superior de resistência bem definida e uma linha inferior de suporte.
  • No gráfico, o padrão assume a forma de um retângulo ou de uma caixa.
  • Rompimentos acima do nível de resistência ou abaixo do nível de suporte costumam sinalizar o início de uma nova tendência.

Esse conceito também pode ser ilustrado com a ferramenta integrada de linhas paralelas alinhadas horizontalmente para exibir os níveis de suporte e resistência. Como essa ferramenta é composta por duas linhas paralelas, ela destaca com eficiência a faixa de preço durante a consolidação. No entanto, o retângulo continua sendo a opção preferida para marcar zonas de consolidação, pois visualmente "delimita" o período específico em que o preço se moveu lateralmente. É importante lembrar que a consolidação é uma fase temporária: em algum momento, o mercado romperá essa faixa e continuará sua tendência ou reverterá na direção oposta. e continuará sua tendência ou reverterá na direção oposta.

Estrutura do retângulo

Estrutura do retângulo

Retângulo com rompimento para baixo

Retângulo com rompimento para baixo


Visão geral

Com a base teórica estabelecida, podemos passar a uma visão geral da implementação prática. Usaremos métodos de programação modular para criar componentes para o sistema de detecção de padrões geométricos. Em particular, desenvolveremos classes separadas para detectar formações triangulares e retangulares na estrutura do mercado. Esses são apenas os primeiros exemplos: a abordagem é extensível, permitindo adicionar suporte a outras formas conforme necessário. Esse é um dos principais benefícios do uso de programação orientada a objetos e modular em MQL5, que nos permite resolver problemas complexos do mundo real com código limpo e de fácil manutenção.

Os arquivos de cabeçalho que criamos nesta seção podem ser facilmente integrados ao EA ou indicador principal. Além disso, podem ser reutilizados em vários projetos, favorecendo a consistência e a eficiência no desenvolvimento.

De acordo com a minha abordagem, cada figura geométrica requer um conjunto de pontos de referência nos dados de preços. Em geral, precisamos de duas máximas e duas mínimas. Esses pontos são ligados por linhas, formando figuras. Por exemplo, dois pontos formam uma linha, enquanto um triângulo pode ser formado por duas linhas que convergem para um ponto específico no futuro. Por outro lado, um retângulo pode ser definido quando há dois toques em cada nível, tanto na resistência quanto no suporte. Assim que essas condições forem atendidas, a figura geométrica poderá ser desenhada conectando os pontos de referência correspondentes.

Para facilitar o entendimento, incluí abaixo algumas ilustrações que mostram como essas figuras podem ser construídas no gráfico usando lógica computacional. Normalmente, para delinear cada figura, precisamos de pelo menos quatro pontos-chave. Nas imagens, começamos pela definição dos pontos a, b, c e d, que servem como referências principais para a estrutura da nossa figura. A partir desses pontos, é possível projetar a figura à frente. As linhas vermelhas pontilhadas representam expectativas teóricas, indicando como o mercado pode reagir aos limites presumidos da figura. A forma como o preço interage com essas linhas pode fornecer pistas importantes sobre o movimento futuro do preço, como possíveis rompimentos ou rejeições.

Formação do triângulo

Formação do triângulo

Na imagem do triângulo acima, os pontos de pivô de a a d são definidos como pontos de referência a partir dos quais se pode construir um triângulo com linhas convergentes que se encontram no ponto X. Os pontos e e f representam movimentos teóricos futuros. Eles têm caráter hipotético e podem não se manifestar exatamente como mostrado na figura. Seu objetivo é nos ajudar a conceitualizar o padrão para que possamos transformar essa ideia em código. O mesmo conceito se aplica à figura do retângulo abaixo, em que a estrutura é definida usando uma lógica semelhante. No entanto, a principal diferença é que seus lados são paralelos.

Formação do retângulo

Formação do retângulo


Implementação

A partir daqui, começa o desenvolvimento propriamente dito. Começaremos criando um arquivo de cabeçalho chamado GeometricPatternDetector.mqh, que conterá todas as classes necessárias para detectar padrões geométricos. Assim que o arquivo de cabeçalho estiver pronto, passaremos ao desenvolvimento de um EA demonstrando como usar as classes de forma eficiente. Agora, vamos seguir as próximas etapas para entender como tudo isso funciona.

Inclusão dos cabeçalhos necessários

No topo de GeometricPatternDetector.mqh, usamos três bibliotecas principais do MQL5 para disponibilizar ao nosso detector todas as ferramentas necessárias. Primeiro, incluímos arrays\ArrayObj.mqh, para poder usar CArrayObj, uma classe de array dinâmico orientada a objetos destinada a armazenar nossos pontos de reversão e resultados de modelagem sem usar ponteiros brutos nem gestão manual de memória.

Em seguida, Indicators\Indicators.mqh usa as funções integradas de indicadores do MetaTrader, como iATR, CopyHigh, CopyLow e CopyTime, permitindo obter dados históricos de preço e calcular facilmente o Average True Range. Por fim, Math\Stat\Math.mqh fornece constantes matemáticas, como M_PI, e funções auxiliares, como MathArctan, que usamos para calcular inclinações, ângulos e tolerâncias de horizontalidade em nossos algoritmos de detecção de padrões. Em conjunto, esses elementos criam a base para um código confiável, legível e de fácil manutenção.

#include <Arrays\ArrayObj.mqh>       // For CArrayObj: dynamic arrays of objects 
#include <Indicators\Indicators.mqh> // For iATR, CopyHigh, CopyLow, CopyTime
#include <Math\Stat\Math.mqh>        // For M_PI, MathArctan, and other math utilities

Classes contêiner

O arquivo de cabeçalho GeometricPatternDetector.mqh começa com a definição de duas classes contêiner simples: SwingPoint e PatternResult. Ambas derivam da classe base CObject em MQL5. A classe SwingPoint contém uma marca temporal, um preço e uma flag booleana que indica se o pivô é uma máxima ou uma mínima. Esse design nos permite coletar pivôs individuais do mercado e gerenciá-los em um único array de objetos.

A classe PatternResult contém todas as informações necessárias para descrever o padrão detectado, ou seja, o símbolo, o timeframe, o tipo de padrão, os três "vértices" que definem o padrão e o ponto em que o rótulo deve ser posicionado. Ao encapsular esses elementos de dados em objetos, o código do detector permanece limpo e consistente, pois pois o armazenamento é feito com CArrayObj em vez de arrays paralelos de primitivos criados manualmente.

// SwingPoint: holds one market pivot
class SwingPoint : public CObject {
public:
   datetime time;
   double   price;
   bool     isHigh;
   SwingPoint(datetime t=0,double p=0.0,bool h=false)
     : time(t), price(p), isHigh(h) {}
};

// PatternResult: holds the details of one detected pattern
class PatternResult : public CObject {
public:
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   ENUM_PATTERN_TYPE type;
   datetime          detectionTime;
   datetime          labelTime;
   double            labelPrice;
   datetime          vertex1Time;
   double            vertex1Price;
   datetime          vertex2Time;
   double            vertex2Price;
   datetime          vertex3Time;
   double            vertex3Price;
   PatternResult(const string _s,const ENUM_TIMEFRAMES _tf,const ENUM_PATTERN_TYPE _t,
                 datetime lt,double lp,
                 datetime v1t,double v1p,
                 datetime v2t,double v2p,
                 datetime v3t,double v3p)
     : symbol(_s), timeframe(_tf), type(_t), detectionTime(TimeCurrent()),
       labelTime(lt), labelPrice(lp),
       vertex1Time(v1t), vertex1Price(v1p),
       vertex2Time(v2t), vertex2Price(v2p),
       vertex3Time(v3t), vertex3Price(v3p) {}
};

Estrutura da classe Detector

No centro do arquivo de cabeçalho está a classe CGeometricPatternDetector. Em sua área private, são declaradas variáveis-membro para armazenar objetos de pivô, parâmetros de configuração (como backstep, multiplicador do ATR e número mínimo de toques), bem como o estado necessário para evitar a duplicação de objetos gráficos (nomes e hashes do último triângulo e do último retângulo, além do horário em que foram detectados).

Quatro métodos auxiliares (IsNewBar, UpdateSwingPoints, CalculateATR e GetSwingHash) são declarados como private. Esses métodos auxiliares cuidam da identificação de novas barras, extração de pontos de pivô a partir dos dados mais recentes de máximas/mínimas, cálculo da sensibilidade de mercado com base no ATR e geração de uma chave de string exclusiva para cada conjunto de quatro pivôs. Juntas, elas dão suporte às duas principais rotinas públicas de detecção, DetectTriangle e DetectRectangle, além do método Update, que integra todo o fluxo, e dos métodos GetLastPattern e ClearPatterns, usados para obter resultados e limpar recursos.

class CGeometricPatternDetector {
private:
   CArrayObj   m_swings;
   int         m_swingLookback;
   double      m_atrMultiplier;
   int         m_minTouchPoints;

   string      m_lastTriangle;
   string      m_lastSwingHash;
   datetime    m_lastTriangleTime;

   string      m_lastRectangle;
   string      m_lastRectangleHash;
   datetime    m_lastRectangleTime;

   bool    IsNewBar(const string sym,const ENUM_TIMEFRAMES tf);
   void    UpdateSwingPoints(const string sym,const ENUM_TIMEFRAMES tf);
   double  CalculateATR(const string sym,const ENUM_TIMEFRAMES tf,const int period=14);
   string  GetSwingHash(SwingPoint *p1,SwingPoint *p2,SwingPoint *p3,SwingPoint *p4);

public:
   CGeometricPatternDetector(int swingLookback=3,double atrMultiplier=1.5,int minTouchPoints=2);
   ~CGeometricPatternDetector();

   void Update(const string sym,const ENUM_TIMEFRAMES tf);
   void DetectTriangle(const string sym,const ENUM_TIMEFRAMES tf);
   void DetectRectangle(const string sym,const ENUM_TIMEFRAMES tf);
   ENUM_PATTERN_TYPE GetLastPattern();
   void ClearPatterns();

   CArrayObj m_currentPatterns;
};

Extração dos pontos de swing

O método de extração de pontos de swing UpdateSwingPoints é chamado a cada nova barra. Ele copia uma janela de máximas, mínimas e timestamps do gráfico e, em seguida, marca a barra central dessa janela como máxima de swing se seu preço for maior que o de ambas as barras vizinhas, ou como mínima de swing se seu preço for menor que o de ambas as barras vizinhas. Cada pivô confirmado é encapsulado em um objeto SwingPoint e adicionado ao array m_swings.

Quando o array ultrapassa o limite fixo, os registros mais antigos são descartados. Essa lista deslizante de pivôs forma a base para identificar tanto triângulos quanto retângulos, garantindo que apenas os pontos de inflexão mais recentes e significativos do mercado sejam considerados.

void CGeometricPatternDetector::UpdateSwingPoints(const string sym,const ENUM_TIMEFRAMES tf)
{
   int bars = m_swingLookback*2 + 1;
   double highs[], lows[];
   datetime times[];
   ArraySetAsSeries(highs,true);
   ArraySetAsSeries(lows,true);
   ArraySetAsSeries(times,true);

   if(CopyHigh(sym,tf,0,bars,highs)<bars ||
      CopyLow(sym,tf,0,bars,lows)<bars   ||
      CopyTime(sym,tf,0,bars,times)<bars)
   {
      Print("Error: Failed to copy price/time data");
      return;
   }

   int mid = m_swingLookback;
   datetime t = times[mid];
   double h = highs[mid], l = lows[mid];

   bool isH = true;
   for(int i=1; i<=m_swingLookback; i++)
      if(h<=highs[mid-i] || h<=highs[mid+i]) { isH=false; break; }
   if(isH) {
      m_swings.Add(new SwingPoint(t,h,true));
      Print("Swing High detected at ",TimeToString(t)," Price: ",h);
   }

   bool isL = true;
   for(int i=1; i<=m_swingLookback; i++)
      if(l>=lows[mid-i] || l>=lows[mid+i]) { isL=false; break; }
   if(isL) {
      m_swings.Add(new SwingPoint(t,l,false));
      Print("Swing Low detected at ",TimeToString(t)," Price: ",l);
   }

   while(m_swings.Total()>50) {
      delete (SwingPoint*)m_swings.At(0);
      m_swings.Delete(0);
   }
}

CalculateATR converte o indicador ATR integrado ao MQL5 em uma unidade de medida sensível ao mercado, enquanto GetSwingHash combina quatro pivôs, cada um com tempo e preço em uma chave textual. O valor do ATR define as tolerâncias tanto para a "horizontalidade" dos triângulos quanto para o alinhamento dos retângulos. O hash em formato string garante que cada conjunto único de pivôs seja desenhado apenas uma vez durante o período de bloqueio.

double CGeometricPatternDetector::CalculateATR(const string sym,const ENUM_TIMEFRAMES tf,const int period)
{
   int h = iATR(sym,tf,period);
   if(h==INVALID_HANDLE) { Print("ATR handle error"); return 0; }
   double buf[]; ArraySetAsSeries(buf,true);
   if(CopyBuffer(h,0,0,1,buf)!=1) { Print("ATR copy error"); return 0; }
   return buf[0];
}

string CGeometricPatternDetector::GetSwingHash(SwingPoint *p1,SwingPoint *p2,SwingPoint *p3,SwingPoint *p4)
{
   return TimeToString(p1.time)+"*"+DoubleToString(p1.price,8)+"*"
        + TimeToString(p2.time)+"*"+DoubleToString(p2.price,8)+"*"
        + TimeToString(p3.time)+"*"+DoubleToString(p3.price,8)+"*"
        + TimeToString(p4.time)+"_"+DoubleToString(p4.price,8);
}

Lógica de detecção do triângulo

A rotina de detecção do triângulo verifica estritamente quatro swings consecutivos na sequência mínimo-máximo, mínimo-máximo. Depois de definir o intervalo mínimo entre barras para cada lado, ela calcula as inclinações dos lados inferior e superior, dividindo a diferença de preço pela diferença de tempo. A tolerância baseada no ATR determina se o topo ou a base do triângulo é suficientemente horizontal para classificar o padrão como descendente ou ascendente; se nenhum dos lados do triângulo for suficientemente plano, o padrão é classificado como simétrico.

Em seguida, calcula-se analiticamente a interseção das duas linhas laterais para encontrar o vértice futuro do triângulo. Um objeto OBJ_TRIANGLE é desenhado com base em dois pontos de pivô e no vértice calculado., e um objeto PatternResult também é criado e armazenado. Para evitar oscilação visual, o detector compara o hash dos quatro pivôs com o último padrão desenhado e bloqueia novos objetos gráficos da mesma forma por um número fixo de barras.

void CGeometricPatternDetector::DetectTriangle(const string sym,const ENUM_TIMEFRAMES tf)
{
   int tot = m_swings.Total();
   if(tot < 4) return;
   ulong barSec = PeriodSeconds(tf);
   ulong minSpan = (ulong)m_swingLookback * barSec;

   for(int i=0; i<=tot-4; i++)
   {
      SwingPoint *p1 = (SwingPoint*)m_swings.At(i);
      SwingPoint *p2 = (SwingPoint*)m_swings.At(i+1);
      SwingPoint *p3 = (SwingPoint*)m_swings.At(i+2);
      SwingPoint *p4 = (SwingPoint*)m_swings.At(i+3);

      if(!( !p1.isHigh && p2.isHigh && !p3.isHigh && p4.isHigh )) continue;
      if((ulong)(p2.time-p1.time)<minSpan ||
         (ulong)(p3.time-p2.time)<minSpan ||
         (ulong)(p4.time-p3.time)<minSpan) continue;

      double m_low  = (p3.price - p1.price) / double(p3.time - p1.time);
      double m_high = (p4.price - p2.price) / double(p4.time - p2.time);
      double tolFlat = CalculateATR(sym,tf,14) * m_atrMultiplier;

      bool lowerFlat = MathAbs(p3.price-p1.price) < tolFlat;
      bool upperFlat = MathAbs(p4.price-p2.price) < tolFlat;

      ENUM_PATTERN_TYPE type;
      if(lowerFlat && m_high < 0)             type = PATTERN_TRIANGLE_DESCENDING;
      else if(upperFlat && m_low > 0)         type = PATTERN_TRIANGLE_ASCENDING;
      else                                    type = PATTERN_TRIANGLE_SYMMETRICAL;

      double denom = m_low - m_high;
      if(MathAbs(denom)<1e-12) continue;
      double num = (p2.price - p1.price) + (m_low*p1.time - m_high*p2.time);
      double tx  = num/denom;
      double px  = p1.price + m_low*(tx-p1.time);

      datetime latest = MathMax(p1.time,p2.time);
      if(tx<=latest || tx>TimeCurrent()+barSec*50) continue;

      if(StringLen(m_lastTriangle)>0)
         ObjectDelete(0,m_lastTriangle);

      string base = "GPD_"+sym+"_"+EnumToString(tf)+"_"+TimeToString(TimeCurrent(),TIME_SECONDS);
      long cid    = ChartID();
      m_lastTriangle = base+"_T";

      ObjectCreate(cid,m_lastTriangle,OBJ_TRIANGLE,0,
                   p1.time,p1.price,
                   p2.time,p2.price,
                   tx,      px);
      ObjectSetInteger(cid,m_lastTriangle,OBJPROP_COLOR,clrOrange);
      ObjectSetInteger(cid,m_lastTriangle,OBJPROP_WIDTH,2);
      ObjectSetInteger(cid,m_lastTriangle,OBJPROP_FILL,false);

      m_lastSwingHash    = GetSwingHash(p1,p2,p3,p4);
      m_lastTriangleTime = TimeCurrent();
      m_currentPatterns.Add(new PatternResult(
         sym,tf,type,
         latest,(p2.price+p4.price)/2.0,
         p1.time,p1.price,
         p2.time,p2.price,
         (datetime)tx,px
      ));

      ChartRedraw(cid);
      break;
   }
}

Lógica de detecção do retângulo

A rotina de detecção de retângulos segue uma sequência semelhante de pivôs, mas aplica restrições mais rigorosas. Primeiro, ela identifica quatro swings no mesmo padrão mínimo-máximo, mínimo-máximo, exigindo que cada swing (do mínimo ao máximo e o segundo do mínimo ao máximo) abranja pelo menos cinco barras e exceda o limite de amplitude medido em ATR. A base do retângulo é alinhada à média dos dois mínimos, desde que ambos estejam dentro da tolerância permitida pelo ATR. Se os dois máximos também estiverem alinhados dentro dessa tolerância, o topo é definido como a média deles. Caso contrário, usa-se uma altura mínima igual a uma unidade de ATR, para que o retângulo permaneça visível.

O retângulo é desenhado usando OBJ_RECTANGLE e se estende do tempo do pivô mais antigo até exatamente vinte barras posteriores, evitando um crescimento ilimitado. O hash único dos pivôs garante que cada retângulo individual seja desenhado apenas uma vez durante o período de bloqueio, e seus detalhes são registrados em PatternResult.

void CGeometricPatternDetector::DetectRectangle(const string sym,const ENUM_TIMEFRAMES tf)
{
   int tot = m_swings.Total();
   if(tot < 4) return;

   ulong barSec    = PeriodSeconds(tf);
   ulong minSpan5  = 5 * barSec;                        
   double tolATR   = CalculateATR(sym,tf,14) * m_atrMultiplier;

   for(int i=0; i<=tot-4; i++)
   {
      SwingPoint *a = (SwingPoint*)m_swings.At(i);
      SwingPoint *b = (SwingPoint*)m_swings.At(i+1);
      SwingPoint *c = (SwingPoint*)m_swings.At(i+2);
      SwingPoint *d = (SwingPoint*)m_swings.At(i+3);

      if(!( !a.isHigh && b.isHigh && !c.isHigh && d.isHigh )) continue;
      if((ulong)(b.time - a.time) < minSpan5 ||
         (ulong)(d.time - c.time) < minSpan5) continue;
      if(MathAbs(b.price - a.price) < tolATR ||
         MathAbs(d.price - c.price) < tolATR) continue;
      if(MathAbs(a.price - c.price) > tolATR) continue;

      bool highAligned = MathAbs(b.price - d.price) < tolATR;
      double lowP  = (a.price + c.price) / 2.0;
      double highP = highAligned ? (b.price + d.price)/2.0 : lowP + tolATR;

      datetime leftT  = MathMin(a.time, c.time);
      datetime rightT = leftT + (datetime)(20 * barSec);

      string rh = TimeToString(leftT,TIME_SECONDS) + "_" +
                  DoubleToString(lowP,8) + "_" +
                  DoubleToString(highP,8);
      datetime lockT = m_lastRectangleTime + (datetime)(40 * barSec);
      if(rh == m_lastRectangleHash && TimeCurrent() < lockT) return;

      if(StringLen(m_lastRectangle) > 0)
         ObjectDelete(0,m_lastRectangle);

      string base = "GPD_"+sym+"_"+EnumToString(tf)+"_"+TimeToString(TimeCurrent(),TIME_SECONDS);
      long cid    = ChartID();
      m_lastRectangle = base+"_Rect";

      ObjectCreate(cid,m_lastRectangle,OBJ_RECTANGLE,0,
                   leftT,   highP,
                   rightT,  lowP);
      ObjectSetInteger(cid,m_lastRectangle,OBJPROP_COLOR, clrBlue);
      ObjectSetInteger(cid,m_lastRectangle,OBJPROP_WIDTH, 2);
      ObjectSetInteger(cid,m_lastRectangle,OBJPROP_FILL,  false);

      m_currentPatterns.Add(new PatternResult(
         sym,tf,PATTERN_RECTANGLE,
         leftT,(highP+lowP)/2.0,
         leftT,highP,
         leftT,lowP,
         rightT,lowP
      ));
      m_lastRectangleHash = rh;
      m_lastRectangleTime = TimeCurrent();

      ChartRedraw(cid);
      break;
   }
}

Uso do arquivo de cabeçalho no EA

Integrar esse arquivo de cabeçalho a um EA é simples. Basta incluí-lo, criar uma instância de CGeometricPatternDetector com os parâmetros de nossa preferência e chamar seu método Update(Symbol(), _Period) no manipulador de eventos OnTick. Após cada atualização, GetLastPattern indica se um novo triângulo ou retângulo foi detectado, e é possível consultar detector.m_currentPatterns para obter informações completas, emitir alertas ou posicionar rótulos no gráfico.

O EA não precisa conhecer a geometria básica nem a lógica dos pivôs; ele simplesmente gerencia o detector e reage aos resultados de alto nível e reage aos resultados de alto nível. Essa separação, com a declaração no arquivo de cabeçalho, a implementação detalhada no mesmo arquivo e o uso simples no EA, demonstra como o design orientado a objetos em MQL5 encapsula a complexidade e produz código reutilizável e de fácil manutenção. Vejamos as etapas detalhadas abaixo.

Inclusão do arquivo de cabeçalho e estado global

No topo de GeometryAnalyzerEA.mq5, incluímos o arquivo de cabeçalho do nosso detector para que o EA possa acessar a classe CGeometricPatternDetector e seus tipos auxiliares. Logo depois, instanciamos um objeto detector separado com os parâmetros escolhidos: lookback de três barras para os pivôs, multiplicador do ATR igual a 1,5 para a tolerância e dois pontos mínimos de interação para os padrões. Também declaramos três variáveis globais: lastAlerted guarda o tipo do último padrão sinalizado, para que não o repitamos na mesma barra; lastBarTime acompanha a formação de uma nova barra; e lastLabelName contém o nome do rótulo de texto criado, para que ele possa ser removido quando surgir um novo padrão.

#include <GeometricPatternDetector.mqh>

//–– detector instance & state
CGeometricPatternDetector  detector(3, 1.5, 2);
ENUM_PATTERN_TYPE          lastAlerted    = PATTERN_NONE;
datetime                   lastBarTime    = 0;
string                     lastLabelName  = "";

Inicialização e limpeza

A função OnInit é executada uma única vez quando o EA é iniciado. Aqui, simplesmente exibimos uma mensagem para confirmar a inicialização, embora também seja possível iniciar um temporizador ou alocar recursos, caso necessário. Por outro lado, OnDeinit é acionada quando o EA é removido ou quando o gráfico é fechado. Nesse procedimento, removemos todos os objetos gráficos dos padrões com detector.ClearPatterns() e removemos pelo nome qualquer rótulo de texto remanescente. Por fim, registramos o motivo da desinicialização, o que facilita diagnosticar por que o EA foi interrompido.

int OnInit()
{
   Print("GeometryAnalyzerEA initialized");
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   detector.ClearPatterns();
   if(StringLen(lastLabelName) > 0)
      ObjectDelete(0, lastLabelName);
   Print("GeometryAnalyzerEA deinitialized, reason=", reason);
}

Detecção de nova barra

No OnTick, o primeiro passo é determinar se o tick recebido pertence a uma barra recém-formada. Extraímos o horário de abertura da barra atual e o comparamos com lastBarTime armazenado. Se a barra for nova, redefinimos lastAlerted para que os padrões possam ser sinalizados novamente e removemos o rótulo de texto anterior para manter o gráfico limpo. Se a barra não for nova, simplesmente retornamos sem fazer mais nada, garantindo que a detecção de padrões seja executada apenas uma vez por barra.

void OnTick()
{
   datetime curBar = iTime(Symbol(), _Period, 0);
   bool isNewBar  = (curBar != lastBarTime);
   if(isNewBar)
   {
      lastBarTime = curBar;
      lastAlerted = PATTERN_NONE;
      if(StringLen(lastLabelName) > 0)
      {
         ObjectDelete(0, lastLabelName);
         lastLabelName = "";
      }
   }
   if(!isNewBar)
      return;
   // …
}

Acionamento do detector

Depois de confirmar a nova barra, chamamos o método de atualização do detector (Update), passando o símbolo e o timeframe atuais. Essa única chamada atualiza os pontos de pivô e executa simultaneamente as duas rotinas de detecção, de triângulos e retângulos. Após o retorno de Update, chamamos GetLastPattern() para saber se um padrão válido foi encontrado nessa barra. Se nenhum novo padrão aparecer, ou se ele coincidir com aquele sobre o qual já emitimos alerta, encerramos a execução antecipadamente.

detector.Update(Symbol(), _Period);

ENUM_PATTERN_TYPE pattern = detector.GetLastPattern();
if(pattern == PATTERN_NONE || pattern == lastAlerted)
   return;
lastAlerted = pattern;

Tratamento do padrão detectado

Se surgir um novo padrão, extraímos o PatternResult mais recente do array de padrões do detector. Em seguida, convertemos o enum do padrão em um nome legível e formatamos seus vértices para registro no log. Os retângulos são tratados de forma especial, pois contêm apenas três pontos de canto. Aproximamos o quarto ponto para obter um conjunto completo de coordenadas. Emitimos tanto um alerta pop-up com Alert quanto uma mensagem no log com Print, indicando o nome do padrão, o tempo, o preço e as coordenadas dos vértices.

int count = detector.m_currentPatterns.Total();
PatternResult *pr = (PatternResult*)detector.m_currentPatterns.At(count - 1);

string name = (pattern == PATTERN_RECTANGLE) ? "Rectangle" :
              (pattern == PATTERN_TRIANGLE_ASCENDING)   ? "Ascending Triangle" :
              (pattern == PATTERN_TRIANGLE_DESCENDING)  ? "Descending Triangle" :
                                                         "Symmetrical Triangle";

Alert("GeometryAnalyzerEA: Detected ", name, " on ", Symbol(), " ", EnumToString(_Period));
Print("GeometryAnalyzerEA: ", name,
      " @", TimeToString(pr.labelTime, TIME_SECONDS),
      " Price=", pr.labelPrice);

Inserção de rótulos

Por fim, posicionamos um rótulo de texto no gráfico no tempo e no preço indicados pelo padrão. Um nome de objeto exclusivo é gerado combinando o nome do padrão com o tempo do rótulo. Usando as funções de objeto gráfico, desenhamos OBJ_TEXT e definimos suas propriedades: conteúdo textual, cor, tamanho da fonte e fundo, para que ele se destaque claramente no gráfico. Registramos o nome do último rótulo para que ele possa ser removido na próxima nova barra antes de desenharmos um novo rótulo. A chamada de redesenho do gráfico garante a renderização imediata.

   lastLabelName = name + "_" + TimeToString(pr.labelTime, TIME_SECONDS);
   long chartId = ChartID();
   if(ObjectCreate(chartId, lastLabelName, OBJ_TEXT, 0, pr.labelTime, pr.labelPrice))
   {
      ObjectSetString(chartId, lastLabelName, OBJPROP_TEXT,     name);
      ObjectSetInteger(chartId, lastLabelName, OBJPROP_COLOR,    clrOrangeRed);
      ObjectSetInteger(chartId, lastLabelName, OBJPROP_FONTSIZE, 12);
      ObjectSetInteger(chartId, lastLabelName, OBJPROP_BACK,     true);
   }
   ChartRedraw(chartId);
}


Testes e resultados

Para avaliar rapidamente a eficiência do EA, usamos o testador de estratégias para visualizar previamente seu comportamento em dados históricos. Os resultados foram promissores: os padrões foram detectados corretamente, e as figuras foram desenhadas conforme esperado. Além disso, tive a oportunidade de observar o funcionamento do EA em um gráfico ao vivo, onde ele se saiu surpreendentemente bem. Os sinais dos padrões eram acionados facilmente ao mesmo tempo em que as figuras eram desenhadas em tempo real, confirmando a confiabilidade da integração. Agora, vejamos a imagem apresentada abaixo.

Visualização do testador de estratégias: GeometricAnalyzerEA

Visualização do testador de estratégias: GeometricAnalyzerEA

Abaixo está o log que mostra a detecção de um triângulo ascendente durante os testes.

2025.05.20 13:40:54.241 2025.01.14 18:45:00   Alert: GeometryAnalyzerEA: Detected Ascending Triangle on AUDJPY.0 PERIOD_M1
2025.05.20 13:40:54.241 2025.01.14 18:45:00   GeometryAnalyzerEA: Ascending Triangle @14:03:00 Price=97.45599999999999 → Vertices: (1736863200@97.381), (1736863380@97.448), (1736873819@97.912)
2025.05.20 13:40:54.242 2025.01.14 18:46:00   Swing High detected at 2025.01.14 18:43 Price: 97.789


Conclusão

Assim, usamos MQL5 com sucesso para desenvolver um sistema automatizado capaz de identificar estruturas triangulares e retangulares no mercado. Esses padrões geométricos, há muito conhecidos na análise técnica, fornecem informações relevantes sobre o comportamento do mercado, especialmente na identificação de possíveis zonas de continuação ou reversão. Ao focar em figuras claramente delimitadas, como triângulos ascendentes e retângulos, demonstramos como a geometria pode transformar o movimento dos preços em estruturas visuais e algorítmicas que são, ao mesmo tempo, acionáveis e confiáveis.

A abordagem que implementamos estabelece uma base sólida para o desenvolvimento de sistemas analíticos mais complexos. A classe GeometricPatternDetector é modular e reutilizável: ela pode ser integrada a outros projetos e expandida para aumentar a precisão da detecção e a flexibilidade.

Embora nosso sistema represente as figuras com precisão no gráfico, há espaço para refinar a lógica de identificação da estrutura, de modo a lidar com casos limítrofes e aumentar a precisão do reconhecimento de padrões. Este projeto demonstra como abordagens algorítmicas podem simplificar a identificação de padrões de mercado complexos, otimizando aquilo que, de outra forma, seria um procedimento manual muito complexo.

Esta jornada faz parte de um aprendizado contínuo. Ao longo do caminho, vimos como o desenvolvimento baseado em classes em MQL5 não apenas aumenta a modularidade, mas também favorece a reutilização de código e a abstração da interface. Isso permite que outros desenvolvedores, ou até mesmo usuários finais, trabalhem com uma interface clara de alto nível sem precisar entender a lógica de detecção de baixo nível.

Para aqueles de nós que criam sistemas desse tipo, é importante dominar a implementação interna. Mas, graças ao design encapsulado, outros usuários ainda podem aproveitar a funcionalidade sem mergulhar no código subjacente. Com essa estrutura, agora podemos desenvolver e expandir o sistema com confiança para identificar quaisquer padrões no mercado, bastando criar as classes correspondentes.

O próximo passo lógico seria integrar uma lógica de execução de ordens baseada em padrões confirmados, como executar ordens de compra em um rompimento a partir de um triângulo ascendente, posicionar stop-loss nos pontos de swing mais recentes e definir take-profits dinâmicos usando a altura do padrão. Aprimoramentos mais avançados podem incluir a confirmação de padrões com indicadores de volume ou osciladores, a aplicação de análise em múltiplos timeframes para validação, além da implementação de um sistema de ranqueamento para priorizar setups de alta qualidade.


Conteúdo do anexo:

Arquivo Descrição
GeometricPatternDetector.mqh
Este arquivo de cabeçalho contém a implementação completa, baseada em classes, da lógica de detecção de padrões geométricos. Ele define estruturas de dados para pontos de swing e resultados de modelagem, gerencia a detecção de pontos de swing, calcula a sensibilidade do mercado com ATR e inclui rotinas para identificar triângulos e retângulos. O arquivo foi projetado para integração modular em EAs.
GeometryAnalyzerEA.mq5
Este EA demonstra o uso prático do arquivo de cabeçalho para identificar padrões. Ele inicializa e atualiza a classe CGeometricPatternDetector a cada nova barra, extrai os resultados dos padrões e anota visualmente no gráfico os padrões detectados. Isso serve como um exemplo simples e real de integração do reconhecimento de padrões orientado a objetos em uma estratégia de trading.

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

Criação de classes Python para trading no MetaTrader 5, análogas às apresentadas em MQL5 Criação de classes Python para trading no MetaTrader 5, análogas às apresentadas em MQL5
O pacote Python MetaTrader 5 oferece uma maneira simples de criar aplicativos de trading para a plataforma MetaTrader 5 na linguagem Python. Embora seja um módulo poderoso e útil, ele não é tão simples quanto a linguagem de programação MQL5 quando se trata de desenvolver soluções para trading algorítmico. Neste artigo, criaremos classes para trading análogas às oferecidas pela linguagem MQL5, a fim de criar uma sintaxe semelhante e tornar o desenvolvimento de robôs de trading em Python tão simples quanto em MQL5.
Algoritmos avançados de execução de ordens em MQL5: TWAP, VWAP e ordens Iceberg Algoritmos avançados de execução de ordens em MQL5: TWAP, VWAP e ordens Iceberg
Um framework MQL5 que oferece a traders de varejo algoritmos de execução de nível institucional (TWAP, VWAP, Iceberg) por meio de um gerenciador de execução unificado e de um analisador de desempenho, para fracionar ordens e analisar o desempenho com mais suavidade e precisão.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Otimização e ajuste fino do código-fonte para melhorar os resultados do backtesting Otimização e ajuste fino do código-fonte para melhorar os resultados do backtesting
Melhore seu código MQL5 otimizando a lógica, aprimorando os cálculos e reduzindo o tempo de execução para aumentar a precisão do backtesting. Ajuste finamente os parâmetros, otimize loops e elimine ineficiências para melhorar o resultado.