Automatização de estratégias de negociação em MQL5 (Parte 28): Criação do padrão harmônico "Morcego" baseado em Price Action com feedback visual
Introdução
No artigo anterior (Parte 27), desenvolvemos um sistema baseado no padrão "Caranguejo" em MetaQuotes Language 5 (MQL5). Esse sistema utiliza padrões harmônicos e proporções precisas de Fibonacci para identificar pontos de reversão de alta probabilidade. Além disso, automatiza a execução das operações e representa os padrões no gráfico por meio de objetos gráficos. Na Parte 28, desenvolveremos um sistema baseado no padrão "Morcego", capaz de identificar configurações harmônicas de alta e de baixa utilizando pontos de pivô e níveis de retração de Fibonacci. O programa abrirá operações com níveis calculados de entrada, stop-loss e múltiplos alvos de take-profit. Também contará com triângulos, linhas de tendência e rótulos para facilitar a identificação visual dos padrões. Abordaremos os seguintes tópicos:
- Estudo da estrutura do padrão harmônico "Morcego"
- Implementação em MQL5
- Testes com dados históricos
- Conclusão
Ao final, você terá uma estratégia estável em MQL5 para negociar com base no padrão harmônico "Morcego", pronta para ser configurada. Vamos à implementação!
Estudo da estrutura do padrão harmônico "Morcego"
O padrão "Morcego" é uma formação harmônica de trading definida por cinco pontos principais de oscilação do preço, identificados pelas letras X, A, B, C e D. Ele apresenta duas configurações: uma de alta e outra de baixa. Ambas são utilizadas para identificar possíveis reversões do mercado com base nas proporções de Fibonacci. Na configuração de alta, o padrão "Morcego" apresenta uma sequência de "mínima-máxima-mínima-máxima-mínima", em que o ponto X corresponde a um swing low, o ponto A a um swing high, o ponto B a um swing low (com retração de aproximadamente 0,5 do segmento XA), o ponto C a um swing high (com extensão entre 0,382 e 0,886 do segmento AB) e o ponto D a um swing low (no nível de retração de 0,886 do segmento XA, acima do ponto X). Já na configuração de baixa, o padrão "Morcego" apresenta a sequência inversa, "máxima-mínima-máxima-mínima-máxima", em que o ponto X corresponde a um swing high, o ponto A a um swing low, o ponto B a um swing high, o ponto C a um swing low e o ponto D a um swing high situado abaixo do ponto X (com extensão de 0,886 do segmento XA). A seguir, apresentamos uma representação visual das duas configurações.
Padrão harmônico "Morcego" de alta:

Padrão harmônico "Morcego" de baixa:

Para identificar esses padrões, adotaremos a seguinte abordagem:
- Definição da onda XA: O movimento inicial do ponto X até o ponto A estabelece a direção do padrão (ascendente na configuração de alta e descendente na de baixa) e serve como principal referência para os cálculos dos níveis de Fibonacci.
- Formação da onda AB: O preço deve recuar até o ponto B, retraindo aproximadamente 50% do movimento XA, indicando uma correção moderada do impulso inicial.
- Análise da onda BC: Essa onda deve corresponder a uma retração entre 38,2% e 88,6% do movimento AB, formando um movimento contrário que prepara a região da reversão final.
- Definição da onda CD: A última onda deve alcançar o nível de retração de 88,6% do movimento XA, delimitando a possível zona de reversão no ponto D, onde o padrão se completa e gera um sinal de negociação.
Com base nesses critérios geométricos e nas proporções de Fibonacci, nosso sistema de negociação identificará de maneira sistemática os padrões "Morcego" válidos nos dados de preços. Assim que um padrão for confirmado, o EA exibirá sua estrutura no gráfico por meio de triângulos, linhas de tendência e rótulos para os pontos X, A, B, C e D, além dos níveis de entrada e take-profit das operações. Isso permitirá abrir operações automaticamente no ponto D, com stop-loss calculado e múltiplos níveis de take-profit, aproveitando a capacidade do padrão de antecipar possíveis reversões do mercado. Vamos à implementação!
Implementação em MQL5
Para criar o programa em MQL5, abra o MetaEditor, acesse o Navegador, localize a pasta "Indicadores" (Indicators), selecione a opção "Criar" (New) e siga as instruções para criar o arquivo. Depois disso, precisaremos declarar algumas variáveis globais no ambiente de desenvolvimento, que serão utilizadas em todo o programa.
//+------------------------------------------------------------------+ //| Bat Pattern EA.mq5. | //| 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 Bat Strategy" #property strict //--- Include the trading library for order functions #include <Trade\Trade.mqh> //--- Include Trade library CTrade obj_Trade; //--- Instantiate a obj_Trade object //--- Input parameters for user configuration input int PivotLeft = 5; // Number of bars to the left for pivot check input int PivotRight = 5; // Number of bars to the right for pivot check input double Tolerance = 0.10; // Allowed deviation (10% of XA move) input double LotSize = 0.01; // Lot size for new orders input bool AllowTrading = true; // Enable or disable trading //--------------------------------------------------------------------------- //--- Bat pattern definition: // //--- Bullish Bat: //--- Pivots (X-A-B-C-D): X swing low, A swing high, B swing low, C swing high, D swing low. //--- Normally XA > 0; Ideal B = A - 0.5*(A-X); Legs within specified ranges. // //--- Bearish Bat: //--- Pivots (X-A-B-C-D): X swing high, A swing low, B swing high, C swing low, D swing high. //--- Normally XA > 0; Ideal B = A + 0.5*(X-A); Legs within specified ranges. //--------------------------------------------------------------------------- //--- Structure for a pivot point struct Pivot { datetime time; //--- Bar time of the pivot double price; //--- Pivot price (High for swing high, low for swing low) bool isHigh; //--- True if swing high; false if swing low }; //--- Global dynamic array for storing pivots in chronological order Pivot pivots[]; //--- Declare a dynamic array to hold identified pivot points //--- Global variables to lock in a pattern (avoid trading on repaint) int g_patternFormationBar = -1; //--- Bar index where the pattern was formed (-1 means none) datetime g_lockedPatternX = 0; //--- The key X pivot time for the locked pattern bool g_tradeTaken = false; //--- Flag to indicate if a trade has been taken for the current pattern
Para estabelecer a base do sistema que automatizará as operações com o padrão harmônico "Morcego", começaremos incluindo a biblioteca "<Trade\Trade.mqh>" e criando uma instância da classe CTrade chamada "obj_Trade", responsável por gerenciar as operações de negociação, como a abertura de posições de compra e venda. Em seguida, definiremos cinco parâmetros de entrada para permitir a configuração pelo usuário. Os parâmetros "PivotLeft" e "PivotRight" terão o valor de 5 barras cada, estabelecendo uma janela de análise com cinco barras à esquerda e cinco à direita para identificar os pivôs. O parâmetro "Tolerance" será definido como 0,10, estabelecendo a margem de tolerância para desvios em relação aos níveis de Fibonacci. Já "LotSize" terá o valor de 0,01, para definir o volume das operações, enquanto "AllowTrading" será definido como true, habilitando a negociação automática.
Na sequência, definiremos a estrutura "Pivot" com os campos "time" (datetime), "price" (double) e "isHigh" (bool), para armazenar os pontos de reversão do preço. Também declararemos "pivots" como um array dinâmico para armazenar esses pontos. Inicializaremos a variável global "g_patternFormationBar" com o valor -1, para acompanhar a formação dos padrões, e "g_lockedPatternX" com 0, para registrar o horário do pivô X. Por fim, definiremos "g_tradeTaken" como false, evitando a abertura de múltiplas operações para o mesmo padrão. Com isso, teremos a estrutura básica do EA para identificar padrões "Morcego" e executar operações com base neles. Para a representação visual, poderemos utilizar funções que desenham linhas, rótulos e triângulos no gráfico.
//+------------------------------------------------------------------+ //| Helper: Draw a filled triangle | //+------------------------------------------------------------------+ void DrawTriangle(string name, datetime t1, double p1, datetime t2, double p2, datetime t3, double p3, color cl, int width, bool fill, bool back) { //--- Attempt to create a triangle object with three coordinate points if(ObjectCreate(0, name, OBJ_TRIANGLE, 0, t1, p1, t2, p2, t3, p3)) { //--- Set the triangle's color ObjectSetInteger(0, name, OBJPROP_COLOR, cl); //--- Set the triangle's line style to solid ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set the line width of the triangle ObjectSetInteger(0, name, OBJPROP_WIDTH, width); //--- Determine if the triangle should be filled ObjectSetInteger(0, name, OBJPROP_FILL, fill); //--- Set whether the object is drawn in the background ObjectSetInteger(0, name, OBJPROP_BACK, back); } } //+------------------------------------------------------------------+ //| Helper: Draw a trend line | //+------------------------------------------------------------------+ void DrawTrendLine(string name, datetime t1, double p1, datetime t2, double p2, color cl, int width, int style) { //--- Create a trend line object connecting two points if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) { //--- Set the trend line's color ObjectSetInteger(0, name, OBJPROP_COLOR, cl); //--- Set the trend line's style (solid, dotted, etc.) ObjectSetInteger(0, name, OBJPROP_STYLE, style); //--- Set the width of the trend line ObjectSetInteger(0, name, OBJPROP_WIDTH, width); } } //+------------------------------------------------------------------+ //| Helper: Draw a dotted trend line | //+------------------------------------------------------------------+ void DrawDottedLine(string name, datetime t1, double p, datetime t2, color lineColor) { //--- Create a horizontal trend line at a fixed price level with dotted style if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p, t2, p)) { //--- Set the dotted line's color ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); //--- Set the line style to dotted ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT); //--- Set the line width to 1 ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); } } //+------------------------------------------------------------------+ //| Helper: Draw anchored text label (for pivots) | //| If isHigh is true, anchor at the bottom (label appears above); | //| if false, anchor at the top (label appears below). | //+------------------------------------------------------------------+ void DrawTextEx(string name, string text, datetime t, double p, color cl, int fontsize, bool isHigh) { //--- Create a text label object at the specified time and price if(ObjectCreate(0, name, OBJ_TEXT, 0, t, p)) { //--- Set the text of the label ObjectSetString(0, name, OBJPROP_TEXT, text); //--- Set the color of the text ObjectSetInteger(0, name, OBJPROP_COLOR, cl); //--- Set the font size for the text ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontsize); //--- Set the font type and style ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold"); //--- Anchor the text depending on whether it's a swing high or low if(isHigh) ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_BOTTOM); else ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_TOP); //--- Center-align the text ObjectSetInteger(0, name, OBJPROP_ALIGN, ALIGN_CENTER); } }
Para representar visualmente o padrão e seus principais níveis no gráfico, criaremos a função "DrawTriangle", que utiliza ObjectCreate para desenhar um triângulo preenchido (OBJ_TRIANGLE) a partir de três pontos definidos pelos horários ("t1", "t2", "t3") e preços ("p1", "p2", "p3"). Com a função ObjectSetInteger, definiremos "OBJPROP_COLOR" com a cor especificada, "OBJPROP_STYLE" como "STYLE_SOLID", "OBJPROP_WIDTH" com a espessura desejada, "OBJPROP_FILL" para ativar ou desativar o preenchimento e "OBJPROP_BACK" para posicionar o objeto no fundo ou em primeiro plano. Em seguida, implementaremos a função "DrawTrendLine", que utiliza o mesmo formato para criar uma linha de tendência (OBJ_TREND) entre dois pontos.
Depois, implementaremos a função "DrawDottedLine", responsável por desenhar uma linha horizontal pontilhada e configurar suas propriedades de maneira semelhante. Por fim, desenvolveremos a função "DrawTextEx", que cria um rótulo de texto (OBJ_TEXT) nas coordenadas ("t", "p"). Utilizaremos ObjectSetString e ObjectSetInteger para definir "OBJPROP_TEXT" com o texto especificado, configurar "OBJPROP_COLOR" e "OBJPROP_FONTSIZE" e estabelecer "OBJPROP_FONT" como "Arial Bold". Com base no parâmetro "isHigh", utilizaremos "OBJPROP_ANCHOR" para posicionar o texto acima de um swing high ou abaixo de um swing low, enquanto "OBJPROP_ALIGN" permitirá centralizar o texto. Agora podemos passar ao manipulador OnTick e identificar os pivôs que serão utilizados posteriormente no reconhecimento dos padrões. Para isso, adotaremos a seguinte lógica.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Declare a static variable to store the time of the last processed bar static datetime lastBarTime = 0; //--- Get the time of the current confirmed bar datetime currentBarTime = iTime(_Symbol, _Period, 1); //--- If the current bar time is the same as the last processed, exit if(currentBarTime == lastBarTime) return; //--- Update the last processed bar time lastBarTime = currentBarTime; //--- Clear the pivot array for fresh analysis ArrayResize(pivots, 0); //--- Get the total number of bars available on the chart int barsCount = Bars(_Symbol, _Period); //--- Define the starting index for pivot detection (ensuring enough left bars) int start = PivotLeft; //--- Define the ending index for pivot detection (ensuring enough right bars) int end = barsCount - PivotRight; //--- Loop through bars from 'end-1' down to 'start' to find pivot points for(int i = end - 1; i >= start; i--) { //--- Assume current bar is both a potential swing high and swing low bool isPivotHigh = true; bool isPivotLow = true; //--- Get the high and low of the current bar double currentHigh = iHigh(_Symbol, _Period, i); double currentLow = iLow(_Symbol, _Period, i); //--- Loop through the window of bars around the current bar for(int j = i - PivotLeft; j <= i + PivotRight; j++) { //--- Skip if the index is out of bounds if(j < 0 || j >= barsCount) continue; //--- Skip comparing the bar with itself if(j == i) continue; //--- If any bar in the window has a higher high, it's not a swing high if(iHigh(_Symbol, _Period, j) > currentHigh) isPivotHigh = false; //--- If any bar in the window has a lower low, it's not a swing low if(iLow(_Symbol, _Period, j) < currentLow) isPivotLow = false; } //--- If the current bar qualifies as either a swing high or swing low if(isPivotHigh || isPivotLow) { //--- Create a new pivot structure Pivot p; //--- Set the pivot's time p.time = iTime(_Symbol, _Period, i); //--- Set the pivot's price depending on whether it is a high or low p.price = isPivotHigh ? currentHigh : currentLow; //--- Set the pivot type (true for swing high, false for swing low) p.isHigh = isPivotHigh; //--- Get the current size of the pivots array int size = ArraySize(pivots); //--- Increase the size of the pivots array by one ArrayResize(pivots, size + 1); //--- Add the new pivot to the array pivots[size] = p; } } }
Começaremos implementando a lógica inicial do manipulador OnTick para detectar os pivôs necessários à identificação do padrão harmônico "Morcego". Primeiro, declararemos a variável estática "lastBarTime", inicializada com 0, para registrar o horário da última barra processada. Esse valor será comparado com "currentBarTime", obtido por meio de iTime com deslocamento 1 para o símbolo e o período gráfico atuais. Para evitar processamento desnecessário, encerraremos a execução do manipulador caso o horário permaneça inalterado. Quando uma nova barra for detectada, atualizaremos "lastBarTime". Em seguida, limparemos o array "pivots" com ArrayResize para iniciar uma nova análise.
Depois, obteremos o número total de barras com Bars e definiremos os limites da busca por pivôs: "start" receberá o valor de "PivotLeft", enquanto "end" corresponderá ao total de barras menos "PivotRight". Percorreremos as barras de "end - 1" até "start". Para cada barra, inicialmente consideraremos verdadeiras as condições de máxima local ("isPivotHigh" = true) e mínima local ("isPivotLow" = true), obtendo seus preços máximo e mínimo com iHigh e iLow. Em seguida, verificaremos as barras vizinhas dentro dos intervalos definidos por "PivotLeft" e "PivotRight" para confirmar se a barra representa um pivô. Utilizando iHigh e iLow, descartaremos a condição de máxima local se alguma barra vizinha apresentar uma máxima superior, ou a condição de mínima local se apresentar uma mínima inferior. Por fim, se a barra atender aos critérios de pivô, criaremos uma estrutura "Pivot" e preencheremos seus campos: "time" receberá o horário obtido com iTime, "price" receberá o preço máximo ou mínimo conforme "isPivotHigh", e "isHigh" indicará se o pivô corresponde a uma máxima local. Em seguida, ampliaremos o array "pivots" com ArrayResize e armazenaremos a estrutura. Ao exibir o conteúdo do array, obteremos o seguinte resultado.

Com esses dados, podemos identificar os pontos de pivô e, quando houver uma quantidade suficiente deles, analisar os movimentos do preço para detectar os padrões. Para isso, implementaremos a seguinte lógica.
//--- Determine the total number of pivots found int pivotCount = ArraySize(pivots); //--- If fewer than five pivots are found, the pattern cannot be formed if(pivotCount < 5) { //--- Reset pattern lock variables g_patternFormationBar = -1; g_lockedPatternX = 0; g_tradeTaken = false; //--- Exit the OnTick function return; } //--- Extract the last five pivots as X, A, B, C, and D Pivot X = pivots[pivotCount - 5]; Pivot A = pivots[pivotCount - 4]; Pivot B = pivots[pivotCount - 3]; Pivot C = pivots[pivotCount - 2]; Pivot D = pivots[pivotCount - 1]; //--- Initialize a flag to indicate if a valid Bat pattern is found bool patternFound = false; //--- Check for the high-low-high-low-high (Bearish reversal) structure if(X.isHigh && (!A.isHigh) && B.isHigh && (!C.isHigh) && D.isHigh) { //--- Calculate the difference between pivot X and A double diff = X.price - A.price; //--- Ensure the difference is positive if(diff > 0) { //--- Calculate the ideal position for pivot B based on Fibonacci ratio double idealB = A.price + 0.5 * diff; //--- Check if actual B is within tolerance of the ideal position if(MathAbs(B.price - idealB) <= Tolerance * diff) { //--- Calculate the AB leg length double AB = B.price - A.price; //--- Calculate the BC leg length double BC = B.price - C.price; //--- Verify that BC is within the acceptable Fibonacci range if((BC >= 0.382 * AB) && (BC <= 0.886 * AB)) { //--- Calculate the extension double extension = D.price - A.price; //--- Verify that the extension is within tolerance of 0.886 and that D is below X if(MathAbs(extension - 0.886 * diff) <= Tolerance * diff && (D.price < X.price)) patternFound = true; } } } } //--- Check for the low-high-low-high-low (Bullish reversal) structure if((!X.isHigh) && A.isHigh && (!B.isHigh) && C.isHigh && (!D.isHigh)) { //--- Calculate the difference between pivot A and X double diff = A.price - X.price; //--- Ensure the difference is positive if(diff > 0) { //--- Calculate the ideal position for pivot B based on Fibonacci ratio double idealB = A.price - 0.5 * diff; //--- Check if actual B is within tolerance of the ideal position if(MathAbs(B.price - idealB) <= Tolerance * diff) { //--- Calculate the AB leg length double AB = A.price - B.price; //--- Calculate the BC leg length double BC = C.price - B.price; //--- Verify that BC is within the acceptable Fibonacci range if((BC >= 0.382 * AB) && (BC <= 0.886 * AB)) { //--- Calculate the extension double extension = A.price - D.price; //--- Verify that the extension is within tolerance of 0.886 and that D is above X if(MathAbs(extension - 0.886 * diff) <= Tolerance * diff && (D.price > X.price)) patternFound = true; } } } }
Nesta etapa, continuaremos a identificar padrões de alta e de baixa com base em critérios precisos de Fibonacci. Primeiro, obteremos o número total de pivôs com "ArraySize(pivots)" e armazenaremos esse valor em "pivotCount". Se houver menos de cinco pivôs, encerraremos a execução, redefinindo "g_patternFormationBar", "g_lockedPatternX" e "g_tradeTaken" para -1, 0 e false, respectivamente, pois o padrão "Morcego" exige os cinco pontos X, A, B, C e D. Em seguida, recuperaremos os cinco pivôs mais recentes do array "pivots" e os atribuiremos aos pontos X (o mais antigo), A, B, C e D (o mais recente), formando a estrutura do padrão.
Depois, verificaremos se os pontos correspondem a um padrão "Morcego" de baixa, caracterizado pela sequência de máxima em X, mínima em A, máxima em B, mínima em C e máxima em D. Para isso, calcularemos a amplitude do movimento XA com "X.price - A.price" e confirmaremos que o resultado é positivo. Em seguida, calcularemos o nível ideal do ponto B pela expressão "A.price + 0.5 diff" e utilizaremos MathAbs para verificar se o desvio está dentro da tolerância definida por "Tolerance diff". Também verificaremos se o movimento BC corresponde a uma proporção entre 0,382 e 0,886 do movimento AB e se a extensão AD corresponde a 0,886 do movimento XA, com o ponto D situado abaixo de X. Se todas as condições forem atendidas, definiremos "patternFound" como true. Na sequência, verificaremos a presença de um padrão "Morcego" de alta, caracterizado pela sequência de mínima em X, máxima em A, mínima em B, máxima em C e mínima em D. Calcularemos a amplitude de XA com "A.price - X.price" e confirmaremos que o resultado é positivo. Depois, verificaremos se B corresponde ao nível de retração de 50% de XA, se BC está entre 38,2% e 88,6% de AB e se o ponto D está no nível de retração de 88,6% do movimento XA, acima do ponto X. Se o padrão atender a todos os critérios, definiremos "patternFound" como true. Dessa forma, teremos a lógica necessária para identificar os padrões "Morcego" que serão utilizados na visualização e na execução das operações. Quando um padrão for identificado, poderemos representá-lo no gráfico.
//--- If a valid Bat pattern is detected if(patternFound) { //--- Print a message indicating the pattern type and detection time Print(patternType, " Bat pattern detected at ", TimeToString(D.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- Create a unique prefix for all graphical objects related to this pattern string signalPrefix = "BAT_" + IntegerToString(X.time); //--- Choose triangle color based on the pattern type color triangleColor = (patternType=="Bullish") ? clrBlue : clrRed; //--- Draw the first triangle connecting pivots X, A, and B DrawTriangle(signalPrefix+"_Triangle1", X.time, X.price, A.time, A.price, B.time, B.price, triangleColor, 2, true, true); //--- Draw the second triangle connecting pivots B, C, and D DrawTriangle(signalPrefix+"_Triangle2", B.time, B.price, C.time, C.price, D.time, D.price, triangleColor, 2, true, true); }
Primeiro, se um padrão válido for encontrado ("patternFound" = true), inicializaremos "patternType" com uma string vazia e atribuiremos o valor "Bearish" quando "D.price" for menor que "X.price", indicando um sinal de venda, ou "Bullish" quando for maior, indicando um sinal de compra. Em seguida, registraremos a identificação do padrão com a função Print, exibindo "patternType" e o horário do pivô D, formatado por TimeToString para incluir a data, os minutos e os segundos. Depois, criaremos um identificador exclusivo chamado "signalPrefix", concatenando "BAT_" com o valor de "X.time", convertido em string pela função IntegerToString. Definiremos "triangleColor" como azul para os padrões de alta ou vermelho para os de baixa e chamaremos a função "DrawTriangle" duas vezes. A primeira chamada desenhará o triângulo XAB, conectando os pontos X, A e B. A segunda desenhará o triângulo BCD, conectando os pontos B, C e D. Em ambas as chamadas, utilizaremos "signalPrefix" com os sufixos "_Triangle1" e "_Triangle2", respectivamente, além dos horários e preços correspondentes aos pontos de pivô. Também passaremos "triangleColor", definiremos a espessura como 2 e ativaremos o preenchimento e a exibição dos triângulos ao fundo, utilizando true para os dois parâmetros. Essa lógica permitirá visualizar claramente a estrutura do padrão "Morcego" no gráfico, facilitando a análise e a tomada de decisões de negociação. O resultado é apresentado a seguir.

Na imagem, podemos observar que o padrão identificado foi representado corretamente no gráfico. Agora, precisamos adicionar as linhas de tendência para completar o contorno do padrão e adicionar rótulos que facilitem a identificação dos níveis do padrão.
//--- Draw boundary trend lines connecting the pivots for clarity DrawTrendLine(signalPrefix+"_TL_XA", X.time, X.price, A.time, A.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_AB", A.time, A.price, B.time, B.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_BC", B.time, B.price, C.time, C.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_CD", C.time, C.price, D.time, D.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_XB", X.time, X.price, B.time, B.price, clrBlack, 2, STYLE_SOLID); DrawTrendLine(signalPrefix+"_TL_BD", B.time, B.price, D.time, D.price, clrBlack, 2, STYLE_SOLID); //--- Retrieve the symbol's point size to calculate offsets for text positioning double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); //--- Calculate an offset (15 points) for positioning text above or below pivots double offset = 15 * point; //--- Determine the Y coordinate for each pivot label based on its type double textY_X = (X.isHigh ? X.price + offset : X.price - offset); double textY_A = (A.isHigh ? A.price + offset : A.price - offset); double textY_B = (B.isHigh ? B.price + offset : B.price - offset); double textY_C = (C.isHigh ? C.price + offset : C.price - offset); double textY_D = (D.isHigh ? D.price + offset : D.price - offset); //--- Draw text labels for each pivot with appropriate anchoring DrawTextEx(signalPrefix+"_Text_X", "X", X.time, textY_X, clrBlack, 11, X.isHigh); DrawTextEx(signalPrefix+"_Text_A", "A", A.time, textY_A, clrBlack, 11, A.isHigh); DrawTextEx(signalPrefix+"_Text_B", "B", B.time, textY_B, clrBlack, 11, B.isHigh); DrawTextEx(signalPrefix+"_Text_C", "C", C.time, textY_C, clrBlack, 11, C.isHigh); DrawTextEx(signalPrefix+"_Text_D", "D", D.time, textY_D, clrBlack, 11, D.isHigh); //--- Calculate the central label's time as the midpoint between pivots X and B datetime centralTime = (X.time + B.time) / 2; //--- Set the central label's price at pivot D's price double centralPrice = D.price; //--- Create the central text label indicating the pattern type if(ObjectCreate(0, signalPrefix+"_Text_Center", OBJ_TEXT, 0, centralTime, centralPrice)) { ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_TEXT, (patternType=="Bullish") ? "Bullish Bat" : "Bearish Bat"); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_COLOR, clrBlack); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_FONTSIZE, 11); ObjectSetString(0, signalPrefix+"_Text_Center", OBJPROP_FONT, "Arial Bold"); ObjectSetInteger(0, signalPrefix+"_Text_Center", OBJPROP_ALIGN, ALIGN_CENTER); }
Para tornar a estrutura do padrão mais clara, adicionaremos outros objetos gráficos. Primeiro, desenharemos seis linhas de tendência contínuas com a função "DrawTrendLine", utilizando "signalPrefix" para gerar nomes exclusivos. Essas linhas conectarão os pontos de pivô nos segmentos XA, AB, BC, CD, XB e BD, utilizando os respectivos horários e preços de cada ponto (por exemplo, "X.time" e "X.price"). Com ObjectSetInteger, definiremos "OBJPROP_COLOR" como "clrBlack", "OBJPROP_WIDTH" como 2 e "OBJPROP_STYLE" como "STYLE_SOLID", destacando os segmentos que compõem o padrão. Em seguida, obteremos o tamanho do ponto do símbolo com "SymbolInfoDouble(_Symbol, SYMBOL_POINT)" e calcularemos um deslocamento de 15 pontos para posicionar os rótulos. Determinaremos as coordenadas verticais ("textY_X", "textY_A", "textY_B", "textY_C" e "textY_D") somando ou subtraindo esse deslocamento conforme cada pivô represente uma máxima ("isHigh" = true) ou uma mínima local. Assim, os rótulos ficarão acima das máximas e abaixo das mínimas. Esse deslocamento pode ser ajustado de acordo com a aparência desejada e o instrumento negociado.
Depois, utilizaremos "DrawTextEx" para criar os rótulos dos pontos X, A, B, C e D. Cada objeto receberá um nome formado por "signalPrefix" e um sufixo correspondente, como "_Text_X", e exibirá a letra do respectivo pivô. Os rótulos serão posicionados no horário de cada pivô, com a coordenada vertical previamente ajustada. Utilizaremos "clrBlack" como cor, tamanho de fonte 11 e o valor de "isHigh" de cada pivô para definir a ancoragem do texto. Por fim, calcularemos a posição do rótulo central, definindo "centralTime" como o ponto médio entre "X.time" e "B.time" e "centralPrice" como "D.price". Criaremos então um objeto de texto com ObjectCreate, utilizando o nome "signalPrefix + '_Text_Center'". Definiremos "OBJPROP_TEXT" como "Bullish Bat" ou "Bearish Bat", conforme o valor de "patternType". Com ObjectSetString e ObjectSetInteger, configuraremos "OBJPROP_COLOR" como "clrBlack", "OBJPROP_FONTSIZE" como 11, "OBJPROP_FONT" como "Arial Bold" e "OBJPROP_ALIGN" como "ALIGN_CENTER". Com isso, o gráfico exibirá tanto a estrutura completa quanto o tipo do padrão identificado. Ao executar o programa, obteremos o seguinte resultado.

Na imagem, podemos observar que as linhas de contorno e os rótulos foram adicionados ao padrão, tornando sua estrutura mais fácil de identificar. O próximo passo será definir os níveis de negociação correspondentes a esse padrão.
//--- Define start and end times for drawing horizontal dotted lines for obj_Trade levels datetime lineStart = D.time; datetime lineEnd = D.time + PeriodSeconds(_Period)*2; //--- Declare variables for entry price and take profit levels double entryPriceLevel, TP1Level, TP2Level, TP3Level, tradeDiff; //--- Calculate obj_Trade levels based on whether the pattern is Bullish or Bearish if(patternType=="Bullish") { //--- Bullish → BUY signal //--- Use the current ASK price as the entry entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_ASK); //--- Set TP3 at pivot C's price TP3Level = C.price; //--- Calculate the total distance to be covered by the obj_Trade tradeDiff = TP3Level - entryPriceLevel; //--- Set TP1 at one-third of the total move TP1Level = entryPriceLevel + tradeDiff/3; //--- Set TP2 at two-thirds of the total move TP2Level = entryPriceLevel + 2*tradeDiff/3; } else { //--- Bearish → SELL signal //--- Use the current BID price as the entry entryPriceLevel = SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Set TP3 at pivot C's price TP3Level = C.price; //--- Calculate the total distance to be covered by the obj_Trade tradeDiff = entryPriceLevel - TP3Level; //--- Set TP1 at one-third of the total move TP1Level = entryPriceLevel - tradeDiff/3; //--- Set TP2 at two-thirds of the total move TP2Level = entryPriceLevel - 2*tradeDiff/3; } //--- Draw dotted horizontal lines to represent the entry and TP levels DrawDottedLine(signalPrefix+"_EntryLine", lineStart, entryPriceLevel, lineEnd, clrMagenta); DrawDottedLine(signalPrefix+"_TP1Line", lineStart, TP1Level, lineEnd, clrForestGreen); DrawDottedLine(signalPrefix+"_TP2Line", lineStart, TP2Level, lineEnd, clrGreen); DrawDottedLine(signalPrefix+"_TP3Line", lineStart, TP3Level, lineEnd, clrDarkGreen); //--- Define a label time coordinate positioned just to the right of the dotted lines datetime labelTime = lineEnd + PeriodSeconds(_Period)/2; //--- Construct the entry label text with the price string entryLabel = (patternType=="Bullish") ? "BUY (" : "SELL ("; entryLabel += DoubleToString(entryPriceLevel, _Digits) + ")"; //--- Draw the entry label on the chart DrawTextEx(signalPrefix+"_EntryLabel", entryLabel, labelTime, entryPriceLevel, clrMagenta, 11, true); //--- Construct and draw the TP1 label string tp1Label = "TP1 (" + DoubleToString(TP1Level, _Digits) + ")"; DrawTextEx(signalPrefix+"_TP1Label", tp1Label, labelTime, TP1Level, clrForestGreen, 11, true); //--- Construct and draw the TP2 label string tp2Label = "TP2 (" + DoubleToString(TP2Level, _Digits) + ")"; DrawTextEx(signalPrefix+"_TP2Label", tp2Label, labelTime, TP2Level, clrGreen, 11, true); //--- Construct and draw the TP3 label string tp3Label = "TP3 (" + DoubleToString(TP3Level, _Digits) + ")"; DrawTextEx(signalPrefix+"_TP3Label", tp3Label, labelTime, TP3Level, clrDarkGreen, 11, true);
Para definir e representar no gráfico os níveis de negociação do padrão identificado, começaremos atribuindo a "lineStart" o horário do pivô D ("D.time") e definindo "lineEnd" como esse horário acrescido de dois períodos, utilizando "PeriodSeconds(_Period) * 2". Declararemos as variáveis "entryPriceLevel", "TP1Level", "TP2Level", "TP3Level" e "tradeDiff" para calcular os níveis das operações. Para um padrão de alta ("patternType == 'Bullish'"), definiremos "entryPriceLevel" como o preço Ask atual, obtido com SymbolInfoDouble, e "TP3Level" como o preço do pivô C. Calcularemos "tradeDiff" pela diferença "TP3Level - entryPriceLevel" e determinaremos "TP1Level" e "TP2Level" somando ao preço de entrada, respectivamente, um terço e dois terços dessa diferença. Para um padrão de baixa, utilizaremos o preço Bid como entrada e o preço do ponto C como "TP3Level". Calcularemos "tradeDiff" como "entryPriceLevel - TP3Level" e determinaremos "TP1Level" e "TP2Level" subtraindo do preço de entrada um terço e dois terços dessa diferença, respectivamente.
Em seguida, utilizaremos a função "DrawDottedLine" para desenhar quatro linhas horizontais pontilhadas entre "lineStart" e "lineEnd": uma linha magenta no nível de entrada "entryPriceLevel" e três linhas de take-profit, sendo "TP1Level" em verde-floresta em tom profundo, "TP2Level" em verde e "TP3Level" em verde-escuro. Por fim, definiremos "labelTime" como "lineEnd" acrescido de meio período e criaremos rótulos com os preços formatados pela função DoubleToString, como "BUY (price)" ou "SELL (price)" para a entrada e "TP1 (price)", entre outros, para os alvos de take-profit. Utilizaremos "DrawTextEx" para posicionar esses rótulos em "labelTime", com as cores correspondentes, tamanho de fonte 11 e ancoragem acima dos respectivos níveis de preço. Após compilar o programa, obteremos o seguinte resultado.
Padrão de baixa:

Padrão de alta:

Nas imagens, podemos observar que os níveis de negociação foram representados corretamente no gráfico. Agora, resta implementar a abertura efetiva das posições.
//--- Retrieve the index of the current bar int currentBarIndex = Bars(_Symbol, _Period) - 1; //--- If no pattern has been previously locked, lock the current pattern formation if(g_patternFormationBar == -1) { g_patternFormationBar = currentBarIndex; g_lockedPatternX = X.time; //--- Print a message that the pattern is detected and waiting for confirmation Print("Pattern detected on bar ", currentBarIndex, ". Waiting for confirmation on next bar."); return; } //--- If still on the same formation bar, the pattern is considered to be repainting if(currentBarIndex == g_patternFormationBar) { Print("Pattern is repainting; still on locked formation bar ", currentBarIndex, ". No obj_Trade yet."); return; } //--- If we are on a new bar compared to the locked formation if(currentBarIndex > g_patternFormationBar) { //--- Check if the locked pattern still corresponds to the same X pivot if(g_lockedPatternX == X.time) { Print("Confirmed pattern (locked on bar ", g_patternFormationBar, "). Opening obj_Trade on bar ", currentBarIndex, "."); //--- Update the pattern formation bar to the current bar g_patternFormationBar = currentBarIndex; //--- Only proceed with trading if allowed and if there is no existing position if(AllowTrading && !PositionSelect(_Symbol) && !g_tradeTaken) { double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0; point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); bool tradeResult = false; //--- For a Bullish pattern, execute a BUY obj_Trade if(patternType=="Bullish") { //--- BUY signal entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double diffTrade = TP2Level - entryPriceTrade; stopLoss = entryPriceTrade - diffTrade * 3; takeProfit = TP2Level; tradeResult = obj_Trade.Buy(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Bat Signal"); if(tradeResult) { Print("Buy order opened successfully."); g_tradeTaken = true; } else Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); } //--- For a Bearish pattern, execute a SELL obj_Trade else if(patternType=="Bearish") { //--- SELL signal entryPriceTrade = SymbolInfoDouble(_Symbol, SYMBOL_BID); double diffTrade = entryPriceTrade - TP2Level; stopLoss = entryPriceTrade + diffTrade * 3; takeProfit = TP2Level; tradeResult = obj_Trade.Sell(LotSize, _Symbol, entryPriceTrade, stopLoss, takeProfit, "Bat Signal"); if(tradeResult) { Print("Sell order opened successfully."); g_tradeTaken = true; } else Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); } } else { //--- If a position is already open, do not execute a new obj_Trade Print("A position is already open for ", _Symbol, ". No new obj_Trade executed."); } } else { //--- If the pattern has changed, update the lock with the new formation bar and X pivot g_patternFormationBar = currentBarIndex; g_lockedPatternX = X.time; g_tradeTaken = false; Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation."); return; } } else { //--- If no valid Bat pattern is detected, reset the pattern lock variables g_patternFormationBar = -1; g_lockedPatternX = 0; g_tradeTaken = false; }
Nesta etapa, concluiremos a lógica do manipulador OnTick, incluindo o controle da abertura de operações e a confirmação do padrão. Primeiro, obteremos o índice da barra atual por meio da expressão "Bars(_Symbol, _Period) - 1" e armazenaremos o resultado em "currentBarIndex". Se nenhum padrão estiver bloqueado para confirmação ("g_patternFormationBar == -1"), atribuiremos "currentBarIndex" a "g_patternFormationBar" e armazenaremos o horário do pivô X ("X.time") em "g_lockedPatternX". Em seguida, registraremos no log que um padrão foi identificado e está aguardando confirmação, encerrando a execução do manipulador. Se o padrão ainda estiver na mesma barra em que foi detectado ("currentBarIndex == g_patternFormationBar"), registraremos a ocorrência de repintura (repainting) do padrão e encerraremos a execução para evitar a abertura prematura de operações.
Por fim, quando uma nova barra se formar ("currentBarIndex > g_patternFormationBar") e o horário do pivô X corresponder ao valor armazenado em "g_lockedPatternX", confirmaremos o padrão, registraremos a confirmação no log e atualizaremos "g_patternFormationBar". Antes de abrir uma operação, verificaremos se a negociação automática está habilitada por "AllowTrading", se não há posições abertas para o símbolo, utilizando PositionSelect, e se nenhuma operação já foi executada para o padrão ("g_tradeTaken" = false). Para um padrão de alta, definiremos "entryPriceTrade" como o preço Ask e calcularemos "diffTrade" pela expressão "TP2Level - entryPriceTrade". Posicionaremos "stopLoss" abaixo do preço de entrada, a uma distância equivalente a três vezes "diffTrade", e definiremos "takeProfit" como "TP2Level". Em seguida, abriremos uma posição de compra com "obj_Trade.Buy", utilizando o volume especificado em "LotSize" e a identificação "Bat Signal". Registraremos no log se a execução foi bem-sucedida ou falhou e definiremos "g_tradeTaken" como true. Para um padrão de baixa, utilizaremos o preço Bid, posicionaremos "stopLoss" acima do preço de entrada, a uma distância equivalente a três vezes a diferença calculada, e abriremos uma posição de venda com "obj_Trade.Sell". Se a negociação automática estiver desabilitada, já houver uma posição aberta ou uma operação tiver sido executada para o padrão, registraremos no log que nenhuma nova operação será realizada. Caso o padrão identificado mude, atualizaremos os valores armazenados e aguardaremos uma nova confirmação. Se nenhum padrão for encontrado, redefiniremos as variáveis globais de controle. Após compilar o programa, obteremos o seguinte resultado.
Sinal de baixa:

Sinal de alta:

Na imagem, podemos observar que o padrão harmônico foi representado no gráfico e que o sistema está preparado para executar as operações correspondentes assim que o padrão for confirmado. Com isso, atingimos nosso objetivo de identificar, visualizar e negociar com base nesse padrão. Resta apenas testar o programa com dados históricos, o que faremos na próxima seção.
Testes com dados históricos
Após realizar testes detalhados com dados históricos, obtivemos os seguintes resultados.
Gráfico do teste com dados históricos:

Relatório do teste com dados históricos:

Conclusão
Neste artigo, desenvolvemos um sistema baseado no padrão harmônico "Morcego" em MQL5. O sistema utiliza Price Action e proporções precisas de Fibonacci para identificar padrões de alta e de baixa, automatiza a abertura de operações com níveis calculados de entrada, stop-loss e múltiplos alvos de take-profit e representa os padrões no gráfico por meio de objetos gráficos, como triângulos e linhas de tendência.
Aviso de responsabilidade: O conteúdo deste artigo tem finalidade exclusivamente educacional. A negociação nos mercados financeiros envolve riscos significativos, e a volatilidade pode resultar em perdas. Antes de utilizar este programa em condições reais de mercado, é fundamental realizar testes históricos rigorosos e adotar práticas adequadas de gestão de risco.
Com os conceitos e métodos de implementação apresentados, você poderá adaptar o sistema baseado no padrão "Morcego" ao seu estilo de negociação e aprimorar suas estratégias algorítmicas. Boas negociações!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/19105
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.