English Русский 中文 Español Deutsch 日本語
preview
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

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

Introdução

No artigo anterior (Parte 25), desenvolvemos um sistema de negociação baseado em linhas de tendência utilizando a linguagem MetaQuotes Language 5 (MQL5). O sistema emprega o ajuste pelo método dos mínimos quadrados para traçar linhas de tendência de suporte e resistência, gera operações automaticamente quando o preço toca essas linhas e fornece feedback visual. Nesta Parte 26, criaremos um sistema de preço médio baseado em pin bars, capaz de identificar esses padrões de velas para abrir operações e gerenciar múltiplas posições por meio de uma estratégia de preço médio, com trailing stop, ajuste do stop para o ponto de equilíbrio (breakeven) e um dashboard para monitoramento em tempo real. Abordaremos os seguintes tópicos:

  1. Entendendo o modelo de preço médio baseado em pin bars
  2. Implementação em MQL5
  3. Testes com dados históricos
  4. Conclusão

Ao final, você terá uma estratégia potente de negociação com pin bars em MQL5, pronta para ser personalizada. Vamos à implementação!


Entendendo o modelo de preço médio baseado em pin bars

Vamos desenvolver um sistema de negociação automatizado que utiliza padrões de velas conhecidos como pin bars. Esses padrões são formados por uma única vela, caracterizada por uma sombra longa e um corpo pequeno, e frequentemente sinalizam possíveis reversões expressivas do preço em níveis importantes do mercado. As estratégias baseadas em pin bars são populares entre os traders porque ajudam a identificar momentos em que o preço sofre rejeição e recua, oferecendo oportunidades de entrada com alta probabilidade de acerto, especialmente quando combinadas com níveis de suporte e resistência. A imagem abaixo ilustra algumas formações típicas desse padrão.

PIN BAR FRAMEWORK

Nossa abordagem se concentrará na identificação de pin bars no timeframe atual e na aplicação de uma estratégia de preço médio para abrir posições adicionais caso o mercado se mova contra a operação inicial. O objetivo é melhorar o resultado geral das operações e, ao mesmo tempo, limitar os riscos por meio de trailing stop e ajuste do stop-loss para o ponto de equilíbrio (breakeven). Para isso, primeiro identificaremos os pin bars em relação aos níveis de suporte ou resistência determinados a partir do fechamento da vela H4 anterior, mantendo as operações alinhadas com zonas relevantes do mercado.

Em seguida, implementaremos um mecanismo de preço médio que permitirá adicionar posições a intervalos de preço predefinidos, proporcionando maior flexibilidade em períodos de alta volatilidade. Por fim, adicionaremos um dashboard para exibir as métricas das operações em tempo real e utilizaremos elementos visuais, como linhas, para marcar os níveis mais importantes. Assim, poderemos acompanhar o comportamento da estratégia e ajustá-la com mais eficiência. Veja abaixo o resultado que pretendemos alcançar antes de passarmos à implementação.

STRATEGY FRAMEWORK


Implementação em MQL5

Para criar o programa em MQL5, abra o MetaEditor, acesse o Navegador, localize a pasta "Indicadores" (Indicators), selecione "Novo" (New) e siga as instruções para criar o arquivo. Depois disso, começaremos declarando alguns parâmetros de entrada e variáveis globais, que tornarão o programa mais flexível.

//+------------------------------------------------------------------+
//|                                      a. Pin Bar Averaging 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 version     "1.00"
#property strict

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

//+------------------------------------------------------------------+
//| Trading signal enumeration                                       |
//+------------------------------------------------------------------+
enum EnableTradingBySignal {                       //--- Define trading signal enum
   ENABLED  = 1,                                   // Enable trading signals
   DISABLED = 0                                    // Disable trading signals
};

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input bool   useSignalMode = DISABLED;             // Set signal mode (ENABLED/DISABLED)
input int    orderDistancePips = 50;               // Set order distance (pips)
input double lotMultiplier = 1;                    // Set lot size multiplier
input bool   useRSIFilter = false;                 // Enable RSI filter
input int    magicNumber = 123456789;              // Set magic number
input double initialLotSize = 0.01;                // Set initial lot size
input int    compoundPercent = 2;                  // Set compounding percent (0 for fixed lots)
input int    maxOrders = 5;                        // Set maximum orders
input double stopLossPips = 400;                   // Set stop loss (pips)
input double takeProfitPips = 200;                 // Set take profit (pips)
input bool   useAutoTakeProfit = true;             // Enable auto take profit
input bool   useTrailingStop = true;               // Enable trailing stop
input double trailingStartPips = 15;               // Set trailing start (pips)
input double breakevenPips = 10;                   // Set breakeven (pips)
input string orderComment = "Forex_Algo_Trader";   // Set order comment
input color  lineColor = clrBlue;                  // Set line color
input int    lineWidth = 2;                        // Set line width

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
bool   isTradingAllowed();                         //--- Declare trading allowed check
double slBreakevenMinus = 0;                       //--- Initialize breakeven minus
double normalizedPoint;                            //--- Declare normalized point
ulong  currentTicket = 0;                          //--- Initialize current ticket
double buyCount, currentBuyLot, totalBuyLots;      //--- Declare buy metrics
double sellCount, currentSellLot, totalSellLots;   //--- Declare sell metrics
double totalSum, totalSwap;                        //--- Declare total sum and swap
double buyProfit, sellProfit, totalOperations;     //--- Declare profit and operations
double buyWeightedSum, sellWeightedSum;            //--- Declare weighted sums
double buyBreakEvenPrice, sellBreakEvenPrice;      //--- Declare breakeven prices
double minBuyLot, minSellLot;                      //--- Declare minimum lot sizes
double maxSellPrice, minBuyPrice;                  //--- Declare price extremes

Para estabelecer a base do sistema de preço médio baseado em pin bars no MQL5, automatizando as operações a partir desses padrões e permitindo um gerenciamento confiável das posições, primeiro incluímos a biblioteca "<Trade\Trade.mqh>" e criamos o objeto "obj_Trade" da classe CTrade, responsável por executar operações de negociação, como abertura e fechamento de posições. Em seguida, definimos a enumeração "EnableTradingBySignal", com os valores "ENABLED" (1) e "DISABLED" (0), para controlar o uso dos sinais de negociação no gerenciamento das posições. Depois, configuramos os parâmetros de entrada que permitirão personalizar o EA: um parâmetro booleano para ativar ou desativar o modo de sinais, a distância entre ordens em pips, o multiplicador do tamanho do lote, a opção de ativar o filtro RSI, o número mágico (magic number) para identificar as posições, o tamanho inicial do lote, o percentual de compounding (0 para lotes fixos), o número máximo de ordens, o stop-loss e o take-profit em pips, as opções de take-profit automático e trailing stop, o nível de ativação do trailing stop e o breakeven em pips, o comentário das ordens, além da cor e da espessura das linhas utilizadas nos elementos visuais.

Por fim, declaramos os elementos globais necessários ao funcionamento do sistema: a função "isTradingAllowed", para verificar as condições de negociação; a variável "slBreakevenMinus", inicializada com 0 para ajustar o stop-loss; "normalizedPoint", para adequar a escala dos valores de preço; e "currentTicket", para acompanhar as operações. Também declaramos os contadores, acumuladores e demais variáveis: "buyCount", "currentBuyLot", "totalBuyLots", "sellCount", "currentSellLot", "totalSellLots", "totalSum", "totalSwap", "buyProfit", "sellProfit", "totalOperations", "buyWeightedSum", "sellWeightedSum", "buyBreakEvenPrice", "sellBreakEvenPrice", "minBuyLot", "minSellLot", "maxSellPrice" e "minBuyPrice", além das variáveis utilizadas no cálculo das métricas das posições. Com isso, estabelecemos a estrutura básica do EA para identificar pin bars e aplicar a estratégia de preço médio. Agora podemos passar à inicialização do programa, já que a maior parte da lógica será executada a cada tick.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   normalizedPoint = _Point;                       //--- Initialize point value
   if (_Digits == 5 || _Digits == 3) {             //--- Check for 5 or 3 digit symbols
      normalizedPoint *= 10;                       //--- Adjust point value
   }
   ChartSetInteger(0, CHART_SHOW_GRID, false);     //--- Disable chart grid
   obj_Trade.SetExpertMagicNumber(magicNumber);    //--- Set magic number for trade object
   obj_Trade.SetTypeFilling(ORDER_FILLING_IOC);    //--- Set order filling type
   return(INIT_SUCCEEDED);                         //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0);                            //--- Delete all chart objects
   ChartRedraw(0);                                 //--- Redraw chart
}

Primeiro, no manipulador OnInit, inicializamos "normalizedPoint" com o valor de _Point e, para símbolos com 5 ou 3 casas decimais, multiplicamos esse valor por 10 com base em _Digits, garantindo a precisão dos cálculos de preço. Também desativamos a grade do gráfico por meio de ChartSetInteger, definindo CHART_SHOW_GRID como false para melhorar a visualização. Em seguida, configuramos "obj_Trade" com o método "SetExpertMagicNumber", utilizando "magicNumber" para identificar as operações. Definimos o tipo de preenchimento das ordens como "ORDER_FILLING_IOC" por meio de "SetTypeFilling" e retornamos "INIT_SUCCEEDED" para indicar que a inicialização foi concluída com sucesso. Depois, passamos ao manipulador OnDeinit, no qual utilizamos ObjectsDeleteAll para remover todos os objetos do gráfico, incluindo os elementos visuais, como o dashboard e as linhas que definiremos posteriormente. Isso garante a limpeza do gráfico, seguida de uma chamada a ChartRedraw para atualizar sua exibição antes do encerramento do programa. Antes de avançarmos para a lógica de negociação mais complexa, vamos definir algumas funções auxiliares que tornarão o programa mais flexível e facilitarão sua manutenção.

//+------------------------------------------------------------------+
//| Count total trades                                               |
//+------------------------------------------------------------------+
int CountTrades() {
   int positionCount = 0;                         //--- Initialize position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL || PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check trade type
         positionCount++;                         //--- Increment position count
      }
   }
   return(positionCount);                         //--- Return total count
}

//+------------------------------------------------------------------+
//| Count buy trades                                                 |
//+------------------------------------------------------------------+
int CountTradesBuy() {
   int buyPositionCount = 0;                      //--- Initialize buy position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyPositionCount++;                      //--- Increment buy count
      }
   }
   return(buyPositionCount);                      //--- Return buy count
}

//+------------------------------------------------------------------+
//| Count sell trades                                                |
//+------------------------------------------------------------------+
int CountTradesSell() {
   int sellPositionCount = 0;                     //--- Initialize sell position count
   for (int trade = PositionsTotal() - 1; trade >= 0; trade--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(trade);    //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellPositionCount++;                     //--- Increment sell count
      }
   }
   return(sellPositionCount);                     //--- Return sell count
}

//+------------------------------------------------------------------+
//| Normalize price                                                  |
//+------------------------------------------------------------------+
double NormalizePrice(double price) {
   return(NormalizeDouble(price, _Digits));       //--- Normalize price to symbol digits
}

//+------------------------------------------------------------------+
//| Get lot digit for normalization                                  |
//+------------------------------------------------------------------+
int fnGetLotDigit() {
   double lotStepValue = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); //--- Get lot step value
   if (lotStepValue == 1) return(0);              //--- Return 0 for step 1
   if (lotStepValue == 0.1) return(1);            //--- Return 1 for step 0.1
   if (lotStepValue == 0.01) return(2);           //--- Return 2 for step 0.01
   if (lotStepValue == 0.001) return(3);          //--- Return 3 for step 0.001
   if (lotStepValue == 0.0001) return(4);         //--- Return 4 for step 0.0001
   return(1);                                     //--- Default to 1
}

//+------------------------------------------------------------------+
//| Check buy orders for specific magic number                       |
//+------------------------------------------------------------------+
int CheckBuyOrders(int magic) {
   int buyOrderCount = 0;                         //--- Initialize buy order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            buyOrderCount++;                      //--- Increment buy count
            break;                                //--- Exit loop
         }
      }
   }
   return(buyOrderCount);                         //--- Return buy order count
}

//+------------------------------------------------------------------+
//| Check sell orders for specific magic number                      |
//+------------------------------------------------------------------+
int CheckSellOrders(int magic) {
   int sellOrderCount = 0;                         //--- Initialize sell order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                   //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            sellOrderCount++;                      //--- Increment sell count
            break;                                 //--- Exit loop
         }
      }
   }
   return(sellOrderCount);                         //--- Return sell order count
}

//+------------------------------------------------------------------+
//| Check total buy orders                                           |
//+------------------------------------------------------------------+
int CheckTotalBuyOrders(int magic) {
   int totalBuyOrderCount = 0;                      //--- Initialize total buy order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);          //--- Get position ticket
      if (ticket == 0) continue;                    //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            totalBuyOrderCount++;                   //--- Increment buy count
         }
      }
   }
   return(totalBuyOrderCount);                      //--- Return total buy count
}

//+------------------------------------------------------------------+
//| Check total sell orders                                          |
//+------------------------------------------------------------------+
int CheckTotalSellOrders(int magic) {
   int totalSellOrderCount = 0;                      //--- Initialize total sell order count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);           //--- Get position ticket
      if (ticket == 0) continue;                     //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magic) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            totalSellOrderCount++;                   //--- Increment sell count
         }
      }
   }
   return(totalSellOrderCount);                      //--- Return total sell count
}

//+------------------------------------------------------------------+
//| Check market buy orders                                          |
//+------------------------------------------------------------------+
int CheckMarketBuyOrders() {
   int marketBuyCount = 0;                        //--- Initialize market buy count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
            marketBuyCount++;                     //--- Increment buy count
         }
      }
   }
   return(marketBuyCount);                        //--- Return market buy count
}

//+------------------------------------------------------------------+
//| Check market sell orders                                         |
//+------------------------------------------------------------------+
int CheckMarketSellOrders() {
   int marketSellCount = 0;                       //--- Initialize market sell count
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching magic
      if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
            marketSellCount++;                    //--- Increment sell count
         }
      }
   }
   return(marketSellCount);                       //--- Return market sell count
}

//+------------------------------------------------------------------+
//| Close all buy positions                                          |
//+------------------------------------------------------------------+
void CloseBuy() {
   while (CheckMarketBuyOrders() > 0) {           //--- Check buy orders exist
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
         ulong ticket = PositionGetTicket(i);      //--- Get position ticket
         if (ticket == 0) continue;               //--- Skip invalid tickets
         if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check symbol and magic
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Close all sell positions                                         |
//+------------------------------------------------------------------+
void CloseSell() {
   while (CheckMarketSellOrders() > 0) {          //--- Check sell orders exist
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
         ulong ticket = PositionGetTicket(i);      //--- Get position ticket
         if (ticket == 0) continue;               //--- Skip invalid tickets
         if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check symbol and magic
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate lot size                                               |
//+------------------------------------------------------------------+
double GetLots() {
   double calculatedLot;                          //--- Initialize calculated lot
   double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); //--- Get minimum lot
   double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX); //--- Get maximum lot
   if (compoundPercent != 0) {                    //--- Check compounding
      calculatedLot = NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * compoundPercent / 100 / 10000, fnGetLotDigit()); //--- Calculate compounded lot
      if (calculatedLot < minLot) calculatedLot = minLot; //--- Enforce minimum lot
      if (calculatedLot > maxLot) calculatedLot = maxLot; //--- Enforce maximum lot
   } else {
      calculatedLot = initialLotSize;             //--- Use fixed lot size
   }
   return(calculatedLot);                         //--- Return calculated lot
}

//+------------------------------------------------------------------+
//| Check account free margin                                        |
//+------------------------------------------------------------------+
double AccountFreeMarginCheck(string symbol, int orderType, double volume) {
   double marginRequired = 0.0;                   //--- Initialize margin required
   double price = orderType == ORDER_TYPE_BUY ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get price
   double calculatedMargin;                       //--- Declare calculated margin
   bool success = OrderCalcMargin(orderType == ORDER_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, symbol, volume, price, calculatedMargin); //--- Calculate margin
   if (success) marginRequired = calculatedMargin; //--- Set margin if successful
   return AccountInfoDouble(ACCOUNT_MARGIN_FREE) - marginRequired; //--- Return free margin
}

//+------------------------------------------------------------------+
//| Check if trading is allowed                                      |
//+------------------------------------------------------------------+
bool isTradingAllowed() {
   bool isAllowed = false;                        //--- Initialize allowed flag
   return(true);                                  //--- Return true
}

Nesta etapa, implementaremos as funções auxiliares responsáveis pela contagem de operações, fechamento de posições, cálculo do tamanho do lote, verificação da margem e das permissões de negociação, para que as operações sejam processadas de forma consistente. Começaremos pelas funções de contagem: "CountTrades" percorre as posições com base na quantidade retornada por PositionsTotal, verifica os tickets válidos com PositionGetTicket, confere "Symbol" e "magicNumber" e incrementa "positionCount" para cada posição de compra ou venda correspondente; "CountTradesBuy" e "CountTradesSell" contam as posições de compra e venda, respectivamente, filtrando-as por "POSITION_TYPE_BUY" ou "POSITION_TYPE_SELL"; "CheckBuyOrders" e "CheckSellOrders" verificam se existe pelo menos uma posição de compra ou venda com o número mágico especificado, encerrando a busca assim que encontram a primeira correspondência; "CheckTotalBuyOrders" e "CheckTotalSellOrders" contam todas as posições de compra ou venda identificadas pelo número mágico; e "CheckMarketBuyOrders" e "CheckMarketSellOrders" contabilizam as posições de compra ou venda associadas ao número mágico informado.

Em seguida, implementaremos a função "NormalizePrice", que utiliza NormalizeDouble para normalizar os preços conforme o número de casas decimais definido por _Digits, e "fnGetLotDigit", que determina a precisão decimal necessária para o tamanho dos lotes com base em SYMBOL_VOLUME_STEP (por exemplo, 0 para 1 e 1 para 0.1). Depois, desenvolveremos as funções "CloseBuy" e "CloseSell" para fechar todas as posições de compra ou venda correspondentes. Para isso, percorreremos as posições em um laço, verificaremos "Symbol" e "magicNumber" e utilizaremos "obj_Trade.PositionClose" até que "CheckMarketBuyOrders" ou "CheckMarketSellOrders" retorne 0. Por fim, implementaremos a função "GetLots" para calcular o tamanho do lote com base em "compoundPercent", normalizando o resultado da expressão "AccountInfoDouble(ACCOUNT_BALANCE) * compoundPercent / 100 / 10000" com a precisão decimal determinada por "fnGetLotDigit" e respeitando os limites definidos por SYMBOL_VOLUME_MIN e SYMBOL_VOLUME_MAX, ou utilizando "initialLotSize". Também criaremos "AccountFreeMarginCheck", que verifica a margem disponível calculando a margem exigida por meio de OrderCalcMargin para o tipo de ordem e o volume especificados, e "isTradingAllowed", que será implementada inicialmente como uma função provisória que retorna true. Para a visualização, precisaremos ainda de funções que desenhem linhas e rótulos no gráfico.

//+------------------------------------------------------------------+
//| Draw support/resistance line                                     |
//+------------------------------------------------------------------+
void MakeLine(double price) {
   string name = "level";                         //--- Set line name
   if (ObjectFind(0, name) != -1) {               //--- Check if line exists
      ObjectMove(0, name, 0, iTime(Symbol(), PERIOD_CURRENT, 0), price); //--- Move line
      return;                                     //--- Exit function
   }
   ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line
   ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); //--- Set color
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   ObjectSetInteger(0, name, OBJPROP_WIDTH, lineWidth); //--- Set width
   ObjectSetInteger(0, name, OBJPROP_BACK, true); //--- Set to background
}

//+------------------------------------------------------------------+
//| Create dashboard label                                           |
//+------------------------------------------------------------------+
void LABEL(string labelName, string fontName, int fontSize, int xPosition, int yPosition, color textColor, int corner, string labelText) {
   if (ObjectFind(0, labelName) < 0) {            //--- Check if label exists
      ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0); //--- Create label
   }
   ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); //--- Set label text
   ObjectSetString(0, labelName, OBJPROP_FONT, fontName); //--- Set font
   ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, fontSize); //--- Set font size
   ObjectSetInteger(0, labelName, OBJPROP_COLOR, textColor); //--- Set text color
   ObjectSetInteger(0, labelName, OBJPROP_CORNER, corner); //--- Set corner
   ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, xPosition); //--- Set x position
   ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, yPosition); //--- Set y position
}

Para criar os elementos visuais do programa, desenvolveremos a função "MakeLine", que desenha uma linha horizontal no preço especificado por "price" para marcar um nível de suporte ou resistência. A linha recebe o nome definido em "level", e sua existência é verificada por meio de ObjectFind. Se já existir, utilizamos ObjectMove para reposicioná-la no horário da barra atual, obtido por iTime. Caso contrário, criamos uma nova linha do tipo OBJ_HLINE com ObjectCreate. Em seguida, utilizamos ObjectSetInteger para definir "OBJPROP_COLOR" como "lineColor", OBJPROP_STYLE como "STYLE_SOLID", "OBJPROP_WIDTH" como "lineWidth" e "OBJPROP_BACK" como true, fazendo com que a linha seja exibida no plano de fundo do gráfico.

Depois, implementaremos a função "LABEL", responsável por criar ou atualizar os rótulos do dashboard. Primeiro, verificamos se o objeto identificado por "labelName" já existe. Caso não exista, criamos um objeto do tipo OBJ_LABEL por meio de "ObjectCreate". Em seguida, utilizamos "ObjectSetString" para definir "OBJPROP_TEXT" com o conteúdo de "labelText" e "OBJPROP_FONT" com o valor de "fontName". Também utilizamos "ObjectSetInteger" para configurar "OBJPROP_FONTSIZE" com "fontSize", "OBJPROP_COLOR" com "textColor", OBJPROP_CORNER com "corner", "OBJPROP_XDISTANCE" com "xPosition" e "OBJPROP_YDISTANCE" com a posição vertical correspondente. Com os elementos visuais definidos, podemos implementar as funções auxiliares dos indicadores.

//+------------------------------------------------------------------+
//| Calculate ATR indicator                                          |
//+------------------------------------------------------------------+
double MyiATR(string symbol, ENUM_TIMEFRAMES timeframe, int period, int shift) {
   int handle = iATR(symbol, timeframe, period);  //--- Create ATR handle
   if (handle == INVALID_HANDLE) return 0;        //--- Check invalid handle
   double buffer[1];                              //--- Declare buffer
   if (CopyBuffer(handle, 0, shift, 1, buffer) != 1) buffer[0] = 0; //--- Copy ATR value
   IndicatorRelease(handle);                      //--- Release handle
   return buffer[0];                              //--- Return ATR value
}

//+------------------------------------------------------------------+
//| Check bullish engulfing pattern                                  |
//+------------------------------------------------------------------+
bool BullishEngulfingExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) <= iClose(Symbol(), PERIOD_CURRENT, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) >= iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) >= 10 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 10 * _Point) { //--- Check bullish engulfing conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bullish harami pattern                                     |
//+------------------------------------------------------------------+
bool BullishHaramiExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 2) < iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 1) < iClose(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) > MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) > 4 * (iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1))) { //--- Check bullish harami conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check doji at bottom pattern                                     |
//+------------------------------------------------------------------+
bool DojiAtBottomExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 3) - iClose(Symbol(), PERIOD_CURRENT, 3) >= 8 * _Point && MathAbs(iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2)) <= 1 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 8 * _Point) { //--- Check doji at bottom conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check doji at top pattern                                        |
//+------------------------------------------------------------------+
bool DojiAtTopExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 3) - iOpen(Symbol(), PERIOD_CURRENT, 3) >= 8 * _Point && MathAbs(iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2)) <= 1 * _Point && iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1) >= 8 * _Point) { //--- Check doji at top conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bearish harami pattern                                     |
//+------------------------------------------------------------------+
bool BearishHaramiExists() {
   if (iClose(Symbol(), PERIOD_CURRENT, 2) > iClose(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 2) < iOpen(Symbol(), PERIOD_CURRENT, 1) && iClose(Symbol(), PERIOD_CURRENT, 2) > iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 1) > iClose(Symbol(), PERIOD_CURRENT, 1) && iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2) > MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iClose(Symbol(), PERIOD_CURRENT, 2) - iOpen(Symbol(), PERIOD_CURRENT, 2) > 4 * (iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1))) { //--- Check bearish harami conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check long up candle pattern                                     |
//+------------------------------------------------------------------+
bool LongUpCandleExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 2) < iClose(Symbol(), PERIOD_CURRENT, 2) && iHigh(Symbol(), PERIOD_CURRENT, 2) - iLow(Symbol(), PERIOD_CURRENT, 2) >= 40 * _Point && iHigh(Symbol(), PERIOD_CURRENT, 2) - iLow(Symbol(), PERIOD_CURRENT, 2) > 2.5 * MyiATR(Symbol(), PERIOD_CURRENT, 14, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) < iOpen(Symbol(), PERIOD_CURRENT, 1) && iOpen(Symbol(), PERIOD_CURRENT, 1) - iClose(Symbol(), PERIOD_CURRENT, 1) > 10 * _Point) { //--- Check long up candle conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check long down candle pattern                                   |
//+------------------------------------------------------------------+
bool LongDownCandleExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) > iClose(Symbol(), PERIOD_CURRENT, 1) && iHigh(Symbol(), PERIOD_CURRENT, 1) - iLow(Symbol(), PERIOD_CURRENT, 1) >= 40 * _Point && iHigh(Symbol(), PERIOD_CURRENT, 1) - iLow(Symbol(), PERIOD_CURRENT, 1) > 2.5 * MyiATR(Symbol(), PERIOD_CURRENT, 14, 1)) { //--- Check long down candle conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check bearish engulfing pattern                                  |
//+------------------------------------------------------------------+
bool BearishEngulfingExists() {
   if (iOpen(Symbol(), PERIOD_CURRENT, 1) >= iClose(Symbol(), PERIOD_CURRENT, 2) && iClose(Symbol(), PERIOD_CURRENT, 1) <= iOpen(Symbol(), PERIOD_CURRENT, 2) && iOpen(Symbol(), PERIOD_CURRENT, 2) - iClose(Symbol(), PERIOD_CURRENT, 2) >= 10 * _Point && iClose(Symbol(), PERIOD_CURRENT, 1) - iOpen(Symbol(), PERIOD_CURRENT, 1) >= 10 * _Point) { //--- Check bearish engulfing conditions
      return(true);                               //--- Return true
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Calculate average range over 4 days                              |
//+------------------------------------------------------------------+
double AveRange4() {
   double rangeSum = 0;                           //--- Initialize range sum
   int count = 0;                                 //--- Initialize count
   int index = 1;                                 //--- Initialize index
   while (count < 4) {                            //--- Loop until 4 days
      MqlDateTime dateTime;                       //--- Declare datetime structure
      TimeToStruct(iTime(Symbol(), PERIOD_CURRENT, index), dateTime); //--- Convert time
      if (dateTime.day_of_week != 0) {            //--- Check non-Sunday
         rangeSum += iHigh(Symbol(), PERIOD_CURRENT, index) - iLow(Symbol(), PERIOD_CURRENT, index); //--- Add range
         count++;                                 //--- Increment count
      }
      index++;                                    //--- Increment index
   }
   return(rangeSum / 4.0);                        //--- Return average range
}

//+------------------------------------------------------------------+
//| Check buy pinbar                                                 |
//+------------------------------------------------------------------+
bool IsBuyPinbar() {
   double currentOpen, currentClose, currentHigh, currentLow; //--- Declare current candle variables
   double previousHigh, previousLow, previousClose, previousOpen; //--- Declare previous candle variables
   double currentRange, previousRange, currentHigherPart, currentHigherPart1; //--- Declare range variables
   currentOpen = iOpen(Symbol(), PERIOD_CURRENT, 1); //--- Get current open
   currentClose = iClose(Symbol(), PERIOD_CURRENT, 1); //--- Get current close
   currentHigh = iHigh(Symbol(), PERIOD_CURRENT, 0); //--- Get current high
   currentLow = iLow(Symbol(), PERIOD_CURRENT, 1); //--- Get current low
   previousOpen = iOpen(Symbol(), PERIOD_CURRENT, 2); //--- Get previous open
   previousClose = iClose(Symbol(), PERIOD_CURRENT, 2); //--- Get previous close
   previousHigh = iHigh(Symbol(), PERIOD_CURRENT, 2); //--- Get previous high
   previousLow = iLow(Symbol(), PERIOD_CURRENT, 2); //--- Get previous low
   currentRange = currentHigh - currentLow;       //--- Calculate current range
   previousRange = previousHigh - previousLow;    //--- Calculate previous range
   currentHigherPart = currentHigh - currentRange * 0.4; //--- Calculate higher part
   currentHigherPart1 = currentHigh - currentRange * 0.4; //--- Calculate higher part
   double averageDailyRange = AveRange4();        //--- Get average daily range
   if ((currentClose > currentHigherPart1 && currentOpen > currentHigherPart) && //--- Check close/open in higher third
       (currentRange > averageDailyRange * 0.5) && //--- Check pinbar size
       (currentLow + currentRange * 0.25 < previousLow)) { //--- Check nose length
      double lowArray[3];                         //--- Declare low array
      CopyLow(Symbol(), PERIOD_CURRENT, 3, 3, lowArray); //--- Copy low prices
      int minIndex = ArrayMinimum(lowArray);      //--- Find minimum low index
      if (lowArray[minIndex] > currentLow) return(true); //--- Confirm buy pinbar
   }
   return(false);                                 //--- Return false
}

//+------------------------------------------------------------------+
//| Check sell pinbar                                                |
//+------------------------------------------------------------------+
bool IsSellPinbar() {
   double currentOpen, currentClose, currentHigh, currentLow; //--- Declare current candle variables
   double previousHigh, previousLow, previousClose, previousOpen; //--- Declare previous candle variables
   double currentRange, previousRange, currentLowerPart, currentLowerPart1; //--- Declare range variables
   currentOpen = iOpen(Symbol(), PERIOD_CURRENT, 1); //--- Get current open
   currentClose = iClose(Symbol(), PERIOD_CURRENT, 1); //--- Get current close
   currentHigh = iHigh(Symbol(), PERIOD_CURRENT, 1); //--- Get current high
   currentLow = iLow(Symbol(), PERIOD_CURRENT, 1); //--- Get current low
   previousOpen = iOpen(Symbol(), PERIOD_CURRENT, 2); //--- Get previous open
   previousClose = iClose(Symbol(), PERIOD_CURRENT, 2); //--- Get previous close
   previousHigh = iHigh(Symbol(), PERIOD_CURRENT, 2); //--- Get previous high
   previousLow = iLow(Symbol(), PERIOD_CURRENT, 2); //--- Get previous low
   currentRange = currentHigh - currentLow;       //--- Calculate current range
   previousRange = previousHigh - previousLow;    //--- Calculate previous range
   currentLowerPart = currentLow + currentRange * 0.4; //--- Calculate lower part
   currentLowerPart1 = currentLow + currentRange * 0.4; //--- Calculate lower part
   double averageDailyRange = AveRange4();        //--- Get average daily range
   if ((currentClose < currentLowerPart1 && currentOpen < currentLowerPart) && //--- Check close/open in lower third
       (currentRange > averageDailyRange * 0.5) && //--- Check pinbar size
       (currentHigh - currentRange * 0.25 > previousHigh)) { //--- Check nose length
      double highArray[3];                        //--- Declare high array
      CopyHigh(Symbol(), PERIOD_CURRENT, 3, 3, highArray); //--- Copy high prices
      int maxIndex = ArrayMaximum(highArray);     //--- Find maximum high index
      if (highArray[maxIndex] < currentHigh) return(true); //--- Confirm sell pinbar
   }
   return(false);                                 //--- Return false
}

Nesta etapa, implementaremos as funções responsáveis por identificar padrões de velas e calcular a amplitude média verdadeira (ATR) utilizada pelo sistema. Começaremos pela função "MyiATR", que calcula o ATR criando um handle por meio de iATR para o ativo, o timeframe e o período especificados. Se o handle for inválido, a função retorna 0. Caso contrário, utiliza CopyBuffer para copiar o valor do ATR para um buffer, libera o handle com IndicatorRelease e retorna o valor calculado.

Em seguida, implementaremos as funções de identificação dos padrões de velas. A função "BullishEngulfingExists" verifica se a vela atual engolfa a vela de baixa anterior, considerando corpos de tamanho significativo; "BullishHaramiExists" identifica uma pequena vela de alta contida em uma vela de baixa maior, utilizando "MyiATR" para comparar os tamanhos dos corpos; "DojiAtBottomExists" detecta um doji entre uma vela de baixa e uma de alta, para o padrão estrela da manhã; "DojiAtTopExists" identifica um doji entre uma vela de alta e uma de baixa, para o padrão estrela da tarde; "BearishHaramiExists" verifica a presença de uma pequena vela de baixa contida em uma vela de alta maior; "LongUpCandleExists" utiliza o ATR para confirmar uma vela de alta forte seguida por uma vela de baixa; "LongDownCandleExists" identifica uma vela de baixa forte; e "BearishEngulfingExists" confirma a presença de uma vela de baixa que engolfa uma vela de alta.

Por fim, implementaremos as funções "IsBuyPinbar" e "IsSellPinbar" para identificar pin bars. Essas funções verificam se os preços de abertura e fechamento da vela atual estão no terço superior ou inferior de sua amplitude, conforme a direção do sinal. Também verificam se a amplitude da vela supera metade da amplitude média diária calculada por "AveRange4", que utiliza a média das diferenças entre as máximas e mínimas de quatro dias, desconsiderando os domingos. Além disso, verificam se a ponta do pin bar ultrapassa a mínima ou a máxima da vela anterior. Essa condição é confirmada pela comparação das mínimas ou máximas recentes, obtidas por CopyLow ou CopyHigh, utilizando as funções "ArrayMinimum" ou ArrayMaximum. Com isso, podemos passar à implementação de outras funções auxiliares para determinar o tipo de sinal a ser exibido e calcular o preço médio ponderado utilizado no gerenciamento das posições.

//+------------------------------------------------------------------+
//| Analyze candlestick patterns                                     |
//+------------------------------------------------------------------+
string CandleStick_Analyzer() {
   string candlePattern, comment1 = "", comment2 = "", comment3 = ""; //--- Initialize pattern strings
   string comment4 = "", comment5 = "", comment6 = "", comment7 = ""; //--- Initialize pattern strings
   string comment8 = "", comment9 = "";                               //--- Initialize pattern strings
   if (BullishEngulfingExists()) comment1 = " Bullish Engulfing ";    //--- Check bullish engulfing
   if (BullishHaramiExists()) comment2 = " Bullish Harami ";          //--- Check bullish harami
   if (LongUpCandleExists()) comment3 = " Bullish LongUp ";           //--- Check long up candle
   if (DojiAtBottomExists()) comment4 = " MorningStar Doji ";         //--- Check morning star doji
   if (DojiAtTopExists()) comment5 = " EveningStar Doji ";            //--- Check evening star doji
   if (BearishHaramiExists()) comment6 = " Bearish Harami ";          //--- Check bearish harami
   if (BearishEngulfingExists()) comment7 = " Bearish Engulfing ";    //--- Check bearish engulfing
   if (LongDownCandleExists()) comment8 = " Bearish LongDown ";       //--- Check long down candle
   candlePattern = comment1 + comment2 + comment3 + comment4 + comment5 + comment6 + comment7 + comment8 + comment9; //--- Combine patterns
   return(candlePattern);                                             //--- Return combined pattern
}

//+------------------------------------------------------------------+
//| Calculate average price for order type                           |
//+------------------------------------------------------------------+
double rata_price(int orderType) {
   double totalVolume = 0;                        //--- Initialize total volume
   double weightedOpenSum = 0;                    //--- Initialize weighted open sum
   double averagePrice = 0;                       //--- Initialize average price
   for (int positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber && (PositionGetInteger(POSITION_TYPE) == orderType)) { //--- Check position match
         totalVolume += PositionGetDouble(POSITION_VOLUME); //--- Add volume
         weightedOpenSum += (PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN)); //--- Add weighted open
      }
   }
   if (totalVolume != 0) {                        //--- Check non-zero volume
      averagePrice = weightedOpenSum / totalVolume; //--- Calculate average price
   }
   return(averagePrice);                          //--- Return average price
}

Para aprimorar o gerenciamento das posições, criaremos a função "CandleStick_Analyzer", que inicializa as variáveis do tipo string, de "comment1" a "comment9", com valores vazios e verifica a presença de padrões de velas utilizando as funções que já definimos, como "BullishEngulfingExists". Quando um padrão é identificado, a função atribui uma descrição correspondente, como "Bullish Engulfing", à respectiva variável. Em seguida, reúne essas descrições em "candlePattern" e retorna uma única string contendo os padrões detectados para exibição no dashboard.

Depois, implementaremos a função "rata_price", responsável por calcular o preço médio ponderado das posições de compra ou venda, conforme o parâmetro "orderType". Primeiro, inicializamos "totalVolume" e "weightedOpenSum" com zero. Em seguida, percorremos as posições com base na quantidade retornada por PositionsTotal e utilizamos PositionGetTicket, PositionGetString e PositionGetInteger para identificar aquelas que correspondem a "Symbol", "magicNumber" e "orderType". Para cada posição válida, acumulamos seu volume (POSITION_VOLUME) em "totalVolume" e o produto de POSITION_VOLUME por POSITION_PRICE_OPEN em "weightedOpenSum". Se "totalVolume" for diferente de zero, calculamos "averagePrice" dividindo "weightedOpenSum" por "totalVolume" e retornamos o resultado. Essas funções permitem analisar os padrões de velas para os sinais de negociação e calcular com precisão o preço médio ponderado das posições, necessário para a estratégia de preço médio e o ajuste do take-profit. Antes de avançarmos, porém, precisamos calcular as métricas das posições abertas. Para isso, definimos a lógica.

//+------------------------------------------------------------------+
//| Calculate position metrics                                       |
//+------------------------------------------------------------------+
void calculatePositionMetrics() {
   buyCount = 0;                                  //--- Reset buy count
   currentBuyLot = 0;                             //--- Reset current buy lot
   totalBuyLots = 0;                              //--- Reset total buy lots
   sellCount = 0;                                 //--- Reset sell count
   currentSellLot = 0;                            //--- Reset current sell lot
   totalSellLots = 0;                             //--- Reset total sell lots
   totalSum = 0;                                  //--- Reset total sum
   totalSwap = 0;                                 //--- Reset total swap
   buyProfit = 0;                                 //--- Reset buy profit
   sellProfit = 0;                                //--- Reset sell profit
   buyWeightedSum = 0;                            //--- Reset buy weighted sum
   sellWeightedSum = 0;                           //--- Reset sell weighted sum
   buyBreakEvenPrice = 0;                         //--- Reset buy breakeven price
   sellBreakEvenPrice = 0;                        //--- Reset sell breakeven price
   minBuyLot = 9999;                              //--- Initialize min buy lot
   minSellLot = 9999;                             //--- Initialize min sell lot
   maxSellPrice = 0;                              //--- Initialize max sell price
   minBuyPrice = 999999999;                       //--- Initialize min buy price
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyCount++;                              //--- Increment buy count
         totalOperations++;                       //--- Increment total operations
         currentBuyLot = PositionGetDouble(POSITION_VOLUME); //--- Set current buy lot
         buyProfit += PositionGetDouble(POSITION_PROFIT); //--- Add buy profit
         totalBuyLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total buy lots
         minBuyLot = MathMin(minBuyLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min buy lot
         buyWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         minBuyPrice = MathMin(minBuyPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update min buy price
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellCount++;                             //--- Increment sell count
         totalOperations++;                       //--- Increment total operations
         currentSellLot = PositionGetDouble(POSITION_VOLUME); //--- Set current sell lot
         sellProfit += PositionGetDouble(POSITION_PROFIT); //--- Add sell profit
         totalSellLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total sell lots
         minSellLot = MathMin(minSellLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min sell lot
         sellWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         maxSellPrice = MathMax(maxSellPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update max sell price
      }
   }
   if (totalBuyLots > 0) {                        //--- Check buy lots
      buyBreakEvenPrice = buyWeightedSum / totalBuyLots; //--- Calculate buy breakeven
   }
   if (totalSellLots > 0) {                       //--- Check sell lots
      sellBreakEvenPrice = sellWeightedSum / totalSellLots; //--- Calculate sell breakeven
   }
}

Para isso, implementaremos a função "calculatePositionMetrics", responsável por calcular os principais indicadores utilizados no gerenciamento de múltiplas posições. Primeiro, zeramos as variáveis de contagem e acumulação e definimos os valores iniciais apropriados para as demais variáveis. Em seguida, percorremos todas as posições com base na quantidade retornada por PositionsTotal e obtemos o ticket de cada uma por meio de PositionGetTicket. Ignoramos os tickets inválidos e as posições de outros ativos, identificadas com PositionGetString. Para cada posição de compra (POSITION_TYPE_BUY), incrementamos "buyCount" e "totalOperations", atualizamos "currentBuyLot", acumulamos POSITION_PROFIT em "buyProfit" e POSITION_VOLUME em "totalBuyLots", atualizamos "minBuyLot" com MathMin, adicionamos o valor ponderado do preço de abertura a "buyWeightedSum" e atualizamos "minBuyPrice". Para as posições de venda (POSITION_TYPE_SELL), realizamos as atualizações equivalentes nas respectivas variáveis. Por fim, se "totalBuyLots" for maior que zero, calculamos "buyBreakEvenPrice" dividindo "buyWeightedSum" por "totalBuyLots". Da mesma forma, se "totalSellLots" for maior que zero, calculamos "sellBreakEvenPrice" dividindo "sellWeightedSum" por "totalSellLots". Com essas funções implementadas, podemos avançar para a lógica de abertura de posições, que será desenvolvida no manipulador OnTick.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime previousBarTime = 0;           //--- Store previous bar time
   if (previousBarTime != iTime(Symbol(), PERIOD_CURRENT, 0)) { //--- Check new bar
      previousBarTime = iTime(Symbol(), PERIOD_CURRENT, 0); //--- Update previous bar time
      ChartRedraw(0);                             //--- Redraw chart
   } else {
      return;                                     //--- Exit if not new bar
   }
   if (iVolume(Symbol(), PERIOD_H4, 0) > iVolume(Symbol(), PERIOD_H4, 1)) return; //--- Exit if volume increased
   double supportResistanceLevel = NormalizeDouble(iClose(Symbol(), PERIOD_H4, 1), _Digits); //--- Get support/resistance level
   ObjectDelete(0, "level");                      //--- Delete existing level line
   MakeLine(supportResistanceLevel);              //--- Draw support/resistance line
   if (SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) > 150) return; //--- Exit if spread too high
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != magicNumber) continue; //--- Skip non-matching positions
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         totalSellPositions++;                    //--- Increment sell count
      }
   }
}

No manipulador OnTick, implementaremos a lógica inicial do sistema de preço médio baseado em pin bars, responsável por controlar as decisões de negociação e atualizar os elementos visuais a cada nova barra. Primeiro, verificamos se uma nova barra foi formada, comparando "previousBarTime", uma variável estática inicializada com zero, com o horário da barra atual, obtido por iTime para o ativo e o timeframe em uso, com deslocamento zero. Se uma nova barra for detectada, atualizamos "previousBarTime" e chamamos ChartRedraw para atualizar o gráfico. Caso contrário, encerramos o processamento.

Em seguida, interrompemos o processamento se o volume de ticks da barra H4 atual, obtido por iVolume, for maior que o da barra anterior. Essa condição é utilizada pelo autor como filtro para evitar operações em condições desfavoráveis. Depois, utilizamos iClose e NormalizeDouble para determinar o nível de suporte ou resistência a partir do preço de fechamento normalizado da barra H4 anterior. Removemos a linha "level", caso já exista, utilizando ObjectDelete, e desenhamos uma nova linha horizontal nesse nível por meio de "MakeLine". Também verificamos se o spread, obtido com SymbolInfoInteger, ultrapassa 150 pontos. Nesse caso, interrompemos o processamento. Se o spread estiver dentro do limite, percorremos as posições abertas com base na quantidade retornada por PositionsTotal, obtemos seus tickets com "PositionGetTicket" e ignoramos os tickets inválidos e as posições que não correspondem ao ativo e ao "magicNumber" especificados. Para cada posição válida, utilizamos PositionGetInteger para identificar se é de compra ou venda e incrementamos "totalBuyPositions" ou "totalSellPositions", respectivamente. Com essa lógica inicial, o EA processa as operações apenas quando uma nova barra é formada e os filtros de mercado são atendidos, mantendo também a referência visual do nível de suporte ou resistência atualizada. Após a compilação, obtemos o seguinte resultado.

SUPPORT RESISTANCE LEVEL MARKER

Na imagem, podemos observar que as linhas de suporte e resistência são atualizadas dinamicamente no gráfico. Agora, precisamos implementar a lógica para adicionar posições de forma dinâmica.

if (CheckMarketBuyOrders() < 70 && CheckMarketSellOrders() < 70) { //--- Check order limits
   if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == DISABLED) { //--- Check buy condition
      if (IsBuyPinbar() && totalBuyPositions < maxOrders && (isTradingAllowed() || totalBuyPositions > 0)) { //--- Check buy pinbar and limits
         double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, _Digits); //--- Calculate buy stop loss
         double buyTakeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + takeProfitPips * normalizedPoint, _Digits); //--- Calculate buy take profit
         if (AccountFreeMarginCheck(Symbol(), ORDER_TYPE_BUY, GetLots()) > 0) { //--- Check margin
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_ASK), buyStopLoss, buyTakeProfit, orderComment); //--- Open buy position
            if (useAutoTakeProfit) {             //--- Check auto take profit
               ModifyTP(ORDER_TYPE_BUY, rata_price(ORDER_TYPE_BUY) + takeProfitPips * normalizedPoint); //--- Modify take profit
            }
            CloseSell();                         //--- Close sell positions
         }
      }
   }
   if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == DISABLED) { //--- Check sell condition
      if (IsSellPinbar() && totalSellPositions < maxOrders && (isTradingAllowed() || totalSellPositions > 0)) { //--- Check sell pinbar and limits
         double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, _Digits); //--- Calculate sell stop loss
         double sellTakeProfit = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - takeProfitPips * normalizedPoint, _Digits); //--- Calculate sell take profit
         if (AccountFreeMarginCheck(Symbol(), ORDER_TYPE_SELL, GetLots()) > 0) { //--- Check margin
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_BID), sellStopLoss, sellTakeProfit, orderComment); //--- Open sell position
            if (useAutoTakeProfit) {             //--- Check auto take profit
               ModifyTP(ORDER_TYPE_SELL, rata_price(ORDER_TYPE_SELL) - takeProfitPips * normalizedPoint); //--- Modify take profit
            }
            CloseBuy();                          //--- Close buy positions
         }
      }
   }
}
if (CountTrades() == 0) {                       //--- Check no trades
   if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == ENABLED) { //--- Check buy signal mode
      if (IsBuyPinbar() && CountTrades() < maxOrders) { //--- Check buy pinbar and limit
         obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_ASK), SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (takeProfitPips * normalizedPoint), orderComment); //--- Open buy position
      }
   }
}
if (CountTrades() == 0) {                       //--- Check no trades
   if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0) && useSignalMode == ENABLED) { //--- Check sell signal mode
      if (IsSellPinbar() && CountTrades() < maxOrders) { //--- Check sell pinbar and limit
         obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, GetLots(), SymbolInfoDouble(_Symbol, SYMBOL_BID), SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_BID) - (takeProfitPips * normalizedPoint), orderComment); //--- Open sell position
      }
   }
}

Continuaremos a implementação do manipulador OnTick, adicionando a lógica de abertura de novas posições com base nos sinais de pin bars e nas condições do mercado. Primeiro, utilizamos "CheckMarketBuyOrders" e "CheckMarketSellOrders" para verificar se a quantidade de posições de compra e de venda é inferior a 70, evitando que o EA ultrapasse os limites operacionais considerados aceitáveis. Em seguida, se "useSignalMode" estiver definido como "DISABLED", avaliamos as condições de compra. Para isso, verificamos se "supportResistanceLevel" está acima do preço de abertura obtido por iOpen, se a função "IsBuyPinbar" identifica um pin bar de compra e se "totalBuyPositions" é inferior a "maxOrders". Também consideramos se a negociação está autorizada pela função "isTradingAllowed" ou se já existem ordens de compra abertas. Quando as condições são atendidas, calculamos "buyStopLoss" e "buyTakeProfit" com base nos preços obtidos por SymbolInfoDouble e nas distâncias definidas em "stopLossPips" e "takeProfitPips", ajustadas por "normalizedPoint". Por fim, verificamos a disponibilidade de margem com "AccountFreeMarginCheck", considerando a margem necessária para a operação, e abrimos uma posição de compra por meio de "obj_Trade".

Abrimos a posição com "PositionOpen", utilizando "GetLots" para determinar o tamanho do lote. Se "useAutoTakeProfit" estiver definido como true, ajustamos o take-profit por meio de "ModifyTP". Também fechamos as posições de venda com "CloseSell". Aplicamos uma lógica semelhante às operações de venda, verificando se "supportResistanceLevel" está abaixo do preço de abertura e utilizando "IsSellPinbar" para identificar o sinal. Em seguida, se não houver posições abertas ("CountTrades" igual a 0) e "useSignalMode" estiver definido como "ENABLED", abrimos uma posição de compra quando "IsBuyPinbar" identifica um pin bar de compra e "CountTrades" é inferior a "maxOrders". Para isso, utilizamos "obj_Trade.PositionOpen" com os níveis de stop-loss e take-profit calculados. A mesma lógica é aplicada às posições de venda, utilizando "IsSellPinbar". Dessa forma, o EA abre posições a partir dos sinais de pin bars em níveis importantes do mercado, com os mecanismos de gerenciamento de risco previstos na estratégia. Após a compilação, obtemos o seguinte resultado.

CONFIRMED SIGNAL

Agora que os sinais são confirmados e as posições são abertas, precisamos gerenciar essas operações. Para isso, definiremos algumas funções auxiliares.

//+------------------------------------------------------------------+
//| Update stop loss and take profit                                 |
//+------------------------------------------------------------------+
void updateStopLossTakeProfit() {
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);           //--- Get position ticket
      if (ticket == 0) continue;                     //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         double buyTakeProfitLevel = (buyBreakEvenPrice + takeProfitPips * _Point) * (takeProfitPips > 0); //--- Calculate buy take profit
         double buyStopLossLevel = PositionGetDouble(POSITION_SL); //--- Get current stop loss
         if (slBreakevenMinus > 0) {                 //--- Check breakeven adjustment
            buyStopLossLevel = (buyBreakEvenPrice - slBreakevenMinus * _Point); //--- Set breakeven stop loss
         }
         if (buyCount == 1) {                        //--- Check single buy position
            buyTakeProfitLevel = NormalizePrice(PositionGetDouble(POSITION_PRICE_OPEN) + takeProfitPips * _Point) * (takeProfitPips > 0); //--- Set take profit
            if (laterUseSL > 0) {                    //--- Check unused stop loss
               buyStopLossLevel = (PositionGetDouble(POSITION_PRICE_OPEN) - laterUseSL * _Point); //--- Set stop loss
            }
         }
         buyTakeProfitLevel = NormalizePrice(buyTakeProfitLevel); //--- Normalize take profit
         buyStopLossLevel = NormalizePrice(buyStopLossLevel); //--- Normalize stop loss
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) >= buyTakeProfitLevel && buyTakeProfitLevel > 0) { //--- Check take profit hit
            obj_Trade.PositionClose(ticket);         //--- Close position
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) <= buyStopLossLevel) { //--- Check stop loss hit
            obj_Trade.PositionClose(ticket);         //--- Close position
         }
         if (NormalizePrice(PositionGetDouble(POSITION_TP)) != buyTakeProfitLevel || NormalizePrice(PositionGetDouble(POSITION_SL)) != buyStopLossLevel) { //--- Check modification needed
            obj_Trade.PositionModify(ticket, buyStopLossLevel, buyTakeProfitLevel); //--- Modify position
         }
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         double sellTakeProfitLevel = (sellBreakEvenPrice - takeProfitPips * _Point) * (takeProfitPips > 0); //--- Calculate sell take profit
         double sellStopLossLevel = PositionGetDouble(POSITION_SL); //--- Get current stop loss
         if (slBreakevenMinus > 0) {                //--- Check breakeven adjustment
            sellStopLossLevel = (sellBreakEvenPrice + slBreakevenMinus * _Point); //--- Set breakeven stop loss
         }
         if (sellCount == 1) {                      //--- Check single sell position
            sellTakeProfitLevel = (PositionGetDouble(POSITION_PRICE_OPEN) - takeProfitPips * _Point) * (takeProfitPips > 0); //--- Set take profit
            if (laterUseSL > 0) {                   //--- Check unused stop loss
               sellStopLossLevel = (PositionGetDouble(POSITION_PRICE_OPEN) + laterUseSL * _Point); //--- Set stop loss
            }
         }
         sellTakeProfitLevel = NormalizePrice(sellTakeProfitLevel); //--- Normalize take profit
         sellStopLossLevel = NormalizePrice(sellStopLossLevel); //--- Normalize stop loss
         if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) <= sellTakeProfitLevel) { //--- Check take profit hit
            obj_Trade.PositionClose(ticket);        //--- Close position
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= sellStopLossLevel && sellStopLossLevel > 0) { //--- Check stop loss hit
            obj_Trade.PositionClose(ticket);        //--- Close position
         }
         if (NormalizePrice(PositionGetDouble(POSITION_TP)) != sellTakeProfitLevel || NormalizePrice(PositionGetDouble(POSITION_SL)) != sellStopLossLevel) { //--- Check modification needed
            obj_Trade.PositionModify(ticket, sellStopLossLevel, sellTakeProfitLevel); //--- Modify position
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Add averaging order                                              |
//+------------------------------------------------------------------+
void addAveragingOrder() {
   int positionIndex = 0;                         //--- Initialize position index
   double lastOpenPrice = 0;                      //--- Initialize last open price
   double lastLotSize = 0;                        //--- Initialize last lot size
   bool isLastBuy = false;                        //--- Initialize buy flag
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   double supportResistanceLevel = iClose(Symbol(), PERIOD_H4, 1); //--- Get support/resistance level
   for (positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice > PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check lower price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = true;                        //--- Set buy flag
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice < PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check higher price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = false;                       //--- Clear buy flag
         totalSellPositions++;                    //--- Increment sell count
      }
   }
   if (isLastBuy) {                               //--- Check buy position
      if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check buy condition
         if (IsBuyPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_BID) <= lastOpenPrice - (orderDistancePips * _Point)) { //--- Check buy pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_ASK), SymbolInfoDouble(_Symbol, SYMBOL_ASK) - stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (takeProfitPips * normalizedPoint), orderComment); //--- Open buy position
            isLastBuy = false;                    //--- Clear buy flag
            return;                               //--- Exit function
         }
      }
   } else if (!isLastBuy) {                       //--- Check sell position
      if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check sell condition
         if (IsSellPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= lastOpenPrice + (orderDistancePips * _Point)) { //--- Check sell pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_BID), SymbolInfoDouble(_Symbol, SYMBOL_BID) + stopLossPips * normalizedPoint, SymbolInfoDouble(_Symbol, SYMBOL_BID) - (takeProfitPips * normalizedPoint), orderComment); //--- Open sell position
            return;                               //--- Exit function
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Add averaging order with auto take profit                        |
//+------------------------------------------------------------------+
void addAveragingOrderWithAutoTP() {
   int positionIndex = 0;                         //--- Initialize position index
   double lastOpenPrice = 0;                      //--- Initialize last open price
   double lastLotSize = 0;                        //--- Initialize last lot size
   bool isLastBuy = false;                        //--- Initialize buy flag
   int totalBuyPositions = 0;                     //--- Initialize buy positions count
   int totalSellPositions = 0;                    //--- Initialize sell positions count
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   double supportResistanceLevel = iClose(Symbol(), PERIOD_H4, 1); //--- Get support/resistance level
   for (positionIndex = 0; positionIndex < PositionsTotal(); positionIndex++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice > PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check lower price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = true;                        //--- Set buy flag
         totalBuyPositions++;                     //--- Increment buy count
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (lastOpenPrice == 0) {                //--- Check initial price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set initial price
         }
         if (lastOpenPrice < PositionGetDouble(POSITION_PRICE_OPEN)) { //--- Check higher price
            lastOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Update last price
         }
         if (lastLotSize < PositionGetDouble(POSITION_VOLUME)) { //--- Check larger lot
            lastLotSize = PositionGetDouble(POSITION_VOLUME); //--- Update lot size
         }
         isLastBuy = false;                       //--- Clear buy flag
         totalSellPositions++;                    //--- Increment sell count
      }
   }
   if (isLastBuy) {                               //--- Check buy position
      if (supportResistanceLevel > iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check buy condition
         if (IsBuyPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_BID) <= lastOpenPrice - (orderDistancePips * _Point)) { //--- Check buy pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_ASK), 0, 0, orderComment); //--- Open buy position
            calculatePositionMetrics();           //--- Calculate position metrics
            updateStopLossTakeProfit();           //--- Update stop loss and take profit
            isLastBuy = false;                    //--- Clear buy flag
            return;                               //--- Exit function
         }
      }
   } else if (!isLastBuy) {                       //--- Check sell position
      if (supportResistanceLevel < iOpen(Symbol(), PERIOD_CURRENT, 0)) { //--- Check sell condition
         if (IsSellPinbar() && SymbolInfoDouble(_Symbol, SYMBOL_ASK) >= lastOpenPrice + (orderDistancePips * _Point)) { //--- Check sell pinbar and distance
            obj_Trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, NormalizeDouble((lastLotSize * lotMultiplier), fnGetLotDigit()), SymbolInfoDouble(_Symbol, SYMBOL_BID), 0, 0, orderComment); //--- Open sell position
            calculatePositionMetrics();           //--- Calculate position metrics
            updateStopLossTakeProfit();           //--- Update stop loss and take profit
            return;                               //--- Exit function
         }
      }
   }
}

Implementaremos as funções "updateStopLossTakeProfit", "addAveragingOrder" e "addAveragingOrderWithAutoTP" para gerenciar os níveis de stop-loss e take-profit e adicionar posições pela estratégia de preço médio, permitindo ajustar esses níveis conforme as condições das posições abertas. Começaremos pela função "updateStopLossTakeProfit", que percorre todas as posições. Para as posições de compra (POSITION_TYPE_BUY), calculamos "buyTakeProfitLevel" somando a "buyBreakEvenPrice" a distância definida por "takeProfitPips", multiplicada por _Point, desde que "takeProfitPips" seja positivo. Em seguida, obtemos o stop-loss atual por meio de PositionGetDouble e, se "slBreakevenMinus" for positivo, ajustamos seu nível para "buyBreakEvenPrice" menos a distância correspondente a "slBreakevenMinus" multiplicada por _Point. Como alternativa, quando existe apenas uma posição de compra ("buyCount == 1"), definimos os níveis de take-profit e stop-loss a partir de POSITION_PRICE_OPEN, considerando as respectivas distâncias configuradas. Normalizamos ambos os níveis com "NormalizePrice" e, se o preço Bid atingir o take-profit ou o stop-loss, fechamos a posição por meio de "obj_Trade.PositionClose". Caso os níveis calculados sejam diferentes dos valores atuais, utilizamos "obj_Trade.PositionModify" para atualizá-los. Aplicamos a mesma lógica às posições de venda, utilizando "sellBreakEvenPrice" e o preço Ask.

Em seguida, implementaremos a função "addAveragingOrder", responsável por identificar a posição de referência para novas entradas pela estratégia de preço médio. Para isso, percorremos as posições com base na quantidade retornada por PositionsTotal, atualizando "lastOpenPrice" com o menor preço de abertura entre as posições de compra ou o maior preço de abertura entre as posições de venda. Também atualizamos "lastLotSize" com o maior volume encontrado e definimos "isLastBuy" conforme o tipo de posição identificado. Para adicionar uma posição de compra, verificamos se "supportResistanceLevel" está acima do preço de abertura atual, se "IsBuyPinbar" identifica um pin bar de compra e se o preço Bid está abaixo de "lastOpenPrice" por uma distância equivalente a "orderDistancePips" multiplicado por _Point. Quando essas condições são atendidas, abrimos uma posição de compra por meio de "obj_Trade.PositionOpen", calculando o novo tamanho do lote pela multiplicação de "lastLotSize" por "lotMultiplier" e normalizando o resultado com a precisão decimal determinada por "fnGetLotDigit". A operação utiliza os níveis calculados de stop-loss e take-profit. Após a abertura, redefinimos "isLastBuy". Para as posições de venda, verificamos se o preço Ask está acima de "lastOpenPrice" por uma distância equivalente a "orderDistancePips * _Point" e, quando as condições correspondentes são atendidas, abrimos uma nova posição de venda seguindo a mesma lógica.

Por fim, implementaremos a função "addAveragingOrderWithAutoTP", que segue a mesma lógica de "addAveragingOrder", mas abre posições sem stop-loss ou take-profit iniciais, ambos definidos como 0. Em seguida, chama "calculatePositionMetrics" para atualizar métricas como "buyBreakEvenPrice" e utiliza "updateStopLossTakeProfit" para definir os níveis com base no ponto de equilíbrio (breakeven), permitindo ajustar dinamicamente as posições adicionadas pela estratégia de preço médio. Agora podemos incorporar essas funções ao processamento dos ticks para que sejam executadas conforme as condições estabelecidas.

if (useSignalMode == ENABLED && CountTradesBuy() >= 1 && CountTradesBuy() < maxOrders && useAutoTakeProfit == false) { //--- Check buy averaging
   addAveragingOrder();                        //--- Add buy averaging order
}
if (useSignalMode == ENABLED && CountTradesSell() >= 1 && CountTradesSell() < maxOrders && useAutoTakeProfit == false) { //--- Check sell averaging
   addAveragingOrder();                        //--- Add sell averaging order
}
if (useSignalMode == ENABLED && CountTradesBuy() >= 1 && CountTradesBuy() < maxOrders && useAutoTakeProfit == true) { //--- Check buy averaging with auto TP
   addAveragingOrderWithAutoTP();              //--- Add buy averaging order with auto TP
}
if (useSignalMode == ENABLED && CountTradesSell() >= 1 && CountTradesSell() < maxOrders && useAutoTakeProfit == true) { //--- Check sell averaging with auto TP
   addAveragingOrderWithAutoTP();              //--- Add sell averaging order with auto TP
}

Para concluir a lógica do manipulador OnTick, adicionaremos as condições de abertura de posições pela estratégia de preço médio, permitindo que o EA aumente dinamicamente sua exposição. Primeiro, se "useSignalMode" estiver definido como "ENABLED", verificamos com "CountTradesBuy" se existe pelo menos uma posição de compra e se a quantidade de posições de compra é inferior a "maxOrders". Se "useAutoTakeProfit" for false, chamamos "addAveragingOrder" para abrir uma posição de compra adicional quando os critérios de identificação de pin bars e distância entre preços forem atendidos, utilizando o tamanho do lote ajustado pelo multiplicador.

Aplicamos a mesma lógica às posições de venda, verificando a quantidade retornada por "CountTradesSell" e chamando "addAveragingOrder" quando "useAutoTakeProfit" for false, para adicionar uma posição de venda nas condições correspondentes. Já para as posições de compra, quando "useAutoTakeProfit" for true, chamamos "addAveragingOrderWithAutoTP" para abrir uma nova posição sem stop-loss ou take-profit iniciais. Em seguida, atualizamos as métricas das posições e ajustamos os níveis com base no ponto de equilíbrio. Por fim, repetimos esse procedimento para as posições de venda quando "useAutoTakeProfit" for true, utilizando "addAveragingOrderWithAutoTP" para adicionar uma posição e ajustar dinamicamente os níveis de stop-loss e take-profit. Dessa forma, o EA gerencia a adição de posições pela estratégia de preço médio no modo de sinais, adaptando as entradas aos movimentos do mercado. Após a compilação, obtemos o seguinte resultado.

AVERAGING SAMPLE

Agora que implementamos a opção de preço médio, falta adicionar a lógica de trailing stop para gerenciar o risco. Como esse mecanismo precisa acompanhar os movimentos do preço a cada tick, sua execução ficará fora da lógica que restringe o processamento à formação de novas barras.

double setPointValue = normalizedPoint;         //--- Set point value for calculations
if (useTrailingStop && trailingStartPips > 0 && breakevenPips < trailingStartPips) { //--- Check trailing stop conditions
   double averageBuyPrice = rata_price(ORDER_TYPE_BUY); //--- Calculate average buy price
   double trailingReference = 0;                //--- Initialize trailing reference
   for (int iTrade = 0; iTrade < PositionsTotal(); iTrade++) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(iTrade); //--- Get position ticket
      if (ticket == 0) continue;                //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check buy position
         if (useAutoTakeProfit) {               //--- Check auto take profit
            trailingReference = averageBuyPrice; //--- Use average buy price
         } else {                               //--- Use open price
            trailingReference = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set open price
         }
         if (SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingReference > trailingStartPips * setPointValue) { //--- Check trailing condition
            if (SymbolInfoDouble(_Symbol, SYMBOL_BID) - ((trailingStartPips - breakevenPips) * setPointValue) > PositionGetDouble(POSITION_SL)) { //--- Check stop loss adjustment
               obj_Trade.PositionModify(ticket, SymbolInfoDouble(_Symbol, SYMBOL_BID) - ((trailingStartPips - breakevenPips) * setPointValue), PositionGetDouble(POSITION_TP)); //--- Modify position
            }
         }
      }
   }
   double averageSellPrice = rata_price(ORDER_TYPE_SELL); //--- Calculate average sell price
   for (int iTrade2 = 0; iTrade2 < PositionsTotal(); iTrade2++) { //--- Iterate through positions
      ulong ticket2 = PositionGetTicket(iTrade2); //--- Get position ticket
      if (ticket2 == 0) continue;               //--- Skip invalid tickets
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magicNumber) { //--- Check sell position
         if (useAutoTakeProfit) {               //--- Check auto take profit
            trailingReference = averageSellPrice; //--- Use average sell price
         } else {                               //--- Use open price
            trailingReference = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set open price
         }
         if (trailingReference - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > trailingStartPips * setPointValue) { //--- Check trailing condition
            if (SymbolInfoDouble(_Symbol, SYMBOL_ASK) + ((trailingStartPips - breakevenPips) * setPointValue) < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0) { //--- Check stop loss adjustment
               obj_Trade.PositionModify(ticket2, SymbolInfoDouble(_Symbol, SYMBOL_ASK) + ((trailingStartPips - breakevenPips) * setPointValue), PositionGetDouble(POSITION_TP)); //--- Modify position
            }
         }
      }
   }
}

Começaremos atribuindo o valor de "normalizedPoint" a "setPointValue", mantendo a mesma unidade de variação de preço nos cálculos. Em seguida, verificamos se "useTrailingStop" está definido como true, se "trailingStartPips" é positivo e se "breakevenPips" é menor que "trailingStartPips", validando as condições necessárias para ativar o trailing stop. Depois, processamos as posições de compra: calculamos "averageBuyPrice" por meio de "rata_price", utilizando ORDER_TYPE_BUY, e percorremos todas as posições para obter os tickets válidos das posições de compra correspondentes a "Symbol" e "magicNumber". Definimos "trailingReference" como "averageBuyPrice" quando "useAutoTakeProfit" for true ou como POSITION_PRICE_OPEN caso contrário. Se o preço Bid estiver acima de "trailingReference" por uma distância superior a "trailingStartPips" multiplicado por "setPointValue", calculamos o novo stop-loss subtraindo de SYMBOL_BID o produto de ("trailingStartPips" - "breakevenPips") por "setPointValue". Se esse novo nível for superior ao stop-loss atual, utilizamos "obj_Trade.PositionModify" para atualizar a posição.

Em seguida, aplicamos uma lógica semelhante às posições de venda. Calculamos "averageSellPrice" por meio de "rata_price", utilizando "ORDER_TYPE_SELL", e percorremos as posições correspondentes. Definimos "trailingReference" como "averageSellPrice" ou POSITION_PRICE_OPEN, conforme o valor de "useAutoTakeProfit". Se o preço Ask estiver abaixo de "trailingReference" por uma distância superior a "trailingStartPips" multiplicado por "setPointValue", calculamos o novo stop-loss somando a SYMBOL_ASK o produto de ("trailingStartPips" - "breakevenPips") por "setPointValue". Se esse novo nível for inferior ao stop-loss atual ou se ainda não houver um stop-loss definido, utilizamos "obj_Trade.PositionModify" para atualizar a posição. Ao modificar o stop-loss, preservamos o take-profit existente, obtido por "PositionGetDouble(POSITION_TP)". Por fim, chamamos ChartRedraw na função chamadora para atualizar a exibição do gráfico. Após a compilação, obtemos o seguinte resultado.

Antes do trailing stop:

BEFORE TRAILING STOP

Depois do trailing stop:

AFTER TRAILING STOP

Agora que concluímos a lógica de gerenciamento das posições, podemos criar um dashboard para visualizar as métricas da conta. Para isso, desenvolveremos uma função específica que também facilitará o gerenciamento desses elementos.

//+------------------------------------------------------------------+
//| Display dashboard information                                    |
//+------------------------------------------------------------------+
void Display_Info() {
   buyCount = 0;                                  //--- Reset buy count
   currentBuyLot = 0;                             //--- Reset current buy lot
   totalBuyLots = 0;                              //--- Reset total buy lots
   sellCount = 0;                                 //--- Reset sell count
   currentSellLot = 0;                            //--- Reset current sell lot
   totalSellLots = 0;                             //--- Reset total sell lots
   totalSum = 0;                                  //--- Reset total sum
   totalSwap = 0;                                 //--- Reset total swap
   buyProfit = 0;                                 //--- Reset buy profit
   sellProfit = 0;                                //--- Reset sell profit
   buyWeightedSum = 0;                            //--- Reset buy weighted sum
   sellWeightedSum = 0;                           //--- Reset sell weighted sum
   buyBreakEvenPrice = 0;                         //--- Reset buy breakeven price
   sellBreakEvenPrice = 0;                        //--- Reset sell breakeven price
   minBuyLot = 9999;                              //--- Initialize min buy lot
   minSellLot = 9999;                             //--- Initialize min sell lot
   maxSellPrice = 0;                              //--- Initialize max sell price
   minBuyPrice = 999999999;                       //--- Initialize min buy price
   for (int i = 0; i < PositionsTotal(); i++) {   //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);        //--- Get position ticket
      if (ticket == 0) continue;                  //--- Skip invalid tickets
      if (PositionGetString(POSITION_SYMBOL) != Symbol()) continue; //--- Skip non-matching symbols
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy position
         buyCount++;                              //--- Increment buy count
         totalOperations++;                       //--- Increment total operations
         currentBuyLot = PositionGetDouble(POSITION_VOLUME); //--- Set current buy lot
         buyProfit += PositionGetDouble(POSITION_PROFIT); //--- Add buy profit
         totalBuyLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total buy lots
         minBuyLot = MathMin(minBuyLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min buy lot
         buyWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         minBuyPrice = MathMin(minBuyPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update min buy price
      }
      if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell position
         sellCount++;                             //--- Increment sell count
         totalOperations++;                       //--- Increment total operations
         currentSellLot = PositionGetDouble(POSITION_VOLUME); //--- Set current sell lot
         sellProfit += PositionGetDouble(POSITION_PROFIT); //--- Add sell profit
         totalSellLots += PositionGetDouble(POSITION_VOLUME); //--- Add to total sell lots
         minSellLot = MathMin(minSellLot, PositionGetDouble(POSITION_VOLUME)); //--- Update min sell lot
         sellWeightedSum += PositionGetDouble(POSITION_VOLUME) * PositionGetDouble(POSITION_PRICE_OPEN); //--- Add weighted open price
         maxSellPrice = MathMax(maxSellPrice, PositionGetDouble(POSITION_PRICE_OPEN)); //--- Update max sell price
      }
   }
   if (totalBuyLots > 0) {                        //--- Check buy lots
      buyBreakEvenPrice = buyWeightedSum / totalBuyLots; //--- Calculate buy breakeven
   }
   if (totalSellLots > 0) {                       //--- Check sell lots
      sellBreakEvenPrice = sellWeightedSum / totalSellLots; //--- Calculate sell breakeven
   }
   int minutesRemaining, secondsRemaining;        //--- Declare time variables
   minutesRemaining = (int)(PeriodSeconds() - (TimeCurrent() - iTime(Symbol(), PERIOD_CURRENT, 0))); //--- Calculate remaining time
   secondsRemaining = minutesRemaining % 60;      //--- Calculate seconds
   minutesRemaining = minutesRemaining / 60;      //--- Calculate minutes
   long currentSpread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
   string spreadPrefix = "", minutesPrefix = "", secondsPrefix = ""; //--- Initialize prefixes
   if (currentSpread < 10) spreadPrefix = "..";   //--- Set spread prefix for single digit
   else if (currentSpread < 100) spreadPrefix = "."; //--- Set spread prefix for double digit
   if (minutesRemaining < 10) minutesPrefix = "0"; //--- Set minutes prefix
   if (secondsRemaining < 10) secondsPrefix = "0"; //--- Set seconds prefix
   int blinkingColorIndex;                        //--- Declare blinking color index
   color equityColor = clrGreen;                  //--- Initialize equity color
   if (AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE) < 0.0) { //--- Check negative equity
      equityColor = clrRed;                       //--- Set equity color to red
   }
   color profitColor = (buyProfit + sellProfit >= 0) ? clrGreen : clrRed; //--- Set profit color
   MqlDateTime currentDateTime;                   //--- Declare datetime structure
   TimeToStruct(TimeCurrent(), currentDateTime);  //--- Convert current time
   if (currentDateTime.sec >= 0 && currentDateTime.sec < 10) { //--- Check first 10 seconds
      blinkingColorIndex = clrRed;                //--- Set red color
   }
   if (currentDateTime.sec >= 10 && currentDateTime.sec < 20) { //--- Check next 10 seconds
      blinkingColorIndex = clrOrange;             //--- Set orange color
   }
   if (currentDateTime.sec >= 20 && currentDateTime.sec < 30) { //--- Check next 10 seconds
      blinkingColorIndex = clrBlue;               //--- Set blue color
   }
   if (currentDateTime.sec >= 30 && currentDateTime.sec < 40) { //--- Check next 10 seconds
      blinkingColorIndex = clrDodgerBlue;         //--- Set dodger blue color
   }
   if (currentDateTime.sec >= 40 && currentDateTime.sec < 50) { //--- Check next 10 seconds
      blinkingColorIndex = clrYellow;             //--- Set yellow color
   }
   if (currentDateTime.sec >= 50 && currentDateTime.sec <= 59) { //--- Check last 10 seconds
      blinkingColorIndex = clrYellow;             //--- Set yellow color
   }
   if (ObjectFind(0, "DashboardBG") < 0) {        //--- Check dashboard background
      ObjectCreate(0, "DashboardBG", OBJ_RECTANGLE_LABEL, 0, 0, 0); //--- Create dashboard background
      ObjectSetInteger(0, "DashboardBG", OBJPROP_CORNER, 0); //--- Set corner
      ObjectSetInteger(0, "DashboardBG", OBJPROP_XDISTANCE, 100); //--- Set x distance
      ObjectSetInteger(0, "DashboardBG", OBJPROP_YDISTANCE, 20); //--- Set y distance
      ObjectSetInteger(0, "DashboardBG", OBJPROP_XSIZE, 260); //--- Set width
      ObjectSetInteger(0, "DashboardBG", OBJPROP_YSIZE, 300); //--- Set height
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BGCOLOR, clrLightGray); //--- Set background color
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set border type
      ObjectSetInteger(0, "DashboardBG", OBJPROP_COLOR, clrBlack); //--- Set border color
      ObjectSetInteger(0, "DashboardBG", OBJPROP_BACK, false); //--- Set to foreground
   }
   if (ObjectFind(0, "CLOSE ALL") < 0) {          //--- Check close all button
      ObjectCreate(0, "CLOSE ALL", OBJ_BUTTON, 0, 0, 0); //--- Create close all button
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_CORNER, 0); //--- Set corner
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_XDISTANCE, 110); //--- Set x distance
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_YDISTANCE, 280); //--- Set y distance
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_XSIZE, 240); //--- Set width
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_YSIZE, 25); //--- Set height
      ObjectSetString(0, "CLOSE ALL", OBJPROP_TEXT, "Close All Positions"); //--- Set button text
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_COLOR, clrWhite); //--- Set text color
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_BGCOLOR, clrRed); //--- Set background color
      ObjectSetInteger(0, "CLOSE ALL", OBJPROP_BORDER_COLOR, clrBlack); //--- Set border color
   }
   string headerText = "Pin Bar Averaging EA";    //--- Set header text
   LABEL("Header", "Impact", 20, 110, 20, clrNavy, 0, headerText); //--- Create header label
   string copyrightText = "Copyright 2025, Allan Munene Mutiiria"; //--- Set copyright text
   LABEL("Copyright", "Arial", 9, 110, 55, clrBlack, 0, copyrightText); //--- Create copyright label
   string linkText = "https://t.me/Forex_Algo_Trader"; //--- Set link text
   LABEL("Link", "Arial", 9, 110, 70, clrBlue, 0, linkText); //--- Create link label
   string accountHeader = "Account Information";  //--- Set account header
   LABEL("AccountHeader", "Arial Bold", 10, 110, 90, clrBlack, 0, accountHeader); //--- Create account header label
   string balanceText = "Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2); //--- Set balance text
   LABEL("Balance", "Arial", 9, 120, 105, clrBlack, 0, balanceText); //--- Create balance label
   string equityText = "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); //--- Set equity text
   LABEL("Equity", "Arial", 9, 120, 120, equityColor, 0, equityText); //--- Create equity label
   string marginText = "Free Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2); //--- Set margin text
   LABEL("Margin", "Arial", 9, 120, 135, clrBlack, 0, marginText); //--- Create margin label
   string profitText = "Open Profit: " + DoubleToString(buyProfit + sellProfit, 2); //--- Set profit text
   LABEL("Profit", "Arial", 9, 120, 150, profitColor, 0, profitText); //--- Create profit label
   string positionsText = "Buy Positions: " + IntegerToString((int)buyCount) + " Sell Positions: " + IntegerToString((int)sellCount); //--- Set positions text
   LABEL("Positions", "Arial", 9, 120, 165, clrBlack, 0, positionsText); //--- Create positions label
   string buyBEText = "Buy Break Even: " + (buyCount > 0 ? DoubleToString(buyBreakEvenPrice, _Digits) : "-"); //--- Set buy breakeven text
   LABEL("BuyBE", "Arial", 9, 120, 180, clrBlack, 0, buyBEText); //--- Create buy breakeven label
   string sellBEText = "Sell Break Even: " + (sellCount > 0 ? DoubleToString(sellBreakEvenPrice, _Digits) : "-"); //--- Set sell breakeven text
   LABEL("SellBE", "Arial", 9, 120, 195, clrBlack, 0, sellBEText); //--- Create sell breakeven label
   string spreadText = "Spread: " + spreadPrefix + IntegerToString((int)currentSpread) + " points"; //--- Set spread text
   LABEL("Spread", "Arial", 9, 120, 210, clrBlack, 0, spreadText); //--- Create spread label
   string timeText = "Time to next bar: " + minutesPrefix + IntegerToString(minutesRemaining) + ":" + secondsPrefix + IntegerToString(secondsRemaining); //--- Set time text
   LABEL("Time", "Arial", 9, 120, 225, clrBlack, 0, timeText); //--- Create time label
   string pinbarText;                             //--- Declare pinbar text
   if (IsBuyPinbar()) pinbarText = "Buy Pinbar";  //--- Check buy pinbar
   else if (IsSellPinbar()) pinbarText = "Sell Pinbar"; //--- Check sell pinbar
   else pinbarText = "None";                      //--- Set no pinbar
   LABEL("Pinbar", "Arial", 9, 120, 240, clrBlack, 0, "Pinbar Signal: " + pinbarText); //--- Create pinbar label
   string patternText = "Candle Pattern: " + CandleStick_Analyzer(); //--- Set candlestick pattern text
   LABEL("Pattern", "Arial", 9, 120, 255, clrBlack, 0, patternText); //--- Create pattern label
}

Implementaremos a função "Display_Info" para criar um dashboard completo de monitoramento das operações em tempo real. Primeiro, reinicializamos as principais métricas, como "buyCount" e as demais variáveis de acompanhamento. Em seguida, percorremos todas as posições para atualizar os indicadores das posições de compra e venda correspondentes a "Symbol". Durante essa etapa, contabilizamos as posições, acumulamos os lucros e os volumes, calculamos as somas ponderadas dos preços de abertura, acompanhamos os preços mínimos e máximos e determinamos os preços de equilíbrio das posições, quando aplicável.

Depois, calculamos o tempo restante até a formação da próxima barra, subtraindo de PeriodSeconds o tempo decorrido entre o horário atual e o horário de abertura da barra, obtido por iTime. Convertemos o resultado em "minutesRemaining" e "secondsRemaining" e definimos os prefixos de formatação utilizados na exibição do spread e do tempo. Em seguida, configuramos "equityColor" para exibir verde ou vermelho, conforme a relação entre o patrimônio líquido (equity) e o saldo da conta, e "profitColor" de acordo com o lucro total. Também definimos um índice de cor que varia conforme o segundo atual, criando um efeito visual intermitente. Por fim, criamos o fundo do dashboard como um objeto OBJ_RECTANGLE_LABEL por meio de ObjectCreate, caso "DashboardBG" ainda não exista. Adicionamos o botão "CLOSE ALL" como um objeto "OBJ_BUTTON" e utilizamos a função "LABEL" para criar os rótulos que exibem o nome do EA, as informações de direitos autorais, um link, os dados da conta de negociação (saldo, patrimônio líquido, margem livre e lucro), a quantidade de posições, os preços de equilíbrio, o spread, o tempo restante até a próxima barra, o sinal de pin bar e os padrões de velas identificados por "CandleStick_Analyzer". Assim, teremos um dashboard que apresenta as informações de negociação de forma clara e atualizada. Para permitir a interação com o botão, implementaremos o tratamento do evento correspondente no manipulador OnChartEvent.

//+------------------------------------------------------------------+
//| Handle chart events                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_OBJECT_CLICK) {           //--- Check object click event
      if (sparam == "CLOSE ALL") {                //--- Check close all button
         ObjectSetInteger(0, "CLOSE ALL", OBJPROP_STATE, false); //--- Reset button state
         for (int positionIndex = PositionsTotal() - 1; positionIndex >= 0; positionIndex--) { //--- Iterate through positions
            ulong ticket = PositionGetTicket(positionIndex); //--- Get position ticket
            if (ticket == 0) continue;            //--- Skip invalid tickets
            if (PositionGetString(POSITION_SYMBOL) == Symbol()) { //--- Check symbol
               obj_Trade.PositionClose(ticket);   //--- Close position
            }
         }
      }
   }
}

Na função OnChartEvent, verificamos se o identificador "id" do evento corresponde a CHARTEVENT_OBJECT_CLICK, indicando que houve um clique em um objeto do gráfico. Em seguida, verificamos se o objeto clicado, identificado por "sparam", é o botão "CLOSE ALL". Se for, redefinimos o estado do botão como false utilizando ObjectSetInteger com a propriedade "OBJPROP_STATE". Depois, percorremos todas as posições, obtendo o ticket de cada uma por meio de PositionGetTicket. Ignoramos os tickets inválidos e utilizamos PositionGetString para verificar se o ativo de cada posição corresponde ao valor atual de "Symbol". Por fim, fechamos as posições correspondentes com "obj_Trade.PositionClose", executando o comando do usuário para encerrar todas as posições do ativo. Assim, o botão "Close All Positions" do dashboard oferece uma maneira prática de gerenciar manualmente as posições abertas. Depois de incluir a chamada da função em OnTick e compilar o programa, obtemos o seguinte resultado.

COMPLETE PIN BAR AVERAGING SYSTEM

Na imagem, podemos observar que o sistema identifica e exibe linhas de suporte ou resistência, abre posições e adiciona novas entradas pela estratégia de preço médio, gerencia as posições abertas e apresenta as métricas da conta de negociação no dashboard. Com isso, concluímos os objetivos propostos. Resta apenas testar o programa com dados históricos, o que faremos na próxima seção.


Testes com dados históricos

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

Gráfico do backtest:

Gráfico

Relatório do backtest:

REPORT


Conclusão

Neste artigo, desenvolvemos em MQL5 um sistema de preço médio baseado em pin bars. O sistema utiliza esses padrões de velas para abrir operações e gerenciar múltiplas posições por meio de uma estratégia de preço médio, complementada por trailing stop, ajuste do stop-loss para o ponto de equilíbrio (breakeven) e um dashboard para monitoramento em tempo real. Com componentes modulares, como as funções "CandleStick_Analyzer" e "addAveragingOrder", o programa oferece uma abordagem disciplinada para negociar reversões de preço, com recursos configuráveis de controle de risco.

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

Com os conceitos e métodos de implementação apresentados, você poderá adaptar esse sistema de preço médio baseado em pin bars ao seu estilo de negociação e aprimorar suas estratégias de negociação algorítmica. Boas operações!

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

Arquivos anexados |
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
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
Neste artigo, 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 programa gera sinais dinâmicos de compra e venda quando o preço toca essas linhas e abre posições com base nos sinais identificados.
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.
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.