English Русский 中文 Español Deutsch 日本語
preview
Automatizando Estratégias de Trading em MQL5 (Parte 15): Padrão Harmônico Cypher Baseado em Price Action com Visualização

Automatizando Estratégias de Trading em MQL5 (Parte 15): Padrão Harmônico Cypher Baseado em Price Action com Visualização

MetaTrader 5 — Negociação |
23 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

Em nosso artigo anterior (Parte 14), desenvolvemos uma estratégia de escalonamento de posições utilizando o Moving Average Convergence Divergence (MACD) e o Relative Strength Indicator (RSI), combinados com métodos estatísticos para escalonar dinamicamente as posições em mercados com tendência. Agora, na Parte 15, nosso foco está na automatização do padrão harmônico Cypher, um padrão de reversão baseado em Fibonacci, utilizando um Expert Advisor (EA) que detecta, visualiza e negocia essa estrutura em MetaQuotes Language 5 (MQL5). Abordaremos os seguintes tópicos:

  1. Entendendo a Estrutura do Padrão Cypher
  2. Implementação em MQL5
  3. Backtesting e Otimização
  4. Conclusão

Ao final deste artigo, você terá um programa totalmente funcional, capaz de identificar padrões Cypher, marcá-los no gráfico com elementos visuais claros e executar operações com precisão. Vamos começar!


Entendendo a Estrutura do Padrão Cypher

O padrão Cypher é uma formação harmônica de trading definida por cinco pontos principais de oscilação — X, A, B, C e D — e apresenta duas formas: padrão de alta e padrão de baixa. Em um Cypher de alta, a estrutura forma uma sequência mínima-máxima-mínima-máxima-mínima, na qual o ponto X corresponde a um fundo, o ponto A a um topo, o ponto B a um fundo, o ponto C a um topo e o ponto D a um fundo (com D posicionado abaixo de X). Por outro lado, um Cypher de baixa forma uma sequência máxima-mínima-máxima-mínima-máxima, com o ponto X correspondendo a um topo e o ponto D posicionado acima de X. representados visualmente os dois tipos de padrão.

Padrão Harmônico Cypher de Alta:

CYPHER DE ALTA

Padrão Harmônico Cypher de Baixa:

CYPHER DE BAIXA

Para identificar esses padrões, utilizaremos a seguinte abordagem estruturada:

  • Definição da Perna XA: O movimento inicial do ponto X até o ponto A estabelece a distância de referência do padrão e define sua direção (ascendente para o padrão de baixa e descendente para o padrão de alta).
  • Definição da Perna AB: Para ambos os tipos de padrão, o ponto B deve apresentar uma retração entre 38,2% e 61,8% do movimento XA, confirmando uma correção moderada do movimento inicial.
  • Análise da Perna BC: Essa perna deve apresentar uma extensão entre 127,2% e 141,4% da perna AB, garantindo um forte movimento contrário antes da perna final.
  • Definição da Perna CD: A perna final deve apresentar uma retração de aproximadamente 78,6% do movimento XC (de X até C), indicando a possível zona de reversão.

Ao aplicar esses critérios geométricos e baseados em Fibonacci, nosso sistema de trading poderá detectar de forma sistemática padrões Cypher válidos nos dados históricos de preços. Depois que um padrão for confirmado, o programa exibirá sua formação no gráfico utilizando triângulos, linhas de tendência e identificações dos pontos X, A, B, C e D, além dos níveis da operação. Essa configuração permite executar operações automaticamente com base nos níveis calculados de entrada, stop-loss e take-profit, aproveitando a capacidade do padrão de sinalizar possíveis reversões do mercado.


Implementação em MQL5

Para criar o programa em MQL5, abra o MetaEditor, vá até o Navigator, pasta Indicators, guia New, aba Experts e siga as instruções para criar o arquivo. Depois de criado, no ambiente de codificação, precisaremos declarar algumas variáveis globais que utilizaremos ao longo de todo o programa.

//+------------------------------------------------------------------+
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA trades based on Cypher Strategy with visualization"
#property strict //--- Forces strict coding rules to catch errors early

//--- Include the Trade library from MQL5 to handle trading operations like buying and selling
#include <Trade\Trade.mqh>
//--- Create an instance (object) of the CTrade class to use for placing trades
CTrade obj_Trade;

//--- Input parameters let the user customize the EA without editing the code
input int    SwingHighCount    = 5;      // How many bars to check on the left to find a swing point (high or low)
input int    SwingLowCount     = 5;      // How many bars to check on the right to confirm a swing point
input double FibonacciTolerance = 0.10;  // Allowed error margin (10%) for Fibonacci ratios in the pattern
input double TradeVolume       = 0.01;   // Size of the trade (e.g., 0.01 lots is small for testing)
input bool   TradingEnabled    = true;   // True = EA can trade; False = only visualize patterns

//--- Define the Cypher pattern rules as a comment for reference
//--- Bullish Cypher: X (low), A (high), B (low), C (high), D (low)
//---   XA > 0; AB = 0.382-0.618 XA; BC = 1.272-1.414 AB; CD = 0.786 XC; D < X
//--- Bearish Cypher: X (high), A (low), B (high), C (low), D (high)
//---   XA > 0; AB = 0.382-0.618 XA; BC = 1.272-1.414 AB; CD = 0.786 XC; D > X

//--- Define a structure (like a custom data type) to store swing point info
struct SwingPoint {  
   datetime TimeOfSwing;    //--- When the swing happened (date and time of the bar)
   double   PriceAtSwing;   //--- Price at the swing (high or low)
   bool     IsSwingHigh;    //--- True = swing high; False = swing low
};  

//--- Create a dynamic array to hold all detected swing points
SwingPoint SwingPoints[];

Aqui, iniciamos a implementação, em MetaQuotes Language 5, do sistema de trading baseado no padrão Cypher, incluindo a biblioteca "Trade.mqh" para habilitar as operações de negociação e criando uma instância da classe "CTrade", denominada "obj_Trade", responsável pela execução das operações.

Definimos os parâmetros de entrada — "SwingHighCount" e "SwingLowCount" (ambos definidos como 5) para a detecção dos pontos de oscilação, "FibonacciTolerance" (0.10) para determinar a tolerância das proporções de Fibonacci, "TradeVolume" (0.01 lote) para definir o volume das operações e "TradingEnabled" (true) para habilitar ou desabilitar as operações — permitindo que o usuário personalize essas configurações.

A estrutura "SwingPoint" é definida com "TimeOfSwing" (datetime), "PriceAtSwing" (double) e "IsSwingHigh" (boolean) para armazenar os dados de cada ponto de oscilação, enquanto o array dinâmico "SwingPoints" armazena todos os pontos de oscilação detectados para posterior análise dos padrões. 

Em seguida, podemos definir as funções que nos ajudarão a visualizar os padrões no gráfico.

//+------------------------------------------------------------------+
//| Helper: Draw a filled triangle                                   |  
//+------------------------------------------------------------------+  
//--- Function to draw a triangle on the chart to highlight pattern segments
void DrawTriangle(string TriangleName, datetime Time1, double Price1, datetime Time2, double Price2, datetime Time3, double Price3, color LineColor, int LineWidth, bool FillTriangle, bool DrawBehind) {  
   //--- Create a triangle object using three points (time, price) on the chart
   if(ObjectCreate(0, TriangleName, OBJ_TRIANGLE, 0, Time1, Price1, Time2, Price2, Time3, Price3)) {  
      ObjectSetInteger(0, TriangleName, OBJPROP_COLOR, LineColor);      //--- Set the triangle’s color (e.g., blue or red)
      ObjectSetInteger(0, TriangleName, OBJPROP_STYLE, STYLE_SOLID);    //--- Use a solid line style
      ObjectSetInteger(0, TriangleName, OBJPROP_WIDTH, LineWidth);      //--- Set the line thickness
      ObjectSetInteger(0, TriangleName, OBJPROP_FILL, FillTriangle);    //--- Fill the triangle with color if true
      ObjectSetInteger(0, TriangleName, OBJPROP_BACK, DrawBehind);      //--- Draw behind candles if true
   }  
}  

Aqui, implementamos a função "DrawTriangle" para melhorar a visualização do padrão Cypher, desenhando um triângulo preenchido no gráfico do MetaTrader 5 e destacando segmentos específicos do padrão para facilitar sua interpretação pelo trader. A função recebe diversos parâmetros que definem a aparência e a posição do triângulo: "TriangleName" (string) fornece um identificador exclusivo para o objeto; "Time1", "Time2" e "Time3" (datetime) especificam as coordenadas de tempo dos três vértices do triângulo; enquanto "Price1", "Price2" e "Price3" (double) definem os respectivos níveis de preço.

Os parâmetros adicionais incluem "LineColor" (color), que determina a cor do contorno (por exemplo, azul para padrões de alta e vermelho para padrões de baixa); "LineWidth" (int), que define a espessura das bordas do triângulo; "FillTriangle" (bool), que determina se o triângulo será preenchido com uma cor; e "DrawBehind" (bool), que controla se o triângulo será desenhado atrás dos candles do gráfico, evitando que os dados de preço fiquem encobertos.

Dentro da função, utilizamos a função ObjectCreate para criar um objeto de triângulo no gráfico, especificando OBJ_TRIANGLE como o tipo do objeto e fornecendo as coordenadas de tempo e preço dos três pontos. A função verifica se o objeto foi criado com sucesso antes de prosseguir com a configuração de suas propriedades.

Se a criação for bem-sucedida, chamamos a função ObjectSetInteger várias vezes para definir os atributos do triângulo: OBJPROP_COLOR recebe o valor de "LineColor", "OBJPROP_STYLE" é definido como "STYLE_SOLID" para criar um contorno contínuo, "OBJPROP_WIDTH" aplica o valor de "LineWidth", "OBJPROP_FILL" utiliza o valor booleano de "FillTriangle" para habilitar ou desabilitar o preenchimento, e OBJPROP_BACK utiliza o valor booleano de "DrawBehind" para garantir que o triângulo seja exibido atrás dos candles quando definido como true.

Agora, podemos definir as demais funções auxiliares seguindo a mesma lógica.

//+------------------------------------------------------------------+  
//| Helper: Draw a trend line                                        |  
//+------------------------------------------------------------------+  
//--- Function to draw a straight line between two points on the chart
void DrawTrendLine(string LineName, datetime StartTime, double StartPrice, datetime EndTime, double EndPrice, color LineColor, int LineWidth, int LineStyle) {  
   //--- Create a trend line object connecting two points (start time/price to end time/price)
   if(ObjectCreate(0, LineName, OBJ_TREND, 0, StartTime, StartPrice, EndTime, EndPrice)) {  
      ObjectSetInteger(0, LineName, OBJPROP_COLOR, LineColor);      //--- Set the line color
      ObjectSetInteger(0, LineName, OBJPROP_STYLE, LineStyle);      //--- Set line style (e.g., solid or dashed)
      ObjectSetInteger(0, LineName, OBJPROP_WIDTH, LineWidth);      //--- Set line thickness
      ObjectSetInteger(0, LineName, OBJPROP_BACK, true);           
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw a dotted trend line                                 |  
//+------------------------------------------------------------------+  
//--- Function to draw a horizontal dotted line (e.g., for entry or take-profit levels)
void DrawDottedLine(string LineName, datetime StartTime, double LinePrice, datetime EndTime, color LineColor) {  
   //--- Create a horizontal line from start time to end time at a fixed price
   if(ObjectCreate(0, LineName, OBJ_TREND, 0, StartTime, LinePrice, EndTime, LinePrice)) {  
      ObjectSetInteger(0, LineName, OBJPROP_COLOR, LineColor);      //--- Set the line color
      ObjectSetInteger(0, LineName, OBJPROP_STYLE, STYLE_DOT);      //--- Use dotted style
      ObjectSetInteger(0, LineName, OBJPROP_WIDTH, 1);              //--- Thin line
   }  
}  

//+------------------------------------------------------------------+  
//| Helper: Draw anchored text label (for pivots and levels)         |  
//+------------------------------------------------------------------+  
//--- Function to place text labels (e.g., "X" or "TP1") on the chart
void DrawTextLabel(string LabelName, string LabelText, datetime LabelTime, double LabelPrice, color TextColor, int FontSize, bool IsAbove) {  
   //--- Create a text object at a specific time and price
   if(ObjectCreate(0, LabelName, OBJ_TEXT, 0, LabelTime, LabelPrice)) {  
      ObjectSetString(0, LabelName, OBJPROP_TEXT, LabelText);          //--- Set the text to display
      ObjectSetInteger(0, LabelName, OBJPROP_COLOR, TextColor);        //--- Set text color
      ObjectSetInteger(0, LabelName, OBJPROP_FONTSIZE, FontSize);      //--- Set text size
      ObjectSetString(0, LabelName, OBJPROP_FONT, "Arial Bold");       //--- Use bold Arial font
      //--- Position text below if it’s a high point, above if it’s a low point
      ObjectSetInteger(0, LabelName, OBJPROP_ANCHOR, IsAbove ? ANCHOR_BOTTOM : ANCHOR_TOP);  
      ObjectSetInteger(0, LabelName, OBJPROP_ALIGN, ALIGN_CENTER);     //--- Center the text
   }  
}  

Aqui, implementamos a função "DrawTrendLine" para desenhar uma linha reta conectando os pontos de oscilação do padrão Cypher no gráfico, utilizando os parâmetros "LineName" (string), "StartTime", "EndTime" (datetime), "StartPrice", "EndPrice" (double), "LineColor" (color), "LineWidth" (int) e "LineStyle" (int). Utilizamos a função ObjectCreate para criar uma linha "OBJ_TREND" e, se a criação for bem-sucedida, definimos "OBJPROP_COLOR", "OBJPROP_STYLE", "OBJPROP_WIDTH" e "OBJPROP_BACK" (true) por meio de "ObjectSetInteger", fazendo com que a linha seja exibida atrás dos candles.

A função "DrawDottedLine" desenha uma linha horizontal pontilhada para representar os níveis da operação, utilizando "LineName" (string), "StartTime", "EndTime" (datetime), "LinePrice" (double) e "LineColor" (color). Criamos um objeto OBJ_TREND com "ObjectCreate" em um nível de preço fixo e, utilizando ObjectSetInteger, definimos "OBJPROP_COLOR", "OBJPROP_STYLE" como "STYLE_DOT" e "OBJPROP_WIDTH" como 1, criando uma marcação visual discreta.

A função "DrawTextLabel" insere rótulos de texto para identificar pontos de oscilação ou níveis da operação e recebe os parâmetros "LabelName", "LabelText" (string), "LabelTime" (datetime), "LabelPrice" (double), "TextColor" (color), "FontSize" (int) e "IsAbove" (bool). Criamos um objeto OBJ_TEXT com "ObjectCreate" e utilizamos "ObjectSetString" para definir "OBJPROP_TEXT" e "OBJPROP_FONT" ("Arial Bold"), além de "ObjectSetInteger" para configurar "OBJPROP_COLOR", "OBJPROP_FONTSIZE", "OBJPROP_ANCHOR" ("ANCHOR_BOTTOM" ou "ANCHOR_TOP") e "OBJPROP_ALIGN" (ALIGN_CENTER), garantindo anotações claras no gráfico.

Com essas variáveis e funções definidas, podemos avançar para o manipulador de eventos OnTick e iniciar o reconhecimento do padrão. No entanto, como não precisamos realizar o processamento a cada tick, devemos definir uma lógica que execute a identificação apenas uma vez por barra.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
//--- Main function that runs every time a new price tick arrives
void OnTick() {  
   //--- Use a static variable to track the last bar’s time so we only process new bars
   static datetime LastProcessedBarTime = 0;  
   //--- Get the time of the second-to-last bar (latest complete bar)
   datetime CurrentBarTime = iTime(_Symbol, _Period, 1);  
   //--- If no new bar has formed, exit to avoid over-processing
   if(CurrentBarTime == LastProcessedBarTime)  
      return;  
   LastProcessedBarTime = CurrentBarTime;  //--- Update to the current bar
}

No manipulador de eventos OnTick, que é executado sempre que um novo tick de preço chega à plataforma MetaTrader 5, declaramos a variável estática "LastProcessedBarTime" para armazenar o horário da última barra processada, garantindo que a função processe apenas novas barras e, assim, otimize o desempenho.

Utilizando a função iTime, obtemos o horário da penúltima barra (a barra completa mais recente) e o armazenamos em "CurrentBarTime". Em seguida, comparamos "CurrentBarTime" com "LastProcessedBarTime" para verificar se uma nova barra foi formada. Se os valores forem iguais, encerramos a função com uma instrução return para evitar processamento redundante.

Se uma nova barra for detectada, atualizamos "LastProcessedBarTime" com o valor de "CurrentBarTime", permitindo que a função prossiga com a lógica de análise dos dados de preço e detecção dos padrões Cypher. Em seguida, precisamos definir as variáveis que serão utilizadas para determinar os níveis dos pontos de oscilação.

//--- Clear the SwingPoints array to start fresh each time
ArrayResize(SwingPoints, 0);  
//--- Get the total number of bars on the chart
int TotalBars = Bars(_Symbol, _Period);  
int StartBarIndex = SwingHighCount;         //--- Start checking swings after SwingHighCount bars
int EndBarIndex = TotalBars - SwingLowCount; //--- Stop before the last SwingLowCount bars

Utilizamos a função ArrayResize para limpar o array "SwingPoints", definindo seu tamanho como 0 e garantindo que o armazenamento dos novos pontos de oscilação seja iniciado do zero a cada nova barra. Em seguida, obtemos o número total de barras do gráfico utilizando a função Bars e armazenamos o resultado em "TotalBars", que define a extensão dos dados históricos a serem analisados.

Para concentrar a detecção nas barras relevantes, definimos "StartBarIndex" com o valor de "SwingHighCount", indicando a primeira barra que pode ser analisada em busca de pontos de oscilação, e calculamos "EndBarIndex" como "TotalBars" menos "SwingLowCount", garantindo que a análise seja interrompida antes das últimas barras, de modo que haja dados suficientes para confirmar os pontos de oscilação.

Com essas definições, podemos percorrer os dados e coletar os pontos de oscilação.

//--- Loop through bars to find swing highs and lows (swing points)
for(int BarIndex = EndBarIndex - 1; BarIndex >= StartBarIndex; BarIndex--) {  
   bool IsSwingHigh = true;   //--- Assume it’s a high until proven otherwise
   bool IsSwingLow = true;    //--- Assume it’s a low until proven otherwise
   double CurrentBarHigh = iHigh(_Symbol, _Period, BarIndex); //--- Get the high of this bar
   double CurrentBarLow = iLow(_Symbol, _Period, BarIndex);   //--- Get the low of this bar
   //--- Check bars to the left and right to confirm it’s a swing point
   for(int NeighborIndex = BarIndex - SwingHighCount; NeighborIndex <= BarIndex + SwingLowCount; NeighborIndex++) {  
      if(NeighborIndex < 0 || NeighborIndex >= TotalBars || NeighborIndex == BarIndex) //--- Skip invalid bars or current bar
         continue;  
      if(iHigh(_Symbol, _Period, NeighborIndex) > CurrentBarHigh) //--- If any bar is higher, not a high
         IsSwingHigh = false;  
      if(iLow(_Symbol, _Period, NeighborIndex) < CurrentBarLow)   //--- If any bar is lower, not a low
         IsSwingLow = false;  
   }  
   //--- If it’s a high or low, store it in the SwingPoints array
   if(IsSwingHigh || IsSwingLow) {  
      SwingPoint NewSwing;  
      NewSwing.TimeOfSwing = iTime(_Symbol, _Period, BarIndex); //--- Store the bar’s time
      NewSwing.PriceAtSwing = IsSwingHigh ? CurrentBarHigh : CurrentBarLow; //--- Store high or low price
      NewSwing.IsSwingHigh = IsSwingHigh;              //--- Mark as high or low
      int CurrentArraySize = ArraySize(SwingPoints);   //--- Get current array size
      ArrayResize(SwingPoints, CurrentArraySize + 1);  //--- Add one more slot
      SwingPoints[CurrentArraySize] = NewSwing;        //--- Add the swing to the array
   }  
}  

Aqui, implementamos a lógica de detecção dos pontos de oscilação para identificar topos e fundos utilizados na formação do padrão Cypher. Utilizamos um loop for para percorrer as barras de "EndBarIndex - 1" até "StartBarIndex", em ordem decrescente, com "BarIndex" representando a barra atual. Para cada barra, inicializamos "IsSwingHigh" e "IsSwingLow" como true, partindo do pressuposto de que a barra corresponde a um ponto de oscilação até que essa condição seja descartada. Em seguida, obtemos os preços máximo e mínimo da barra utilizando as funções iHigh e iLow, armazenando-os em "CurrentBarHigh" e "CurrentBarLow". Um loop for aninhado verifica as barras vizinhas entre "BarIndex - SwingHighCount" e "BarIndex + SwingLowCount", utilizando "NeighborIndex" e uma instrução continue para ignorar índices inválidos ou a própria barra atual.

Se a máxima de qualquer barra vizinha superar "CurrentBarHigh" ou sua mínima ficar abaixo de "CurrentBarLow" (por meio de iHigh e iLow), definimos "IsSwingHigh" ou "IsSwingLow" como false, respectivamente. Se uma dessas condições permanecer true, criamos uma instância de "SwingPoint" chamada "NewSwing", atribuindo a "TimeOfSwing" o horário da barra obtido por iTime, a "PriceAtSwing" o valor de "CurrentBarHigh" ou "CurrentBarLow", de acordo com "IsSwingHigh", e definimos "IsSwingHigh" com o valor correspondente.

Em seguida, utilizamos a função "ArraySize" para obter o tamanho atual de "SwingPoints", aumentamos seu tamanho em uma posição com ArrayResize e armazenamos "NewSwing" no novo índice do array "SwingPoints", formando assim a coleção de pontos de oscilação que será utilizada na análise do padrão. Ao exibirmos os dados utilizando a função ArrayPrint(SwingPoints), obtemos o seguinte resultado.

RESULTADO DOS DADOS ESTRUTURADOS

Com esses dados, podemos extrair os pontos de pivô e, se houver uma quantidade suficiente deles, analisar e detectar os padrões. A seguir, apresentamos a lógica utilizada para isso.

//--- Check if we have enough swing points (need 5 for Cypher: X, A, B, C, D)
int TotalSwingPoints = ArraySize(SwingPoints);  
if(TotalSwingPoints < 5)  
   return;  //--- Exit if not enough swing points

//--- Assign the last 5 swing points to X, A, B, C, D (most recent is D)
SwingPoint PointX = SwingPoints[TotalSwingPoints - 5];  
SwingPoint PointA = SwingPoints[TotalSwingPoints - 4];  
SwingPoint PointB = SwingPoints[TotalSwingPoints - 3];  
SwingPoint PointC = SwingPoints[TotalSwingPoints - 2];  
SwingPoint PointD = SwingPoints[TotalSwingPoints - 1];  

//--- Variables to track if we found a pattern and its type
bool PatternFound = false;  
string PatternDirection = "";  

//--- Check for Bearish Cypher pattern
if(PointX.IsSwingHigh && !PointA.IsSwingHigh && PointB.IsSwingHigh && !PointC.IsSwingHigh && PointD.IsSwingHigh) {  
   double LegXA = PointX.PriceAtSwing - PointA.PriceAtSwing;  //--- Calculate XA leg (should be positive)
   if(LegXA > 0) {  
      double LegAB = PointB.PriceAtSwing - PointA.PriceAtSwing; //--- AB leg
      double LegBC = PointB.PriceAtSwing - PointC.PriceAtSwing; //--- BC leg
      double LegXC = PointX.PriceAtSwing - PointC.PriceAtSwing; //--- XC leg
      double LegCD = PointD.PriceAtSwing - PointC.PriceAtSwing; //--- CD leg
      //--- Check Fibonacci rules and D > X for bearish
      if(LegAB >= 0.382 * LegXA && LegAB <= 0.618 * LegXA &&  
         LegBC >= 1.272 * LegAB && LegBC <= 1.414 * LegAB &&  
         MathAbs(LegCD - 0.786 * LegXC) <= FibonacciTolerance * LegXC && PointD.PriceAtSwing > PointX.PriceAtSwing) {  
         PatternFound = true;  
         PatternDirection = "Bearish";  
      }  
   }  
}  
//--- Check for Bullish Cypher pattern
else if(!PointX.IsSwingHigh && PointA.IsSwingHigh && !PointB.IsSwingHigh && PointC.IsSwingHigh && !PointD.IsSwingHigh) {  
   double LegXA = PointA.PriceAtSwing - PointX.PriceAtSwing;  //--- Calculate XA leg (should be positive)
   if(LegXA > 0) {  
      double LegAB = PointA.PriceAtSwing - PointB.PriceAtSwing; //--- AB leg
      double LegBC = PointC.PriceAtSwing - PointB.PriceAtSwing; //--- BC leg
      double LegXC = PointC.PriceAtSwing - PointX.PriceAtSwing; //--- XC leg
      double LegCD = PointC.PriceAtSwing - PointD.PriceAtSwing; //--- CD leg
      //--- Check Fibonacci rules and D < X for bullish
      if(LegAB >= 0.382 * LegXA && LegAB <= 0.618 * LegXA &&  
         LegBC >= 1.272 * LegAB && LegBC <= 1.414 * LegAB &&  
         MathAbs(LegCD - 0.786 * LegXC) <= FibonacciTolerance * LegXC && PointD.PriceAtSwing < PointX.PriceAtSwing) {  
         PatternFound = true;  
         PatternDirection = "Bullish";  
      }  
   }  
}  

Aqui, damos continuidade à validação do padrão Cypher, verificando se há pontos de oscilação suficientes e analisando os cinco últimos para identificar uma possível formação do padrão. Utilizamos a função ArraySize para determinar o número de elementos no array "SwingPoints", armazenando esse valor em "TotalSwingPoints". Se "TotalSwingPoints" for menor que 5, encerramos a execução com uma instrução return, pois o padrão Cypher exige cinco pontos (X, A, B, C e D). Se houver pontos suficientes, atribuímos os cinco últimos pontos de oscilação a "PointX", "PointA", "PointB", "PointC" e "PointD" a partir do array "SwingPoints", utilizando os índices de "TotalSwingPoints - 5" até "TotalSwingPoints - 1", sendo "PointD" o ponto mais recente.

Em seguida, inicializamos "PatternFound" como false para indicar se um padrão válido foi detectado e "PatternDirection" como uma string vazia para armazenar o tipo do padrão. Para verificar a formação de um Cypher de baixa, confirmamos se "PointX.IsSwingHigh" é true, "PointA.IsSwingHigh" é false, "PointB.IsSwingHigh" é true, "PointC.IsSwingHigh" é false e "PointD.IsSwingHigh" é true, garantindo a sequência topo-fundo-topo-fundo-topo.

Se essa condição for atendida, calculamos o comprimento das pernas: "LegXA" como "PointX.PriceAtSwing" menos "PointA.PriceAtSwing" (positivo no padrão de baixa), "LegAB" como "PointB.PriceAtSwing" menos "PointA.PriceAtSwing", "LegBC" como "PointB.PriceAtSwing" menos "PointC.PriceAtSwing", "LegXC" como "PointX.PriceAtSwing" menos "PointC.PriceAtSwing" e "LegCD" como "PointD.PriceAtSwing" menos "PointC.PriceAtSwing".

Em seguida, validamos as proporções de Fibonacci: verificamos se "LegAB" corresponde a 38,2% a 61,8% de "LegXA", se "LegBC" corresponde a 127,2% a 141,4% de "LegAB", se "LegCD" está dentro da tolerância definida por "FibonacciTolerance" em relação a 78,6% de "LegXC", utilizando a função "MathAbs", e se "PointD.PriceAtSwing" é superior a "PointX.PriceAtSwing". Se todas essas condições forem atendidas, definimos "PatternFound" como true e "PatternDirection" como "Bearish".

Para um Cypher de alta, verificamos a sequência oposta: "PointX.IsSwingHigh" como false, "PointA.IsSwingHigh" como true, "PointB.IsSwingHigh" como false, "PointC.IsSwingHigh" como true e "PointD.IsSwingHigh" como false.

Calculamos "LegXA" como "PointA.PriceAtSwing" menos "PointX.PriceAtSwing" (positivo no padrão de alta), "LegAB" como "PointA.PriceAtSwing" menos "PointB.PriceAtSwing", "LegBC" como "PointC.PriceAtSwing" menos "PointB.PriceAtSwing", "LegXC" como "PointC.PriceAtSwing" menos "PointX.PriceAtSwing" e "LegCD" como "PointC.PriceAtSwing" menos "PointD.PriceAtSwing".

Aplicamos as mesmas verificações das proporções de Fibonacci, acrescentando a condição de que "PointD.PriceAtSwing" seja inferior a "PointX.PriceAtSwing". Se todas as condições forem atendidas, "PatternFound" é definido como true e "PatternDirection" como "Bullish", permitindo que a lógica subsequente de visualização e execução das operações seja aplicada. Se o padrão for identificado, podemos prosseguir com sua representação no gráfico.

//--- If a pattern is found, visualize it and trade
if(PatternFound) {  
   //--- Log the pattern detection in the Experts tab
   Print(PatternDirection, " Cypher pattern detected at ", TimeToString(PointD.TimeOfSwing, TIME_DATE|TIME_MINUTES));  
   
   //--- Create a unique prefix for all chart objects using D’s time
   string ObjectPrefix = "CY_" + IntegerToString(PointD.TimeOfSwing);  
   //--- Set triangle color: blue for bullish, red for bearish
   color TriangleColor = (PatternDirection == "Bullish") ? clrBlue : clrRed;  
   
   //--- **Visualization Steps**
   //--- 1. Draw two filled triangles to highlight the pattern
   DrawTriangle(ObjectPrefix + "_Triangle1", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, TriangleColor, 2, true, true);  
   DrawTriangle(ObjectPrefix + "_Triangle2", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, TriangleColor, 2, true, true);  
}

Quando "PatternFound" é true, prosseguimos com a visualização do padrão Cypher detectado. Utilizamos a função Print para registrar a detecção do padrão na guia Experts, exibindo "PatternDirection" seguido de uma mensagem informando que um padrão Cypher foi detectado. O horário é formatado pela função TimeToString, utilizando "PointD.TimeOfSwing" e as flags "TIME_DATE|TIME_MINUTES" para facilitar a leitura.

Para organizar os objetos no gráfico, criamos um prefixo exclusivo chamado "ObjectPrefix", concatenando "CY_" com a representação em string de "PointD.TimeOfSwing", obtida por meio da função IntegerToString. Dessa forma, os objetos pertencentes a cada padrão recebem nomes distintos. Em seguida, definimos "TriangleColor" utilizando um operador ternário, atribuindo "clrBlue" ao padrão "Bullish" ou "clrRed" ao padrão "Bearish", de acordo com o valor de "PatternDirection".

Para representar visualmente o padrão, chamamos a função "DrawTriangle" duas vezes: primeiro, para desenhar um triângulo chamado "ObjectPrefix + '_Triangle1'", conectando "PointX", "PointA" e "PointB" por meio de seus respectivos valores de "TimeOfSwing" e "PriceAtSwing"; depois, para desenhar "ObjectPrefix + '_Triangle2'", conectando "PointB", "PointC" e "PointD".Em ambos os casos, utilizamos "TriangleColor", espessura de linha igual a 2 e o valor "true" tanto para o preenchimento quanto para a exibição atrás dos candles, destacando visualmente a estrutura do padrão no gráfico. Veja o resultado obtido até aqui.

MAPEAMENTO DOS TRIÂNGULOS DO PADRÃO HARMÔNICO CYPHER

Pela imagem, podemos observar que o padrão detectado foi corretamente mapeado e representado no gráfico. Agora, precisamos continuar o mapeamento adicionando as linhas de tendência para delimitar completamente sua estrutura, além de inserir rótulos que facilitem a identificação dos níveis.

//--- 2. Draw six trend lines connecting the swing points
DrawTrendLine(ObjectPrefix + "_Line_XA", PointX.TimeOfSwing, PointX.PriceAtSwing, PointA.TimeOfSwing, PointA.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_AB", PointA.TimeOfSwing, PointA.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_BC", PointB.TimeOfSwing, PointB.PriceAtSwing, PointC.TimeOfSwing, PointC.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_CD", PointC.TimeOfSwing, PointC.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_XB", PointX.TimeOfSwing, PointX.PriceAtSwing, PointB.TimeOfSwing, PointB.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  
DrawTrendLine(ObjectPrefix + "_Line_BD", PointB.TimeOfSwing, PointB.PriceAtSwing, PointD.TimeOfSwing, PointD.PriceAtSwing, clrBlack, 2, STYLE_SOLID);  

//--- 3. Draw labels for each swing point (X, A, B, C, D)
double LabelOffset = 15 * SymbolInfoDouble(_Symbol, SYMBOL_POINT); //--- Offset in points for label placement
DrawTextLabel(ObjectPrefix + "_Label_X", "X", PointX.TimeOfSwing, PointX.PriceAtSwing + (PointX.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointX.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_A", "A", PointA.TimeOfSwing, PointA.PriceAtSwing + (PointA.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointA.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_B", "B", PointB.TimeOfSwing, PointB.PriceAtSwing + (PointB.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointB.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_C", "C", PointC.TimeOfSwing, PointC.PriceAtSwing + (PointC.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointC.IsSwingHigh);  
DrawTextLabel(ObjectPrefix + "_Label_D", "D", PointD.TimeOfSwing, PointD.PriceAtSwing + (PointD.IsSwingHigh ? LabelOffset : -LabelOffset), clrBlack, 11, PointD.IsSwingHigh);  

//--- 4. Draw a central label to identify the pattern
datetime CenterTime = (PointX.TimeOfSwing + PointB.TimeOfSwing) / 2;  //--- Middle time between X and B
double CenterPrice = PointD.PriceAtSwing;                            //--- Place it at D’s price level
if(ObjectCreate(0, ObjectPrefix + "_Label_Center", OBJ_TEXT, 0, CenterTime, CenterPrice)) {  
   ObjectSetString(0, ObjectPrefix + "_Label_Center", OBJPROP_TEXT, "Cypher"); //--- Label as "Cypher"
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_COLOR, clrBlack);  
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_FONTSIZE, 11);  
   ObjectSetString(0, ObjectPrefix + "_Label_Center", OBJPROP_FONT, "Arial Bold");  
   ObjectSetInteger(0, ObjectPrefix + "_Label_Center", OBJPROP_ALIGN, ALIGN_CENTER);  
}  

Damos continuidade ao processo de visualização para representar o padrão Cypher com mais detalhes no gráfico. Chamamos a função "DrawTrendLine" seis vezes para desenhar linhas pretas contínuas conectando os pontos de oscilação. Cada linha recebe um nome formado por "ObjectPrefix" acrescido de um sufixo exclusivo (por exemplo, "_Line_XA"). Essas linhas conectam "PointX" a "PointA", "PointA" a "PointB", "PointB" a "PointC", "PointC" a "PointD", "PointX" a "PointB" e "PointB" a "PointD", utilizando os respectivos valores de "TimeOfSwing" e "PriceAtSwing", com espessura de linha igual a 2 e STYLE_SOLID para delimitar claramente a estrutura do padrão.

Em seguida, adicionamos rótulos de texto a cada ponto de oscilação. Para isso, calculamos "LabelOffset" como 15 vezes o tamanho do ponto do símbolo, obtido por meio da função SymbolInfoDouble com SYMBOL_POINT, permitindo posicionar adequadamente os rótulos. Chamamos a função "DrawTextLabel" cinco vezes para identificar "PointX", "PointA", "PointB", "PointC" e "PointD", utilizando nomes como "ObjectPrefix + '_Label_X'" e os textos "X", "A", "B", "C" e "D". Cada rótulo utiliza os valores de "TimeOfSwing" e "PriceAtSwing" do respectivo ponto, com o preço ajustado por "LabelOffset" — somado quando "IsSwingHigh" é true e subtraído quando é false —, além da cor "clrBlack", tamanho de fonte 11 e do valor de "IsSwingHigh" para determinar se o rótulo será posicionado acima ou abaixo do ponto.

Por fim, criamos um rótulo central para identificar o padrão, calculando "CenterTime" como a média entre "PointX.TimeOfSwing" e "PointB.TimeOfSwing" e definindo "CenterPrice" como "PointD.PriceAtSwing". Utilizamos a função ObjectCreate para criar, nessas coordenadas, um objeto de texto chamado "ObjectPrefix + '_Label_Center'", do tipo "OBJ_TEXT". Se a criação for bem-sucedida, utilizamos "ObjectSetString" para definir "OBJPROP_TEXT" como "Cypher" e "OBJPROP_FONT" como "Arial Bold". Em seguida, usamos ObjectSetInteger para definir "OBJPROP_COLOR" como "clrBlack", "OBJPROP_FONTSIZE" como 11 e "OBJPROP_ALIGN" como "ALIGN_CENTER", identificando claramente o padrão no gráfico. Após a compilação, obtemos o seguinte resultado.

PADRÃO COM CONTORNOS E RÓTULOS

Pela imagem, podemos observar que adicionamos os contornos e os rótulos ao padrão, tornando sua representação mais clara e informativa. O próximo passo é determinar os níveis da operação para esse padrão.

//--- 5. Draw trade levels (entry, take-profits) as dotted lines
datetime LineStartTime = PointD.TimeOfSwing;                    //--- Start at D’s time
datetime LineEndTime = PointD.TimeOfSwing + PeriodSeconds(_Period) * 2; //--- Extend 2 bars to the right
double EntryPrice, StopLossPrice, TakeProfitPrice, TakeProfit1Level, TakeProfit2Level, TakeProfit3Level, TradeDistance;  
if(PatternDirection == "Bullish") {  
   EntryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Buy at current ask price
   StopLossPrice = PointX.PriceAtSwing;                //--- Stop-loss at X (below entry for bullish)
   TakeProfitPrice = PointC.PriceAtSwing;              //--- Take-profit at C (target level)
   TakeProfit3Level = PointC.PriceAtSwing;             //--- Highest TP at C
   TradeDistance = TakeProfit3Level - EntryPrice;      //--- Distance to TP3
   TakeProfit1Level = EntryPrice + TradeDistance / 3;  //--- First TP at 1/3 of the distance
   TakeProfit2Level = EntryPrice + 2 * TradeDistance / 3; //--- Second TP at 2/3
} else {  
   EntryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Sell at current bid price
   StopLossPrice = PointX.PriceAtSwing;                //--- Stop-loss at X (above entry for bearish)
   TakeProfitPrice = PointC.PriceAtSwing;              //--- Take-profit at C
   TakeProfit3Level = PointC.PriceAtSwing;             //--- Lowest TP at C
   TradeDistance = EntryPrice - TakeProfit3Level;      //--- Distance to TP3
   TakeProfit1Level = EntryPrice - TradeDistance / 3;  //--- First TP at 1/3
   TakeProfit2Level = EntryPrice - 2 * TradeDistance / 3; //--- Second TP at 2/3
}  

DrawDottedLine(ObjectPrefix + "_EntryLine", LineStartTime, EntryPrice, LineEndTime, clrMagenta); //--- Entry line
DrawDottedLine(ObjectPrefix + "_TP1Line", LineStartTime, TakeProfit1Level, LineEndTime, clrForestGreen); //--- TP1 line
DrawDottedLine(ObjectPrefix + "_TP2Line", LineStartTime, TakeProfit2Level, LineEndTime, clrGreen);      //--- TP2 line
DrawDottedLine(ObjectPrefix + "_TP3Line", LineStartTime, TakeProfit3Level, LineEndTime, clrDarkGreen);  //--- TP3 line

//--- 6. Draw labels for trade levels
datetime LabelTime = LineEndTime + PeriodSeconds(_Period) / 2; //--- Place labels further right
string EntryLabelText = (PatternDirection == "Bullish") ? "BUY (" : "SELL (";  
EntryLabelText += DoubleToString(EntryPrice, _Digits) + ")"; //--- Add price to label
DrawTextLabel(ObjectPrefix + "_EntryLabel", EntryLabelText, LabelTime, EntryPrice, clrMagenta, 11, true);  

string TP1LabelText = "TP1 (" + DoubleToString(TakeProfit1Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP1Label", TP1LabelText, LabelTime, TakeProfit1Level, clrForestGreen, 11, true);  

string TP2LabelText = "TP2 (" + DoubleToString(TakeProfit2Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP2Label", TP2LabelText, LabelTime, TakeProfit2Level, clrGreen, 11, true);  

string TP3LabelText = "TP3 (" + DoubleToString(TakeProfit3Level, _Digits) + ")";  
DrawTextLabel(ObjectPrefix + "_TP3Label", TP3LabelText, LabelTime, TakeProfit3Level, clrDarkGreen, 11, true);  

Aqui, damos continuidade à representação dos níveis da operação do padrão Cypher, adicionando linhas pontilhadas e rótulos. Definimos "LineStartTime" como "PointD.TimeOfSwing" e "LineEndTime" como duas barras após esse ponto, utilizando a função PeriodSeconds multiplicada por 2, estabelecendo assim o intervalo de tempo das linhas horizontais.

Para um padrão "Bullish" (quando "PatternDirection" é "Bullish"), definimos "EntryPrice" como o preço ask atual por meio de SymbolInfoDouble com SYMBOL_ASK, "StopLossPrice" como "PointX.PriceAtSwing" e tanto "TakeProfitPrice" quanto "TakeProfit3Level" como "PointC.PriceAtSwing". Calculamos "TradeDistance" como "TakeProfit3Level" menos "EntryPrice" e, em seguida, calculamos "TakeProfit1Level" e "TakeProfit2Level" adicionando a "EntryPrice", respectivamente, um terço e dois terços de "TradeDistance".

Para um padrão de baixa, utilizamos SYMBOL_BID para definir "EntryPrice", configuramos "StopLossPrice" e "TakeProfit3Level" de forma equivalente, calculamos "TradeDistance" como "EntryPrice" menos "TakeProfit3Level" e subtraímos de "EntryPrice", respectivamente, um terço e dois terços de "TradeDistance" para obter "TakeProfit1Level" e "TakeProfit2Level".

Em seguida, chamamos a função "DrawDottedLine" quatro vezes para desenhar as linhas horizontais: "ObjectPrefix + '_EntryLine'" em "EntryPrice", utilizando "clrMagenta", e "ObjectPrefix + '_TP1Line'", "ObjectPrefix + '_TP2Line'" e "ObjectPrefix + '_TP3Line'" em "TakeProfit1Level", "TakeProfit2Level" e "TakeProfit3Level", utilizando, respectivamente, "clrForestGreen", "clrGreen" e "clrDarkGreen". Todas as linhas são desenhadas de "LineStartTime" até "LineEndTime".

Para os rótulos, definimos "LabelTime" como "LineEndTime" acrescido de metade da duração de uma barra, utilizando "PeriodSeconds". Criamos "EntryLabelText" como "BUY " ou ("SELL"), de acordo com "PatternDirection", acrescentando "EntryPrice" formatado pela função "DoubleToString" com "_Digits". Em seguida, chamamos "DrawTextLabel" para "ObjectPrefix + '_EntryLabel'" no nível de "EntryPrice", utilizando "clrMagenta".

Da mesma forma, definimos "TP1LabelText", "TP2LabelText" e "TP3LabelText" com os preços formatados de "TakeProfit1Level", "TakeProfit2Level" e "TakeProfit3Level". Para cada um deles, chamamos "DrawTextLabel" no respectivo nível, utilizando "clrForestGreen", "clrGreen" e "clrDarkGreen", todos com tamanho de fonte 11 e posicionados acima do nível de preço, facilitando a identificação dos níveis da operação. Este é o resultado.

Padrão de baixa:

BAIXA

Padrão de alta:

ALTA

Pelas imagens, podemos observar que os níveis da operação foram corretamente mapeados. Agora, precisamos apenas abrir efetivamente as posições de negociação.

//--- **Trading Logic**
//--- Check if trading is allowed and no position is already open
if(TradingEnabled && !PositionSelect(_Symbol)) {  
   //--- Place a buy or sell order based on pattern type
   bool TradeSuccessful = (PatternDirection == "Bullish") ?  
      obj_Trade.Buy(TradeVolume, _Symbol, EntryPrice, StopLossPrice, TakeProfitPrice, "Cypher Buy") :  
      obj_Trade.Sell(TradeVolume, _Symbol, EntryPrice, StopLossPrice, TakeProfitPrice, "Cypher Sell");  
   //--- Log the result of the trade attempt
   if(TradeSuccessful)  
      Print(PatternDirection, " order opened successfully.");  
   else  
      Print(PatternDirection, " order failed: ", obj_Trade.ResultRetcodeDescription());  
}  

//--- Force the chart to update and show all drawn objects
ChartRedraw();  

Aqui, implementamos a lógica de negociação responsável por executar as operações do padrão Cypher quando as condições forem atendidas. Verificamos se "TradingEnabled" é true e se não existe nenhuma posição aberta para o símbolo atual, utilizando a função PositionSelect com _Symbol. Dessa forma, as operações são executadas somente quando estão habilitadas e não há uma posição existente que possa gerar conflito. Se ambas as condições forem atendidas, utilizamos um operador ternário para executar a operação de acordo com "PatternDirection": para um padrão "Bullish", chamamos a função "obj_Trade.Buy" com os parâmetros "TradeVolume", _Symbol, "EntryPrice", "StopLossPrice", "TakeProfitPrice" e o comentário "Cypher Buy"; para um padrão de baixa, chamamos "obj_Trade.Sell" com os mesmos parâmetros, mas utilizando o comentário "Cypher Sell". O resultado da operação é armazenado em "TradeSuccessful".

Em seguida, registramos o resultado utilizando a função Print, exibindo "PatternDirection" e a mensagem "order opened successfully" caso "TradeSuccessful" seja true a mensagem "order failed", acompanhada da descrição do erro obtida por meio de "obj_Trade.ResultRetcodeDescription", caso seja false. Por fim, chamamos a função ChartRedraw para forçar a atualização do gráfico do MetaTrader 5, garantindo que todos os objetos desenhados, como triângulos, linhas e rótulos, sejam imediatamente exibidos ao usuário. 

Por último, precisamos apenas excluir os padrões do gráfico quando o programa for removido.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
//--- Runs when the EA stops (e.g., removed from chart)
void OnDeinit(const int reason) {  
   //--- Remove all objects starting with "CY_" (our Cypher pattern objects)
   ObjectsDeleteAll(0, "CY_");  
}  

No manipulador de eventos OnDeinit, utilizamos a função ObjectsDeleteAll para remover todos os objetos do gráfico cujos nomes começam com o prefixo "CY_", garantindo que todas as representações visuais relacionadas ao padrão Cypher, como triângulos, linhas de tendência e rótulos, sejam removidas do gráfico e mantendo a área de trabalho limpa quando o sistema deixar de estar ativo. Após a compilação, obtemos o seguinte resultado.

CONFIRMAÇÃO DA OPERAÇÃO

Pela imagem, podemos observar que o padrão Cypher é representado no gráfico e que, após sua confirmação, o sistema também consegue executar a operação correspondente. Dessa forma, cumprimos o objetivo proposto de identificar, representar graficamente e negociar o padrão. Resta agora realizar o backtesting do programa, assunto abordado na próxima seção.


Backtesting e Otimização

Durante os testes iniciais de backtesting, identificamos um problema crítico: o sistema apresentava tendência a redesenhar os padrões. Esse repainting (repintura/redesenho do padrão) ocorria quando um padrão Cypher parecia válido em uma determinada barra, mas era alterado ou desaparecia à medida que novos dados de preço eram recebidos, resultando em sinais de negociação pouco confiáveis. Esse problema gerava falsos positivos, nos quais as operações eram executadas com base em padrões que posteriormente se mostravam inválidos, afetando negativamente o desempenho. A seguir, apresentamos um exemplo do problema.

PADRÃO REDESENHADO

Para solucionar esse problema, implementamos um mecanismo de bloqueio do padrão utilizando as variáveis globais "g_patternFormationBar" e "g_lockedPatternX". Esse mecanismo bloqueia o padrão assim que ele é detectado e realiza sua confirmação na barra seguinte, garantindo que o ponto de oscilação X permaneça consistente. Essa correção reduziu significativamente a redesenho dos padrões, conforme confirmado pelos testes posteriores, que apresentaram uma detecção mais estável e menos operações baseadas em padrões inválidos. A seguir, apresentamos um exemplo de código para bloquear o padrão e garantir que o sistema aguarde sua estabilização antes de executar uma operação.

//--- If the pattern has changed, update the lock
g_patternFormationBar = CurrentBarIndex;
g_lockedPatternX = PointX.TimeOfSwing;
Print("Cypher pattern has changed; updating lock on bar ", CurrentBarIndex, ". Waiting for confirmation.");
return;

Adicionamos uma lógica de confirmação para sempre aguardar uma barra adicional, garantindo que o padrão esteja confirmado e estável antes da execução da operação. Dessa forma, evitamos abrir uma posição prematuramente e descobrir posteriormente que o movimento observado correspondia apenas ao início da formação do padrão. Após adicionarmos o mecanismo de bloqueio do padrão, podemos observar que o problema foi solucionado.

BLOQUEIO APRIMORADO DO PADRÃO

Após a correção e a realização de testes completos de backtesting, obtivemos os seguintes resultados.

Gráfico do backtest:

GRÁFICO

Relatório de backtest:

RELATÓRIO


Conclusão

Em conclusão, desenvolvemos com sucesso um Expert Advisor em MetaQuotes Language 5 capaz de detectar e negociar o padrão harmônico Cypher com precisão. Ao integrar a detecção de pontos de oscilação, a validação baseada em Fibonacci, uma representação gráfica detalhada e um mecanismo de bloqueio do padrão para evitar a repintura, criamos um sistema robusto que se adapta dinamicamente às condições do mercado.

Aviso: Este artigo tem finalidade exclusivamente educacional. A negociação nos mercados financeiros envolve riscos financeiros significativos, e as condições de mercado podem ser imprevisíveis. Embora a estratégia apresentada ofereça uma abordagem estruturada para a negociação com padrões harmônicos, ela não garante rentabilidade. É essencial realizar backtests abrangentes e adotar uma gestão de risco adequada antes de utilizar este programa em um ambiente de negociação real.

Ao aplicar essas técnicas, você poderá aprimorar suas habilidades na negociação com padrões harmônicos, aprofundar sua análise técnica e desenvolver ainda mais suas estratégias de trading algorítmico. Boa sorte em sua jornada no trading!

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

Arquivos anexados |
Últimos Comentários | Ir para discussão (4)
Kyle Young Sangster
Kyle Young Sangster | 3 mai. 2025 em 19:30
Olá, ótimo artigo. Obrigado pelo seu empenho. Você poderia compartilhar o arquivo de código completo? Anexá-lo ao final do artigo?
Muito obrigado
Allan Munene Mutiiria
Allan Munene Mutiiria | 5 mai. 2025 em 07:04
Kyle Young Sangster #:
Olá, ótimo artigo. Obrigado pelo seu empenho. Você poderia compartilhar o arquivo de código completo? Anexá-lo no final do artigo?
Muito obrigado

De nada. Você ao menos verificou? Obrigado.

Muhammad Syamil Bin Abdullah
Muhammad Syamil Bin Abdullah | 7 jun. 2025 em 11:08
Obrigado por compartilhar este artigo. É um código útil para implementar outros padrões harmônicos.
Allan Munene Mutiiria
Allan Munene Mutiiria | 7 jun. 2025 em 17:15
Muhammad Syamil Bin Abdullah #:
Obrigado por compartilhar este artigo. É um código útil para implementar outros padrões harmônicos.

Claro. De nada.

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 60): Aprendizado por Inferência (Wasserstein-VAE) com Padrões da Média Móvel e do Oscilador Estocástico Técnicas do MQL5 Wizard que você deve conhecer (Parte 60): Aprendizado por Inferência (Wasserstein-VAE) com Padrões da Média Móvel e do Oscilador Estocástico
Concluímos nossa análise sobre o uso complementar da Média Móvel (MA) e do Oscilador Estocástico examinando o papel que o aprendizado por inferência pode desempenhar após as etapas de aprendizado supervisionado e aprendizado por reforço. Existem diversas maneiras de abordar o aprendizado por inferência nesse contexto. No entanto, nossa abordagem consiste em utilizar Variational Autoencoders (VAEs). Primeiro, exploraremos essa abordagem em Python e, em seguida, exportaremos nosso modelo treinado no formato ONNX para utilizá-lo em um Expert Advisor desenvolvido com o MQL5 Wizard no MetaTrader.
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.
Ciência de Dados e ML (Parte 36): Lidando com Mercados Financeiros Tendenciosos Ciência de Dados e ML (Parte 36): Lidando com Mercados Financeiros Tendenciosos
Os mercados financeiros não são perfeitamente equilibrados. Alguns mercados apresentam tendência de alta, outros apresentam tendência de baixa, enquanto outros apresentam movimento lateral, indicando incerteza quanto à direção. Quando essas informações desbalanceadas são utilizadas para treinar modelos de machine learning, elas podem gerar resultados enganosos, especialmente porque as condições do mercado mudam com frequência. Neste artigo, discutiremos diferentes maneiras de lidar com esse problema.