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
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:
- Desenvolvimento de um modelo de negociação baseado em linhas de tendência
- Implementação em MQL5
- Testes com dados históricos
- 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.

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.

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.

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 ×[], 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 ×[], 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.

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.

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.

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.

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.

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:

Relatório do teste com dados históricos:

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
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.