English Русский 中文 Español Deutsch 日本語
preview
Automatização de estratégias de negociação em MQL5 (Parte 22): Desenvolvimento de um sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes

Automatização de estratégias de negociação em MQL5 (Parte 22): Desenvolvimento de um sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes

MetaTrader 5 — Negociação |
36 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior (Parte 21), exploramos uma estratégia de negociação baseada em uma rede neural, aprimorada com taxas de aprendizado adaptativas para aumentar a precisão das previsões dos movimentos do mercado em MetaQuotes Language 5 (MQL5). Nesta Parte 22, nosso foco será o desenvolvimento de um sistema de recuperação por zonas (Zone Recovery System), integrado a uma estratégia de negociação a favor da tendência que combina o indicador Envelopes com o índice de força relativa (RSI) para automatizar operações e gerenciar perdas com eficiência. Abordaremos os seguintes tópicos:

  1. Entendendo a arquitetura do sistema de recuperação por zonas aplicado à negociação a favor da tendência
  2. Implementação em MQL5
  3. Backtesting
  4. Conclusão

Ao final, você terá um sistema de negociação robusto, desenvolvido em MQL5 para operar em condições dinâmicas de mercado, pronto para implementação e testes.


Entendendo a arquitetura da estratégia de tendência com recuperação por zonas

A recuperação por zonas (zone recovery) é uma estratégia de negociação que busca transformar perdas potenciais em lucro por meio da abertura de operações adicionais quando o mercado se move contra a posição inicial, com o objetivo de encerrar o conjunto de operações com lucro ou, pelo menos, no ponto de equilíbrio. Imagine que você compre um par de moedas esperando uma valorização, mas o preço comece a cair. É nesse momento que entra em ação a recuperação por zonas: estabelecemos uma faixa de preços, ou "zona", na qual abrimos operações na direção oposta para compensar as perdas caso o preço volte a subir. Nosso objetivo é desenvolver um sistema automatizado em MetaQuotes Language 5 (MQL5) que aplique esse conceito ao mercado de câmbio, buscando manter os riscos baixos e maximizar os lucros.

Utilizaremos dois indicadores técnicos para identificar os melhores momentos de entrada no mercado. Um deles avaliará a força dos movimentos de preço, permitindo abrir operações apenas quando houver um movimento direcional forte e evitando sinais fracos ou pouco claros. O outro, o Envelopes, traça duas bandas que delimitam um canal em torno de uma média móvel dos preços, indicando quando os preços sobem ou caem excessivamente e sinalizando possíveis pontos de reação do preço favoráveis à entrada no mercado. Em conjunto, esses indicadores ajudarão a identificar oportunidades de negociação com maior probabilidade de sucesso, quando o preço apresentar sinais de mudança de direção para retomar o movimento a favor da tendência predominante.

Veja como planejamos integrar todos esses elementos: começaremos abrindo uma operação quando os indicadores sinalizarem uma mudança de direção do preço, por exemplo, quando ele atingir uma das bandas do Envelopes com forte impulso. Se o mercado se mover contra nossa posição, ativaremos a recuperação por zonas, abrindo operações na direção oposta dentro de uma faixa de preços previamente definida, cuja amplitude será cuidadosamente ajustada para equilibrar o risco e a capacidade de recuperação das perdas. Também limitaremos o número de operações para evitar a abertura excessiva de posições e manter a disciplina da estratégia. Essa abordagem permitirá aproveitar as oportunidades oferecidas pelas tendências e, ao mesmo tempo, contar com um mecanismo de proteção caso o mercado não se comporte como esperado, adaptando-se tanto a condições de maior volatilidade quanto a períodos de maior estabilidade. A seguir, apresentamos o plano de implementação.

PLANO DA ESTRATÉGIA


Implementação em MQL5

Para desenvolver o programa em MQL5, abra o MetaEditor, acesse o "Navegador", selecione a pasta Indicators, clique em "Criar" e siga as instruções para criar o arquivo. Começaremos declarando algumas variáveis de entrada que permitirão ajustar facilmente os principais parâmetros do programa.

//+------------------------------------------------------------------+
//|                 Envelopes Trend Bounce with Zone Recovery 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

enum TradingLotSizeOptions { FIXED_LOTSIZE, UNFIXED_LOTSIZE };         //--- Define lot size options

input group "======= EA GENERAL SETTINGS ======="
input TradingLotSizeOptions lotOption = UNFIXED_LOTSIZE;               // Lot Size Option
input double initialLotSize = 0.01;                                    // Initial Lot Size
input double riskPercentage = 1.0;                                     // Risk Percentage (%)
input int    riskPoints = 300;                                         // Risk Points
input int    magicNumber = 123456789;                                  // Magic Number
input int    maxOrders = 1;                                            // Maximum Initial Positions
input double zoneTargetPoints = 600;                                   // Zone Target Points
input double zoneSizePoints = 300;                                     // Zone Size Points
input bool   restrictMaxOrders = true;                                 // Apply Maximum Orders Restriction

Nesta etapa, estabeleceremos a base do nosso sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes em MQL5, definindo os componentes necessários e os parâmetros configuráveis pelo usuário. Primeiro, incluiremos a biblioteca <Trade/Trade.mqh>, que disponibiliza a classe CTrade para executar operações de negociação, como abrir e fechar posições. Essa biblioteca é fundamental, pois fornece ao EA os recursos necessários para interagir com o mercado, especialmente para enviar ordens. A seguir, mostramos como abrir o arquivo.

ARQUIVO DE OPERAÇÕES DE NEGOCIAÇÃO EM MQL5

Em seguida, definimos o tipo enumerado TradingLotSizeOptions com dois valores: FIXED_LOTSIZE e UNFIXED_LOTSIZE. Isso permite que o usuário escolha entre um tamanho de lote fixo e um tamanho calculado dinamicamente com base nos parâmetros de risco, oferecendo flexibilidade para dimensionar as posições de acordo com diferentes estilos de negociação. Depois, configuramos os parâmetros de entrada do grupo EA GENERAL SETTINGS (configurações gerais do EA), que podem ser alterados pelo usuário na plataforma MetaTrader 5.

O parâmetro lotOption, definido por padrão como UNFIXED_LOTSIZE, determina se o EA utilizará um tamanho de lote fixo ou calculado com base no risco. O parâmetro initialLotSize (0.01) estabelece o tamanho do lote para operações com volume fixo, enquanto riskPercentage (1.0%) e riskPoints (300) definem, respectivamente, o percentual do saldo da conta destinado ao risco e a distância do stop-loss em pontos, utilizados no cálculo dinâmico do tamanho do lote. Essas configurações determinam o risco assumido em cada operação, permitindo adequar o funcionamento do EA à tolerância ao risco do usuário.

Atribuiremos um magicNumber exclusivo (123456789) para identificar as operações do nosso EA e diferenciá-las das demais operações realizadas na mesma conta. Os parâmetros de entrada maxOrders (1) e restrictMaxOrders (true) limitam o número de posições iniciais, impedindo que o EA abra posições em excesso simultaneamente. Por fim, zoneTargetPoints (600) e zoneSizePoints (300) definem, respectivamente, o objetivo de lucro e a amplitude da zona de recuperação, ambos em pontos, estabelecendo os limites utilizados pela estratégia. Após a compilação, obtemos o seguinte resultado.

DADOS DE ENTRADA CARREGADOS

Após carregar os dados de entrada, podemos começar a definir a lógica central do sistema. Primeiro, declararemos algumas estruturas e classes, pois adotaremos a programação orientada a objetos (OOP).

class MarketZoneTrader {
private:
   //--- Trade State Definition
   enum TradeState { INACTIVE, RUNNING, TERMINATING };                 //--- Define trade lifecycle states

   //--- Data Structures
   struct TradeMetrics {
      bool   operationSuccess;                                         //--- Track operation success
      double totalVolume;                                              //--- Sum closed trade volumes
      double netProfitLoss;                                            //--- Accumulate profit/loss
   };

   struct ZoneBoundaries {
      double zoneHigh;                                                 //--- Upper recovery zone boundary
      double zoneLow;                                                  //--- Lower recovery zone boundary
      double zoneTargetHigh;                                           //--- Upper profit target
      double zoneTargetLow;                                            //--- Lower profit target
   };

   struct TradeConfig {
      string         marketSymbol;                                     //--- Trading symbol
      double         openPrice;                                        //--- Position entry price
      double         initialVolume;                                    //--- Initial trade volume
      long           tradeIdentifier;                                  //--- Magic number
      string         tradeLabel;                                       //--- Trade comment
      ulong          activeTickets[];                                  //--- Active position tickets
      ENUM_ORDER_TYPE direction;                                       //--- Trade direction
      double         zoneProfitSpan;                                   //--- Profit target range
      double         zoneRecoverySpan;                                 //--- Recovery zone range
      double         accumulatedBuyVolume;                             //--- Total buy volume
      double         accumulatedSellVolume;                            //--- Total sell volume
      TradeState     currentState;                                     //--- Current trade state
   };

   struct LossTracker {
      double tradeLossTracker;                                         //--- Track cumulative profit/loss
   };
};

Nesta etapa, definiremos a estrutura principal do nosso sistema de negociação a favor da tendência com o indicador Envelopes em MQL5, criando a classe MarketZoneTrader e concentrando-nos em sua seção privada, que contém os estados de negociação e as estruturas de dados. Essa arquitetura permitirá estruturar os componentes essenciais para gerenciar operações, acompanhar a recuperação por zonas e monitorar o desempenho da estratégia. Começaremos definindo a classe MarketZoneTrader, que servirá de base para o nosso EA, encapsulando a lógica da estratégia de negociação.

Na seção privada, adicionaremos o tipo enumerado TradeState com três estados: INACTIVE (inativo), RUNNING (em execução) e TERMINATING (em encerramento). Esses estados permitem acompanhar o ciclo de vida das operações, identificando se o EA está aguardando, gerenciando posições ativamente ou encerrando posições. Esse controle é fundamental para coordenar a execução da estratégia, especialmente em ações como a abertura de operações de recuperação e o encerramento de posições.

Em seguida, criaremos a estrutura TradeMetrics para armazenar os principais dados sobre os resultados das operações. Ela incluirá o campo operationSuccess, que indica se uma operação de negociação foi executada com sucesso, como o fechamento de uma posição; o campo totalVolume, que acumula o volume das operações encerradas; e o campo netProfitLoss, que registra o lucro ou prejuízo líquido acumulado dessas operações. Essa estrutura permitirá avaliar os resultados da estratégia e acompanhar seus indicadores de desempenho durante a recuperação ou o encerramento das posições.

Depois, definiremos a estrutura ZoneBoundaries, responsável por armazenar os níveis de preço utilizados na estratégia de recuperação por zonas. As variáveis zoneHigh e zoneLow representam os limites superior e inferior da zona de recuperação, nos quais abrimos operações na direção oposta para compensar as perdas. Já os parâmetros zoneTargetHigh e zoneTargetLow estabelecem os níveis-alvo de lucro acima e abaixo da zona, indicando quando as posições podem ser encerradas com lucro. Esses limites são fundamentais para a estratégia, pois determinam os pontos em que devemos iniciar as operações de recuperação ou encerrar as posições. A seguir, apresentamos uma representação visual dessas zonas para facilitar a compreensão da finalidade dessa estrutura.

EXEMPLO DE ZONAS

Os parâmetros de configuração das operações são armazenados na estrutura TradeConfig. Ela inclui marketSymbol, que identifica o par de moedas, openPrice, que armazena o preço de entrada, e initialVolume, que define o volume inicial da operação. O campo tradeIdentifier armazena nosso número mágico exclusivo, enquanto tradeLabel adiciona um comentário para identificar a operação. O array activeTickets permite acompanhar as posições abertas, e direction indica se a operação é de compra ou de venda. Também utilizamos zoneProfitSpan e zoneRecoverySpan para definir, em unidades de preço, as distâncias correspondentes ao objetivo de lucro e à zona de recuperação, além de accumulatedBuyVolume e accumulatedSellVolume para acompanhar o volume total das operações de compra e venda, respectivamente. Por fim, currentState, baseado no tipo enumerado TradeState, acompanha o estado atual da negociação, integrando esses componentes.

Por último, adicionamos a estrutura LossTracker, que contém apenas a variável tradeLossTracker, responsável por acompanhar o lucro ou prejuízo acumulado de todas as operações. Isso nos permite avaliar o impacto financeiro das medidas de recuperação e ajustar a estratégia caso as perdas se tornem excessivas. Em seguida, podemos declarar alguns atributos adicionais da classe para armazenar informações menos relevantes, mas ainda necessárias ao gerenciamento das operações.

//--- Member Variables
TradeConfig           m_tradeConfig;                                //--- Store trade configuration
ZoneBoundaries        m_zoneBounds;                                 //--- Store zone boundaries
LossTracker           m_lossTracker;                                //--- Track profit/loss
string                m_lastError;                                  //--- Store error message
int                   m_errorStatus;                                //--- Store error code
CTrade                m_tradeExecutor;                              //--- Manage trade execution
int                   m_handleRsi;                                  //--- RSI indicator handle
int                   m_handleEnvUpper;                             //--- Upper Envelopes handle
int                   m_handleEnvLower;                             //--- Lower Envelopes handle
double                m_rsiBuffer[];                                //--- RSI data buffer
double                m_envUpperBandBuffer[];                       //--- Upper Envelopes buffer
double                m_envLowerBandBuffer[];                       //--- Lower Envelopes buffer
TradingLotSizeOptions m_lotOption;                                  //--- Lot size option
double                m_initialLotSize;                             //--- Fixed lot size
double                m_riskPercentage;                             //--- Risk percentage
int                   m_riskPoints;                                 //--- Risk points
int                   m_maxOrders;                                  //--- Maximum positions
bool                  m_restrictMaxOrders;                          //--- Position restriction flag
double                m_zoneTargetPoints;                           //--- Profit target points
double                m_zoneSizePoints;                             //--- Recovery zone points

Definiremos os principais atributos na seção privada da classe MarketZoneTrader para gerenciar as configurações de negociação, a recuperação por zonas e os dados dos indicadores. Utilizaremos m_tradeConfig (estrutura TradeConfig) para armazenar informações das operações, como símbolo e direção; m_zoneBounds (estrutura ZoneBoundaries) para armazenar os limites da zona de recuperação e os preços-alvo de lucro; e m_lossTracker (estrutura LossTracker) para acompanhar os lucros e prejuízos. As variáveis m_lastError (string) e m_errorStatus (inteiro) serão utilizadas para registrar erros, enquanto o objeto m_tradeExecutor (classe CTrade) ficará responsável pela execução das operações de negociação.

Os handles dos indicadores m_handleRsi, m_handleEnvUpper e m_handleEnvLower permitem acessar os dados do RSI e do Envelopes. Os arrays m_rsiBuffer, m_envUpperBandBuffer e m_envLowerBandBuffer armazenam os valores desses indicadores. Os parâmetros de entrada serão armazenados nas variáveis m_lotOption (TradingLotSizeOptions), m_initialLotSize, m_riskPercentage, m_riskPoints, m_maxOrders, m_restrictMaxOrders, m_zoneTargetPoints e m_zoneSizePoints, utilizadas para controlar o tamanho dos lotes, os limites de posições e as dimensões das zonas. Esses atributos fornecem a base necessária para gerenciar as operações e os indicadores, preparando a implementação da lógica de negociação nas próximas etapas. Agora, precisamos definir algumas funções auxiliares que serão utilizadas com frequência no programa.

//--- Error Handling
void logError(string message, int code) {
   //--- Error Logging Start
   m_lastError = message;                                           //--- Store error message
   m_errorStatus = code;                                            //--- Store error code
   Print("Error: ", message);                                       //--- Log error to Experts tab
   //--- Error Logging End
}

//--- Market Data Access
double getMarketVolumeStep() {
   //--- Volume Step Retrieval Start
   return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_VOLUME_STEP); //--- Retrieve broker's volume step
   //--- Volume Step Retrieval End
}

double getMarketAsk() {
   //--- Ask Price Retrieval Start
   return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_ASK); //--- Retrieve ask price
   //--- Ask Price Retrieval End
}

double getMarketBid() {
   //--- Bid Price Retrieval Start
   return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_BID); //--- Retrieve bid price
   //--- Bid Price Retrieval End
}

Nesta etapa, adicionaremos funções auxiliares essenciais para o tratamento de erros e o acesso aos dados de mercado. A função logError armazena message em m_lastError e code em m_errorStatus, além de exibir a mensagem por meio de Print na aba "Experts", facilitando a depuração. A função getMarketVolumeStep utiliza SymbolInfoDouble com SYMBOL_VOLUME_STEP para obter o incremento mínimo de volume permitido pela corretora para o símbolo m_tradeConfig.marketSymbol, permitindo ajustar o volume das operações às regras de negociação. As funções getMarketAsk e getMarketBid consultam, respectivamente, as cotações ask e bid por meio de SymbolInfoDouble, utilizando SYMBOL_ASK e SYMBOL_BID para obter os preços necessários à execução das operações.

Agora podemos definir as principais funções responsáveis pela execução das operações de negociação. Começaremos pelas rotinas mais simples, que permitirão inicializar operações, armazenar as ordens para acompanhamento e monitoramento e encerrar posições.

//--- Trade Initialization
bool configureTrade(ulong ticket) {
   //--- Trade Configuration Start
   if (!PositionSelectByTicket(ticket)) {                               //--- Select position by ticket
      logError("Failed to select ticket " + IntegerToString(ticket), INIT_FAILED); //--- Log selection failure
      return false;                                                     //--- Return failure
   }
   m_tradeConfig.marketSymbol = PositionGetString(POSITION_SYMBOL);     //--- Set symbol
   m_tradeConfig.tradeLabel = __FILE__;                                 //--- Set trade comment
   m_tradeConfig.tradeIdentifier = PositionGetInteger(POSITION_MAGIC);  //--- Set magic number
   m_tradeConfig.direction = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE);   //--- Set direction
   m_tradeConfig.openPrice = PositionGetDouble(POSITION_PRICE_OPEN);    //--- Set entry price
   m_tradeConfig.initialVolume = PositionGetDouble(POSITION_VOLUME);    //--- Set initial volume
   m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number for executor
   return true;                                                         //--- Return success
   //--- Trade Configuration End
}

//--- Trade Ticket Management
void storeTradeTicket(ulong ticket) {
   //--- Ticket Storage Start
   int ticketCount = ArraySize(m_tradeConfig.activeTickets);        //--- Get ticket count
   ArrayResize(m_tradeConfig.activeTickets, ticketCount + 1);       //--- Resize ticket array
   m_tradeConfig.activeTickets[ticketCount] = ticket;               //--- Store ticket
   //--- Ticket Storage End
}

//--- Trade Execution
ulong openMarketTrade(ENUM_ORDER_TYPE tradeDirection, double tradeVolume, double price) {
   //--- Trade Opening Start
   ulong ticket = 0;                                                //--- Initialize ticket
   if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, tradeDirection, tradeVolume, price, 0, 0, m_tradeConfig.tradeLabel)) { //--- Open position
      ticket = m_tradeExecutor.ResultOrder();                       //--- Get ticket
   } else {
      Print("Failed to open trade: Direction=", EnumToString(tradeDirection), ", Volume=", tradeVolume); //--- Log failure
   }
   return ticket;                                                   //--- Return ticket
   //--- Trade Opening End
}

//--- Trade Closure
void closeActiveTrades(TradeMetrics &metrics) {
   //--- Trade Closure Start
   for (int i = ArraySize(m_tradeConfig.activeTickets) - 1; i >= 0; i--) {    //--- Iterate tickets in reverse
      if (m_tradeConfig.activeTickets[i] > 0) {                               //--- Check valid ticket
         if (m_tradeExecutor.PositionClose(m_tradeConfig.activeTickets[i])) { //--- Close position
            m_tradeConfig.activeTickets[i] = 0;                               //--- Clear ticket
            metrics.totalVolume += m_tradeExecutor.ResultVolume();            //--- Accumulate volume
            if ((ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE) == ORDER_TYPE_BUY) { //--- Check buy position
               metrics.netProfitLoss += m_tradeExecutor.ResultVolume() * (m_tradeExecutor.ResultPrice() - PositionGetDouble(POSITION_PRICE_OPEN)); //--- Calculate buy profit
            } else {                                                          //--- Handle sell position
               metrics.netProfitLoss += m_tradeExecutor.ResultVolume() * (PositionGetDouble(POSITION_PRICE_OPEN) - m_tradeExecutor.ResultPrice()); //--- Calculate sell profit
            }
         } else {
            metrics.operationSuccess = false;                                  //--- Mark failure
            Print("Failed to close ticket: ", m_tradeConfig.activeTickets[i]); //--- Log failure
         }
      }
   }
   //--- Trade Closure End
}

//--- Bar Detection
bool isNewBar() {
   //--- New Bar Detection Start
   static datetime previousTime = 0;                                      //--- Store previous bar time
   datetime currentTime = iTime(m_tradeConfig.marketSymbol, Period(), 0); //--- Get current bar time
   bool result = (currentTime != previousTime);                           //--- Check for new bar
   previousTime = currentTime;                                            //--- Update previous time
   return result;                                                         //--- Return new bar status
   //--- New Bar Detection End
}

Nesta etapa, aprofundaremos a lógica central do programa, implementando funções para abrir operações, acompanhar posições, executar ordens, encerrar operações e determinar quando executar cada ação. Começaremos criando a função configureTrade, que preparará os dados da operação associada a um ticket específico. Primeiro, tentaremos selecionar a posição com PositionSelectByTicket. Se a seleção falhar, registraremos o erro por meio de logError e retornaremos false. Caso seja bem-sucedida, preencheremos m_tradeConfig com os dados da posição: obteremos marketSymbol usando PositionGetString, definiremos tradeLabel como FILE e recuperaremos tradeIdentifier e direction com PositionGetInteger, convertendo este último para ENUM_ORDER_TYPE. Em seguida, obteremos openPrice e initialVolume com PositionGetDouble e configuraremos o número mágico em m_tradeExecutor por meio de SetExpertMagicNumber, deixando o objeto preparado para executar operações.

Depois, criaremos a função storeTradeTicket para manter o registro das posições abertas. Consultaremos o tamanho do array m_tradeConfig.activeTickets com ArraySize, aumentaremos seu tamanho em um elemento usando ArrayResize e armazenaremos o novo ticket na posição adicionada, permitindo acompanhar quais posições estão abertas. Em seguida, desenvolveremos a função openMarketTrade para executar operações a mercado. Chamaremos m_tradeExecutor.PositionOpen, passando os parâmetros tradeDirection, tradeVolume e price, além dos dados de m_tradeConfig. Se a abertura for bem-sucedida, atribuiremos a ticket o valor obtido por ResultOrder. Caso contrário, registraremos o erro com Print, permitindo identificar eventuais falhas na execução das operações.

Na sequência, implementaremos o fechamento das posições com a função closeActiveTrades. Percorreremos o array m_tradeConfig.activeTickets em ordem inversa, utilizando m_tradeExecutor.PositionClose para encerrar cada posição associada a um ticket válido. Quando o fechamento for bem-sucedido, adicionaremos ResultVolume a metrics.totalVolume e calcularemos metrics.netProfitLoss com auxílio das funções PositionGetInteger e PositionGetDouble, consultando a direção e os dados da posição. Se alguma operação falhar, definiremos metrics.operationSuccess como false e registraremos a ocorrência com Print, permitindo acompanhar o resultado de cada tentativa de fechamento.

Por fim, adicionaremos a função isNewBar, que permitirá limitar a execução de operações a uma vez por barra, reduzindo o consumo de recursos. Utilizaremos iTime para obter o horário de abertura da barra atual do símbolo m_tradeConfig.marketSymbol, compararemos esse valor com previousTime e atualizaremos a variável quando houver uma diferença. Dessa forma, poderemos identificar o surgimento de uma nova barra e verificar os sinais de negociação. Para completar essa etapa, ainda precisaremos de uma função para calcular o volume das operações e outra para abrir posições.

//--- Lot Size Calculation
double calculateLotSize(double riskPercent, int riskPips) {
   //--- Lot Size Calculation Start
   double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * riskPercent / 100;                //--- Calculate risk amount
   double tickSize = SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_TRADE_TICK_SIZE);   //--- Get tick size
   double tickValue = SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_TRADE_TICK_VALUE); //--- Get tick value
   if (tickSize == 0 || tickValue == 0) {                           //--- Validate tick data
      Print("Invalid tick size or value");                          //--- Log invalid data
      return -1;                                                    //--- Return invalid lot
   }
   double lotValue = (riskPips * _Point) / tickSize * tickValue;    //--- Calculate lot value
   if (lotValue == 0) {                                             //--- Validate lot value
      Print("Invalid lot value");                                   //--- Log invalid lot
      return -1;                                                    //--- Return invalid lot
   }
   return NormalizeDouble(riskMoney / lotValue, 2);                 //--- Return normalized lot size
   //--- Lot Size Calculation End
}

//--- Order Execution
int openOrder(ENUM_ORDER_TYPE orderType, double stopLoss, double takeProfit) {
   //--- Order Opening Start
   int ticket;                                                      //--- Initialize ticket
   double openPrice;                                                //--- Initialize open price
   
   if (orderType == ORDER_TYPE_BUY) {                               //--- Check buy order
      openPrice = NormalizeDouble(getMarketAsk(), Digits());        //--- Set buy price
   } else if (orderType == ORDER_TYPE_SELL) {                       //--- Check sell order
      openPrice = NormalizeDouble(getMarketBid(), Digits());        //--- Set sell price
   } else {
      Print("Invalid order type");                                  //--- Log invalid type
      return -1;                                                    //--- Return invalid ticket
   }
   
   double lotSize = 0;                                              //--- Initialize lot size
   
   if (m_lotOption == FIXED_LOTSIZE) {                              //--- Check fixed lot
      lotSize = m_initialLotSize;                                   //--- Use fixed lot size
   } else if (m_lotOption == UNFIXED_LOTSIZE) {                     //--- Check dynamic lot
      lotSize = calculateLotSize(m_riskPercentage, m_riskPoints);   //--- Calculate risk-based lot
   }
   
   if (lotSize <= 0) {                                              //--- Validate lot size
      Print("Invalid lot size: ", lotSize);                         //--- Log invalid lot
      return -1;                                                    //--- Return invalid ticket
   }
   
   if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, 0, 0, __FILE__)) { //--- Open position
      ticket = (int)m_tradeExecutor.ResultOrder();                  //--- Get ticket
      Print("New trade opened: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize); //--- Log success
   } else {
      ticket = -1;                                                  //--- Set invalid ticket
      Print("Failed to open order: Type=", EnumToString(orderType), ", Volume=", lotSize); //--- Log failure
   }
   
   return ticket;                                                   //--- Return ticket
   //--- Order Opening End
}

Começaremos pela função calculateLotSize, que determina o tamanho do lote com base nos parâmetros de risco. Primeiro, calcularemos riskMoney, aplicando riskPercent ao saldo da conta obtido por meio de AccountInfoDouble com ACCOUNT_BALANCE. Em seguida, obteremos tickSize e tickValue para m_tradeConfig.marketSymbol utilizando SymbolInfoDouble com SYMBOL_TRADE_TICK_SIZE e SYMBOL_TRADE_TICK_VALUE. Se algum desses valores for igual a zero, registraremos o erro com Print e retornaremos -1 para evitar cálculos inválidos. Depois, calcularemos lotValue utilizando riskPips, _Point, tickSize e tickValue. Se o resultado for zero, registraremos outro erro e retornaremos -1. Por fim, retornaremos o tamanho do lote normalizado com NormalizeDouble para duas casas decimais, buscando adequá-lo às exigências da corretora.

Em seguida, criaremos a função openOrder para abrir operações. Inicializaremos ticket e openPrice e verificaremos o valor de orderType. Para ORDER_TYPE_BUY, obteremos o preço de compra com getMarketAsk e o normalizaremos utilizando NormalizeDouble com Digits. Para ORDER_TYPE_SELL, utilizaremos getMarketBid. Se orderType contiver um valor inválido, exibiremos uma mensagem com Print e retornaremos -1. Determinaremos lotSize de acordo com m_lotOption: para FIXED_LOTSIZE, utilizaremos m_initialLotSize; para UNFIXED_LOTSIZE, chamaremos calculateLotSize com os parâmetros m_riskPercentage e m_riskPoints. Se lotSize for inválido, registraremos o erro com Print e retornaremos -1. Depois, abriremos a posição por meio de m_tradeExecutor.PositionOpen, passando m_tradeConfig.marketSymbol, orderType, lotSize, openPrice e FILE como comentário. Se a abertura for bem-sucedida, atribuiremos a ticket o valor retornado por ResultOrder e exibiremos o resultado com Print. Caso contrário, definiremos ticket como -1 e exibiremos uma mensagem de erro. Por fim, retornaremos o valor de ticket.

Depois disso, precisaremos inicializar os valores do sistema. Poderíamos utilizar uma função específica para essa finalidade, mas, para simplificar, recorreremos ao construtor da classe. É recomendável declará-lo na seção pública para que possa ser acessado pelas demais partes do programa. Também definiremos o destrutor.

public:
   //--- Constructor
   MarketZoneTrader(TradingLotSizeOptions lotOpt, double initLot, double riskPct, int riskPts, int maxOrds, bool restrictOrds, double targetPts, double sizePts) {
      //--- Constructor Start
      m_tradeConfig.currentState = INACTIVE;                           //--- Set initial state
      ArrayResize(m_tradeConfig.activeTickets, 0);                     //--- Initialize ticket array
      m_tradeConfig.zoneProfitSpan = targetPts * _Point;               //--- Set profit target
      m_tradeConfig.zoneRecoverySpan = sizePts * _Point;               //--- Set recovery zone
      m_lossTracker.tradeLossTracker = 0.0;                            //--- Initialize loss tracker
      m_lotOption = lotOpt;                                            //--- Set lot size option
      m_initialLotSize = initLot;                                      //--- Set initial lot
      m_riskPercentage = riskPct;                                      //--- Set risk percentage
      m_riskPoints = riskPts;                                          //--- Set risk points
      m_maxOrders = maxOrds;                                           //--- Set max positions
      m_restrictMaxOrders = restrictOrds;                              //--- Set restriction flag
      m_zoneTargetPoints = targetPts;                                  //--- Set target points
      m_zoneSizePoints = sizePts;                                      //--- Set zone points
      m_tradeConfig.marketSymbol = _Symbol;                            //--- Set symbol
      m_tradeConfig.tradeIdentifier = magicNumber;                     //--- Set magic number
      //--- Constructor End
   }

   //--- Destructor
   ~MarketZoneTrader() {
      //--- Destructor Start
      cleanup();                                                       //--- Release resources
      //--- Destructor End
   }

Prosseguiremos com a implementação do construtor e do destrutor da classe MarketZoneTrader em sua seção pública. Começaremos pelo construtor MarketZoneTrader, que recebe os parâmetros lotOpt, initLot, riskPct, riskPts, maxOrds, restrictOrds, targetPts e sizePts. Inicializaremos o estado de negociação definindo m_tradeConfig.currentState como INACTIVE, indicando que não há operações ativas. Em seguida, esvaziaremos o array m_tradeConfig.activeTickets utilizando ArrayResize com tamanho zero, preparando-o para armazenar novos tickets. Calcularemos m_tradeConfig.zoneProfitSpan e m_tradeConfig.zoneRecoverySpan multiplicando targetPts e sizePts, respectivamente, por _Point. Dessa forma, definiremos as distâncias correspondentes ao objetivo de lucro e à zona de recuperação em unidades de preço. Também inicializaremos m_lossTracker.tradeLossTracker com 0.0 para começar a acompanhar os lucros e prejuízos sem valores acumulados anteriormente.

Em seguida, atribuiremos os parâmetros de entrada aos atributos correspondentes da classe: lotOpt a m_lotOption, initLot a m_initialLotSize, riskPct a m_riskPercentage, riskPts a m_riskPoints, maxOrds a m_maxOrders, restrictOrds a m_restrictMaxOrders, targetPts a m_zoneTargetPoints e sizePts a m_zoneSizePoints. Definiremos m_tradeConfig.marketSymbol como _Symbol para operar o símbolo do gráfico atual e atribuiremos magicNumber a m_tradeConfig.tradeIdentifier para identificar exclusivamente as operações do EA. Com essa configuração, o EA utilizará os parâmetros definidos pelo usuário e estará preparado para iniciar as operações.

Em seguida, definiremos o destrutor ~MarketZoneTrader para liberar os recursos utilizados pela classe. Chamaremos a função cleanup para liberar todos os recursos alocados, como os handles dos indicadores, garantindo que o EA seja encerrado corretamente, sem vazamentos de memória. Vale lembrar que o construtor e o destrutor têm o mesmo nome da classe, mas o destrutor é precedido por um til (~). Sua função é realizar a limpeza necessária quando o objeto é destruído.

//--- Cleanup
void cleanup() {
   //--- Cleanup Start
   IndicatorRelease(m_handleRsi);                                   //--- Release RSI handle
   ArrayFree(m_rsiBuffer);                                          //--- Free RSI buffer
   IndicatorRelease(m_handleEnvUpper);                              //--- Release upper Envelopes handle
   ArrayFree(m_envUpperBandBuffer);                                 //--- Free upper Envelopes buffer
   IndicatorRelease(m_handleEnvLower);                              //--- Release lower Envelopes handle
   ArrayFree(m_envLowerBandBuffer);                                 //--- Free lower Envelopes buffer
   //--- Cleanup End
}

Utilizaremos IndicatorRelease para liberar os handles dos indicadores e ArrayFree para liberar os arrays utilizados no armazenamento de dados. Como estamos tratando dos indicadores, também definiremos a função de inicialização que será chamada quando o programa for iniciado.

//--- Getters
TradeState getCurrentState() {
   //--- Get Current State Start
   return m_tradeConfig.currentState;                               //--- Return trade state
   //--- Get Current State End
}

double getZoneTargetHigh() {
   //--- Get Target High Start
   return m_zoneBounds.zoneTargetHigh;                              //--- Return profit target high
   //--- Get Target High End
}

double getZoneTargetLow() {
   //--- Get Target Low Start
   return m_zoneBounds.zoneTargetLow;                               //--- Return profit target low
   //--- Get Target Low End
}

double getZoneHigh() {
   //--- Get Zone High Start
   return m_zoneBounds.zoneHigh;                                    //--- Return recovery zone high
   //--- Get Zone High End
}

double getZoneLow() {
   //--- Get Zone Low Start
   return m_zoneBounds.zoneLow;                                     //--- Return recovery zone low
   //--- Get Zone Low End
}

//--- Initialization
int initialize() {
   //--- Initialization Start
   m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number
   int totalPositions = PositionsTotal();                               //--- Get total positions
   
   for (int i = 0; i < totalPositions; i++) {                           //--- Iterate positions
      ulong ticket = PositionGetTicket(i);                              //--- Get ticket
      if (PositionSelectByTicket(ticket)) {                             //--- Select position
         if (PositionGetString(POSITION_SYMBOL) == m_tradeConfig.marketSymbol && PositionGetInteger(POSITION_MAGIC) == m_tradeConfig.tradeIdentifier) { //--- Check symbol and magic
            if (activateTrade(ticket)) {                                //--- Activate position
               Print("Existing position activated: Ticket=", ticket);   //--- Log activation
            } else {
               Print("Failed to activate existing position: Ticket=", ticket); //--- Log failure
            }
         }
      }
   }
   
   m_handleRsi = iRSI(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 8, PRICE_CLOSE); //--- Initialize RSI
   if (m_handleRsi == INVALID_HANDLE) {                             //--- Check RSI
      Print("Failed to initialize RSI indicator");                  //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvUpper = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1); //--- Initialize upper Envelopes
   if (m_handleEnvUpper == INVALID_HANDLE) {                        //--- Check upper Envelopes
      Print("Failed to initialize upper Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   m_handleEnvLower = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4); //--- Initialize lower Envelopes
   if (m_handleEnvLower == INVALID_HANDLE) {                        //--- Check lower Envelopes
      Print("Failed to initialize lower Envelopes indicator");      //--- Log failure
      return INIT_FAILED;                                           //--- Return failure
   }
   
   ArraySetAsSeries(m_rsiBuffer, true);                             //--- Set RSI buffer
   ArraySetAsSeries(m_envUpperBandBuffer, true);                    //--- Set upper Envelopes buffer
   ArraySetAsSeries(m_envLowerBandBuffer, true);                    //--- Set lower Envelopes buffer
   
   Print("EA initialized successfully");                            //--- Log success
   return INIT_SUCCEEDED;                                           //--- Return success
   //--- Initialization End
}

Começaremos criando funções getter simples para acessar os principais dados de negociação. A função getCurrentState retornará m_tradeConfig.currentState, permitindo verificar se o sistema está no estado INACTIVE, RUNNING ou TERMINATING. Em seguida, criaremos getZoneTargetHigh e getZoneTargetLow para obter os valores de m_zoneBounds.zoneTargetHigh e m_zoneBounds.zoneTargetLow, que representam os preços-alvo de lucro das operações. Depois, adicionaremos getZoneHigh e getZoneLow para acessar m_zoneBounds.zoneHigh e m_zoneBounds.zoneLow, correspondentes aos limites da zona de recuperação.

Na sequência, criaremos a função initialize para configurar nosso EA. Primeiro, utilizaremos SetExpertMagicNumber para atribuir m_tradeConfig.tradeIdentifier a m_tradeExecutor, permitindo identificar as operações executadas pelo EA. Depois, verificaremos as posições existentes com PositionsTotal e percorreremos cada uma delas em um laço, obtendo os respectivos tickets por meio de PositionGetTicket. Se PositionSelectByTicket selecionar a posição com sucesso e ela corresponder a m_tradeConfig.marketSymbol e m_tradeConfig.tradeIdentifier, conforme os valores obtidos com PositionGetString e PositionGetInteger, chamaremos activateTrade para assumir seu gerenciamento. Registraremos o sucesso ou a falha dessa operação com Print.

Em seguida, configuraremos os indicadores. Criaremos o handle do RSI com iRSI para m_tradeConfig.marketSymbol, utilizando um período de 8 barras, o timeframe atual e PRICE_CLOSE. Se m_handleRsi for igual a INVALID_HANDLE, registraremos o erro com Print e retornaremos INIT_FAILED. Depois, inicializaremos os indicadores Envelopes: criaremos m_handleEnvUpper com iEnvelopes, utilizando uma média móvel simples de 150 períodos, desvio de 0,1 e PRICE_CLOSE; para m_handleEnvLower, utilizaremos um período de 95 barras e desvio de 1,4. Se algum dos handles for igual a INVALID_HANDLE, registraremos o erro e retornaremos INIT_FAILED. Por fim, configuraremos m_rsiBuffer, m_envUpperBandBuffer e m_envLowerBandBuffer como arrays de séries temporais por meio de ArraySetAsSeries, registraremos a inicialização bem-sucedida com Print e retornaremos INIT_SUCCEEDED. Agora podemos chamar essa função no manipulador de eventos OnInit, mas primeiro precisaremos criar uma instância da classe.

//--- Global Instance
MarketZoneTrader *trader = NULL;                                        //--- Declare trader instance

Nesta etapa, declararemos um ponteiro global para a classe MarketZoneTrader, que permitirá acessar uma única instância do nosso sistema de negociação. Criaremos a variável trader como um ponteiro para MarketZoneTrader e a inicializaremos com NULL. Dessa forma, poderemos utilizar a mesma instância em todo o EA para gerenciar as operações de negociação, incluindo a inicialização de operações, a execução de ordens e o gerenciamento da recuperação por zonas. Ao inicializar trader com NULL, deixaremos o ponteiro preparado para receber uma instância da classe posteriormente, evitando que o programa tente acessar o objeto antes de sua criação e configuração. Agora podemos chamar a função de inicialização.

int OnInit() {
   //--- EA Initialization Start
   trader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints, zoneSizePoints); //--- Create trader instance
   return trader.initialize();                                           //--- Initialize EA
   //--- EA Initialization End
}

No manipulador de eventos OnInit, começaremos criando uma instância da classe MarketZoneTrader e atribuindo-a ao ponteiro global trader. Passaremos os parâmetros de entrada definidos pelo usuário, lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints e zoneSizePoints, ao construtor para configurar o sistema de negociação. Em seguida, chamaremos a função initialize por meio de trader para inicializar o EA, incluindo a identificação das operações, a verificação das posições existentes e a inicialização dos indicadores. Por fim, retornaremos o resultado dessa função, indicando se a inicialização foi bem-sucedida. Essa rotina prepara o EA para iniciar as operações com as configurações especificadas. Após a compilação, obtemos o seguinte resultado.

INICIALIZAÇÃO

Como podemos observar na imagem, o programa foi inicializado com sucesso. No entanto, surge um problema quando tentamos remover o EA. Observe a figura abaixo.

VAZAMENTO DE MEMÓRIA

A imagem mostra que alguns objetos não foram destruídos, causando um vazamento de memória. Para resolver esse problema, precisamos liberar os objetos que permanecem alocados. Para isso, utilizaremos a seguinte lógica.

void OnDeinit(const int reason) {
   //--- EA Deinitialization Start
   if (trader != NULL) {                                                 //--- Check trader existence
      delete trader;                                                     //--- Delete trader
      trader = NULL;                                                     //--- Clear pointer
      Print("EA deinitialized");                                         //--- Log deinitialization
   }
   //--- EA Deinitialization End
}

No manipulador de eventos OnDeinit, começaremos verificando se o ponteiro trader é diferente de NULL, confirmando que existe uma instância de MarketZoneTrader a ser destruída. Nesse caso, utilizaremos o operador delete para destruir o objeto apontado por trader e liberar a memória alocada, evitando vazamentos de memória. Em seguida, definiremos trader como NULL para impedir acessos acidentais à memória já liberada. Por fim, exibiremos uma mensagem com Print para confirmar a desinicialização do EA. Essa rotina permitirá encerrar o EA corretamente, liberando os recursos utilizados. Agora podemos prosseguir com a implementação da lógica principal de avaliação dos sinais de negociação e gerenciamento das posições abertas. Para isso, precisaremos de algumas funções auxiliares.

//--- Position Management
bool activateTrade(ulong ticket) {
   //--- Position Activation Start
   m_tradeConfig.currentState = INACTIVE;                           //--- Set state to inactive
   ArrayResize(m_tradeConfig.activeTickets, 0);                     //--- Clear tickets
   m_lossTracker.tradeLossTracker = 0.0;                            //--- Reset loss tracker
   if (!configureTrade(ticket)) {                                    //--- Configure trade
      return false;                                                 //--- Return failure
   }
   storeTradeTicket(ticket);                                        //--- Store ticket
   if (m_tradeConfig.direction == ORDER_TYPE_BUY) {                 //--- Handle buy position
      m_zoneBounds.zoneHigh = m_tradeConfig.openPrice;              //--- Set zone high
      m_zoneBounds.zoneLow = m_zoneBounds.zoneHigh - m_tradeConfig.zoneRecoverySpan; //--- Set zone low
      m_tradeConfig.accumulatedBuyVolume = m_tradeConfig.initialVolume; //--- Set buy volume
      m_tradeConfig.accumulatedSellVolume = 0.0;                    //--- Reset sell volume
   } else {                                                         //--- Handle sell position
      m_zoneBounds.zoneLow = m_tradeConfig.openPrice;               //--- Set zone low
      m_zoneBounds.zoneHigh = m_zoneBounds.zoneLow + m_tradeConfig.zoneRecoverySpan; //--- Set zone high
      m_tradeConfig.accumulatedSellVolume = m_tradeConfig.initialVolume; //--- Set sell volume
      m_tradeConfig.accumulatedBuyVolume = 0.0;                     //--- Reset buy volume
   }
   m_zoneBounds.zoneTargetHigh = m_zoneBounds.zoneHigh + m_tradeConfig.zoneProfitSpan; //--- Set target high
   m_zoneBounds.zoneTargetLow = m_zoneBounds.zoneLow - m_tradeConfig.zoneProfitSpan; //--- Set target low
   m_tradeConfig.currentState = RUNNING;                            //--- Set state to running
   return true;                                                     //--- Return success
   //--- Position Activation End
}

//--- Tick Processing
void processTick() {
   //--- Tick Processing Start
   double askPrice = NormalizeDouble(getMarketAsk(), Digits());     //--- Get ask price
   double bidPrice = NormalizeDouble(getMarketBid(), Digits());     //--- Get bid price
   
   if (!isNewBar()) return;                                         //--- Exit if not new bar
   
   if (!CopyBuffer(m_handleRsi, 0, 0, 3, m_rsiBuffer)) {            //--- Load RSI data
      Print("Error loading RSI data. Reverting.");                  //--- Log RSI failure
      return;                                                       //--- Exit
   }
   
   if (!CopyBuffer(m_handleEnvUpper, 0, 0, 3, m_envUpperBandBuffer)) { //--- Load upper Envelopes
      Print("Error loading upper envelopes data. Reverting.");         //--- Log failure
      return;                                                          //--- Exit
   }
   
   if (!CopyBuffer(m_handleEnvLower, 1, 0, 3, m_envLowerBandBuffer)) { //--- Load lower Envelopes
      Print("Error loading lower envelopes data. Reverting.");         //--- Log failure
      return;                                                          //--- Exit
   }
   
   int ticket = 0;                                                     //--- Initialize ticket
   
   const int rsiOverbought = 70;                                       //--- Set RSI overbought level
   const int rsiOversold = 30;                                         //--- Set RSI oversold level
   
   if (m_rsiBuffer[1] < rsiOversold && m_rsiBuffer[2] > rsiOversold && m_rsiBuffer[0] < rsiOversold) { //--- Check buy signal
      if (askPrice > m_envUpperBandBuffer[0]) {                        //--- Confirm price above upper Envelopes
         if (!m_restrictMaxOrders || PositionsTotal() < m_maxOrders) { //--- Check position limit
            ticket = openOrder(ORDER_TYPE_BUY, 0, 0);                  //--- Open buy order
         }
      }
   } else if (m_rsiBuffer[1] > rsiOverbought && m_rsiBuffer[2] < rsiOverbought && m_rsiBuffer[0] > rsiOverbought) { //--- Check sell signal
      if (bidPrice < m_envLowerBandBuffer[0]) {                        //--- Confirm price below lower Envelopes
         if (!m_restrictMaxOrders || PositionsTotal() < m_maxOrders) { //--- Check position limit
            ticket = openOrder(ORDER_TYPE_SELL, 0, 0);                 //--- Open sell order
         }
      }
   }
   
   if (ticket > 0) {                                                //--- Check if trade opened
      if (activateTrade(ticket)) {                                  //--- Activate position
         Print("New position activated: Ticket=", ticket);          //--- Log activation
      } else {
         Print("Failed to activate new position: Ticket=", ticket); //--- Log failure
      }
   }
   //--- Tick Processing End
}

Nesta etapa, continuaremos o desenvolvimento do programa implementando as funções activateTrade e processTick na classe MarketZoneTrader para gerenciar posições e processar os ticks do mercado. Começaremos pela função activateTrade, que ativa uma operação a partir de um ticket específico. Primeiro, definiremos m_tradeConfig.currentState como INACTIVE e esvaziaremos o array m_tradeConfig.activeTickets utilizando ArrayResize para limpar a lista de tickets. Também redefiniremos m_lossTracker.tradeLossTracker como 0.0 e, em seguida, chamaremos configureTrade com o ticket informado. Se a configuração falhar, retornaremos false. Caso contrário, armazenaremos o ticket com storeTradeTicket. Para uma operação de compra, quando m_tradeConfig.direction for igual a ORDER_TYPE_BUY, definiremos m_zoneBounds.zoneHigh como m_tradeConfig.openPrice e calcularemos m_zoneBounds.zoneLow subtraindo m_tradeConfig.zoneRecoverySpan desse preço. Também atribuiremos m_tradeConfig.initialVolume a m_tradeConfig.accumulatedBuyVolume e reinicializaremos m_tradeConfig.accumulatedSellVolume.

Para uma operação de venda, definiremos m_zoneBounds.zoneLow como m_tradeConfig.openPrice e calcularemos m_zoneBounds.zoneHigh adicionando m_tradeConfig.zoneRecoverySpan ao preço de abertura. Também ajustaremos os volumes acumulados de compra e venda conforme a direção da operação. Em seguida, calcularemos m_zoneBounds.zoneTargetHigh e m_zoneBounds.zoneTargetLow com base em m_tradeConfig.zoneProfitSpan, alteraremos m_tradeConfig.currentState para RUNNING e retornaremos true.

Depois, criaremos a função processTick para processar os ticks do mercado. Obteremos askPrice e bidPrice por meio de getMarketAsk e getMarketBid, normalizando os preços com NormalizeDouble e Digits. Se isNewBar retornar false, encerraremos o processamento para economizar recursos. Em seguida, utilizaremos CopyBuffer para copiar os dados de m_handleRsi para m_rsiBuffer, de m_handleEnvUpper para m_envUpperBandBuffer e de m_handleEnvLower para m_envLowerBandBuffer. Se alguma dessas operações falhar, registraremos o erro com Print e interromperemos o processamento. Para avaliar os sinais de negociação, definiremos rsiOverought como 70 e rsiOversold como 30.

Se m_rsiBuffer indicar sobrevenda e askPrice estiver acima da banda superior representada por m_envUpperBandBuffer, utilizaremos openOrder para abrir uma operação de compra, desde que m_restrictMaxOrders seja false ou PositionsTotal seja inferior a m_maxOrders. Quando o RSI indicar sobrecompra e bidPrice estiver abaixo da banda inferior representada por m_envLowerBandBuffer, abriremos uma operação de venda. Se a abertura retornar um ticket válido, chamaremos activateTrade e registraremos o resultado no log. Agora podemos chamar processTick no manipulador de eventos OnTick para avaliar os sinais de negociação e inicializar as posições.

void OnTick() {
   //--- Tick Handling Start
   if (trader != NULL) {                                                 //--- Check trader existence
      trader.processTick();                                              //--- Process tick
   }
   //--- Tick Handling End
}

No manipulador de eventos OnTick, começaremos verificando se o ponteiro trader, que referencia nossa instância da classe MarketZoneTrader, é diferente de NULL, confirmando que o sistema de negociação foi inicializado. Se o objeto existir, chamaremos trader.processTick para processar os ticks recebidos, avaliar as posições, verificar os sinais dos indicadores e executar operações quando necessário. Após a compilação, obtemos o seguinte resultado.

POSIÇÃO INICIAL

Como podemos observar na imagem, identificamos e avaliamos um sinal de negociação e abrimos uma posição de compra. Agora precisamos implementar o gerenciamento das posições abertas. Para manter a modularidade do código, distribuiremos essa lógica em funções específicas.

//--- Market Tick Evaluation
void evaluateMarketTick() {
   //--- Tick Evaluation Start
   if (m_tradeConfig.currentState == INACTIVE) return;              //--- Exit if inactive
   if (m_tradeConfig.currentState == TERMINATING) {                 //--- Check terminating state
      finalizePosition();                                           //--- Finalize position
      return;                                                       //--- Exit
   }
}

Nesta etapa, implementaremos a função evaluateMarketTick na classe MarketZoneTrader para avaliar as condições de mercado enquanto houver operações ativas. Primeiro, verificaremos se m_tradeConfig.currentState é igual a INACTIVE. Nesse caso, encerraremos imediatamente a execução da função para evitar processamento desnecessário quando não houver operações ativas. Em seguida, verificaremos se m_tradeConfig.currentState é igual a TERMINATING. Se essa condição for atendida, chamaremos finalizePosition para fechar todas as posições abertas e concluir o ciclo de negociação, encerrando a execução da função logo depois. A seguir, apresentamos a função responsável pelo fechamento das operações.

//--- Position Finalization
bool finalizePosition() {
   //--- Position Finalization Start
   m_tradeConfig.currentState = TERMINATING;                        //--- Set terminating state
   TradeMetrics metrics = {true, 0.0, 0.0};                         //--- Initialize metrics
   closeActiveTrades(metrics);                                       //--- Close all trades
   if (metrics.operationSuccess) {                                  //--- Check success
      ArrayResize(m_tradeConfig.activeTickets, 0);                  //--- Clear tickets
      m_tradeConfig.currentState = INACTIVE;                        //--- Set inactive state
      Print("Position closed successfully");                        //--- Log success
   } else {
      Print("Failed to close position");                            //--- Log failure
   }
   return metrics.operationSuccess;                                 //--- Return status
   //--- Position Finalization End
}

Para começar, definiremos m_tradeConfig.currentState como TERMINATING, indicando que o ciclo de negociação está sendo encerrado. Isso evita que a rotina de gerenciamento continue sendo executada durante o fechamento das posições. Em seguida, inicializaremos uma instância da estrutura TradeMetrics chamada metrics, definindo operationSuccess como true, totalVolume como 0.0 e netProfitLoss como 0.0 para acompanhar os resultados do fechamento das operações. Chamaremos closeActiveTrades com metrics para encerrar todas as posições abertas registradas em m_tradeConfig.activeTickets. Se metrics.operationSuccess permanecer true, esvaziaremos o array m_tradeConfig.activeTickets utilizando ArrayResize para limpar a lista de tickets, definiremos m_tradeConfig.currentState como INACTIVE para indicar que o sistema está inativo e registraremos a conclusão bem-sucedida com Print.

Se ocorrer alguma falha no fechamento, registraremos o erro com Print. Por fim, retornaremos metrics.operationSuccess para indicar se o procedimento foi concluído com sucesso. Caso não seja necessário encerrar posições nesta etapa, poderemos prosseguir com a avaliação dos preços em relação aos limites da zona de recuperação e aos níveis-alvo. Começaremos pelas posições de compra.

double currentPrice;                                             //--- Initialize price
if (m_tradeConfig.direction == ORDER_TYPE_BUY) {                 //--- Handle buy position
   currentPrice = getMarketBid();                                //--- Get bid price
   if (currentPrice > m_zoneBounds.zoneTargetHigh) {             //--- Check profit target
      Print("Closing position: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh); //--- Log closure
      finalizePosition();                                        //--- Close position
      return;                                                    //--- Exit
   } else if (currentPrice < m_zoneBounds.zoneLow) {             //--- Check recovery trigger
      Print("Triggering recovery trade: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow); //--- Log recovery
      triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice);       //--- Open sell recovery
   }
}

Em seguida, implementaremos a lógica de gerenciamento das posições de compra na função evaluateMarketTick da classe MarketZoneTrader. Primeiro, declararemos a variável currentPrice para armazenar a cotação atual. Se m_tradeConfig.direction for igual a ORDER_TYPE_BUY, obteremos o preço bid por meio de getMarketBid e o atribuiremos a currentPrice, pois essa é a cotação utilizada para encerrar uma posição de compra. Depois, verificaremos se currentPrice ultrapassou m_zoneBounds.zoneTargetHigh. Se isso ocorrer, registraremos o sinal de fechamento com Print, exibindo o preço bid e o nível-alvo, chamaremos finalizePosition para encerrar a operação e sairemos da função com return.

Se currentPrice cair abaixo de m_zoneBounds.zoneLow, registraremos o sinal de recuperação com Print e chamaremos triggerRecoveryTrade, passando ORDER_TYPE_SELL e currentPrice para abrir uma operação de venda e reduzir as perdas. Essa lógica permite encerrar posições de compra quando o preço atinge o objetivo de lucro ou iniciar a recuperação quando o mercado se move contra a posição, tornando a estratégia mais flexível. A seguir, apresentamos a lógica da função responsável pela abertura das operações de recuperação.

//--- Recovery Trade Handling
void triggerRecoveryTrade(ENUM_ORDER_TYPE tradeDirection, double price) {
   //--- Recovery Trade Start
   TradeMetrics metrics = {true, 0.0, 0.0};                         //--- Initialize metrics
   closeActiveTrades(metrics);                                      //--- Close existing trades
   for (int i = 0; i < 10 && !metrics.operationSuccess; i++) {      //--- Retry closure
      Sleep(1000);                                                  //--- Wait 1 second
      metrics.operationSuccess = true;                              //--- Reset success flag
      closeActiveTrades(metrics);                                   //--- Retry closure
   }
   m_lossTracker.tradeLossTracker += metrics.netProfitLoss;         //--- Update loss tracker
   if (m_lossTracker.tradeLossTracker > 0 && metrics.operationSuccess) { //--- Check positive profit
      Print("Closing position due to positive profit: ", m_lossTracker.tradeLossTracker); //--- Log closure
      finalizePosition();                                           //--- Close position
      m_lossTracker.tradeLossTracker = 0.0;                         //--- Reset loss tracker
      return;                                                       //--- Exit
   }
   double tradeSize = determineRecoverySize(tradeDirection);        //--- Calculate trade size
   ulong ticket = openMarketTrade(tradeDirection, tradeSize, price); //--- Open recovery trade
   if (ticket > 0) {                                                //--- Check if trade opened
      storeTradeTicket(ticket);                                     //--- Store ticket
      m_tradeConfig.direction = tradeDirection;                     //--- Update direction
      if (tradeDirection == ORDER_TYPE_BUY) m_tradeConfig.accumulatedBuyVolume += tradeSize; //--- Update buy volume
      else m_tradeConfig.accumulatedSellVolume += tradeSize;        //--- Update sell volume
      Print("Recovery trade opened: Ticket=", ticket, ", Direction=", EnumToString(tradeDirection), ", Volume=", tradeSize); //--- Log recovery trade
   }
   //--- Recovery Trade End
}

//--- Recovery Size Calculation
double determineRecoverySize(ENUM_ORDER_TYPE tradeDirection) {
   //--- Recovery Size Calculation Start
   double tradeSize = -m_lossTracker.tradeLossTracker / m_tradeConfig.zoneProfitSpan; //--- Calculate lot size
   tradeSize = MathCeil(tradeSize / getMarketVolumeStep()) * getMarketVolumeStep(); //--- Round to volume step
   return tradeSize;                                                //--- Return trade size
   //--- Recovery Size Calculation End
}

Para tratar as situações em que o movimento do mercado exige o início da recuperação, implementaremos a função triggerRecoveryTrade, responsável por gerenciar as operações de recuperação quando o preço se move contra nossa posição. Primeiro, inicializaremos uma instância da estrutura TradeMetrics chamada metrics, definindo operationSuccess como true, totalVolume como 0.0 e netProfitLoss como 0.0. Em seguida, chamaremos closeActiveTrades com metrics para encerrar as posições existentes. Se metrics.operationSuccess for false, repetiremos a tentativa de fechamento até 10 vezes, aguardando um segundo entre as tentativas com Sleep e reinicializando operationSuccess antes de cada nova tentativa.

Atualizaremos m_lossTracker.tradeLossTracker adicionando metrics.netProfitLoss ao valor acumulado. Se m_lossTracker.tradeLossTracker for positivo e metrics.operationSuccess for true, registraremos o fechamento com Print, chamaremos finalizePosition, redefiniremos m_lossTracker.tradeLossTracker como 0.0 e sairemos da função com return. Caso contrário, calcularemos o volume da operação de recuperação, tradeSize, utilizando determineRecoverySize com tradeDirection. Em seguida, abriremos uma nova operação por meio de openMarketTrade, passando os parâmetros tradeDirection, tradeSize e price.

Se a função retornar um ticket válido, nós o armazenaremos com storeTradeTicket, atualizaremos m_tradeConfig.direction e ajustaremos m_tradeConfig.accumulatedBuyVolume ou m_tradeConfig.accumulatedSellVolume conforme tradeDirection. Também registraremos a operação com Print, utilizando EnumToString. Em seguida, criaremos a função determineRecoverySize para calcular o tamanho do lote das operações de recuperação. Calcularemos tradeSize dividindo o valor negativo de m_lossTracker.tradeLossTracker por m_tradeConfig.zoneProfitSpan, determinando o volume necessário para compensar as perdas. Depois, ajustaremos tradeSize ao incremento de volume permitido pela corretora, arredondando para cima com MathCeil e utilizando getMarketVolumeStep, e retornaremos o resultado. Com isso, teremos implementado a lógica de recuperação e poderemos prosseguir com o gerenciamento das posições de venda. Como essa lógica é inversa à utilizada para as posições de compra, não precisaremos nos deter nos mesmos detalhes. Ao final, a implementação completa terá a seguinte estrutura.

//--- Market Tick Evaluation
void evaluateMarketTick() {
   //--- Tick Evaluation Start
   if (m_tradeConfig.currentState == INACTIVE) return;              //--- Exit if inactive
   if (m_tradeConfig.currentState == TERMINATING) {                 //--- Check terminating state
      finalizePosition();                                           //--- Finalize position
      return;                                                       //--- Exit
   }
   double currentPrice;                                             //--- Initialize price
   if (m_tradeConfig.direction == ORDER_TYPE_BUY) {                 //--- Handle buy position
      currentPrice = getMarketBid();                                //--- Get bid price
      if (currentPrice > m_zoneBounds.zoneTargetHigh) {             //--- Check profit target
         Print("Closing position: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh); //--- Log closure
         finalizePosition();                                        //--- Close position
         return;                                                    //--- Exit
      } else if (currentPrice < m_zoneBounds.zoneLow) {             //--- Check recovery trigger
         Print("Triggering recovery trade: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow); //--- Log recovery
         triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice);       //--- Open sell recovery
      }
   } else if (m_tradeConfig.direction == ORDER_TYPE_SELL) {         //--- Handle sell position
      currentPrice = getMarketAsk();                                //--- Get ask price
      if (currentPrice < m_zoneBounds.zoneTargetLow) {              //--- Check profit target
         Print("Closing position: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow); //--- Log closure
         finalizePosition();                                        //--- Close position
         return;                                                    //--- Exit
      } else if (currentPrice > m_zoneBounds.zoneHigh) {            //--- Check recovery trigger
         Print("Triggering recovery trade: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh); //--- Log recovery
         triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice);        //--- Open buy recovery
      }
   }
   //--- Tick Evaluation End
}

Agora, a função permite gerenciar a recuperação em ambas as direções de negociação. Após a compilação, obtemos o seguinte resultado.

RESULTADO FINAL

Como podemos observar na imagem, conseguimos gerenciar as posições abertas a partir de sinais de reação do preço no contexto da tendência. Resta testar o programa com dados históricos.


Backtesting

Após realizar um backtesting detalhado, obtivemos os seguintes resultados.

Gráfico do backtesting:

GRÁFICO

Relatório do backtesting:

RELATÓRIO


Conclusão

Desenvolvemos um programa robusto em MQL5 que implementa um sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes, combinando o índice de força relativa (RSI) e o Envelopes para identificar oportunidades de negociação e gerenciar perdas por meio de zonas de recuperação bem definidas. Adotamos a programação orientada a objetos (OOP) e utilizamos componentes como a classe MarketZoneTrader, as estruturas TradeConfig e ZoneBoundaries e as funções processTick e triggerRecoveryTrade. Com isso, criamos um sistema flexível, que pode ser adaptado a diferentes condições de mercado por meio do ajuste de parâmetros como zoneTargetPoints e riskPercentage.

Aviso: Este artigo tem finalidade exclusivamente educacional. A negociação nos mercados financeiros envolve riscos significativos, e a volatilidade do mercado pode causar perdas. Teste cuidadosamente o programa com dados históricos e avalie e monitore todos os riscos envolvidos antes de utilizá-lo em operações reais.

Com base nos conhecimentos adquiridos neste artigo, você poderá aprimorar o sistema de recuperação por zonas ou adaptar sua lógica para desenvolver novas estratégias de negociação, ampliando seus conhecimentos sobre trading algorítmico. Boas operações!

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

Últimos Comentários | Ir para discussão (2)
Sabrina Hellal
Sabrina Hellal | 9 jul. 2025 em 13:44
Muito obrigado 🙏
Allan Munene Mutiiria
Allan Munene Mutiiria | 9 jul. 2025 em 16:46
Sabrina Hellal #:
Muito obrigado 🙏

De nada. Obrigado

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.
Arbitragem estatística com ações cointegradas (Parte 1): testes de cointegração de Engle-Granger e Johansen Arbitragem estatística com ações cointegradas (Parte 1): testes de cointegração de Engle-Granger e Johansen
Este artigo apresenta uma introdução acessível aos testes de cointegração mais utilizados, voltada para traders, além de um guia prático para interpretar seus resultados. Os testes de Engle-Granger e Johansen permitem identificar pares ou grupos de ativos com relações estatisticamente significativas e uma dinâmica comum de longo prazo. O teste de Johansen é especialmente útil para carteiras com três ou mais ativos, pois avalia simultaneamente a força dos vetores de cointegração que relacionam todos os instrumentos.
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.
Implementação em MQL5 de módulos úteis de outras linguagens (Parte 1): Criação de uma biblioteca SQLite3 semelhante à do Python Implementação em MQL5 de módulos úteis de outras linguagens (Parte 1): Criação de uma biblioteca SQLite3 semelhante à do Python
O módulo sqlite3 do Python oferece uma maneira simples, rápida e prática de trabalhar com bancos de dados SQLite. Neste artigo, criaremos um módulo semelhante com base nas funções nativas do MQL5 para bancos de dados, com o objetivo de tornar o uso do SQLite3 em MQL5 tão simples e conveniente quanto em Python.