English Русский 中文 Español Deutsch 日本語
preview
Automatização de estratégias de negociação em MQL5 (Parte 29): Desenvolvimento de um sistema de negociação baseado no padrão harmônico de Gartley e em Price Action

Automatização de estratégias de negociação em MQL5 (Parte 29): Desenvolvimento de um sistema de negociação baseado no padrão harmônico de Gartley e em Price Action

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

Introdução

No artigo anterior (Parte 28), desenvolvemos um sistema baseado no padrão "Morcego" em MetaQuotes Language 5 (MQL5), capaz de identificar suas formações de alta e de baixa por meio de níveis precisos de Fibonacci. Nesta Parte 29, criaremos um programa para identificar padrões harmônicos de Gartley de alta e de baixa, utilizando pontos de reversão e níveis específicos de retração de Fibonacci. O sistema abrirá operações com pontos de entrada determinados dinamicamente e alvos de lucro em múltiplos níveis. Também incluirá triângulos, linhas de tendência e rótulos para representar visualmente os padrões com clareza. Abordaremos os seguintes tópicos:

  1. Análise da estrutura do padrão harmônico de Gartley
  2. Implementação em MQL5
  3. Testes com dados históricos
  4. Conclusão

Ao final, você terá uma estratégia de negociação em MQL5 cuidadosamente desenvolvida, baseada no padrão harmônico de Gartley e pronta para ser ajustada conforme suas necessidades. Vamos à implementação!


Análise da estrutura do padrão harmônico de Gartley

O padrão de Gartley é um padrão harmônico formado por cinco pontos extremos principais, identificados pelas letras X, A, B, C e D. Ele apresenta duas configurações: uma de alta e outra de baixa. Ambas buscam identificar zonas com alta probabilidade de reversão por meio de níveis precisos de Fibonacci. Na configuração de alta, o padrão de Gartley segue a sequência "fundo-topo-fundo-topo-fundo", em que X representa um fundo (swing low), A representa um topo (swing high), B corresponde a um fundo (swing low), com retração de aproximadamente 0,618 do movimento XA, C representa um topo (swing high), com extensão entre 0,382 e 0,886 do movimento AB, e D corresponde a um fundo (swing low), situado acima de X, no nível de retração de 0,786 de XA. Já na configuração de baixa, o padrão de Gartley segue a sequência "topo-fundo-topo-fundo-topo", em que X representa um topo (swing high), A representa um fundo (swing low), B corresponde a um topo (swing high), C representa um fundo (swing low) e D corresponde a um topo (swing high), situado abaixo de X, no nível de retração de 0,786 de XA. A seguir, apresentamos as representações visuais das duas configurações.

Padrão harmônico de Gartley de alta:

BULLISH GARTLEY HARMONIC PATTERN

Padrão harmônico de Gartley de baixa:

BEARISH GARTLEY HARMONIC PATTERN

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 para o padrão de alta e descendente para o de baixa) e serve de referência para os cálculos posteriores dos níveis de Fibonacci.
  • Formação da onda AB: O ponto B deve apresentar uma retração de aproximadamente 0,618 do comprimento de XA, indicando uma correção significativa, porém controlada, do impulso inicial.
  • Análise da onda BC: Esse segmento deve corresponder a um valor entre 0,382 e 0,886 do comprimento de AB, formando um contramovimento que antecede a zona final de reversão.
  • Configuração da onda CD: O segmento final deve atingir o nível de retração de 0,786 do comprimento de XA, indicando uma 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á sistematicamente padrões de Gartley válidos nos dados de preços. Após a confirmação de um padrão, o sistema representará sua formação no gráfico por meio de triângulos, linhas de tendência e rótulos que identificam 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 um stop loss calculado e zonas de take profit em múltiplos níveis, aproveitando a capacidade preditiva do padrão para identificar 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 "Novo" (New) e siga as instruções para criar o arquivo. Após concluir essa etapa, precisaremos declarar algumas variáveis globais no ambiente de desenvolvimento, que serão utilizadas em todo o programa.

//+------------------------------------------------------------------+
//|                                       Gartley 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 Gartley 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

//---------------------------------------------------------------------------  
//--- Gartley pattern definition:  
//  
//--- Bullish Gartley:  
//---   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.618*(A-X); Legs within specified ranges; D at 0.786 retracement.  
//  
//--- Bearish Gartley:  
//---   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.618*(X-A); Legs within specified ranges; D at 0.786 retracement.  
//---------------------------------------------------------------------------

//--- 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

Para estabelecer a base do sistema de negociação com o padrão de Gartley, começaremos incluindo a biblioteca "<Trade\Trade.mqh>" e criando uma instância da classe CTrade chamada "obj_Trade", responsável por gerenciar 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 personalização do sistema: "PivotLeft" e "PivotRight", ambos com valor 5, estabelecem uma janela de análise de cinco barras de cada lado para identificar pontos de pivô; "Tolerance" recebe o valor 0,10, permitindo uma tolerância de 10% nos níveis de Fibonacci; "LotSize" é definido como 0,01 para determinar o volume das operações; e "AllowTrading" recebe o valor true para habilitar 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 dos preços. Declararemos também "pivots" como um array dinâmico para armazenar esses pontos e inicializaremos duas variáveis globais: "g_patternFormationBar" com o valor -1, para acompanhar a barra em que o padrão se forma, e "g_lockedPatternX" com o valor 0, para armazenar a data e a hora do pivô X, utilizadas na confirmação do padrão. Com isso, teremos a estrutura básica necessária para identificar padrões de Gartley 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);  
   }  
}

Vamos implementar as funções de visualização do sistema para representar claramente o padrão e seus níveis de negociação no gráfico. Primeiro, criaremos a função "DrawTriangle", que utiliza ObjectCreate para desenhar um triângulo preenchido (OBJ_TRIANGLE) definido por três pontos, com coordenadas de tempo ("t1", "t2", "t3") e preço ("p1", "p2", "p3"). Usaremos ObjectSetInteger para configurar "OBJPROP_COLOR" com a cor especificada, "OBJPROP_STYLE" como STYLE_SOLID, "OBJPROP_WIDTH" com a espessura definida, "OBJPROP_FILL" para ativar ou desativar o preenchimento e "OBJPROP_BACK" para determinar se o objeto será exibido em primeiro ou segundo plano.

Em seguida, implementaremos a função "DrawTrendLine", que cria uma linha de tendência (OBJ_TREND) entre dois pontos. Depois, desenvolveremos a função "DrawDottedLine", que desenha uma linha horizontal pontilhada usando "OBJ_TREND", no preço especificado, entre "t1" e "t2". Por fim, criaremos a função "DrawTextEx", responsável por inserir um rótulo de texto (OBJ_TEXT) nas coordenadas ("t", "p"). Com ObjectSetString e ObjectSetInteger, definiremos "OBJPROP_TEXT" com o texto especificado, configuraremos "OBJPROP_COLOR" e "OBJPROP_FONTSIZE" e ajustaremos "OBJPROP_FONT" para "Arial Bold". Usaremos "OBJPROP_ANCHOR" para posicionar o texto acima dos topos (swing highs) ou abaixo dos fundos (swing lows), conforme o valor de "isHigh", e "OBJPROP_ALIGN" para centralizá-lo. Dessa forma, garantiremos que o padrão de Gartley e seus níveis de negociação sejam exibidos com clareza no gráfico. Agora, podemos passar ao manipulador OnTick e começar a identificar os pontos de reversão que utilizaremos posteriormente para reconhecer o padrão. Veja a lógica que adotaremos para isso.

//+------------------------------------------------------------------+  
//| 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;  
      }  
   }
}

Nesta etapa, implementaremos a lógica inicial da função OnTick para detectar pontos de reversão, estabelecendo a base para a identificação dos padrões harmônicos de Gartley. Primeiro, declararemos uma variável estática chamada "lastBarTime", inicializada com 0, para registrar a última barra processada. Compararemos seu valor com "currentBarTime", obtido pela função iTime com deslocamento 1 para o símbolo e o período gráfico atuais. Para evitar processamento desnecessário, encerraremos a execução caso o valor não tenha mudado. Quando uma nova barra for detectada, atualizaremos "lastBarTime". Em seguida, limparemos o array "pivots" usando ArrayResize para iniciar uma nova análise.

Depois, obteremos o número total de barras com Bars e definiremos o intervalo de busca dos pontos de pivô, atribuindo a "start" o valor de "PivotLeft" e a "end" o número total de barras menos "PivotRight". Percorreremos as barras de "end - 1" até "start". Para cada barra, consideraremos inicialmente que ela pode representar tanto um topo (swing high), definindo "isPivotHigh" como true, quanto um fundo (swing low), definindo "isPivotLow" como true. Obteremos então seus preços máximo e mínimo por meio de iHigh e "iLow". Em seguida, verificaremos as barras vizinhas dentro dos intervalos definidos por "PivotLeft" e "PivotRight", utilizando "iHigh" e "iLow" para confirmar os pivôs. Descartaremos a possibilidade de um pivô de máxima caso alguma barra vizinha apresente uma máxima superior, ou de um pivô de mínima caso apresente uma mínima inferior. Por fim, se a barra atender aos critérios de um pivô, criaremos uma estrutura "Pivot", preencheremos o campo "time" com o valor retornado por "iTime", definiremos "price" com o preço máximo ou mínimo, conforme "isPivotHigh", e definiremos o valor do sinalizador booleano "isHigh". Depois, ampliaremos o array "pivots" com ArrayResize e armazenaremos a estrutura. Ao exibir os dados dos pivôs, obteremos o seguinte array:

PIVOTS DATA STRUCTURE

Com esses dados, podemos identificar os pontos de pivô e, se houver pivôs em número suficiente, conseguiremos analisá-los para detectar padrões. A seguir, apresentamos a lógica que implementaremos para isso.

//--- 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;  
   //--- 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 Gartley pattern is found  
bool patternFound = false;  
//--- Initialize pattern type  
string patternType = "";  
//--- 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.618 * 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 retracement  
            double retrace = D.price - A.price;  
            //--- Verify that the retracement is within tolerance of 0.786 and that D is below X  
            if(MathAbs(retrace - 0.786 * diff) <= Tolerance * diff && (D.price < X.price)) {  
               patternFound = true;  
               patternType = "Bearish";  
            }  
         }  
      }  
   }  
}  
//--- 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.618 * 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 retracement  
            double retrace = A.price - D.price;  
            //--- Verify that the retracement is within tolerance of 0.786 and that D is above X  
            if(MathAbs(retrace - 0.786 * diff) <= Tolerance * diff && (D.price > X.price)) {  
               patternFound = true;  
               patternType = "Bullish";  
            }  
         }  
      }  
   }  
}

Primeiro, determinaremos o número total de pivôs com "ArraySize(pivots)", armazenando esse valor em "pivotCount". Se forem encontrados menos de 5 pivôs, encerraremos a execução e redefiniremos "g_patternFormationBar" para -1 e "g_lockedPatternX" para 0, já que o padrão de Gartley exige os pontos X, A, B, C e D. Em seguida, extrairemos do array "pivots" os cinco pivôs mais recentes e os atribuiremos a "X" (o mais antigo), "A", "B", "C" e "D" (o mais recente), formando assim a estrutura do padrão.

Depois, verificaremos a presença de um padrão de Gartley de baixa (X no topo, A no fundo, B no topo, C no fundo e D no topo), calculando a amplitude de XA como "X.price - A.price". Confirmaremos que esse valor é positivo e calcularemos o ponto B ideal como "A.price + 0.618 * diff", verificando com MathAbs se B está dentro da tolerância definida por "Tolerance * diff". Em seguida, validaremos BC dentro da faixa de 0,382 a 0,886 do segmento AB e confirmaremos que AD corresponde a uma retração de 0,786 do segmento XA, com D situado abaixo de X. Se o padrão for válido, definiremos "patternFound" como true e atribuiremos "Bearish" a "patternType". Por fim, verificaremos a presença de um padrão de Gartley de alta (X no fundo, A no topo, B no fundo, C no topo e D no fundo), calculando XA como "A.price - X.price" e confirmando que o valor é positivo. Verificaremos se B está no nível de retração de 0,618, se BC está dentro da faixa de 0,382 a 0,886 de AB e se AD corresponde a 0,786 do comprimento de XA, com D posicionado acima de X. Se o padrão for válido, definiremos "patternFound" como true e atribuiremos "Bullish" a "patternType". Se um padrão for detectado, poderemos passar à sua visualização no gráfico.

//--- If a valid Gartley pattern is detected  
if(patternFound) {  
   //--- Print a message indicating the pattern type and detection time  
   Print(patternType, " Gartley 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 = "GA_" + 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);  
}

Se um padrão válido for detectado ("patternFound" igual a true), registraremos essa detecção com a função Print, exibindo "patternType" ("Bullish" ou "Bearish") e a data e a hora do pivô D, formatadas com TimeToString, incluindo data, minutos e segundos. Em seguida, criaremos o identificador exclusivo "signalPrefix", concatenando "GA_" com "X.time", convertido em string pela função IntegerToString. Depois, definiremos "triangleColor" como azul para padrões de alta e vermelho para padrões de baixa. Por fim, chamaremos "DrawTriangle" duas vezes para visualizar o padrão: primeiro, para desenhar o triângulo XAB, conectando X, A e B; depois, para desenhar o triângulo BCD, conectando B, C e D. Usaremos "signalPrefix" com os sufixos "_Triangle1" e "_Triangle2", os valores correspondentes de tempo e preço dos pivôs, "triangleColor" e largura 2. Também habilitaremos o preenchimento e a exibição em segundo plano por meio de sinalizadores definidos como true. Obteremos o seguinte resultado.

TRIANGLES SET

A imagem mostra que o padrão detectado foi representado corretamente no gráfico. Agora, vamos adicionar linhas de tendência para mostrar completamente seu contorno, além de incluir rótulos para facilitar a identificação dos níveis.

//--- 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 Gartley" : "Bearish Gartley");  
   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);  
}

Em seguida, aprimoramos a visualização dos padrões detectados, adicionando objetos gráficos detalhados para representar sua estrutura. Primeiro, desenharemos seis linhas de tendência contínuas com "DrawTrendLine", usando um "signalPrefix" exclusivo para conectar os principais pontos de pivô: XA, AB, BC, CD, XB e BD. Essas linhas ligarão os pivôs por seus respectivos valores de tempo e preço, como "X.time" e "X.price". Para destacar os segmentos do padrão, definiremos "OBJPROP_COLOR" como black, "OBJPROP_WIDTH" como 2 e "OBJPROP_STYLE" como "STYLE_SOLID" por meio de ObjectSetInteger. Em seguida, obteremos o tamanho do ponto do símbolo com a função "SymbolInfoDouble(_Symbol, SYMBOL_POINT)" e calcularemos um deslocamento de 15 pontos para posicionar os rótulos. Depois, definiremos as coordenadas Y ("textY_X", "textY_A", "textY_B", "textY_C", "textY_D") somando ou subtraindo esse deslocamento, conforme cada pivô seja um topo ("isHigh" igual a true) ou um fundo, para posicionar os rótulos acima dos topos e abaixo dos fundos.

Na sequência, usaremos "DrawTextEx" para criar rótulos de texto para os pivôs X, A, B, C e D, com "signalPrefix" e sufixos como "_Text_X", exibindo a letra correspondente no instante de tempo do pivô e com a coordenada Y ajustada. Para configurar os rótulos, usaremos a cor "clrBlack" e o tamanho de fonte 11; o sinalizador booleano "isHigh" determinará a ancoragem do texto. Por fim, calcularemos a posição central do rótulo em "centralTime" como o ponto médio entre "X.time" e "B.time", e definiremos "centralPrice" com o valor de "D.price", criando um objeto de texto com ObjectCreate e o nome "signalPrefix + '_Text_Center'". Configuraremos "OBJPROP_TEXT" como "Bullish Gartley" ou "Bearish Gartley", de acordo com "patternType", e ajustaremos "OBJPROP_COLOR" para "clrBlack", "OBJPROP_FONTSIZE" para 11, "OBJPROP_FONT" para "Arial Bold" e "OBJPROP_ALIGN" para "ALIGN_CENTER" usando ObjectSetString e ObjectSetInteger. Essa lógica garante uma representação visual completa da estrutura e do tipo de padrão no gráfico. Ao executar o programa, obteremos o seguinte resultado.

PATTERN WITH LABELS AND EDGES

A imagem mostra que adicionamos as linhas de contorno e os rótulos ao padrão, tornando sua visualização mais clara. O próximo passo é definir os níveis de negociação do padrão.

//--- Define start and end times for drawing horizontal dotted lines for 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 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 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 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 visualizar os níveis de negociação do padrão detectado, atribuiremos a "lineStart" a data e a hora do pivô D ("D.time") e definiremos "lineEnd" como dois períodos após esse instante, usando "PeriodSeconds(_Period) * 2". Declararemos as variáveis "entryPriceLevel", "TP1Level", "TP2Level", "TP3Level" e "tradeDiff" para os cálculos das operações. Em seguida, para o padrão de alta ("patternType == 'Bullish'"), definiremos "entryPriceLevel" com o preço atual Ask por meio de SymbolInfoDouble, atribuiremos "TP3Level" ao preço do pivô C, calcularemos "tradeDiff" como "TP3Level - entryPriceLevel" e determinaremos "TP1Level" e "TP2Level" como um terço e dois terços de "tradeDiff", somados a "entryPriceLevel". Para o padrão de baixa, usaremos o preço Bid, atribuiremos "TP3Level" ao preço do ponto C, calcularemos "tradeDiff" como "entryPriceLevel - TP3Level" e obteremos "TP1Level" e "TP2Level" subtraindo um terço e dois terços da diferença entre os preços da operação.

Em seguida, utilizaremos a função "DrawDottedLine" para desenhar quatro linhas horizontais pontilhadas, desde "lineStart" até "lineEnd": uma linha de entrada no nível "entryPriceLevel", na cor magenta, e três linhas de take profit nos níveis "TP1Level" (verde-floresta em tom profundo), "TP2Level" (verde) e "TP3Level" (verde-escuro). Por fim, definiremos "labelTime" como "lineEnd" mais metade da duração de um período e criaremos rótulos de texto com os preços formatados pela função DoubleToString, como "BUY (price)" ou "SELL (price)" para a entrada, "TP1 (price)" e assim por diante. Usaremos "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. Dessa forma, os níveis de entrada e take profit das operações baseadas no padrão de Gartley ficarão claramente identificados no gráfico. Após compilar o programa, obteremos o seguinte resultado.

Padrão de baixa:

BEARISH PATTERN

Padrão de alta:

BULLISH PATTERN

As imagens mostram 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 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 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)) {  
         double entryPriceTrade = 0, stopLoss = 0, takeProfit = 0;  
         point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);  
         bool tradeResult = false;  
         //--- For a Bullish pattern, execute a BUY 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, "Gartley Signal");  
            if(tradeResult)  
               Print("Buy order opened successfully.");  
            else  
               Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription());  
         }  
         //--- For a Bearish pattern, execute a SELL 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, "Gartley Signal");  
            if(tradeResult)  
               Print("Sell order opened successfully.");  
            else  
               Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription());  
         }  
      }  
      else {  
         //--- If a position is already open, do not execute a new trade  
         Print("A position is already open for ", _Symbol, ". No new 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;  
      Print("Pattern has changed; updating lock on bar ", currentBarIndex, ". Waiting for confirmation.");  
      return;  
   }  
}  
}  
else {  
   //--- If no valid Gartley pattern is detected, reset the pattern lock variables  
   g_patternFormationBar = -1;  
   g_lockedPatternX = 0;  
}

Nesta etapa, concluiremos a lógica do manipulador OnTick, implementando a abertura de operações e a confirmação dos padrões detectados. Primeiro, obteremos o índice da barra atual por meio da expressão "Bars(_Symbol, _Period) - 1" e armazenaremos o resultado em "currentBarIndex". Em seguida, se não houver nenhum padrão com a formação registrada ("g_patternFormationBar == -1"), atribuiremos "currentBarIndex" a "g_patternFormationBar", armazenaremos a data e a hora do pivô X ("X.time") em "g_lockedPatternX" e registraremos no log que um padrão foi detectado e aguarda confirmação. Depois, encerraremos a execução. Se o padrão ainda estiver na mesma barra em que sua formação foi registrada ("currentBarIndex == g_patternFormationBar"), consideraremos que a formação do padrão ainda pode mudar (repainting) e encerraremos a execução para evitar a abertura prematura de operações.

Por fim, se uma nova barra tiver se formado ("currentBarIndex > g_patternFormationBar") e o pivô X corresponder ao valor armazenado em "g_lockedPatternX", confirmaremos o padrão, registraremos a confirmação no log, atualizaremos "g_patternFormationBar" e verificaremos se a negociação está habilitada e, usando PositionSelect, se não há posição aberta para o símbolo consultado. Para um padrão de alta, definiremos "entryPriceTrade" com o preço Ask, calcularemos "diffTrade" como "TP2Level - entryPriceTrade" e posicionaremos "stopLoss" abaixo do preço de entrada, a uma distância equivalente a três vezes "diffTrade". Definiremos "takeProfit" como "TP2Level" e abriremos uma operação de compra com "obj_Trade.Buy", utilizando "LotSize" e o comentário "Gartley Signal", registrando no log se a operação foi executada com sucesso ou se houve falha. 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 operação de venda com "obj_Trade.Sell". Se a negociação estiver desabilitada ou já existir uma posição aberta para o símbolo consultado, registraremos no log que nenhuma nova operação será executada. Se o padrão mudar, atualizaremos os dados armazenados e aguardaremos uma nova confirmação. Caso nenhum padrão seja encontrado, redefiniremos as variáveis globais. Após compilar o programa, obteremos o seguinte resultado.

Sinal de baixa:

BEARISH SIGNAL

Sinal de alta:

BULLISH SIGNAL

As imagens mostram que conseguimos representar graficamente o padrão harmônico e executar operações com base nele assim que sua formação for confirmada. Com isso, atingimos nosso objetivo de identificar, visualizar e negociar o padrão de Gartley. Agora, resta testar o programa com dados históricos, como veremos na próxima seção.


Testes com dados históricos

Após realizar testes históricos detalhados, obtivemos os seguintes resultados.

Gráfico dos testes históricos:

GRAPH

Relatório dos testes históricos:

REPORT


Conclusão

Neste artigo, desenvolvemos um sistema de negociação em MQL5 baseado no padrão de Gartley. O sistema utiliza a ação do preço (Price Action) para identificar padrões harmônicos de Gartley de alta e de baixa com níveis precisos de Fibonacci, automatiza a execução de operações com pontos de entrada e níveis de stop loss calculados, além de alvos de take profit em múltiplos níveis. Também representa visualmente os padrões por meio de objetos gráficos, como triângulos e linhas de tendência.

Isenção de responsabilidade: O conteúdo deste artigo destina-se exclusivamente a fins educacionais. A negociação nos mercados financeiros envolve riscos significativos, e a volatilidade do mercado 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 gerenciamento de riscos.

Com os conceitos e métodos de implementação apresentados, você poderá adaptar este sistema baseado no padrão de Gartley 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/19111

Arquivos anexados |
Últimos Comentários | Ir para discussão (1)
Miguel Angel Vico Alba
Miguel Angel Vico Alba | 30 ago. 2025 em 09:57
Este artigo é um excelente exemplo didático para aprender a estruturar um EA e visualizar padrões harmônicos no MQL5.

Apenas um esclarecimento para os leitores: embora a introdução diga que “você terá uma estratégia poderosa pronta para personalização”, isso não deve ser interpretado como um sistema pronto para negociação.

As principais razões são:
  • Definição incompleta do Gartley: o código verifica apenas se o ponto D está em torno de 0,786 de XA. Na negociação harmônica, a ideia central é a Zona de Reversão Potencial (PRZ), que requer a confluência de múltiplas proporções (por exemplo, 0,786 de XA juntamente com 1,27–1,618 de BC). Sem isso, surgem muitos padrões falsos.
  • Regras de Stop Loss / Take Profit: aqui elas são definidas como simples frações do movimento em direção a C. Na negociação harmônica, o stop é geralmente colocado além de X, e as metas são baseadas nas retrações de Fibonacci de AD. Se isso não for seguido, a relação risco/recompensa torna-se arbitrária.
Portanto, como exercício de programação e visualização, é excelente. Mas, para a negociação real, um iniciante deve entender que são necessárias mais regras e validações antes de confiar em tal programa.
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.
MetaTrader e Planilhas Google via PythonAnywhere: Guia para um fluxo seguro de dados MetaTrader e Planilhas Google via PythonAnywhere: Guia para um fluxo seguro de dados
Neste artigo, apresentamos uma maneira segura de exportar dados do MetaTrader para as Planilhas Google. As Planilhas Google são especialmente úteis por funcionarem na nuvem, permitindo acessar os dados armazenados a qualquer momento e de qualquer lugar. Assim, os traders podem consultar os dados de negociação e outras informações relacionadas, exportados para as Planilhas Google, e realizar análises adicionais para orientar suas futuras operações, independentemente de onde estejam.
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.
Do iniciante ao especialista: Criação de relatórios detalhados de negociação com o EA Reporting EA Do iniciante ao especialista: Criação de relatórios detalhados de negociação com o EA Reporting EA
Neste artigo, vamos explorar como aprimorar o conteúdo dos relatórios de negociação e enviar o documento final por e-mail em formato PDF. Essa etapa representa um avanço em relação ao trabalho anterior, dando continuidade ao estudo de como aproveitar os recursos de MQL5 e Python para gerar relatórios de negociação e agendar sua produção em formatos práticos e profissionais. Acompanhe esta análise para conhecer novas formas de otimizar a geração de relatórios no ecossistema MQL5.