English Русский 中文 Español Deutsch 日本語
preview
Automatizando Estratégias de Negociação em MQL5 (Parte 3): O Sistema Zone Recovery RSI para Gestão Dinâmica de Operações

Automatizando Estratégias de Negociação em MQL5 (Parte 3): O Sistema Zone Recovery RSI para Gestão Dinâmica de Operações

MetaTrader 5Negociação |
89 8
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior (Parte 2 da série), demonstramos como transformar a Estratégia Kumo Breakout em um Expert Advisor (EA) totalmente funcional usando a MetaQuotes Language 5 (MQL5). Neste artigo (Parte 3), focamos no Sistema Zone Recovery RSI, uma estratégia avançada projetada para gerenciar operações e recuperar perdas de forma dinâmica. Esse sistema combina o Índice de Força Relativa (RSI) para acionar sinais de entrada com um mecanismo de Zone Recovery que posiciona operações contrárias quando o mercado se move contra a posição inicial. O objetivo é mitigar rebaixamentos e melhorar a lucratividade geral adaptando-se às condições de mercado.

Percorremos o processo de codificação da lógica de recuperação, gerenciamento de posições com dimensionamento dinâmico de lote e utilização do RSI para sinais de entrada e recuperação de operações. Ao final deste artigo, você terá uma compreensão clara de como implementar o Sistema Zone Recovery RSI, testar sua performance usando o Strategy Tester do MQL5 e otimizá-lo para melhor gestão de risco e retorno. O artigo está estruturado da seguinte forma para facilitar o entendimento.

  1. Design da Estratégia e Conceitos-Chave
  2. Implementação em MQL5
  3. Backtesting e Análise de Performance
  4. Conclusão


Design da Estratégia e Conceitos-Chave

O Sistema Zone Recovery RSI combina o indicador Índice de Força Relativa (RSI) para entradas de operações com um mecanismo de Zone Recovery para gerenciar movimentos adversos de preço. As entradas são acionadas quando o RSI cruza níveis-chave — tipicamente 30 para sobrevenda (compra) e 70 para sobrecompra (venda). No entanto, o verdadeiro poder do sistema está em sua capacidade de recuperar operações perdedoras usando um modelo de Zone Recovery bem estruturado.

O sistema Zone Recovery estabelece quatro níveis críticos de preço para cada operação: Zone High, Zone Low, Target High e Target Low. Quando uma operação é aberta, esses níveis são calculados em relação ao preço de entrada. Para uma operação de compra, o Zone Low é definido abaixo do preço de entrada, enquanto o Zone High fica no preço de entrada. Para uma operação de venda, o Zone High fica acima do preço de entrada, enquanto o Zone Low se alinha a ele. Se o mercado ultrapassar o Zone Low (para compras) ou o Zone High (para vendas), uma operação contrária é acionada na direção oposta com um tamanho de lote maior, baseado em um multiplicador pré-definido. Os níveis Target High e Target Low definem os pontos de realização de lucro para compras e vendas, garantindo que as operações sejam encerradas com lucro quando o mercado se move a favor da posição. Essa abordagem permite recuperar perdas enquanto controla o risco por meio de um dimensionamento sistemático de posição e ajustes de níveis. Aqui está uma ilustração que resume todo o modelo.

ILUSTRAÇÃO DO ZONE RECOVERY


Implementação em MQL5

Após aprender todas as teorias sobre a estratégia de negociação Zone Recovery, vamos então automatizar a teoria e criar um Expert Advisor (EA) em MetaQuotes Language 5 (MQL5) para o MetaTrader 5.

Para criar um Expert Advisor (EA), no seu terminal MetaTrader 5, clique na aba Ferramentas e selecione MetaQuotes Language Editor, ou simplesmente pressione F4 no seu teclado. Alternativamente, você pode clicar no ícone IDE (Integrated Development Environment) na barra de ferramentas. Isso abrirá o MetaQuotes Language Editor, que permite escrever robôs de negociação, indicadores técnicos, scripts e bibliotecas de funções. Quando o MetaEditor estiver aberto, na barra de ferramentas, navegue até a aba Arquivo e selecione Novo Arquivo, ou simplesmente pressione CTRL + N, para criar um novo documento. Alternativamente, você pode clicar no ícone Novo na barra de ferramentas. Isso resultará em um pop-up do Assistente MQL.

No Assistente que aparece, selecione Expert Advisor (template) e clique em Avançar. Nas propriedades gerais do Expert Advisor, na seção de nome, forneça o nome do arquivo do seu EA. Observe que, para especificar ou criar uma pasta caso ela não exista, você usa a barra invertida antes do nome do EA. Por exemplo, aqui temos "Experts" por padrão. Isso significa que nosso EA será criado na pasta Experts e podemos encontrá-lo lá. As outras seções são bastante simples, mas você pode seguir o link na parte inferior do Assistente para saber como realizar o processo com precisão.

NOVO NOME DO EA

Após fornecer o nome desejado para o arquivo do Expert Advisor, clique em Avançar, clique em Avançar novamente e depois clique em Concluir. Depois de fazer tudo isso, estamos prontos para codificar e programar nossa estratégia.

Primeiro, começamos definindo alguns metadados sobre o Expert Advisor (EA). Isso inclui o nome do EA, as informações de copyright e um link para o site da MetaQuotes. Também especificamos a versão do EA, definida como "1.00".

//+------------------------------------------------------------------+
//|                                      1. Zone Recovery RSI EA.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

Isso exibirá os metadados do sistema ao carregar o programa. Podemos então avançar para adicionar algumas variáveis globais que usaremos dentro do programa. Primeiro, incluímos uma instância de negociação usando #include no início do código-fonte. Isso nos dá acesso à "classe CTrade", que utilizaremos para criar um objeto de negociação. Isso é crucial, pois precisamos dele para abrir operações.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os colchetes angulares indicam que o arquivo Trade.mqh será retirado do diretório padrão (geralmente é terminal_installation_directory\MQL5\Include). O diretório atual não é incluído na busca. A linha pode ser colocada em qualquer parte do programa, mas normalmente todas as inclusões são posicionadas no início do código-fonte para uma melhor estrutura e referência mais simples. A declaração do objeto "obj_Trade" da classe CTrade nos dará acesso facilmente aos métodos contidos nessa classe, graças aos desenvolvedores do MQL5.

CLASSE CTRADE

Depois disso, precisamos declarar várias variáveis globais importantes que usaremos no sistema de negociação.

// Global variables for RSI logic
int rsiPeriod = 14;                //--- The period used for calculating the RSI indicator.
int rsiHandle;                     //--- Handle for the RSI indicator, used to retrieve RSI values.
double rsiBuffer[];                //--- Array to store the RSI values retrieved from the indicator.
datetime lastBarTime = 0;          //--- Holds the time of the last processed bar to prevent duplicate signals.

Para lidar com a geração de sinais, configuramos as variáveis globais necessárias para gerenciar a lógica do indicador Índice de Força Relativa (RSI). Primeiro, definimos "rsiPeriod" como 14, que determina o número de barras de preço usadas para calcular o RSI. Essa é uma configuração padrão na análise técnica, permitindo avaliar condições de sobrecompra ou sobrevenda no mercado. Em seguida, criamos "rsiHandle", uma referência para o indicador RSI. Esse identificador permitirá solicitar e recuperar valores do RSI na plataforma MetaTrader, possibilitando acompanhar os movimentos do indicador em tempo real.

Para armazenar esses valores do RSI, usamos "rsiBuffer", um array que guarda a saída do indicador. Analisaremos esse buffer para detectar pontos-chave de cruzamento, como quando o RSI cai abaixo de 30 (possível sinal de compra) ou sobe acima de 70 (possível sinal de venda). Por fim, introduzimos "lastBarTime", que armazena o horário da barra analisada mais recentemente. Essa variável garantirá que processemos apenas um sinal por barra, evitando que múltiplas operações sejam acionadas dentro da mesma barra. Depois disso, podemos definir uma classe que cuidará do mecanismo de recuperação.

// Global ZoneRecovery object
class ZoneRecovery {

//---

};

Aqui, criamos um sistema Zone Recovery usando uma classe chamada "ZoneRecovery", que serve como um contêiner para todas as variáveis, funções e lógica necessárias para gerenciar o processo de recuperação. Ao usar uma classe, podemos organizar o código em um objeto autocontido, permitindo gerenciar operações, acompanhar o progresso da recuperação e calcular níveis essenciais para cada ciclo de operação. Essa abordagem fornece melhor estrutura, reutilização e escalabilidade para lidar com múltiplas posições simultaneamente. Uma classe pode conter três encapsulamentos de membros na forma de private, protected e public. Primeiro, vamos definir os membros privados.

private:
   CTrade trade;                    //--- Object to handle trading operations.
   double initialLotSize;           //--- The initial lot size for the first trade.
   double currentLotSize;           //--- The lot size for the current trade in the sequence.
   double zoneSize;                 //--- Distance in points defining the range of the recovery zone.
   double targetSize;               //--- Distance in points defining the target profit range.
   double multiplier;               //--- Multiplier to increase lot size in recovery trades.
   string symbol;                   //--- Symbol for trading (e.g., currency pair).
   ENUM_ORDER_TYPE lastOrderType;   //--- Type of the last executed order (BUY or SELL).
   double lastOrderPrice;           //--- Price at which the last order was executed.
   double zoneHigh;                 //--- Upper boundary of the recovery zone.
   double zoneLow;                  //--- Lower boundary of the recovery zone.
   double zoneTargetHigh;           //--- Upper boundary for target profit range.
   double zoneTargetLow;            //--- Lower boundary for target profit range.
   bool isRecovery;                 //--- Flag indicating whether the recovery process is active.

Aqui, definimos as variáveis membro privadas da classe "ZoneRecovery", que armazenam dados essenciais para gerenciar o processo de zone recovery. Essas variáveis nos permitem acompanhar o estado da estratégia, calcular os níveis-chave da zona de recuperação e gerenciar a lógica de execução de operações.

Usamos o objeto "CTrade" para lidar com todas as operações de negociação, como abrir, modificar e fechar trades. A variável "initialLotSize" representa o tamanho do lote da primeira operação, enquanto "currentLotSize" acompanha o tamanho do lote para operações subsequentes de recuperação, que aumentam com base no "multiplier". As variáveis "zoneSize" e "targetSize" definem os limites críticos do sistema de recuperação. Especificamente, a zona de recuperação é delimitada por "zoneHigh" e "zoneLow", enquanto o alvo de lucro é definido por "zoneTargetHigh" e "zoneTargetLow".

Para acompanhar o fluxo das operações, armazenamos o "lastOrderType" (BUY ou SELL) e o "lastOrderPrice" no qual a operação anterior foi executada. Essas informações ajudam a determinar como posicionar as próximas operações em resposta aos movimentos do mercado. A variável "symbol" identifica o instrumento financeiro utilizado, enquanto o indicador "isRecovery" aponta se o sistema está ativamente em processo de recuperação. Mantendo essas variáveis como privadas, garantimos que somente a lógica interna da classe possa modificá-las, preservando a integridade e a precisão dos cálculos do sistema. Depois disso, agora podemos definir diretamente as funções da classe, em vez de chamá-las para depois defini-las, apenas por simplicidade. Assim, em vez de declarar as funções necessárias e defini-las mais tarde, simplesmente as declaramos e definimos de uma vez. Vamos definir primeiro a função responsável por calcular as zonas de recuperação.

// Calculate dynamic zones and targets
void CalculateZones() {
   if (lastOrderType == ORDER_TYPE_BUY) {
      zoneHigh = lastOrderPrice;                 //--- Upper boundary starts from the last BUY price.
      zoneLow = zoneHigh - zoneSize;             //--- Lower boundary is calculated by subtracting zone size.
      zoneTargetHigh = zoneHigh + targetSize;    //--- Profit target above the upper boundary.
      zoneTargetLow = zoneLow - targetSize;      //--- Buffer below the lower boundary for recovery trades.
   } else if (lastOrderType == ORDER_TYPE_SELL) {
      zoneLow = lastOrderPrice;                  //--- Lower boundary starts from the last SELL price.
      zoneHigh = zoneLow + zoneSize;             //--- Upper boundary is calculated by adding zone size.
      zoneTargetLow = zoneLow - targetSize;      //--- Buffer below the lower boundary for profit range.
      zoneTargetHigh = zoneHigh + targetSize;    //--- Profit target above the upper boundary.
   }
   Print("Zone recalculated: ZoneHigh=", zoneHigh, ", ZoneLow=", zoneLow, ", TargetHigh=", zoneTargetHigh, ", TargetLow=", zoneTargetLow);
}

Aqui, projetamos a função "CalculateZones", que desempenha um papel vital na definição dos níveis essenciais para nossa estratégia Zone Recovery. O objetivo principal dessa função é calcular os quatro limites essenciais — "zoneHigh", "zoneLow", "zoneTargetHigh" e "zoneTargetLow" — que guiam nossas entradas, recuperações e pontos de saída com lucro. Esses limites são dinâmicos e ajustam-se com base no tipo e no preço da última ordem executada, garantindo que mantenhamos controle sobre o processo de recuperação.

Se nossa última ordem foi uma BUY, definimos "zoneHigh" como o preço no qual a ordem BUY foi executada. A partir desse ponto, calculamos "zoneLow" subtraindo o "zoneSize" de "zoneHigh", criando uma faixa de recuperação abaixo do preço original da BUY. Para estabelecer nossos alvos de lucro, calculamos "zoneTargetHigh" adicionando "targetSize" ao "zoneHigh", enquanto "zoneTargetLow" é posicionado abaixo de "zoneLow" pelo mesmo "targetSize". Essa estrutura nos permitirá posicionar operações de recuperação abaixo da entrada original da BUY e definir os limites superior e inferior de nossa faixa de lucro.

Se nossa última ordem foi uma SELL, invertemos a lógica. Aqui, definimos "zoneLow" como o preço da última ordem SELL. Depois, calculamos "zoneHigh" adicionando "zoneSize" a "zoneLow", formando o limite superior da faixa de recuperação. Os alvos de lucro são estabelecidos calculando "zoneTargetLow" como um valor abaixo de "zoneLow", enquanto "zoneTargetHigh" é definido acima de "zoneHigh", ambos pela distância "targetSize". Essa configuração novamente nos permitirá iniciar operações de recuperação acima da entrada original da SELL, além de definir a zona de realização de lucro.

Ao final desse processo, teremos estabelecido os limites da zona de recuperação e os alvos de lucro tanto para operações BUY quanto SELL. Para auxiliar na depuração e avaliação da estratégia, usamos a função Print para exibir os valores de "zoneHigh", "zoneLow", "zoneTargetHigh" e "zoneTargetLow" no log. Assim, podemos definir outra função para cuidar da lógica de execução das operações.

// Open a trade based on the given type
bool OpenTrade(ENUM_ORDER_TYPE type) {
   if (type == ORDER_TYPE_BUY) {
      if (trade.Buy(currentLotSize, symbol)) {
         lastOrderType = ORDER_TYPE_BUY;         //--- Mark the last trade as BUY.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price.
         CalculateZones();                       //--- Recalculate zones after placing the trade.
         Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
         isRecovery = true;                      //--- Set recovery state to true after the first trade.
         return true;
      }
   } else if (type == ORDER_TYPE_SELL) {
      if (trade.Sell(currentLotSize, symbol)) {
         lastOrderType = ORDER_TYPE_SELL;        //--- Mark the last trade as SELL.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price.
         CalculateZones();                       //--- Recalculate zones after placing the trade.
         Print(isRecovery ? "RECOVERY SELL order placed" : "INITIAL SELL order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
         isRecovery = true;                      //--- Set recovery state to true after the first trade.
         return true;
      }
   }
   return false;                                 //--- Return false if the trade fails.
}

Aqui, definimos uma função chamada "OpenTrade" que retorna um valor boolean. O propósito dessa função é abrir uma operação dependendo se queremos executar uma ordem BUY ou SELL. Primeiro verificamos se o tipo de ordem solicitado é BUY. Se for, usamos a função "trade.Buy" para tentar abrir uma posição de compra com o lote atual e o símbolo especificado. Se a operação for aberta com sucesso, definimos "lastOrderType" como BUY, então armazenamos o preço atual do símbolo usando a função SymbolInfoDouble para obter o preço Bid. Esse preço representa o valor em que abrimos a posição. Recalculamos então as zonas de recuperação chamando a função "CalculateZones", que ajusta os níveis das zonas com base na nova posição.

Em seguida, imprimimos uma mensagem no log indicando se essa foi uma BUY inicial ou uma BUY de recuperação. Usamos um operador ternário para verificar se o indicador "isRecovery" está verdadeiro ou falso — se estiver verdadeiro, a mensagem afirmará que é uma ordem de recuperação; caso contrário, indicará que é a ordem inicial. Depois disso, definimos a flag "isRecovery" como verdadeira, sinalizando que quaisquer operações subsequentes serão consideradas parte do processo de recuperação. Por fim, a função retorna true, confirmando que a operação foi colocada com sucesso.

Se o tipo de ordem for SELL, seguimos os mesmos passos. Tentamos abrir uma posição SELL chamando a função "trade.Sell" com os mesmos parâmetros e, após a execução bem-sucedida, armazenamos "lastOrderPrice" e ajustamos as zonas de recuperação da mesma forma. Imprimimos uma mensagem indicando se essa foi uma SELL inicial ou uma SELL de recuperação, novamente usando um operador ternário para verificar a flag "isRecovery". A flag "isRecovery" é então definida como verdadeira, e a função retorna true para indicar que a operação foi colocada com sucesso. Se, por qualquer motivo, a operação não for aberta com sucesso, a função retornará false, indicando que a tentativa de operação falhou. Essas são as funções cruciais que precisamos manter como privadas. As outras podem ser públicas, sem problema algum.

public:
   // Constructor
   ZoneRecovery(double initialLot, double zonePts, double targetPts, double lotMultiplier, string _symbol) {
      initialLotSize = initialLot;
      currentLotSize = initialLot;                 //--- Start with the initial lot size.
      zoneSize = zonePts * _Point;                 //--- Convert zone size to points.
      targetSize = targetPts * _Point;             //--- Convert target size to points.
      multiplier = lotMultiplier;
      symbol = _symbol;                            //--- Initialize the trading symbol.
      lastOrderType = ORDER_TYPE_BUY;
      lastOrderPrice = 0.0;                        //--- No trades exist initially.
      isRecovery = false;                          //--- No recovery process active at initialization.
   }

Aqui, declaramos a seção public da classe "ZoneRecovery", que contém o construtor. O construtor é usado para inicializar um objeto da classe "ZoneRecovery" com parâmetros específicos no momento em que ele é criado. O construtor recebe como entrada: "initialLot", "zonePts", "targetPts", "lotMultiplier" e "_symbol".

Começamos atribuindo o valor de "initialLot" para "initialLotSize" e "currentLotSize", garantindo que ambos iniciem com o mesmo valor, que representa o tamanho do lote da primeira operação. Em seguida, calculamos o "zoneSize" multiplicando "zonePts" (a distância da zona em pontos) por _Point, que é uma constante interna que representa o menor movimento de preço do símbolo. Da mesma forma, "targetSize" é calculado convertendo "targetPts" (distância do alvo de lucro) em pontos usando a mesma abordagem. O "multiplier" é definido como "lotMultiplier", que será usado posteriormente para ajustar o tamanho do lote nas operações de recuperação.

Em seguida, o "symbol" é atribuído à variável "symbol" para indicar qual instrumento de negociação será usado. "lastOrderType" é definido inicialmente como ORDER_TYPE_BUY, assumindo que a primeira operação será uma compra. "lastOrderPrice" é definido como "0.0", pois nenhuma operação foi executada ainda. Por fim, "isRecovery" é definido como "false", indicando que o processo de recuperação ainda não está ativo. Esse construtor garante que o objeto "ZoneRecovery" seja corretamente inicializado e preparado para gerenciar operações e processos de recuperação. Agora definimos uma função para acionar operações com base em sinais externos.

// Trigger trade based on external signals
void HandleSignal(ENUM_ORDER_TYPE type) {
   if (lastOrderPrice == 0.0)                   //--- Open the first trade if no trades exist.
      OpenTrade(type);
}

Aqui, definimos uma função chamada "HandleSignal" que recebe um tipo ENUM_ORDER_TYPE como parâmetro, representando o tipo de operação a ser executada (BUY ou SELL). Primeiro, verificamos se "lastOrderPrice" é "0.0", o que indica que nenhuma operação anterior foi executada. Se essa condição for verdadeira, significa que essa é a primeira operação a ser aberta, então chamamos a função "OpenTrade" passando o parâmetro "type". A função "OpenTrade" então cuidará da lógica para abrir uma ordem BUY ou SELL com base no sinal recebido. Agora podemos gerenciar as zonas abrindo operações de recuperação conforme a lógica abaixo.

// Manage zone recovery positions
void ManageZones() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current BID price.

   // Open recovery trades based on zones
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) {
      currentLotSize *= multiplier;            //--- Increase lot size for recovery.
      OpenTrade(ORDER_TYPE_SELL);              //--- Open a SELL order for recovery.
   } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) {
      currentLotSize *= multiplier;            //--- Increase lot size for recovery.
      OpenTrade(ORDER_TYPE_BUY);               //--- Open a BUY order for recovery.
   }
}

Para gerenciar as operações abertas, definimos uma função void chamada "ManageZones", responsável por administrar as operações de recuperação com base nas zonas de preço pré-definidas. Dentro dessa função, primeiro obtemos o preço BID atual do símbolo especificado usando a função SymbolInfoDouble com o parâmetro SYMBOL_BID. Isso nos fornece o preço atual de mercado no qual o ativo está sendo negociado.

Em seguida, verificamos o tipo da última operação executada usando a variável "lastOrderType". Se a última operação foi uma BUY e o preço atual caiu até ou abaixo de "zoneLow" (o limite inferior da zona de recuperação), aumentamos o "currentLotSize" multiplicando-o pelo "multiplier" para alocar mais capital na operação de recuperação. Depois disso, chamamos a função "OpenTrade" com o parâmetro ORDER_TYPE_SELL, indicando que precisamos abrir uma posição SELL para lidar com a perda da operação BUY anterior.

Da mesma forma, se a última operação foi uma SELL e o preço atual subiu até ou acima de "zoneHigh" (o limite superior da zona de recuperação), novamente aumentamos o "currentLotSize" multiplicando-o pelo "multiplier", ampliando o tamanho do lote para a operação de recuperação. Então, chamamos a função "OpenTrade" com o parâmetro ORDER_TYPE_BUY, abrindo uma posição BUY para recuperar a operação SELL anterior. Simples assim. Agora, após abrirmos as operações iniciais e de recuperação, precisamos de uma lógica para fechá-las em um ponto adequado. Então, vamos definir abaixo a lógica de fechamento ou de alvo.

// Check and close trades at zone targets
void CheckCloseAtTargets() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current BID price.

   // Close BUY trades at target high
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) {
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Loop through all open positions.
         if (PositionGetSymbol(i) == symbol) { //--- Check if the position belongs to the current symbol.
            ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Retrieve the ticket number.
            int retries = 10;
            while (retries > 0) {
               if (trade.PositionClose(ticket)) { //--- Attempt to close the position.
                  Print("Closed BUY position with ticket: ", ticket);
                  break;
               } else {
                  Print("Failed to close BUY position with ticket: ", ticket, ". Retrying... Error: ", GetLastError());
                  retries--;
                  Sleep(100);                   //--- Wait 100ms before retrying.
               }
            }
            if (retries == 0)
               Print("Gave up on closing BUY position with ticket: ", ticket);
         }
      }
      Reset();                                  //--- Reset the strategy after closing all positions.
   }
   // Close SELL trades at target low
   else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) {
      for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Loop through all open positions.
         if (PositionGetSymbol(i) == symbol) { //--- Check if the position belongs to the current symbol.
            ulong ticket = PositionGetInteger(POSITION_TICKET); //--- Retrieve the ticket number.
            int retries = 10;
            while (retries > 0) {
               if (trade.PositionClose(ticket)) { //--- Attempt to close the position.
                  Print("Closed SELL position with ticket: ", ticket);
                  break;
               } else {
                  Print("Failed to close SELL position with ticket: ", ticket, ". Retrying... Error: ", GetLastError());
                  retries--;
                  Sleep(100);                   //--- Wait 100ms before retrying.
               }
            }
            if (retries == 0)
               Print("Gave up on closing SELL position with ticket: ", ticket);
         }
      }
      Reset();                                  //--- Reset the strategy after closing all positions.
   }
}

Aqui, definimos uma função chamada "CheckCloseAtTargets", responsável por verificar se alguma operação aberta atingiu seu nível de alvo pré-definido e fechá-la adequadamente.

Primeiro, obtemos o preço BID atual do símbolo usando a função SymbolInfoDouble com o parâmetro SYMBOL_BID. Esse valor é o preço atual de mercado do símbolo, que usaremos para comparar com os níveis-alvo (seja "zoneTargetHigh" ou "zoneTargetLow") para decidir se as operações devem ser encerradas.

Em seguida, verificamos se o último tipo de ordem foi BUY e se o preço atual atingiu ou ultrapassou o "zoneTargetHigh" (o nível-alvo de uma operação BUY). Se essas condições forem atendidas, percorremos todas as posições abertas usando a função PositionsTotal, iniciando pela última posição. Para cada posição aberta, verificamos se ela pertence ao mesmo símbolo usando a função PositionGetSymbol. Se o símbolo corresponder, recuperamos o ticket da posição usando a função PositionGetInteger com o parâmetro "POSITION_TICKET".

Depois disso, tentamos fechar a posição chamando a função "trade.PositionClose" com o ticket obtido. Se a posição for fechada com sucesso, imprimimos uma mensagem confirmando o fechamento da posição BUY, incluindo o número do ticket. Se o fechamento falhar, repetimos a tentativa até 10 vezes, imprimindo uma mensagem de erro a cada tentativa e usando a função Sleep para aguardar 100 milissegundos antes de tentar novamente. Se, após 10 tentativas, ainda não conseguirmos fechar a posição, imprimimos uma mensagem de falha e seguimos para a próxima posição aberta. Quando todas as posições forem fechadas ou o limite de tentativas for atingido, chamamos a função "Reset" para reiniciar a estratégia, garantindo que o estado seja limpo para operações futuras.

Da mesma forma, se o último tipo de ordem for SELL e o preço atual tiver atingido ou caído abaixo de "zoneTargetLow" (o alvo de uma operação SELL), o processo é repetido para todas as posições SELL. A função tentará fechar essas posições da mesma forma, repetindo tentativas se necessário e imprimindo mensagens de status a cada etapa. Usamos uma função externa para reiniciar o status, mas aqui está a lógica adotada.

// Reset the strategy after hitting targets
void Reset() {
   currentLotSize = initialLotSize;             //--- Reset lot size to the initial value.
   lastOrderType = -1;                          //--- Clear the last order type.
   lastOrderPrice = 0.0;                        //--- Clear the last order price.
   isRecovery = false;                          //--- Set recovery state to false.
   Print("Strategy reset after closing trades.");
}

Definimos uma função chamada "Reset", responsável por redefinir as variáveis internas da estratégia e preparar o sistema para a próxima operação ou cenário de reinicialização. Começamos redefinindo "currentLotSize" para "initialLotSize", o que significa que, após uma série de operações de recuperação ou atingir níveis-alvo, retornamos o tamanho do lote ao valor original. Isso garante que a estratégia comece novamente com o lote inicial para qualquer nova operação.

Em seguida, limpamos "lastOrderType" definindo-o como -1, indicando que não há tipo de ordem anterior (nem BUY nem SELL). Isso evita que a lógica futura dependa indevidamente de uma ordem anterior. Da mesma forma, redefinimos "lastOrderPrice" para 0.0, limpando o preço da última operação executada. Então, definimos a flag "isRecovery" como false, sinalizando que o processo de recuperação não está mais ativo. Isso é importante porque garante que quaisquer operações futuras sejam tratadas como operações iniciais e não como parte de um ciclo de recuperação.

Por fim, imprimimos uma mensagem usando a função Print, indicando que a estratégia foi redefinida com sucesso após o fechamento de todas as operações. Isso fornece feedback no terminal, ajudando o trader a acompanhar quando a estratégia foi reiniciada e garantindo que o estado esteja correto para operações futuras. Em essência, a função limpa todas as variáveis essenciais que acompanham as condições de operação, estados de recuperação e tamanhos de lote, retornando o sistema às suas configurações padrão para novas operações. E isso é tudo o que precisamos para que a classe trate todos os sinais recebidos. Agora podemos prosseguir para inicializar o objeto da classe passando os parâmetros padrão.

ZoneRecovery zoneRecovery(0.1, 200, 400, 2.0, _Symbol);
//--- Inicializa o objeto ZoneRecovery com os parâmetros especificados.

Aqui, criamos uma instância da classe "ZoneRecovery" chamando seu construtor e passando os parâmetros necessários. Especificamente, inicializamos o objeto "zoneRecovery" com os seguintes valores:

  • "0.1" como o tamanho inicial do lote. Isso significa que a primeira operação utilizará um lote de 0.1.
  • "200" como o tamanho da zona, que é o número de pontos que define a faixa da zona de recuperação. Esse valor é multiplicado por _Point para convertê-lo em pontos reais do símbolo especificado.
  • "400" como o tamanho do alvo, definindo a distância em pontos até o nível de lucro desejado. Assim como o tamanho da zona, isso também é convertido em pontos usando _Point.
  • "2.0" como o multiplicador, que será utilizado para aumentar o tamanho do lote em operações de recuperação, se necessário.
  • "_Symbol" é utilizado como o símbolo de negociação para essa instância específica de ZoneRecovery, correspondente ao instrumento que o trader está utilizando.

Ao inicializar "zoneRecovery" com esses parâmetros, configuramos o objeto para lidar com a lógica de negociação dessa estratégia específica, incluindo o gerenciamento das zonas de recuperação, ajustes do tamanho de lote e níveis-alvo para quaisquer operações que sejam abertas ou gerenciadas. Esse objeto está pronto para executar operações de negociação com base na estratégia de recuperação definida assim que o sistema for executado. Agora podemos avançar para os manipuladores de eventos, onde nos concentramos na geração de sinais. Começamos com o manipulador de eventos OnInit. Aqui, precisamos apenas inicializar o identificador do indicador e definir o array de armazenamento como série temporal.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   //--- Initialize RSI indicator
   rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE); //--- Create RSI indicator handle.
   if (rsiHandle == INVALID_HANDLE) { //--- Check if RSI handle creation failed.
      Print("Failed to create RSI handle. Error: ", GetLastError());
      return(INIT_FAILED); //--- Return failure status if RSI initialization fails.
   }
   ArraySetAsSeries(rsiBuffer, true); //--- Set the RSI buffer as a time series to align values.
   Print("Zone Recovery Strategy initialized."); //--- Log successful initialization.
   return(INIT_SUCCEEDED); //--- Return success status.
}

Aqui, inicializamos o indicador RSI e preparamos o sistema para negociação executando uma série de tarefas de configuração na função OnInit. Primeiro, criamos um identificador do indicador RSI chamando a função iRSI, passando o símbolo atual (_Symbol), o timeframe PERIOD_CURRENT, o "rsiPeriod" especificado e o tipo de preço PRICE_CLOSE. Esse passo define o RSI para uso na estratégia.

Em seguida, verificamos se a criação do identificador foi bem-sucedida, confirmando se o handle é diferente de INVALID_HANDLE. Se a criação falhar, imprimimos uma mensagem de erro com o código específico usando a função GetLastError e retornamos "INIT_FAILED" para sinalizar a falha. Se a criação do handle for bem-sucedida, procedemos definindo o buffer do RSI como série temporal usando ArraySetAsSeries para alinhar o buffer com a série temporal do gráfico, garantindo que os valores mais recentes estejam no índice 0. Por fim, imprimimos uma mensagem de sucesso confirmando a inicialização da "Zone Recovery Strategy" e retornamos INIT_SUCCEEDED, sinalizando que a configuração foi concluída com êxito e o Expert Advisor está pronto para iniciar a operação. Aqui está uma ilustração.

INICIALIZAÇÃO DE SUCESSO

No entanto, como criamos e inicializamos um indicador, precisamos liberá-lo quando não precisarmos mais do programa para liberar recursos. Aqui está a lógica que adotamos.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (rsiHandle != INVALID_HANDLE) //--- Check if RSI handle is valid.
      IndicatorRelease(rsiHandle); //--- Release RSI indicator handle to free resources.

   Print("Zone Recovery Strategy deinitialized."); //--- Log deinitialization message.
}

Aqui, desinicializamos a estratégia e liberamos quaisquer recursos usados pelo indicador RSI quando o Expert Advisor (EA) for removido ou parado. Na função OnDeinit, primeiro verificamos se o "rsiHandle" é válido confirmando que ele não é igual a INVALID_HANDLE. Isso assegura que o identificador do indicador RSI exista antes de tentarmos liberá-lo.

Se o handle for válido, usamos a função IndicatorRelease para liberar os recursos associados ao indicador RSI, garantindo que a memória seja corretamente gerenciada e não permaneça em uso após o EA ser interrompido. Por fim, imprimimos a mensagem "Zone Recovery Strategy deinitialized" no log para informar que o processo de desinicialização foi concluído, confirmando que o sistema foi encerrado corretamente. Isso assegura que o EA possa ser removido com segurança sem deixar recursos desnecessariamente alocados. Aqui está um exemplo de resultado.

LIBERAÇÃO BEM-SUCEDIDA DA ALÇA DO INDICADOR RSI

Após cuidar da instância quando o programa é parado, podemos passar para o manipulador de evento final, que é o principal, onde os ticks são processados: o manipulador de eventos OnTick.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Copy RSI values
   if (CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) <= 0) { //--- Attempt to copy RSI buffer values.
      Print("Failed to copy RSI buffer. Error: ", GetLastError()); //--- Log failure if copying fails.
      return; //--- Exit the function on failure to avoid processing invalid data.
   }

//---

}

Na função OnTick, primeiramente tentamos copiar os valores do RSI para o array "rsiBuffer" usando a função CopyBuffer. A função CopyBuffer é chamada com os parâmetros: o handle do indicador RSI "rsiHandle", o índice do buffer 0 (que indica o buffer primário do RSI), a posição inicial 1 (onde começar a copiar os dados), o número de valores a copiar 2, e o array "rsiBuffer", que armazenará os dados copiados. Essa função recupera os dois valores mais recentes do RSI e os armazena no buffer.

Em seguida, verificamos se a operação de cópia foi bem-sucedida avaliando se o valor retornado é maior que 0. Se a operação falhar (isto é, retornar um valor menor ou igual a 0), registramos uma mensagem de erro indicando que a "RSI buffer copy" falhou usando a função Print e exibimos o código GetLastError para fornecer detalhes sobre a falha. Após registrar o erro, encerramos imediatamente a função usando "return" para evitar qualquer processamento adicional baseado em dados do RSI inválidos ou ausentes. Isso garante que o EA não tente tomar decisões de negociação com dados incompletos ou incorretos, evitando potenciais erros ou perdas. Se não terminarmos o processo, isso significa que temos os dados solicitados e podemos continuar a tomar decisões de negociação.

//--- Check RSI crossover signals
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar.
if (currentBarTime != lastBarTime) { //--- Ensure processing happens only once per bar.
   lastBarTime = currentBarTime; //--- Update the last processed bar time.
   if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) { //--- Check for RSI crossing below 30 (oversold signal).
      Print("BUY SIGNAL"); //--- Log a BUY signal.
      zoneRecovery.HandleSignal(ORDER_TYPE_BUY); //--- Trigger the Zone Recovery BUY logic.
   } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) { //--- Check for RSI crossing above 70 (overbought signal).
      Print("SELL SIGNAL"); //--- Log a SELL signal.
      zoneRecovery.HandleSignal(ORDER_TYPE_SELL); //--- Trigger the Zone Recovery SELL logic.
   }
}

Aqui, verificamos sinais de cruzamento do RSI em cada nova barra de mercado para acionar potenciais operações. Começamos obtendo o timestamp da barra atual usando a função iTime. A função recebe o símbolo (_Symbol), o timeframe (PERIOD_CURRENT) e o índice da barra (0 para a barra atual). Isso fornece o "currentBarTime", que representa o timestamp da barra mais recentemente formada.

Em seguida, asseguramos que a lógica de negociação seja executada apenas uma vez por barra comparando o "currentBarTime" com o "lastBarTime". Se os tempos forem diferentes, significa que uma nova barra foi formada, então procedemos com o processamento. Atualizamos então o "lastBarTime" para coincidir com o "currentBarTime" a fim de acompanhar a barra mais recentemente processada e evitar execuções repetidas durante a mesma barra.

O próximo passo é detectar sinais de cruzamento do RSI. Primeiro verificamos se o valor do RSI cruzou abaixo de 30 (condição de sobrevenda) comparando "rsiBuffer[1]" (o valor do RSI da barra anterior) com "rsiBuffer[0]" (o valor do RSI da barra atual). Se o RSI da barra anterior estava acima de 30 e o RSI da barra atual está em ou abaixo de 30, isso indica um potencial sinal de BUY, então imprimimos a mensagem "BUY SIGNAL" e chamamos a função "HandleSignal" do objeto "zoneRecovery" para acionar o processo de recuperação para uma ordem BUY.

De forma similar, verificamos se o RSI cruzou acima de 70 (condição de sobrecompra). Se o RSI da barra anterior estava abaixo de 70 e o RSI da barra atual está em ou acima de 70, isso sinaliza um potencial SELL, e imprimimos "SELL SIGNAL". Então, chamamos "HandleSignal" novamente, mas desta vez para uma ordem SELL, acionando a lógica correspondente de Zone Recovery para SELL. Finalmente, apenas chamamos as funções respectivas para gerenciar as zonas abertas e fechá-las quando os alvos forem atingidos.

//--- Manage zone recovery logic
zoneRecovery.ManageZones(); //--- Perform zone recovery logic for active positions.

//--- Check and close at zone targets
zoneRecovery.CheckCloseAtTargets(); //--- Evaluate and close trades when target levels are reached.

Aqui, usamos o operador ponto (".") para chamar funções que fazem parte da classe "ZoneRecovery". Primeiro, usamos "zoneRecovery.ManageZones()" para executar o método "ManageZones", que trata a lógica de gerenciamento das operações de zone recovery com base no preço atual e nas zonas de recuperação definidas. Esse método ajusta o tamanho do lote para operações de recuperação e abre novas posições conforme necessário.

Em seguida, chamamos "zoneRecovery.CheckCloseAtTargets()" para acionar o método "CheckCloseAtTargets", que verifica se o preço atingiu os níveis-alvo para fechamento das posições. Se as condições forem atendidas, tenta fechar as operações abertas, garantindo que a estratégia permaneça dentro de seus limites de lucro ou perda definidos. Ao usar o operador ponto, acessamos e executamos esses métodos no objeto "zoneRecovery" para gerir o processo de recuperação de forma eficaz. Para garantir que os métodos sejam chamados com sucesso a cada tick, executamos o programa, e aqui está o resultado.

CONFIRMAÇÃO DE REINICIALIZAÇÃO DO MANIPULADOR DE EVENTOS ONTICK 

Na imagem, podemos ver que chamamos com sucesso os métodos da classe para deixar o programa pronto no primeiro tick, o que confirma que nossa classe do programa está conectada e pronta para operar. Para confirmar isso também, executamos o programa, e aqui estão as confirmações de trade.

REGISTRO E CONFIRMAÇÃO DE COMPRA

A partir da imagem, podemos ver que confirmamos um sinal de compra, abrimos uma posição a partir dele, adicionamo-la ao sistema de zone recovery, recalculamos os níveis da zona, identificamos que é uma posição inicial e, quando o alvo é atingido, fechamos a posição e reiniciamos o sistema para a próxima operação. Vamos testar e ver um caso em que entramos em um sistema de zone recovery. 

INÍCIO DA ZONE RECOVERY

Na imagem, vemos que quando o mercado nos contraria por 200 pontos, assumimos que a tendência é de alta e a seguimos abrindo uma posição de compra com tamanho de lote maior, 0.2 neste caso.

FULL RECOVERY

Pode-se novamente observar que, quando o mercado atinge os níveis-alvo, fechamos as operações e reiniciamos para outra operação. Enquanto o sistema estiver em modo de recuperação, ignoramos qualquer sinal de entrada que venha. Isso verifica que alcançamos com sucesso nosso objetivo, e o que resta é realizar o backtest do programa e analisar seu desempenho. Isso é tratado na seção seguinte.


Backtesting e Análise de Performance

Nesta seção, concentramos no processo de backtesting e análise de desempenho do nosso Sistema Zone Recovery RSI. O backtesting nos permite avaliar a eficácia da estratégia em dados históricos, identificar falhas potenciais e ajustar os parâmetros para obter melhores resultados em negociação ao vivo.

Começamos configurando o Strategy Tester na plataforma MetaTrader 5. O Strategy Tester permite simular condições de mercado históricas e executar operações como se estivessem ocorrendo em tempo real. Para rodar um backtest, selecionamos o símbolo relevante, o timeframe e o período de teste. Também garantimos que o "visual mode" esteja habilitado caso queiramos ver as operações sendo executadas no gráfico.

Depois que o ambiente de backtesting estiver pronto, configuramos os inputs do nosso programa. Depois que o ambiente de backtesting estiver pronto, configuramos os inputs do nosso programa. Entradas-chave incluem "initial lot size", "zone size", "target size" e "multiplier". Ao variar esses inputs, podemos analisar como eles afetam a lucratividade geral da estratégia. Aqui está o que alteramos.

ZoneRecovery zoneRecovery(0.1, 700, 1400, 2.0, _Symbol);
//--- Initialize the ZoneRecovery object with specified parameters.

Configuramos o sistema para rodar a partir do dia um de janeiro de 2024 por um ano inteiro e aqui estão os resultados.

Gráfico do testador de estratégia:

Gráfico 1

Relatórios do Strategy Tester:

RELATÓRIO 1

A partir do gráfico e do relatório obtidos, podemos ter certeza de que nossa estratégia está funcionando conforme esperado. Entretanto, ainda podemos aumentar a performance do programa assegurando que maximizemos lucros ao adicionar uma lógica de trailing, pela qual, em vez de esperar pelo alvo de lucro completo — o que nos expõe a instâncias de recuperação na maioria das vezes — protegemos os pequenos lucros que já temos e os maximizamos aplicando um trailing stop. Como só podemos aplicar trailing nas posições iniciais, podemos adotar uma lógica que garanta o trailing apenas dessas primeiras posições e, se entrarmos em modo de recuperação, aguardamos a recuperação completa. Portanto, primeiro precisaremos de uma função de trailing stop.

//+------------------------------------------------------------------+
//|      FUNCTION TO APPLY TRAILING STOP                             |
//+------------------------------------------------------------------+
void applyTrailingStop(double slPoints, CTrade &trade_object, int magicNo=0, double minProfitPoints=0){
   double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - slPoints*_Point, _Digits); //--- Calculate the stop loss price for BUY trades
   double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + slPoints*_Point, _Digits); //--- Calculate the stop loss price for SELL trades
   
   for (int i = PositionsTotal() - 1; i >= 0; i--){ //--- Loop through all open positions
      ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position
      if (ticket > 0){ //--- Check if the ticket is valid
         if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number
            if (PositionGetString(POSITION_SYMBOL) == _Symbol && //--- Check if the position belongs to the current symbol
               (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)){ //--- Check if the position matches the given magic number or if no magic number is specified
               
               double positionOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get the opening price of the position
               double positionSl = PositionGetDouble(POSITION_SL); //--- Get the current stop loss of the position
               
               if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ //--- Check if the position is a BUY trade
                  double minProfitPrice = NormalizeDouble(positionOpenPrice + minProfitPoints * _Point, _Digits); //--- Calculate the minimum price at which profit is locked
                  if (buySl > minProfitPrice &&  //--- Check if the calculated stop loss is above the minimum profit price
                      buySl > positionOpenPrice && //--- Check if the calculated stop loss is above the opening price
                      (buySl > positionSl || positionSl == 0)){ //--- Check if the calculated stop loss is greater than the current stop loss or if no stop loss is set
                     trade_object.PositionModify(ticket, buySl, PositionGetDouble(POSITION_TP)); //--- Modify the position to update the stop loss
                  }
               }
               else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ //--- Check if the position is a SELL trade
                  double minProfitPrice = NormalizeDouble(positionOpenPrice - minProfitPoints * _Point, _Digits); //--- Calculate the minimum price at which profit is locked
                  if (sellSl < minProfitPrice &&  //--- Check if the calculated stop loss is below the minimum profit price
                      sellSl < positionOpenPrice && //--- Check if the calculated stop loss is below the opening price
                      (sellSl < positionSl || positionSl == 0)){ //--- Check if the calculated stop loss is less than the current stop loss or if no stop loss is set
                     trade_object.PositionModify(ticket, sellSl, PositionGetDouble(POSITION_TP)); //--- Modify the position to update the stop loss
                  }
               }
            }
         }
      }
   }
}

Aqui, criamos uma função chamada "applyTrailingStop" que nos permite aplicar um trailing stop a todas as posições BUY e SELL abertas. O objetivo desse trailing stop é proteger e travar lucros à medida que o mercado se move a favor das nossas operações. Usamos o objeto "CTrade" para modificar automaticamente os níveis de stop-loss das operações. Para garantir que o trailing stop não seja ativado prematuramente, incluímos uma condição que exige que um lucro mínimo seja alcançado antes que o stop-loss comece a traquear. Essa abordagem evita ajustes precoces do stop-loss e assegura que capturemos uma quantia mínima de lucro antes de iniciar o trailing.

Definimos quatro parâmetros-chave nessa função. O parâmetro "slPoints" especifica a distância, em pontos, do preço de mercado atual até o novo nível de stop-loss. O parâmetro "trade_object" refere-se ao objeto "CTrade", que nos permite gerenciar posições abertas, modificar stop-loss e ajustar take-profit. O parâmetro "magicNo" serve como identificador único para filtrar trades. Se "magicNo" estiver definido como 0, aplicamos o trailing stop a todas as operações, independentemente do magic number. Por fim, o parâmetro "minProfitPoints" define o lucro mínimo (em pontos) que deve ser alcançado antes que o trailing stop seja ativado. Isso garante que só ajustemos o stop-loss depois que a posição estiver em lucro suficiente.

Aqui, começamos calculando os preços do trailing stop-loss para operações BUY e SELL. Para BUYs, calculamos o novo preço de stop-loss subtraindo "slPoints" do preço BID atual. Para SELLs, calculamos adicionando "slPoints" ao preço ASK atual. Esses preços de stop-loss são normalizados usando _Digits para garantir precisão conforme a casa decimal do símbolo. Essa normalização assegura que os preços obedeçam ao número correto de casas decimais para o instrumento financeiro específico.

Em seguida, percorremos todas as posições abertas, começando pela última posição e indo até a primeira. Esse loop reverso é essencial porque modificar posições durante um loop direto pode causar erros no índice das posições. Para cada posição, obtemos seu "ticket", que é o identificador único daquela posição. Se o ticket for válido, usamos a função PositionSelectByTicket para selecionar e acessar os detalhes da posição.

Depois de selecionar a posição, verificamos se ela corresponde ao símbolo atual e se seu magic number corresponde ao "magicNo" fornecido. Se "magicNo" estiver definido como 0, aplicamos o trailing stop a todas as operações, independentemente do magic number. Após identificar uma posição compatível, determinamos se ela é uma operação BUY ou SELL.

Se a posição for BUY, calculamos o preço mínimo que o mercado deve atingir antes que o stop-loss comece a traquear. Esse valor é obtido somando "minProfitPoints" ao preço de abertura da posição. Em seguida, verificamos se o preço calculado para o trailing stop é superior tanto ao preço de abertura da posição quanto ao stop-loss atual da posição. Se essas condições forem atendidas, modificamos a posição usando "trade_object.PositionModify", atualizando o preço do stop-loss para a operação BUY.

Se a posição for SELL, seguimos um processo análogo. Calculamos o preço mínimo de lucro subtraindo "minProfitPoints" do preço de abertura da posição. Verificamos se o preço calculado para o trailing stop está abaixo tanto do preço de abertura quanto do stop-loss atual. Se as condições forem atendidas, modificamos a posição usando "trade_object.PositionModify", atualizando o stop-loss para a operação SELL.

Agora, munidos dessa função, precisamos de lógica para localizar as posições iniciais primeiro, e a essas funções podemos adicionar a lógica de trailing stop. Para isso, precisaremos definir uma variável booleana na classe zone recovery, mas uma coisa importante: torne-a acessível em qualquer lugar do programa tornando-a pública.

public:
   bool isFirstPosition;

Aqui, temos uma variável public chamada "isFirstPosition" dentro da classe "ZoneRecovery". Essa variável é do tipo boolean (bool), o que significa que só pode conter dois valores possíveis: true ou false. A função dessa variável é rastrear se a operação atual é a primeira posição no processo de Zone Recovery. Quando "isFirstPosition" é true, isso indica que nenhuma operação anterior foi aberta, sendo esta a posição inicial. Essa distinção é essencial porque a lógica aplicada à primeira operação muda, já que desejamos aplicar a lógica de trailing stop nela.

Como declaramos "isFirstPosition" como pública, ela pode ser acessada e modificada de fora da classe "ZoneRecovery". Isso permite que outras partes do programa verifiquem se uma posição é a primeira da sequência ou atualizem seu status adequadamente. Agora, dentro da função responsável por abrir operações, precisamos atribuir as flags booleanas que indicam se é uma primeira posição ou não, assim que uma operação é aberta.

if (trade.Buy(currentLotSize, symbol)) {
   lastOrderType = ORDER_TYPE_BUY;         //--- Mark the last trade as BUY.
   lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Store the current BID price.
   CalculateZones();                       //--- Recalculate zones after placing the trade.
   Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
   isFirstPosition = isRecovery ? false : true;
   isRecovery = true;                      //--- Set recovery state to true after the first trade.
   return true;
}

Aqui, definimos a variável "isFirstPosition" como false se a posição for registrada como uma posição de recuperação, ou como true se a variável "isRecovery" for false. Além disso, no construtor e nas funções de reset, definimos a variável como false por padrão. A partir disso, podemos ir ao manipulador de eventos "OnTick" e aplicar o trailing stop quando tivermos uma posição inicial.

if (zoneRecovery.isFirstPosition == true){ //--- Check if this is the first position in the Zone Recovery process
   applyTrailingStop(100, obj_Trade, 0, 100); //--- Apply a trailing stop with 100 points, passing the "obj_Trade" object, a magic number of 0, and a minimum profit of 100 points
}

Aqui, verificamos se a variável "zoneRecovery.isFirstPosition" é true, indicando que essa é a primeira posição no processo de Zone Recovery. Se for, chamamos a função "applyTrailingStop". Os parâmetros passados são "100" pontos para a distância do trailing stop, "obj_Trade" como o objeto de trade, um magic number "0" para identificar a operação e um lucro mínimo de "100" pontos. Isso garante que, uma vez que a operação alcance 100 pontos de lucro, o trailing stop seja aplicado para proteger os ganhos ao mover o stop-loss conforme o preço avança a favor da operação. No entanto, quando fechamos as operações pelo trailing stop, ainda permanecem resíduos da lógica de zone recovery, já que não realizamos o reset. Isso faz com que o sistema abra operações de recuperação mesmo quando não há posições abertas. Aqui está o que queremos dizer com isso.

GIF DE LÓGICA DE PARADA TRASEIRA COM DEFEITO

A partir da visualização, vemos que precisamos resetar o sistema assim que a posição inicial for encerrada por trailing stop. Aqui está a lógica que precisamos adotar para isso.

if (zoneRecovery.isFirstPosition == true && PositionsTotal() == 0){ //--- Check if this is the first position and if there are no open positions
   zoneRecovery.Reset(); //--- Reset the Zone Recovery system, restoring initial settings and clearing previous trade data
}

Aqui, verificamos se a variável "isFirstPosition" é true e se não existem posições abertas. Se ambas as condições forem atendidas, significa que tínhamos uma posição inicial que foi fechada — por qualquer motivo — e agora que ela não existe mais, chamamos a função "zoneRecovery.Reset()". Isso reinicia o sistema Zone Recovery restaurando suas configurações iniciais e limpando qualquer dado relacionado à operação anterior, garantindo que o processo de recuperação recomece limpo. Essas modificações tornam o sistema perfeito. Após executar os testes finais, temos os seguintes resultados.

Gráfico do testador de estratégia:

GRÁFICO FINAL DO TESTADOR

Relatórios do Strategy Tester:

RELATÓRIO FINAL DE TESTE

A partir da imagem, podemos ver que reduzimos o número de posições de recuperação, o que aumenta significativamente nossa taxa de acerto. Isso confirma que alcançamos nosso objetivo de criar um sistema de zone recovery com lógica dinâmica de gerenciamento de operações.


Conclusão

Em conclusão, demonstramos como construir um Expert Advisor em MetaQuotes Language 5 (MQL5) usando a estratégia Zone Recovery. Ao combinar o Relative Strength Index (RSI) indicator com a lógica "Zone Recovery", criamos um sistema capaz de detectar sinais de entrada, gerenciar posições de recuperação e proteger lucros com trailing stop. Os elementos-chave incluíram identificação de sinais, execução automática de operações e recuperação dinâmica de posições.

Aviso: Este artigo serve como um guia educacional para o desenvolvimento de Programas em MQL5. Embora a estratégia "Zone Recovery RSI" ofereça uma abordagem estruturada para o gerenciamento de operações, as condições de mercado permanecem imprevisíveis. Negociar envolve risco financeiro, e desempenho passado não garante resultados futuros. Testes adequados e gestão de risco são essenciais antes da negociação real.

Dominar os conceitos apresentados neste guia permite criar sistemas de negociação mais adaptáveis e explorar novas estratégias para trading algorítmico. Feliz codificação e bons trades!

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

Arquivos anexados |
Últimos Comentários | Ir para discussão (8)
Allan Munene Mutiiria
Allan Munene Mutiiria | 30 jan. 2025 em 16:00
Amir Jafary #:

Não consigo encontrar os arquivos tradq mq onde estão

Isso está perfeitamente explicado, mesmo com a imagem.

Silk Road Trading LLC
Ryan L Johnson | 30 jan. 2025 em 20:38
wupan123898 controlar grandes perdas. O autor tem algum método para evitar grandes perdas?

Bem dito. A estratégia pressupõe que o preço sairá da faixa inicialmente negociada para recuperar as perdas. Como sabemos, pelo menos no que diz respeito ao mercado cambial, os preços variam com mais frequência do que se tornam tendências. O método para atenuar as perdas catastróficas da Martingale seria adicionar um filtro de volatilidade para as negociações iniciais.

Allan Munene Mutiiria
Allan Munene Mutiiria | 30 jan. 2025 em 21:52
Ryan L Johnson #:

Bem dito. A estratégia pressupõe que o preço sairá da faixa inicialmente negociada para recuperar as perdas. Como sabemos, pelo menos no que diz respeito ao mercado de câmbio, os preços variam com mais frequência do que as tendências. O método para atenuar as perdas catastróficas da Martingale seria adicionar um filtro de volatilidade para as negociações iniciais.

Com certeza.

Silk Road Trading LLC
Ryan L Johnson | 16 mar. 2025 em 18:43
Allan Munene Mutiiria função também não é nada demais e funcionará melhor, mais rápido e de forma mais flexível do que o ATR incorporado...
ENUM_TIMEFRAMES _atrTimeFrame = PERIOD_D1;
int             _atrPeriod    = 20;
double          _atrValue     = 0;
   MqlRates _rates[]; int _ratesCopied = CopyRates(_Symbol,_atrTimeFrame,0,_atrPeriod+1,_rates);
                      if (_ratesCopied>0)
                           for (int i=1; i<_ratesCopied; i++) _atrValue += MathMax(_rates[i].high,_rates[i-1].close)-MathMin(_rates[i].low,_rates[i-1].close);
                                                              _atrValue /= MathMax(_ratesCopied,1);

masuchai ma
masuchai ma | 21 mar. 2025 em 23:51

Eu dei uma olhada no código e me perguntei por que não usar o preço de venda para calcular no caso de compra? Haverá algum conflito se eu mudar isso? Obrigado.

Gerenciamento de riscos (Parte 4): Conclusão dos métodos-chave da classe Gerenciamento de riscos (Parte 4): Conclusão dos métodos-chave da classe
Este artigo é a quarta parte da nossa série sobre gerenciamento de riscos em MQL5, onde continuamos a explorar métodos avançados de proteção e otimização de estratégias de negociação. Após termos estabelecido as bases importantes nas partes anteriores, agora focaremos em finalizar todos os métodos que ficaram pendentes na terceira parte, incluindo as funções responsáveis por verificar o atingimento de determinados níveis de lucro ou prejuízo. Além disso, o artigo introduz novos eventos-chave que garantem um controle mais preciso e flexível.
Gerenciamento de riscos (Parte 3): Criação da classe principal de gerenciamento de riscos Gerenciamento de riscos (Parte 3): Criação da classe principal de gerenciamento de riscos
Neste artigo começaremos a criação da classe principal de gerenciamento de riscos, que será o elemento chave para o controle de riscos no sistema. Vamos nos concentrar na construção das bases, na definição das principais estruturas, variáveis e funções. Além disso, implementaremos os métodos necessários para atribuir valores de lucro máximo e prejuízo máximo, estabelecendo assim o alicerce do gerenciamento de riscos.
Automatização de estratégias de trading com MQL5 (Parte 13): Criação de um algoritmo de negociação para o padrão "Cabeça e Ombros" Automatização de estratégias de trading com MQL5 (Parte 13): Criação de um algoritmo de negociação para o padrão "Cabeça e Ombros"
Neste artigo, automatizaremos o padrão "Cabeça e Ombros" em MQL5. Analisaremos sua arquitetura, implementaremos um EA para sua detecção e negociação, e testaremos os resultados no histórico. Esse processo revela um algoritmo de negociação prático, que pode ser aprimorado.
Simulação de mercado: A união faz a força (I) Simulação de mercado: A união faz a força (I)
Estamos chegando aos finalmente. O desenvolvimento do replay / simulador está quase concluído. É bem verdade que ainda precisaremos fazer algumas poucas coisas. Mas frente a tudo que realmente já foi feito. Implementar o que falta será moleza. Mas como tudo que será mostrado neste artigo, precisará ser adequadamente digerido e compreendido. Quero que você, meu caro leitor e entusiasta.