English Русский 中文 Español Deutsch 日本語
preview
Automatização de estratégias de negociação em MQL5 (Parte 25): EA para negociação com linhas de tendência ajustadas pelo método dos mínimos quadrados e geração dinâmica de sinais

Automatização de estratégias de negociação em MQL5 (Parte 25): EA para negociação com linhas de tendência ajustadas pelo método dos mínimos quadrados e geração dinâmica de sinais

MetaTrader 5 — Negociação |
15 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior (Parte 24), desenvolvemos um sistema de negociação de rompimentos na sessão de Londres utilizando a linguagem MetaQuotes Language 5 (MQL5). O sistema utilizava as faixas de preço formadas antes da abertura da sessão de Londres para posicionar ordens pendentes, com gerenciamento de risco e trailing stops, permitindo negociar de maneira eficiente durante a sessão. Nesta Parte 25, desenvolveremos um programa de negociação baseado em linhas de tendência que utiliza o ajuste pelo método dos mínimos quadrados (least squares fit) para determinar as linhas de tendência que atuam como suporte e resistência. O sistema gerará automaticamente sinais de compra e venda quando o preço tocar essas linhas, contará com elementos visuais, como setas, e permitirá configurar os parâmetros de negociação. Abordaremos os seguintes tópicos:

  1. Desenvolvimento de um modelo de negociação baseado em linhas de tendência
  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 baseada em tendências, potente e pronta para ser personalizada. Vamos à implementação!


Desenvolvimento de um modelo de negociação baseado em linhas de tendência

A negociação com linhas de tendência é uma estratégia que utiliza linhas diagonais traçadas nos gráficos de preços para conectar topos (resistência) ou fundos (suporte), ajudando os traders a identificar a tendência predominante. Em uma tendência de alta, os traders compram próximo às linhas de tendência ascendentes (suporte). Já em uma tendência de baixa, vendem próximo às linhas de tendência descendentes (resistência), na expectativa de que o preço reaja ao tocar essas linhas e retome o movimento na direção da tendência. O rompimento de uma linha de tendência costuma sinalizar uma possível reversão ou perda de força da tendência, levando os traders a encerrar ou inverter suas posições. A imagem a seguir ilustra uma linha de tendência descendente.

DOWNWARD TRENDLINE

Agora, desenvolveremos um programa de negociação baseado em linhas de tendência para automatizar as operações. Para isso, utilizaremos o ajuste pelo método dos mínimos quadrados para determinar as linhas de tendência que atuam como suporte e resistência, permitindo gerar sinais precisos de compra e venda quando o preço tocar essas linhas.

O ajuste pelo método dos mínimos quadrados é uma técnica estatística utilizada para determinar a reta (ou curva) que melhor se ajusta a um conjunto de pontos, minimizando a soma dos quadrados dos desvios verticais (erros) entre os valores observados e a linha ajustada. Esse método será importante para nossa estratégia, pois permitirá obter o melhor ajuste linear para os pontos de reversão identificados, fornecendo uma base para previsões, análise de tendências e modelagem de dados no contexto do trading. A seguir, veremos a fundamentação estatística desse método.

LEAST SQUARES OF FIT METHOD

Nosso objetivo é combinar a determinação matemática das linhas de tendência com indicações visuais e parâmetros de negociação configuráveis, permitindo aproveitar os movimentos de rejeição do preço após o toque nas linhas de tendência, mesmo em mercados dinâmicos. Para isso, identificaremos os pontos de reversão, ajustaremos linhas de tendência com um número suficiente de toques (no mínimo três), verificaremos se essas linhas permanecem válidas e executaremos operações com gerenciamento de risco. Ao mesmo tempo, exibiremos no gráfico as linhas de tendência e os pontos de toque para facilitar a análise visual. Observe o resultado que pretendemos alcançar antes de passarmos à implementação.

TRENDLINE FRAMEWORK


Implementação em MQL5

Para criar o programa em MQL5, abra o MetaEditor, acesse o Navegador, localize a pasta "Indicadores" (Indicators), acesse a aba "Novo" (New) e siga as instruções para criar o arquivo. Depois disso, começaremos a definir os parâmetros de entrada e as estruturas de dados no ambiente de desenvolvimento, tornando o programa mais flexível.
//+------------------------------------------------------------------+
//|                                       a. Trendline Trader EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2025, Allan Munene Mutiiria."
#property link        "https://t.me/Forex_Algo_Trader"
#property description "Trendline Trader using mean Least Squares Fit"
#property version     "1.00"
#property strict

#include <Trade\Trade.mqh>                         //--- Include Trade library for trading operations
CTrade obj_Trade;                                  //--- Instantiate trade object

//+------------------------------------------------------------------+
//| Swing point structure                                            |
//+------------------------------------------------------------------+
struct Swing {                                     //--- Define swing point structure
   datetime time;                                  //--- Store swing time
   double   price;                                 //--- Store swing price
};

//+------------------------------------------------------------------+
//| Starting point structure                                         |
//+------------------------------------------------------------------+
struct StartingPoint {                             //--- Define starting point structure
   datetime time;                                  //--- Store starting point time
   double   price;                                 //--- Store starting point price
   bool     is_support;                            //--- Indicate support/resistance flag
};

//+------------------------------------------------------------------+
//| Trendline storage structure                                      |
//+------------------------------------------------------------------+
struct TrendlineInfo {                             //--- Define trendline info structure
   string   name;                                  //--- Store trendline name
   datetime start_time;                            //--- Store start time
   datetime end_time;                              //--- Store end time
   double   start_price;                           //--- Store start price
   double   end_price;                             //--- Store end price
   double   slope;                                 //--- Store slope
   bool     is_support;                            //--- Indicate support/resistance flag
   int      touch_count;                           //--- Store number of touches
   datetime creation_time;                         //--- Store creation time
   int      touch_indices[];                       //--- Store touch indices array
   bool     is_signaled;                           //--- Indicate signal flag
};

//+------------------------------------------------------------------+
//| Forward declarations                                             |
//+------------------------------------------------------------------+
void DetectSwings();                               //--- Declare swing detection function
void SortSwings(Swing &swings[], int count);       //--- Declare swing sorting function
double CalculateAngle(datetime time1, double price1, datetime time2, double price2); //--- Declare angle calculation function
bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen); //--- Declare trendline validation function
void FindAndDrawTrendlines(bool isSupport);        //--- Declare trendline finding/drawing function
void UpdateTrendlines();                           //--- Declare trendline update function
void RemoveTrendlineFromStorage(int index);        //--- Declare trendline removal function
bool IsStartingPointUsed(datetime time, double price, bool is_support); //--- Declare starting point usage check function
void LeastSquaresFit(const datetime &times[], const double &prices[], int n, double &slope, double &intercept); //--- Declare least squares fit function

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input int    LookbackBars = 200;                   // Set bars for swing detection lookback
input double TouchTolerance = 10.0;                // Set tolerance for touch points (points)
input int    MinTouches = 3;                       // Set minimum touch points for valid trendline
input double PenetrationTolerance = 5.0;           // Set allowance for bar penetration (points)
input int    ExtensionBars = 100;                  // Set bars to extend trendline right
input int    MinBarSpacing = 10;                   // Set minimum bar spacing between touches
input double inpLot = 0.01;                        // Set lot size
input double inpSLPoints = 100.0;                  // Set stop loss (points)
input double inpRRRatio = 1.1;                     // Set risk:reward ratio
input double MinAngle = 1.0;                       // Set minimum inclination angle (degrees)
input double MaxAngle = 89.0;                      // Set maximum inclination angle (degrees)
input bool   DeleteExpiredObjects = false;         // Enable deletion of expired/broken objects
input bool   EnableTradingSignals = true;          // Enable buy/sell signals and trades
input bool   DrawTouchArrows = true;               // Enable drawing arrows at touch points
input bool   DrawLabels = true;                    // Enable drawing trendline/point labels
input color  SupportLineColor = clrGreen;          // Set color for support trendlines
input color  ResistanceLineColor = clrRed;         // Set color for resistance trendlines

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
Swing swingLows[];                                 //--- Store swing lows
int numLows = 0;                                   //--- Track number of swing lows
Swing swingHighs[];                                //--- Store swing highs
int numHighs = 0;                                  //--- Track number of swing highs
TrendlineInfo trendlines[];                        //--- Store trendlines
int numTrendlines = 0;                             //--- Track number of trendlines
StartingPoint startingPoints[];                    //--- Store used starting points
int numStartingPoints = 0;                         //--- Track number of starting points

Começaremos configurando os principais componentes do programa para automatizar as operações com base nos toques nas linhas de tendência. Primeiro, incluiremos a biblioteca "" e criaremos uma instância da classe "CTrade", chamada "obj_Trade", para gerenciar as operações de negociação, como a execução de ordens de compra e venda. Em seguida, definiremos três estruturas: "Swing", com os campos "time" (datetime) e "price" (double), para registrar os pontos de reversão; "StartingPoint", com "time" (datetime), "price" (double) e "is_support" (bool), para acompanhar os pontos iniciais de suporte ou resistência já utilizados; e "TrendlineInfo", com "name" (string), "start_time" e "end_time" (datetime), "start_price" e "end_price" (double), "slope" (double), "is_support" (bool), "touch_count" (int), "creation_time" (datetime), "touch_indices" (array de int) e "is_signaled" (bool), para armazenar as informações de cada linha de tendência.

Na sequência, declararemos os protótipos das funções responsáveis pelas principais tarefas: "DetectSwings", para identificar os pontos de reversão; "SortSwings", para ordenar esses pontos; "CalculateAngle", para calcular o ângulo de inclinação da linha de tendência; "ValidateTrendline", para verificar se a linha atende aos critérios de validação; "FindAndDrawTrendlines", para identificar e traçar as linhas de tendência; "UpdateTrendlines", para atualizá-las; "RemoveTrendlineFromStorage", para removê-las do armazenamento; "IsStartingPointUsed", para verificar se um ponto inicial já foi utilizado; e "LeastSquaresFit", para calcular o coeficiente angular e o coeficiente linear por meio do ajuste pelo método dos mínimos quadrados.

Por fim, configuraremos os parâmetros de entrada e as variáveis globais. Entre os parâmetros de entrada, teremos "LookbackBars" (200), que define o número de barras analisadas para identificar os pontos de reversão; "TouchTolerance" (10,0 pontos), que estabelece a tolerância para considerar um toque válido; "MinTouches" (3), que determina o número mínimo de toques necessário para validar uma linha de tendência; além dos demais parâmetros, cujas funções são autoexplicativas. Quanto às variáveis globais, utilizaremos os arrays "swingLows" e "swingHighs", acompanhados dos contadores "numLows" e "numHighs" (inicializados com 0), para armazenar os pontos de reversão. Também teremos os arrays "trendlines" e "startingPoints", com os respectivos contadores "numTrendlines" e "numStartingPoints" (inicializados com 0), para armazenar as linhas de tendência e os pontos iniciais. Essa estrutura estabelece a base do EA para identificar linhas de tendência e executar operações com base nelas de maneira eficiente. Com os componentes definidos, podemos inicializar os arrays de armazenamento na rotina de preparação do programa.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   ArrayResize(trendlines, 0);                     //--- Resize trendlines array
   numTrendlines = 0;                              //--- Reset trendlines count
   ArrayResize(startingPoints, 0);                 //--- Resize starting points array
   numStartingPoints = 0;                          //--- Reset starting points count
   return(INIT_SUCCEEDED);                         //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ArrayResize(trendlines, 0);                     //--- Resize trendlines array
   numTrendlines = 0;                              //--- Reset trendlines count
   ArrayResize(startingPoints, 0);                 //--- Resize starting points array
   numStartingPoints = 0;                          //--- Reset starting points count
}

Para garantir a inicialização correta e a liberação dos recursos, prepararemos o EA na função OnInit. Primeiro, utilizaremos ArrayResize para definir o tamanho do array "trendlines" como 0 e atribuiremos 0 a "numTrendlines", eliminando os dados existentes sobre as linhas de tendência. Em seguida, reduziremos o tamanho do array "startingPoints" para 0 e redefiniremos "numStartingPoints" como 0, limpando os registros dos pontos iniciais. Por fim, retornaremos "INIT_SUCCEEDED" para indicar que a inicialização foi concluída com sucesso.

Na função OnDeinit, realizaremos a mesma limpeza para liberar os recursos quando o EA for removido, evitar vazamentos de memória e garantir que ele possa iniciar uma nova execução sem dados residuais. Com a inicialização definida, podemos avançar para a lógica da estratégia. Para organizar essa lógica em módulos, utilizaremos funções específicas para cada tarefa. Começaremos pela identificação dos pontos de reversão, que servirão de referência para traçar as linhas de tendência.

//+------------------------------------------------------------------+
//| Check for new bar                                                |
//+------------------------------------------------------------------+
bool IsNewBar() {
   static datetime lastTime = 0;                      //--- Store last bar time
   datetime currentTime = iTime(_Symbol, _Period, 0); //--- Get current bar time
   if (lastTime != currentTime) {                     //--- Check for new bar
      lastTime = currentTime;                         //--- Update last time
      return true;                                    //--- Indicate new bar
   }
   return false;                                      //--- Indicate no new bar
}

//+------------------------------------------------------------------+
//| Sort swings by time (ascending, oldest first)                    |
//+------------------------------------------------------------------+
void SortSwings(Swing &swings[], int count) {
   for (int i = 0; i < count - 1; i++) {               //--- Iterate through swings
      for (int j = 0; j < count - i - 1; j++) {        //--- Compare adjacent swings
         if (swings[j].time > swings[j + 1].time) {    //--- Check time order
            Swing temp = swings[j];                    //--- Store temporary swing
            swings[j] = swings[j + 1];                 //--- Swap swings
            swings[j + 1] = temp;                      //--- Complete swap
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Detect swing highs and lows                                      |
//+------------------------------------------------------------------+
void DetectSwings() {
   numLows = 0;                                         //--- Reset lows count
   ArrayResize(swingLows, 0);                           //--- Resize lows array
   numHighs = 0;                                        //--- Reset highs count
   ArrayResize(swingHighs, 0);                          //--- Resize highs array
   int totalBars = iBars(_Symbol, _Period);             //--- Get total bars
   int effectiveLookback = MathMin(LookbackBars, totalBars); //--- Calculate effective lookback
   if (effectiveLookback < 5) {                         //--- Check sufficient bars
      Print("Not enough bars for swing detection.");    //--- Log insufficient bars
      return;                                           //--- Exit function
   }
   for (int i = 2; i < effectiveLookback - 2; i++) {    //--- Iterate through bars
      double low_i = iLow(_Symbol, _Period, i);         //--- Get current low
      double low_im1 = iLow(_Symbol, _Period, i - 1);   //--- Get previous low
      double low_im2 = iLow(_Symbol, _Period, i - 2);   //--- Get two bars prior low
      double low_ip1 = iLow(_Symbol, _Period, i + 1);   //--- Get next low
      double low_ip2 = iLow(_Symbol, _Period, i + 2);   //--- Get two bars next low
      if (low_i < low_im1 && low_i < low_im2 && low_i < low_ip1 && low_i < low_ip2) { //--- Check for swing low
         Swing s;                                       //--- Create swing struct
         s.time = iTime(_Symbol, _Period, i);           //--- Set swing time
         s.price = low_i;                               //--- Set swing price
         ArrayResize(swingLows, numLows + 1);           //--- Resize lows array
         swingLows[numLows] = s;                        //--- Add swing low
         numLows++;                                     //--- Increment lows count
      }
      double high_i = iHigh(_Symbol, _Period, i);       //--- Get current high
      double high_im1 = iHigh(_Symbol, _Period, i - 1); //--- Get previous high
      double high_im2 = iHigh(_Symbol, _Period, i - 2); //--- Get two bars prior high
      double high_ip1 = iHigh(_Symbol, _Period, i + 1); //--- Get next high
      double high_ip2 = iHigh(_Symbol, _Period, i + 2); //--- Get two bars next high
      if (high_i > high_im1 && high_i > high_im2 && high_i > high_ip1 && high_i > high_ip2) { //--- Check for swing high
         Swing s;                                       //--- Create swing struct
         s.time = iTime(_Symbol, _Period, i);           //--- Set swing time
         s.price = high_i;                              //--- Set swing price
         ArrayResize(swingHighs, numHighs + 1);         //--- Resize highs array
         swingHighs[numHighs] = s;                      //--- Add swing high
         numHighs++;                                    //--- Increment highs count
      }
   }
   if (numLows > 0) SortSwings(swingLows, numLows);     //--- Sort swing lows
   if (numHighs > 0) SortSwings(swingHighs, numHighs);  //--- Sort swing highs
}

Nesta etapa, implementaremos as principais funções para detectar novas barras e identificar pontos de reversão, estabelecendo a base para a análise das linhas de tendência. Primeiro, criaremos a função "IsNewBar", responsável por verificar se uma nova barra foi formada. Para isso, manteremos a variável estática "lastTime", inicialmente definida como 0, e a compararemos com "currentTime", obtida pela função iTime para o símbolo e período atuais, utilizando deslocamento 0 para acessar a barra corrente. Se os valores forem diferentes, atualizaremos "lastTime" e retornaremos true, indicando a formação de uma nova barra. Caso contrário, retornaremos false. Em seguida, implementaremos a função "SortSwings", que ordenará o array "swings" pelo campo "time", em ordem crescente, dos registros mais antigos para os mais recentes. Utilizaremos o algoritmo de ordenação por bolha (bubble sort), percorrendo os elementos até "count - 1" e comparando os valores de "time" de estruturas "Swing" adjacentes. Quando esses valores forem diferentes, as estruturas serão trocadas de posição com o auxílio da estrutura temporária "temp".

Por fim, implementaremos a função "DetectSwings". Inicialmente, redefiniremos os contadores "numLows" e "numHighs" como 0 e esvaziaremos os arrays "swingLows" e "swingHighs" por meio do redimensionamento para tamanho 0. Em seguida, calcularemos "effectiveLookback" como o menor valor entre "LookbackBars" e o número total de barras retornado por iBars. Se houver menos de cinco barras disponíveis, registraremos uma mensagem no log e encerraremos a execução da função. Caso contrário, percorreremos as barras do índice 2 até "effectiveLookback - 2" para identificar os fundos e topos de reversão, comparando os valores retornados por iLow e iHigh com os das duas barras anteriores e das duas posteriores. Para cada ponto identificado, criaremos uma estrutura "Swing", preenchendo o campo "time" com o valor obtido por iTime e o campo "price" com o valor retornado por iLow ou iHigh, conforme se trate de um fundo ou topo. Essas estruturas serão adicionadas aos arrays "swingLows" ou "swingHighs" com o auxílio de ArrayResize, e os respectivos contadores serão incrementados. Se os arrays não estiverem vazios, seus elementos serão ordenados pela função "SortSwings". Isso permitirá identificar os pontos de reversão em tempo hábil e traçar as linhas de tendência com precisão. A seguir, definiremos as funções responsáveis por calcular a inclinação dessas linhas, aplicar os limites de inclinação e verificar sua validade.

//+------------------------------------------------------------------+
//| Calculate visual inclination angle                               |
//+------------------------------------------------------------------+
double CalculateAngle(datetime time1, double price1, datetime time2, double price2) {
   int x1, y1, x2, y2;                                               //--- Declare coordinate variables
   if (!ChartTimePriceToXY(0, 0, time1, price1, x1, y1)) return 0.0; //--- Convert time1/price1 to XY
   if (!ChartTimePriceToXY(0, 0, time2, price2, x2, y2)) return 0.0; //--- Convert time2/price2 to XY
   double dx = (double)(x2 - x1);                                    //--- Calculate x difference
   double dy = (double)(y2 - y1);                                    //--- Calculate y difference
   if (dx == 0.0) return (dy > 0.0 ? -90.0 : 90.0);                  //--- Handle vertical line case
   double angle = MathArctan(-dy / dx) * 180.0 / M_PI;               //--- Calculate angle in degrees
   return angle;                                                     //--- Return angle
}

//+------------------------------------------------------------------+
//| Validate trendline                                               |
//+------------------------------------------------------------------+
bool ValidateTrendline(bool isSupport, datetime start_time, datetime ref_time, double ref_price, double slope, double tolerance_pen) {
   int bar_start = iBarShift(_Symbol, _Period, start_time);          //--- Get start bar index
   if (bar_start < 0) return false;                                  //--- Check invalid bar index
   for (int bar = bar_start; bar >= 0; bar--) {                      //--- Iterate through bars
      datetime bar_time = iTime(_Symbol, _Period, bar);              //--- Get bar time
      double dk = (double)(bar_time - ref_time);                     //--- Calculate time difference
      double line_price = ref_price + slope * dk;                    //--- Calculate line price
      if (isSupport) {                                               //--- Check support case
         double low = iLow(_Symbol, _Period, bar);                   //--- Get bar low
         if (low < line_price - tolerance_pen) return false;         //--- Check if broken
      } else {                                                       //--- Handle resistance case
         double high = iHigh(_Symbol, _Period, bar);                 //--- Get bar high
         if (high > line_price + tolerance_pen) return false;        //--- Check if broken
      }
   }
   return true;                                                      //--- Return valid
}

Vamos implementar as funções essenciais para calcular os ângulos de inclinação das linhas de tendência e verificar se permanecem válidas, garantindo a identificação confiável dessas linhas. Primeiro, criaremos a função "CalculateAngle", que converte dois pontos, definidos por "time1", "price1" e "time2", "price2", em coordenadas do gráfico por meio da função ChartTimePriceToXY, obtendo os valores "x1", "y1", "x2" e "y2". Se a conversão falhar, a função retornará 0.0. Em seguida, calcularemos as diferenças entre as coordenadas horizontais ("dx") e verticais ("dy"). Para tratar linhas verticais, retornaremos -90.0 ou 90.0 quando "dx" for igual a zero. Nos demais casos, calcularemos o ângulo de inclinação em graus com a expressão "MathArctan(-dy / dx) * 180.0 / M_PI", considerando a orientação visual da linha no gráfico.

Em seguida, implementaremos a função "ValidateTrendline", responsável por verificar se uma linha de tendência permanece válida. Primeiro, obteremos o índice da barra inicial ("bar_start") por meio de iBarShift, utilizando "start_time" como referência. Se esse índice for inválido, a função retornará false. Caso contrário, percorreremos as barras de "bar_start" até 0, calculando o preço correspondente à linha de tendência em cada instante "bar_time" com a fórmula "ref_price + slope * dk", em que "dk" representa a diferença entre o horário da barra e o horário de referência. Para linhas de suporte ("isSupport" igual a true), verificaremos se o valor retornado por iLow está abaixo de "line_price - tolerance_pen". Se isso ocorrer, consideraremos que houve um rompimento e retornaremos false. Para linhas de resistência, verificaremos se o valor retornado por iHigh ultrapassa "line_price + tolerance_pen", também retornando false em caso de rompimento. Se nenhuma dessas condições for satisfeita, a função retornará true, indicando que a linha de tendência continua válida. Agora, podemos passar à função responsável pelo ajuste pelo método dos mínimos quadrados. Vamos apresentar essa lógica da forma mais simples possível.

//+------------------------------------------------------------------+
//| Perform least-squares fit for slope and intercept                |
//+------------------------------------------------------------------+
void LeastSquaresFit(const datetime &times[], const double &prices[], int n, double &slope, double &intercept) {
   double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0; //--- Initialize sums
   for (int k = 0; k < n; k++) {                        //--- Iterate through points
      double x = (double)times[k];                      //--- Convert time to x
      double y = prices[k];                             //--- Set price as y
      sum_x += x;                                       //--- Accumulate x
      sum_y += y;                                       //--- Accumulate y
      sum_xy += x * y;                                  //--- Accumulate x*y
      sum_x2 += x * x;                                  //--- Accumulate x^2
   }
   slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x); //--- Calculate slope
   intercept = (sum_y - slope * sum_x) / n;             //--- Calculate intercept
}

Utilizaremos a função "LeastSquaresFit" para calcular o coeficiente angular e o coeficiente linear das linhas de tendência, determinando a reta que melhor se ajusta aos pontos analisados. Primeiro, inicializaremos as variáveis "sum_x", "sum_y", "sum_xy" e "sum_x2" com zero, para acumular os valores necessários aos cálculos pelo método dos mínimos quadrados. Em seguida, percorreremos os "n" pontos armazenados nos arrays "times" e "prices". Em cada iteração, converteremos "times[k]" para o tipo double, atribuindo o resultado a "x", enquanto "prices[k]" será atribuído a "y". Acumularemos "x" em "sum_x", "y" em "sum_y", o produto "x * y" em "sum_xy" e o quadrado "x * x" em "sum_x2". Por fim, calcularemos o coeficiente angular "slope" pela fórmula "(n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x)" e o coeficiente linear "intercept" pela expressão "(sum_y - slope * sum_x) / n". Com esses dois coeficientes, obteremos a reta que melhor se ajusta ao conjunto de pontos fornecidos, permitindo representar matematicamente a linha de tendência. Se quiser conhecer melhor a formulação matemática, consulte a ilustração a seguir.

LEAST SQUARES FIT METHOD

Esse procedimento permitirá determinar matematicamente a posição das linhas de tendência com precisão, contribuindo para a geração de sinais de negociação confiáveis. Agora, definiremos a função responsável pelo gerenciamento dessas linhas.

//+------------------------------------------------------------------+
//| Check if starting point is already used                          |
//+------------------------------------------------------------------+
bool IsStartingPointUsed(datetime time, double price, bool is_support) {
   for (int i = 0; i < numStartingPoints; i++) {  //--- Iterate through starting points
      if (startingPoints[i].time == time && MathAbs(startingPoints[i].price - price) < TouchTolerance * _Point && startingPoints[i].is_support == is_support) { //--- Check match
         return true;                             //--- Return used
      }
   }
   return false;                                   //--- Return not used
}

//+------------------------------------------------------------------+
//| Remove trendline from storage and optionally chart objects       |
//+------------------------------------------------------------------+
void RemoveTrendlineFromStorage(int index) {
   if (index < 0 || index >= numTrendlines) return;                    //--- Check valid index
   Print("Removing trendline from storage: ", trendlines[index].name); //--- Log removal
   if (DeleteExpiredObjects) {                                         //--- Check deletion flag
      ObjectDelete(0, trendlines[index].name);                         //--- Delete trendline object
      for (int m = 0; m < trendlines[index].touch_count; m++) {        //--- Iterate touches
         string arrow_name = trendlines[index].name + "_touch" + IntegerToString(m); //--- Generate arrow name
         ObjectDelete(0, arrow_name);                                  //--- Delete touch arrow
         string text_name = trendlines[index].name + "_point_label" + IntegerToString(m); //--- Generate text name
         ObjectDelete(0, text_name);                                   //--- Delete point label
      }
      string label_name = trendlines[index].name + "_label";           //--- Generate label name
      ObjectDelete(0, label_name);                                     //--- Delete trendline label
      string signal_arrow = trendlines[index].name + "_signal_arrow";  //--- Generate signal arrow name
      ObjectDelete(0, signal_arrow);                                   //--- Delete signal arrow
      string signal_text = trendlines[index].name + "_signal_text";    //--- Generate signal text name
      ObjectDelete(0, signal_text);                                    //--- Delete signal text
   }
   for (int i = index; i < numTrendlines - 1; i++) {                   //--- Shift array
      trendlines[i] = trendlines[i + 1];                               //--- Copy next trendline
   }
   ArrayResize(trendlines, numTrendlines - 1);                         //--- Resize trendlines array
   numTrendlines--;                                                    //--- Decrement trendlines count
}

Vamos implementar as funções auxiliares responsáveis por gerenciar os pontos iniciais das linhas de tendência e remover os registros que não são mais necessários, permitindo acompanhar essas linhas e manter os objetos do gráfico atualizados. Primeiro, criaremos a função "IsStartingPointUsed", que percorre os "numStartingPoints" elementos do array "startingPoints" para verificar se os valores fornecidos de "time", "price" e "is_support" correspondem a algum ponto inicial já registrado. A comparação de "time" será exata, enquanto "price" será comparado utilizando MathAbs, com uma tolerância de "TouchTolerance * _Point". Também verificaremos se o valor de "is_support" corresponde ao do registro existente. A função retornará true se encontrar um ponto correspondente ou false caso contrário. Em seguida, implementaremos a função "RemoveTrendlineFromStorage", que verifica se o parâmetro de entrada "index" está dentro dos limites definidos por "numTrendlines". Se o índice for inválido, a função encerrará sua execução. Caso contrário, registrará a remoção da linha de tendência.

Se "DeleteExpiredObjects" for true, removeremos o objeto gráfico da linha de tendência por meio de ObjectDelete, utilizando o nome armazenado em "trendlines[index].name". Em seguida, percorreremos os "touch_count" pontos de toque para excluir as setas e os rótulos correspondentes, cujos nomes são formados a partir do nome da linha de tendência, dos sufixos "_touch" e "_point_label" e do índice convertido por IntegerToString(m). Também removeremos o rótulo da linha de tendência, a seta do sinal e o texto associado, utilizando "label_name", "signal_arrow" e "signal_text". Por fim, deslocaremos os elementos seguintes do array "trendlines" para preencher a posição removida, até "numTrendlines - 1". Depois, ajustaremos o tamanho do array com ArrayResize e decrementaremos "numTrendlines". Esse procedimento ajudará a evitar a duplicação de linhas de tendência e permitirá remover com eficiência as linhas de tendência rompidas ou que perderam a validade. Agora, utilizaremos as funções auxiliares definidas anteriormente para implementar a busca e o traçado das linhas de tendência.

//+------------------------------------------------------------------+
//| Find and draw trendlines if no active one exists                 |
//+------------------------------------------------------------------+
void FindAndDrawTrendlines(bool isSupport) {
   bool has_active = false;                       //--- Initialize active flag
   for (int i = 0; i < numTrendlines; i++) {      //--- Iterate through trendlines
      if (trendlines[i].is_support == isSupport) { //--- Check type match
         has_active = true;                       //--- Set active flag
         break;                                   //--- Exit loop
      }
   }
   if (has_active) return;                        //--- Exit if active trendline exists
   Swing swings[];                                //--- Initialize swings array
   int numSwings;                                 //--- Initialize swings count
   color lineColor;                               //--- Initialize line color
   string prefix;                                 //--- Initialize prefix
   if (isSupport) {                               //--- Handle support case
      numSwings = numLows;                        //--- Set number of lows
      ArrayResize(swings, numSwings);             //--- Resize swings array
      for (int i = 0; i < numSwings; i++) {       //--- Iterate through lows
         swings[i].time = swingLows[i].time;      //--- Copy low time
         swings[i].price = swingLows[i].price;    //--- Copy low price
      }
      lineColor = SupportLineColor;               //--- Set support line color
      prefix = "Trendline_Support_";              //--- Set support prefix
   } else {                                       //--- Handle resistance case
      numSwings = numHighs;                       //--- Set number of highs
      ArrayResize(swings, numSwings);             //--- Resize swings array
      for (int i = 0; i < numSwings; i++) {       //--- Iterate through highs
         swings[i].time = swingHighs[i].time;     //--- Copy high time
         swings[i].price = swingHighs[i].price;   //--- Copy high price
      }
      lineColor = ResistanceLineColor;            //--- Set resistance line color
      prefix = "Trendline_Resistance_";           //--- Set resistance prefix
   }
   if (numSwings < 2) return;                     //--- Exit if insufficient swings
   double pointValue = _Point;                    //--- Get point value
   double touch_tolerance = TouchTolerance * pointValue; //--- Calculate touch tolerance
   double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance
   int best_j = -1;                               //--- Initialize best j index
   int max_touches = 0;                           //--- Initialize max touches
   int best_touch_indices[];                      //--- Initialize best touch indices
   double best_slope = 0.0;                       //--- Initialize best slope
   double best_intercept = 0.0;                   //--- Initialize best intercept
   datetime best_min_time = 0;                    //--- Initialize best min time
   for (int i = 0; i < numSwings - 1; i++) {      //--- Iterate through first points
      for (int j = i + 1; j < numSwings; j++) {   //--- Iterate through second points
         datetime time1 = swings[i].time;         //--- Get first time
         double price1 = swings[i].price;         //--- Get first price
         datetime time2 = swings[j].time;         //--- Get second time
         double price2 = swings[j].price;         //--- Get second price
         double dt = (double)(time2 - time1);     //--- Calculate time difference
         if (dt <= 0) continue;                   //--- Skip invalid time difference
         double initial_slope = (price2 - price1) / dt; //--- Calculate initial slope
         int touch_indices[];                     //--- Initialize touch indices
         ArrayResize(touch_indices, 0);           //--- Resize touch indices
         int touches = 0;                         //--- Initialize touches count
         ArrayResize(touch_indices, touches + 1); //--- Add first index
         touch_indices[touches] = i;              //--- Set first index
         touches++;                               //--- Increment touches
         ArrayResize(touch_indices, touches + 1); //--- Add second index
         touch_indices[touches] = j;              //--- Set second index
         touches++;                               //--- Increment touches
         for (int k = 0; k < numSwings; k++) {    //--- Iterate through swings
            if (k == i || k == j) continue;       //--- Skip used indices
            datetime tk = swings[k].time;         //--- Get swing time
            double dk = (double)(tk - time1);     //--- Calculate time difference
            double expected = price1 + initial_slope * dk; //--- Calculate expected price
            double actual = swings[k].price;      //--- Get actual price
            if (MathAbs(expected - actual) <= touch_tolerance) { //--- Check touch within tolerance
               ArrayResize(touch_indices, touches + 1); //--- Add index
               touch_indices[touches] = k;        //--- Set index
               touches++;                         //--- Increment touches
            }
         }
         if (touches >= MinTouches) {             //--- Check minimum touches
            ArraySort(touch_indices);             //--- Sort touch indices
            bool valid_spacing = true;            //--- Initialize spacing flag
            for (int m = 0; m < touches - 1; m++) { //--- Iterate through touches
               int idx1 = touch_indices[m];       //--- Get first index
               int idx2 = touch_indices[m + 1];   //--- Get second index
               int bar1 = iBarShift(_Symbol, _Period, swings[idx1].time); //--- Get first bar
               int bar2 = iBarShift(_Symbol, _Period, swings[idx2].time); //--- Get second bar
               int diff = MathAbs(bar1 - bar2);   //--- Calculate bar difference
               if (diff < MinBarSpacing) {        //--- Check minimum spacing
                  valid_spacing = false;          //--- Mark invalid spacing
                  break;                          //--- Exit loop
               }
            }
            if (valid_spacing) {                  //--- Check valid spacing
               datetime touch_times[];            //--- Initialize touch times
               double touch_prices[];             //--- Initialize touch prices
               ArrayResize(touch_times, touches); //--- Resize times array
               ArrayResize(touch_prices, touches); //--- Resize prices array
               for (int m = 0; m < touches; m++) { //--- Iterate through touches
                  int idx = touch_indices[m];      //--- Get index
                  touch_times[m] = swings[idx].time;   //--- Set time
                  touch_prices[m] = swings[idx].price; //--- Set price
               }
               double slope, intercept;                //--- Declare slope and intercept
               LeastSquaresFit(touch_times, touch_prices, touches, slope, intercept); //--- Perform least squares fit
               int adjusted_touch_indices[];           //--- Initialize adjusted indices
               ArrayResize(adjusted_touch_indices, 0); //--- Resize adjusted indices
               int adjusted_touches = 0;               //--- Initialize adjusted touches count
               for (int k = 0; k < numSwings; k++) {   //--- Iterate through swings
                  double expected = intercept + slope * (double)swings[k].time; //--- Calculate expected price
                  double actual = swings[k].price;     //--- Get actual price
                  if (MathAbs(expected - actual) <= touch_tolerance) { //--- Check touch
                     ArrayResize(adjusted_touch_indices, adjusted_touches + 1); //--- Add index
                     adjusted_touch_indices[adjusted_touches] = k; //--- Set index
                     adjusted_touches++;               //--- Increment adjusted touches
                  }
               }
               if (adjusted_touches >= MinTouches) { //--- Check minimum adjusted touches
                  datetime temp_min_time = swings[adjusted_touch_indices[0]].time; //--- Get min time
                  double temp_ref_price = intercept + slope * (double)temp_min_time; //--- Calculate ref price
                  if (ValidateTrendline(isSupport, temp_min_time, temp_min_time, temp_ref_price, slope, pen_tolerance)) { //--- Validate trendline
                     datetime temp_max_time = swings[adjusted_touch_indices[adjusted_touches - 1]].time; //--- Get max time
                     double temp_max_price = intercept + slope * (double)temp_max_time; //--- Calculate max price
                     double angle = CalculateAngle(temp_min_time, temp_ref_price, temp_max_time, temp_max_price); //--- Calculate angle
                     double abs_angle = MathAbs(angle); //--- Get absolute angle
                     if (abs_angle >= MinAngle && abs_angle <= MaxAngle) { //--- Check angle range
                        if (adjusted_touches > max_touches || (adjusted_touches == max_touches && j > best_j)) { //--- Check better trendline
                           max_touches = adjusted_touches; //--- Update max touches
                           best_j = j;                     //--- Update best j
                           best_slope = slope;             //--- Update best slope
                           best_intercept = intercept;     //--- Update best intercept
                           best_min_time = temp_min_time;  //--- Update best min time
                           ArrayResize(best_touch_indices, adjusted_touches); //--- Resize best indices
                           ArrayCopy(best_touch_indices, adjusted_touch_indices); //--- Copy indices
                        }
                     }
                  }
               }
            }
         }
      }
   }
   if (max_touches < MinTouches) {                //--- Check insufficient touches
      string type = isSupport ? "Support" : "Resistance"; //--- Set type string
      return;                                     //--- Exit function
   }
   int touch_indices[];                           //--- Initialize touch indices
   ArrayResize(touch_indices, max_touches);       //--- Resize touch indices
   ArrayCopy(touch_indices, best_touch_indices);  //--- Copy best indices
   int touches = max_touches;                     //--- Set touches count
   datetime min_time = best_min_time;             //--- Set min time
   double price_min = best_intercept + best_slope * (double)min_time; //--- Calculate min price
   datetime max_time = swings[touch_indices[touches - 1]].time; //--- Set max time
   double price_max = best_intercept + best_slope * (double)max_time; //--- Calculate max price
   datetime start_time_check = min_time;          //--- Set start time check
   double start_price_check = swings[touch_indices[0]].price; //--- Set start price check
   if (IsStartingPointUsed(start_time_check, start_price_check, isSupport)) { //--- Check used starting point
      return;                                     //--- Skip if used
   }
   datetime time_end = iTime(_Symbol, _Period, 0) + PeriodSeconds(_Period) * ExtensionBars; //--- Calculate end time
   double dk_end = (double)(time_end - min_time);      //--- Calculate end time difference
   double price_end = price_min + best_slope * dk_end; //--- Calculate end price
   string unique_name = prefix + TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS); //--- Generate unique name
   if (ObjectFind(0, unique_name) < 0) {               //--- Check if trendline exists
      ObjectCreate(0, unique_name, OBJ_TREND, 0, min_time, price_min, time_end, price_end); //--- Create trendline
      ObjectSetInteger(0, unique_name, OBJPROP_COLOR, lineColor);   //--- Set color
      ObjectSetInteger(0, unique_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
      ObjectSetInteger(0, unique_name, OBJPROP_WIDTH, 1);           //--- Set width
      ObjectSetInteger(0, unique_name, OBJPROP_RAY_RIGHT, false);   //--- Disable right ray
      ObjectSetInteger(0, unique_name, OBJPROP_RAY_LEFT, false);    //--- Disable left ray
      ObjectSetInteger(0, unique_name, OBJPROP_BACK, false);        //--- Set to foreground
   }
   ArrayResize(trendlines, numTrendlines + 1);                      //--- Resize trendlines array
   trendlines[numTrendlines].name = unique_name;                    //--- Set trendline name
   trendlines[numTrendlines].start_time = min_time;                 //--- Set start time
   trendlines[numTrendlines].end_time = time_end;                   //--- Set end time
   trendlines[numTrendlines].start_price = price_min;               //--- Set start price
   trendlines[numTrendlines].end_price = price_end;                 //--- Set end price
   trendlines[numTrendlines].slope = best_slope;                    //--- Set slope
   trendlines[numTrendlines].is_support = isSupport;                //--- Set type
   trendlines[numTrendlines].touch_count = touches;                 //--- Set touch count
   trendlines[numTrendlines].creation_time = TimeCurrent();         //--- Set creation time
   trendlines[numTrendlines].is_signaled = false;                   //--- Set signaled flag
   ArrayResize(trendlines[numTrendlines].touch_indices, touches);   //--- Resize touch indices
   ArrayCopy(trendlines[numTrendlines].touch_indices, touch_indices); //--- Copy touch indices
   numTrendlines++;                                                 //--- Increment trendlines count
   ArrayResize(startingPoints, numStartingPoints + 1);              //--- Resize starting points array
   startingPoints[numStartingPoints].time = start_time_check; //--- Set starting point time
   startingPoints[numStartingPoints].price = start_price_check; //--- Set starting point price
   startingPoints[numStartingPoints].is_support = isSupport; //--- Set starting point type
   numStartingPoints++;                           //--- Increment starting points count
   if (DrawTouchArrows) {                         //--- Check draw arrows
      for (int m = 0; m < touches; m++) {         //--- Iterate through touches
         int idx = touch_indices[m];              //--- Get touch index
         datetime tk_time = swings[idx].time;     //--- Get touch time
         double tk_price = swings[idx].price;     //--- Get touch price
         string arrow_name = unique_name + "_touch" + IntegerToString(m); //--- Generate arrow name
         if (ObjectFind(0, arrow_name) < 0) {                             //--- Check if arrow exists
            ObjectCreate(0, arrow_name, OBJ_ARROW, 0, tk_time, tk_price); //--- Create touch arrow
            ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, 159);      //--- Set arrow code
            ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor
            ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, lineColor);    //--- Set color
            ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1);            //--- Set width
            ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false);         //--- Set to foreground
         }
      }
   }
   double angle = CalculateAngle(min_time, price_min, max_time, price_max); //--- Calculate angle
   string type = isSupport ? "Support" : "Resistance"; //--- Set type string
   Print(type + " Trendline " + unique_name + " drawn with " + IntegerToString(touches) + " touches. Inclination angle: " + DoubleToString(angle, 2) + " degrees."); //--- Log trendline
   if (DrawLabels) {                              //--- Check draw labels
      datetime mid_time = min_time + (max_time - min_time) / 2; //--- Calculate mid time
      double dk_mid = (double)(mid_time - min_time); //--- Calculate mid time difference
      double mid_price = price_min + best_slope * dk_mid; //--- Calculate mid price
      double label_offset = 20 * _Point * (isSupport ? -1 : 1); //--- Calculate label offset
      double label_price = mid_price + label_offset; //--- Calculate label price
      int label_anchor = isSupport ? ANCHOR_TOP : ANCHOR_BOTTOM; //--- Set label anchor
      string label_text = type + " Trendline";    //--- Set label text
      string label_name = unique_name + "_label"; //--- Generate label name
      if (ObjectFind(0, label_name) < 0) {        //--- Check if label exists
         ObjectCreate(0, label_name, OBJ_TEXT, 0, mid_time, label_price); //--- Create label
         ObjectSetString(0, label_name, OBJPROP_TEXT, label_text); //--- Set text
         ObjectSetInteger(0, label_name, OBJPROP_COLOR, clrBlack); //--- Set color
         ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8); //--- Set font size
         ObjectSetInteger(0, label_name, OBJPROP_ANCHOR, label_anchor); //--- Set anchor
         ObjectSetDouble(0, label_name, OBJPROP_ANGLE, angle); //--- Set angle
         ObjectSetInteger(0, label_name, OBJPROP_BACK, false); //--- Set to foreground
      }
      color point_label_color = isSupport ? clrSaddleBrown : clrDarkGoldenrod; //--- Set point label color
      double point_text_offset = 20.0 * _Point;   //--- Set point text offset
      for (int m = 0; m < touches; m++) {         //--- Iterate through touches
         int idx = touch_indices[m];              //--- Get touch index
         datetime tk_time = swings[idx].time;     //--- Get touch time
         double tk_price = swings[idx].price;     //--- Get touch price
         double text_price;                       //--- Initialize text price
         int point_text_anchor;                   //--- Initialize text anchor
         if (isSupport) {                         //--- Handle support
            text_price = tk_price - point_text_offset; //--- Set text price below
            point_text_anchor = ANCHOR_LEFT;      //--- Set left anchor
         } else {                                 //--- Handle resistance
            text_price = tk_price + point_text_offset; //--- Set text price above
            point_text_anchor = ANCHOR_BOTTOM;    //--- Set bottom anchor
         }
         string text_name = unique_name + "_point_label" + IntegerToString(m); //--- Generate text name
         string point_text = "Pt " + IntegerToString(m + 1); //--- Set point text
         if (ObjectFind(0, text_name) < 0) {     //--- Check if text exists
            ObjectCreate(0, text_name, OBJ_TEXT, 0, tk_time, text_price); //--- Create text
            ObjectSetString(0, text_name, OBJPROP_TEXT, point_text); //--- Set text
            ObjectSetInteger(0, text_name, OBJPROP_COLOR, point_label_color); //--- Set color
            ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 8); //--- Set font size
            ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, point_text_anchor); //--- Set anchor
            ObjectSetDouble(0, text_name, OBJPROP_ANGLE, 0); //--- Set angle
            ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground
         }
      }
   }
}

Nesta etapa, implementaremos a função "FindAndDrawTrendlines", responsável por identificar e traçar linhas de tendência com pontos de toque ótimos, mantendo apenas uma linha ativa de cada tipo. Primeiro, verificaremos se já existe uma linha de tendência ativa, percorrendo os "numTrendlines" elementos do array "trendlines". Se encontrarmos uma linha cujo campo "is_support" corresponda ao parâmetro de entrada "isSupport", definiremos "has_active" como true e encerraremos a busca, evitando criar outra linha do mesmo tipo. Em seguida, configuraremos os dados conforme o tipo de linha. Para uma linha de suporte, atribuiremos "numLows" a "numSwings", preencheremos o array "swings" com os dados de "swingLows", definiremos "lineColor" como "SupportLineColor" e utilizaremos "Trendline_Support_" como valor de "prefix". Para uma linha de resistência, utilizaremos "numHighs", "swingHighs", "ResistanceLineColor" e "Trendline_Resistance_", respectivamente. Se "numSwings" for menor que 2, encerraremos a execução da função, pois não haverá pontos suficientes para definir uma linha de tendência. Depois, calcularemos as tolerâncias "touch_tolerance" e "pen_tolerance", multiplicando "TouchTolerance" e "PenetrationTolerance" por _Point. Por fim, percorreremos os pares de pontos do conjunto de "numSwings" elementos para calcular a inclinação inicial "initial_slope" e identificar os pontos que estejam dentro da tolerância de toque estabelecida, armazenando seus índices em "touch_indices".

Se o número de toques atingir o valor de "MinTouches" e os pontos atenderem ao espaçamento mínimo definido por "MinBarSpacing", verificado por meio de iBarShift, utilizaremos a função "LeastSquaresFit" para calcular os coeficientes "slope" e "intercept". Em seguida, verificaremos novamente os pontos de toque e validaremos a linha com as funções "ValidateTrendline" e "CalculateAngle", considerando os limites "MinAngle" e "MaxAngle". Durante essa avaliação, atualizaremos "best_j", "max_touches", "best_slope", "best_intercept", "best_min_time" e "best_touch_indices" para selecionar a melhor linha de tendência. Por fim, se "max_touches" for maior ou igual a "MinTouches" e o ponto inicial ainda não tiver sido utilizado, conforme a verificação feita por "IsStartingPointUsed", criaremos a linha de tendência como um objeto OBJ_TREND por meio de ObjectCreate, utilizando "unique_name". Também desenharemos as setas dos pontos de toque e os respectivos rótulos se "DrawTouchArrows" e "DrawLabels" estiverem definidos como true. Depois, armazenaremos as informações da linha em "trendlines", adicionaremos seu ponto inicial à lista de pontos iniciais e registraremos o resultado no log. Com isso, teremos a linha de tendência criada e registrada. Resta agora acompanhar as linhas existentes, atualizá-las continuamente e verificar possíveis toques e rompimentos para gerar sinais de negociação. Para simplificar a implementação, reuniremos essa lógica em uma única função.

//+------------------------------------------------------------------+
//| Update trendlines and check for signals                          |
//+------------------------------------------------------------------+
void UpdateTrendlines() {
   datetime current_time = iTime(_Symbol, _Period, 0);       //--- Get current time
   double pointValue = _Point;                               //--- Get point value
   double pen_tolerance = PenetrationTolerance * pointValue; //--- Calculate penetration tolerance
   double touch_tolerance = TouchTolerance * pointValue;     //--- Calculate touch tolerance
   for (int i = numTrendlines - 1; i >= 0; i--) {            //--- Iterate trendlines backward
      string type = trendlines[i].is_support ? "Support" : "Resistance"; //--- Determine trendline type
      string name = trendlines[i].name;                      //--- Get trendline name
      if (current_time > trendlines[i].end_time) {           //--- Check if expired
         PrintFormat("%s trendline %s is no longer valid (expired). End time: %s, Current time: %s.", type, name, TimeToString(trendlines[i].end_time), TimeToString(current_time)); //--- Log expiration
         RemoveTrendlineFromStorage(i);                      //--- Remove trendline
         continue;                                           //--- Skip to next
      }
      datetime prev_bar_time = iTime(_Symbol, _Period, 1);   //--- Get previous bar time
      double dk = (double)(prev_bar_time - trendlines[i].start_time); //--- Calculate time difference
      double line_price = trendlines[i].start_price + trendlines[i].slope * dk; //--- Calculate line price
      double prev_low = iLow(_Symbol, _Period, 1);           //--- Get previous bar low
      double prev_high = iHigh(_Symbol, _Period, 1);         //--- Get previous bar high
      bool broken = false;                                   //--- Initialize broken flag
      if (trendlines[i].is_support && prev_low < line_price - pen_tolerance) { //--- Check support break
         PrintFormat("%s trendline %s is no longer valid (broken by price). Line price: %.5f, Prev low: %.5f, Penetration: %.5f points.", type, name, line_price, prev_low, PenetrationTolerance); //--- Log break
         RemoveTrendlineFromStorage(i);           //--- Remove trendline
         broken = true;                           //--- Set broken flag
      } else if (!trendlines[i].is_support && prev_high > line_price + pen_tolerance) { //--- Check resistance break
         PrintFormat("%s trendline %s is no longer valid (broken by price). Line price: %.5f, Prev high: %.5f, Penetration: %.5f points.", type, name, line_price, prev_high, PenetrationTolerance); //--- Log break
         RemoveTrendlineFromStorage(i);           //--- Remove trendline
         broken = true;                           //--- Set broken flag
      }
      if (!broken && !trendlines[i].is_signaled && EnableTradingSignals) { //--- Check for trading signal
         bool touched = false;                    //--- Initialize touched flag
         string signal_type = "";                 //--- Initialize signal type
         color signal_color = clrNONE;            //--- Initialize signal color
         int arrow_code = 0;                      //--- Initialize arrow code
         int anchor = 0;                          //--- Initialize anchor
         double text_angle = 0.0;                 //--- Initialize text angle
         double text_offset = 0.0;                //--- Initialize text offset
         double text_price = 0.0;                 //--- Initialize text price
         int text_anchor = 0;                     //--- Initialize text anchor
         if (trendlines[i].is_support && MathAbs(prev_low - line_price) <= touch_tolerance) { //--- Check support touch
            touched = true;                       //--- Set touched flag
            signal_type = "BUY";                  //--- Set buy signal
            signal_color = clrBlue;               //--- Set blue color
            arrow_code = 217;                     //--- Set up arrow for support (BUY)
            anchor = ANCHOR_TOP;                  //--- Set top anchor
            text_angle = -90.0;                   //--- Set vertical upward for BUY
            text_offset = -20 * pointValue;        //--- Set text offset
            text_price = line_price + text_offset; //--- Calculate text price
            text_anchor = ANCHOR_LEFT;            //--- Set left anchor
            double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Get ask price
            double SL = NormalizeDouble(Ask - inpSLPoints * _Point, _Digits); //--- Calculate stop loss
            double TP = NormalizeDouble(Ask + (inpSLPoints * inpRRRatio) * _Point, _Digits); //--- Calculate take profit
            obj_Trade.Buy(inpLot, _Symbol, Ask, SL, TP); //--- Execute buy trade
         } else if (!trendlines[i].is_support && MathAbs(prev_high - line_price) <= touch_tolerance) { //--- Check resistance touch
            touched = true;                       //--- Set touched flag
            signal_type = "SELL";                 //--- Set sell signal
            signal_color = clrRed;                //--- Set red color
            arrow_code = 218;                     //--- Set down arrow for resistance (SELL)
            anchor = ANCHOR_BOTTOM;               //--- Set bottom anchor
            text_angle = 90.0;                    //--- Set vertical downward for SELL
            text_offset = 20 * pointValue;       //--- Set text offset
            text_price = line_price + text_offset; //--- Calculate text price
            text_anchor = ANCHOR_BOTTOM;          //--- Set bottom anchor
            double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Get bid price
            double SL = NormalizeDouble(Bid + inpSLPoints * _Point, _Digits); //--- Calculate stop loss
            double TP = NormalizeDouble(Bid - (inpSLPoints * inpRRRatio) * _Point, _Digits); //--- Calculate take profit
            obj_Trade.Sell(inpLot, _Symbol, Bid, SL, TP); //--- Execute sell trade
         }
         if (touched) {                           //--- Check if touched
            PrintFormat("Signal generated for %s trendline %s: %s at price %.5f, time %s.", type, name, signal_type, line_price, TimeToString(current_time)); //--- Log signal
            string arrow_name = name + "_signal_arrow"; //--- Generate signal arrow name
            if (ObjectFind(0, arrow_name) < 0) {  //--- Check if arrow exists
               ObjectCreate(0, arrow_name, OBJ_ARROW, 0, prev_bar_time, line_price); //--- Create signal arrow
               ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, arrow_code); //--- Set arrow code
               ObjectSetInteger(0, arrow_name, OBJPROP_ANCHOR, anchor); //--- Set anchor
               ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, signal_color); //--- Set color
               ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 1); //--- Set width
               ObjectSetInteger(0, arrow_name, OBJPROP_BACK, false); //--- Set to foreground
            }
            string text_name = name + "_signal_text"; //--- Generate signal text name
            if (ObjectFind(0, text_name) < 0) {   //--- Check if text exists
               ObjectCreate(0, text_name, OBJ_TEXT, 0, prev_bar_time, text_price); //--- Create signal text
               ObjectSetString(0, text_name, OBJPROP_TEXT, " " + signal_type); //--- Set text content
               ObjectSetInteger(0, text_name, OBJPROP_COLOR, signal_color); //--- Set color
               ObjectSetInteger(0, text_name, OBJPROP_FONTSIZE, 10); //--- Set font size
               ObjectSetInteger(0, text_name, OBJPROP_ANCHOR, text_anchor); //--- Set anchor
               ObjectSetDouble(0, text_name, OBJPROP_ANGLE, text_angle); //--- Set angle
               ObjectSetInteger(0, text_name, OBJPROP_BACK, false); //--- Set to foreground
            }
            trendlines[i].is_signaled = true;     //--- Set signaled flag
         }
      }
   }
}

Criaremos a função "UpdateTrendlines" para monitorar as linhas de tendência ativas e executar as ações necessárias. Ela terá o tipo de retorno void, pois não precisará retornar nenhum valor. Primeiro, obteremos "current_time" por meio de iTime, utilizando a barra atual, e definiremos "pointValue" como _Point. Em seguida, calcularemos "pen_tolerance" como "PenetrationTolerance * pointValue" e "touch_tolerance" como "TouchTolerance * pointValue". Depois, percorreremos os "numTrendlines" elementos do array "trendlines" em ordem inversa, identificando o tipo de cada linha como "Support" ou "Resistance" de acordo com o campo "is_support". Também verificaremos se "current_time" ultrapassou "end_time". Se isso ocorrer, registraremos a expiração da linha no log por meio de PrintFormat e a removeremos utilizando "RemoveTrendlineFromStorage".

Para as linhas que ainda estiverem dentro do período de validade, calcularemos o preço correspondente à linha de tendência no instante "prev_bar_time", obtido por iTime com deslocamento 1, referente à barra anterior. Utilizaremos a expressão "start_price + slope * dk" para determinar esse preço. Em seguida, verificaremos se houve rompimento, comparando "prev_low" ou "prev_high" com "line_price", considerando a tolerância "pen_tolerance". Se a linha tiver sido rompida, registraremos o evento com PrintFormat e a removeremos por meio de "RemoveTrendlineFromStorage".

Por fim, se a linha não tiver sido rompida, "is_signaled" for false e "EnableTradingSignals" estiver definido como true, verificaremos se houve um toque que justifique a geração de um sinal. Para uma linha de suporte, se "prev_low" estiver dentro da tolerância "touch_tolerance" em relação a "line_price", geraremos um sinal "BUY" e executaremos uma operação de compra por meio de "obj_Trade.Buy", utilizando "inpLot", "Ask", "SL" e "TP", sendo os níveis de stop-loss e take-profit calculados com base em "inpSLPoints" e "inpRRRatio". Também desenharemos uma seta azul apontando para cima (código 217) e o texto correspondente ao sinal. Para uma linha de resistência, se "prev_high" estiver dentro da tolerância permitida, geraremos um sinal "SELL", executaremos uma operação de venda por meio de "obj_Trade.Sell" e desenharemos uma seta vermelha apontando para baixo (código 218), acompanhada do texto correspondente. Os eventos serão registrados com PrintFormat, enquanto os objetos gráficos serão criados com ObjectCreate e configurados por meio de ObjectSetInteger e ObjectSetString. Por fim, definiremos "is_signaled" como true, evitando a geração de novos sinais para a mesma linha de tendência. Dessa forma, manteremos as linhas atualizadas e geraremos sinais de negociação com base nos toques identificados. Os códigos utilizados para as setas podem ser escolhidos conforme sua preferência. A seguir, apresentamos os códigos Wingdings disponíveis no MQL5.

MQL5 WINGDINGS

Agora, podemos chamar essas funções no manipulador de eventos OnTick para que o sistema forneça informações de acompanhamento com base nos ticks.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (!IsNewBar()) return;                        //--- Exit if not new bar
   DetectSwings();                                 //--- Detect swings
   UpdateTrendlines();                             //--- Update trendlines
   FindAndDrawTrendlines(true);                    //--- Find/draw support trendlines
}

No manipulador de eventos OnTick, implementaremos a lógica de identificação das linhas de tendência e de execução das operações a cada nova barra. Primeiro, chamaremos a função "IsNewBar" para verificar se uma nova barra foi formada. Se ela retornar false, encerraremos imediatamente a execução do manipulador para evitar processamento desnecessário. Em seguida, chamaremos "DetectSwings" para identificar e atualizar os topos e fundos de reversão armazenados nos arrays "swingHighs" e "swingLows". Depois, executaremos "UpdateTrendlines" para verificar as linhas de tendência existentes, remover aquelas que foram rompidas ou perderam a validade e gerar sinais de negociação quando o preço tocar uma linha dentro da tolerância estabelecida. Por fim, chamaremos "FindAndDrawTrendlines" com o parâmetro true para criar linhas de tendência de suporte, garantindo que uma nova linha seja traçada apenas quando não houver outra linha ativa desse tipo. Após a compilação, obteremos o seguinte resultado.

CONFIRMED SUPPORT TRENDLINE

A imagem mostra que o sistema identifica, analisa e traça as linhas de tendência de suporte, além de executar operações quando o preço toca essas linhas. As linhas de tendência expiradas também são removidas corretamente do array de armazenamento. Podemos aplicar a mesma lógica às linhas de tendência de resistência, chamando a mesma função utilizada para as linhas de suporte, mas passando false como parâmetro de entrada.

//--- other ontick functions

FindAndDrawTrendlines(false);                   //--- Find/draw resistance trendlines

//---

Depois de adicionar essa chamada e compilar o programa, obteremos o seguinte resultado.

RESISTANCE TRENDLINE

A imagem mostra que o sistema também identifica as linhas de tendência de resistência e executa operações com base nelas. Após integrar todos os componentes e realizar os testes, obteremos o seguinte resultado.

COMBINED OUTCOME

A imagem mostra que o sistema identifica e exibe as linhas de tendência no gráfico, além de executar operações quando o preço as toca. Com isso, alcançamos os objetivos estabelecidos. Resta testar o programa com dados históricos, como veremos 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:

Gráfico

Relatório do teste com dados históricos:

REPORT


Conclusão

Neste artigo, desenvolvemos um programa de negociação baseado em linhas de tendência em MQL5, utilizando o ajuste pelo método dos mínimos quadrados para identificar linhas de tendência confiáveis que atuam como suporte e resistência. O programa gera automaticamente sinais de compra e venda e utiliza elementos visuais, como setas e rótulos, para representá-los no gráfico. O uso de componentes modulares, como a estrutura "TrendlineInfo" e a função "FindAndDrawTrendlines", permite implementar uma abordagem disciplinada de negociação baseada em tendências, que pode ser aprimorada por meio do ajuste de seus parâmetros.

Isenção de responsabilidade: O conteúdo deste artigo tem finalidade exclusivamente educacional. A negociação nos mercados financeiros envolve riscos significativos, e a volatilidade pode provocar perdas. Antes de utilizar este programa em condições reais de mercado, é fundamental realizar testes rigorosos com dados históricos e adotar práticas adequadas de gerenciamento de risco.

Com os conceitos e métodos de implementação apresentados, você poderá adaptar esse sistema de linhas de tendência 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/19077

Arquivos anexados |
Automação de estratégias de negociação em MQL5 (Parte 26): Criação de um sistema de preço médio baseado em pin bars para negociação com múltiplas posições Automação de estratégias de negociação em MQL5 (Parte 26): Criação de um sistema de preço médio baseado em pin bars para negociação com múltiplas posições
Neste artigo, desenvolvemos em MQL5 um sistema de preço médio baseado em pin bars, que identifica esses padrões para abrir operações e utiliza uma estratégia de preço médio para gerenciar múltiplas posições, com recursos de trailing stop e ajuste do stop para o ponto de equilíbrio (breakeven). Também combinamos parâmetros configuráveis com um dashboard que permite monitorar as posições e os lucros em tempo real.
Integração do MQL5 com pacotes de processamento de dados (Parte 5): Aprendizado adaptativo e flexibilidade Integração do MQL5 com pacotes de processamento de dados (Parte 5): Aprendizado adaptativo e flexibilidade
Nesta parte, vamos nos concentrar na criação de um modelo de negociação flexível e adaptativo, treinado com dados históricos do XAUUSDт Também vamos prepará-lo para exportação no formato ONNX e possível integração a sistemas de negociação em ambiente real.
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.
Desenvolvimento de um EA dinâmico para múltiplos pares (Parte 4): Ajuste do risco com base na volatilidade Desenvolvimento de um EA dinâmico para múltiplos pares (Parte 4): Ajuste do risco com base na volatilidade
Nesta etapa, configuramos o EA multipar para ajustar o tamanho das posições e a exposição ao risco em tempo real com base em indicadores de volatilidade, como o ATR. Isso torna a gestão de risco mais consistente, reforça a proteção das posições e melhora o desempenho em diferentes condições de mercado.