English Русский 中文 Español Deutsch 日本語
preview
Automatizando Estratégias de Trading em MQL5 (Parte 4): Construindo um Sistema de Recuperação por Zonas em Múltiplos Níveis

Automatizando Estratégias de Trading em MQL5 (Parte 4): Construindo um Sistema de Recuperação por Zonas em Múltiplos Níveis

MetaTrader 5Negociação |
41 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior (Parte 3 da série), exploramos o Sistema de Zone Recovery com RSI, demonstrando como integrar a geração de sinais baseada em RSI com um mecanismo dinâmico de Zone Recovery para gerenciar operações e se recuperar de movimentos desfavoráveis do mercado utilizando a MetaQuotes Language 5 (MQL5). Neste artigo (Parte 4), avançamos a partir dessas bases ao introduzir um Sistema de Recuperação por Zonas em Múltiplos Níveis, uma abordagem sofisticada de gerenciamento de trades capaz de lidar simultaneamente com múltiplos sinais independentes.

Este sistema utiliza o indicador Relative Strength Indicator (RSI) para gerar sinais de negociação e incorpora dinamicamente cada sinal em uma estrutura de array, permitindo integração perfeita com a lógica de Zone Recovery. O objetivo principal é escalar o mecanismo de recuperação para gerenciar múltiplas configurações de trade de forma eficiente, reduzindo os drawdowns gerais e melhorando os resultados de negociação.

Nós o guiamos pelo desenho do blueprint da estratégia, pela codificação do sistema em MQL5 e pelo backtesting de seu desempenho. Para facilitar o entendimento e a organização, dividimos as etapas nos seguintes tópicos:

  1. Blueprint da Estratégia
  2. Implementação em MQL5
  3. Backtesting
  4. Conclusão

Ao final, você terá uma compreensão prática de como construir e otimizar um Sistema de Recuperação por Zonas em Múltiplos Níveis para um gerenciamento de trades dinâmico e robusto.


Blueprint da Estratégia

O Sistema de Recuperação por Zonas em Múltiplos Níveis utilizará uma estrutura bem organizada para gerenciar múltiplos sinais de negociação de forma eficaz. Para alcançar isso, definiremos uma estrutura (struct) que atua como o blueprint para a criação de cestas individuais de trades. Cada sinal de negociação gerado pelo indicador RSI corresponderá à sua cesta exclusiva, armazenada como um elemento dentro de um array. Por exemplo, quando o sistema gerar o Sinal 1, criaremos a Cesta 1, que não apenas armazenará os detalhes da operação inicial, mas também gerenciará todas as posições de recuperação associadas a esse sinal. Da mesma forma, o Sinal 2 iniciará a Cesta 2, e essa cesta acompanhará e executará de forma independente todas as operações de recuperação com base nos parâmetros do Sinal 2. Aqui está uma visualização das propriedades da cesta e dos sinais.

VISUALIZAÇÃO DA CESTA DE ORDENS

Cada cesta incluirá dados essenciais, como a direção do sinal (compra ou venda), o preço de entrada, os níveis de recuperação, os tamanhos de lote calculados dinamicamente e outros parâmetros específicos da operação. À medida que novos sinais forem identificados pelo RSI, nós os adicionaremos ao array, garantindo que o sistema possa lidar com múltiplos sinais simultaneamente. As operações de recuperação serão calculadas e executadas dinamicamente dentro de suas respectivas cestas, garantindo que cada configuração seja gerenciada de forma independente e sem interferência das demais. Aqui está um exemplo dos sinais sendo tratados separadamente.

EXEMPLO DE SINAIS SEPARADOS

Ao estruturar o sistema dessa forma, garantiremos um alto grau de escalabilidade e flexibilidade. Cada cesta atuará como uma unidade autônoma, permitindo que o sistema responda dinamicamente às condições de mercado para cada sinal. Esse design simplificará o rastreamento e o gerenciamento de configurações complexas de trades, pois cada sinal e suas operações de recuperação associadas estarão organizados de forma clara. O sistema de cestas baseado em array servirá como a base para a construção de um Sistema de Recuperação por Zonas em Múltiplos Níveis robusto e adaptável, capaz de lidar com diversos cenários de negociação mantendo eficiência e clareza. Vamos começar então!


Implementação em MQL5

Após aprender todas as teorias sobre a estratégia de negociação de Recuperação por Zonas em Múltiplos Níveis, 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 teclado. Alternativamente, você pode clicar no ícone do IDE (Integrated Development Environment) na barra de ferramentas. Isso abrirá o ambiente do MetaQuotes Language Editor, que permite a criação de robôs de negociação, indicadores técnicos, scripts e bibliotecas de funções. Depois que 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á na abertura de um pop-up do MQL Wizard.

No assistente que aparecer, selecione Expert Advisor (template) e clique em Next. Nas propriedades gerais do Expert Advisor, na seção de nome, informe o nome do arquivo do seu expert. 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 demais seções são bastante diretas, mas você pode seguir o link na parte inferior do assistente para saber como realizar o processo com mais precisão.

NOVO NOME DO EA

Depois de informar o nome desejado do arquivo do Expert Advisor, clique em Next, clique novamente em Next e, em seguida, clique em Finish. Após concluir todas essas etapas, estaremos 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, que é definida como “1.00”.

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

Isso exibirá os metadados do sistema ao carregar o programa. Em seguida, podemos avançar para a adição de algumas variáveis de entrada que serão exibidas na interface do usuário, conforme a seguir.

sinput group "General EA Settings"
input double inputlot = 0.01;
input double inputzonesizepts = 200;
input double inputzonetragetpts = 400;
input double inputlotmultiplier = 2.0;
input double inputTrailingStopPts = 50; // Trailing stop distance in points
input double inputMinimumProfitPts = 50; // Minimum profit points before trailing stop starts
input bool inputTrailingStopEnabled = true; // Enable or disable trailing stop

Definimos um grupo de parâmetros de entrada sob a categoria “General EA Settings”, permitindo que os usuários configurem definições essenciais do Expert Advisor (EA) antes de executá-lo. Essas entradas são declaradas utilizando o tipo de dado input em MQL5, tornando-as ajustáveis diretamente no painel de configurações de entrada do EA, sem a necessidade de modificar o código. Cada parâmetro de entrada é específico no controle do comportamento do EA e do gerenciamento de risco.

O parâmetro “inputlot” define o tamanho inicial do lote para abertura das operações, com valor padrão de 0.01, permitindo um controle preciso sobre o volume negociado. O parâmetro “inputzonesizepts” especifica o tamanho da zona de recuperação em pontos, definido como 200 por padrão, determinando a distância entre as operações de recuperação. O parâmetro “inputzonetragetpts”, com valor padrão de 400, define a distância do lucro alvo em pontos, orientando o EA sobre quando fechar as posições de forma lucrativa.

Para lidar com as operações de recuperação, utilizamos o parâmetro “inputlotmultiplier”, definido como 2.0 por padrão, permitindo que o EA calcule dinamicamente tamanhos de lote crescentes para as operações de recuperação com base no multiplicador. Além disso, a funcionalidade de trailing stop é introduzida por meio de três parâmetros. O “inputTrailingStopPts” define a distância do trailing stop em pontos, definida como 50, que ajusta o stop loss conforme o mercado se move a favor da operação. O parâmetro “inputMinimumProfitPts”, também definido como 50, garante que o trailing stop só seja ativado após a operação atingir um limite mínimo de lucro.

Por fim, o parâmetro “inputTrailingStopEnabled”, do tipo de dado bool, permite que os usuários ativem ou desativem o recurso de trailing stop conforme necessário. Essa flexibilidade garante que o EA possa se adaptar a diferentes estratégias de negociação, perfis de risco e condições de mercado, oferecendo uma estrutura personalizável para um gerenciamento eficiente de operações e riscos. Em seguida, como iremos abrir operações, precisamos incluir alguns arquivos extras para que possamos incluir uma instância de negociação usando #include. Isso nos dá acesso à “CTrade class”, que utilizaremos para criar um objeto de negociação. Isso é crucial, pois precisamos dela para abrir operações.

#include <Trade/Trade.mqh>
//--- Includes the MQL5 Trade library for handling trading operations.

O pré-processador substituirá a linha #include <Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os sinais de menor e maior indicam que o arquivo Trade.mqh será obtido a partir 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 lugar do programa, mas normalmente todas as inclusões são posicionadas no início do código-fonte, para uma melhor estruturação do código e facilidade de referência. Aqui está o arquivo específico conforme exibido na seção do navegador.

CLASSE CTRADE

Depois disso, precisamos declarar várias variáveis globais importantes que utilizaremos 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.

Aqui, definimos um conjunto de variáveis globais para gerenciar a lógica do indicador RSI, que irá conduzir os sinais de negociação do EA. Essas variáveis são projetadas para lidar com o cálculo, a obtenção e o processamento dos valores do RSI de forma eficiente. Ao declará-las como globais, garantimos que estejam acessíveis em todo o EA, permitindo uma geração de sinais consistente e eficaz. Definimos a variável “rsiPeriod”, do tipo de dado int, como 14, especificando o período de análise utilizado para calcular o indicador RSI. Esse valor determina o número de barras que o EA analisará para calcular os valores do RSI, dando-nos controle sobre a sensibilidade do indicador. Em seguida, declaramos o “rsiHandle”, outra variável do tipo int, que será usada para armazenar o handle do indicador RSI. Esse handle é obtido quando inicializamos o RSI usando a função iRSI, permitindo recuperar os valores do RSI diretamente do buffer do indicador do terminal.

Para armazenar esses valores do RSI, criamos o “rsiBuffer[]”, um array dinâmico do tipo de dado double. Esse array armazenará os valores calculados do RSI para cada barra, que utilizaremos para identificar condições de sobrecompra ou sobrevenda no mercado. Além disso, definimos a variável “lastBarTime”, do tipo “datetime”, para armazenar o tempo da última barra processada. Ao acompanhar esse valor, garantimos que o EA processe um novo sinal apenas quando uma nova barra surgir, evitando sinais duplicados para a mesma barra. Agora podemos definir os parâmetros comuns da “Basket”, em uma estrutura que iremos vincular a cada sinal gerado. Para isso, utilizamos a lógica de struct, cuja sintaxe geral é a seguinte:

//--- Struct to track individual position recovery states
struct PositionRecovery {
        //--- Member 1
        //--- Member 2
        //--- Member 3

        //--- Method 1
        //...
};

Para agrupar variáveis de dados relacionadas, precisamos de uma estrutura, e aqui está o protótipo geral dela. Definimos uma struct chamada “PositionRecovery”, que serve como um blueprint para organizar e gerenciar dados relacionados aos estados individuais de recuperação de posições dentro do EA. A struct atua como um tipo de dado personalizado, permitindo agrupar variáveis relacionadas (membros) e funções (métodos) em uma única entidade.

Explicação da Sintaxe:

"struct" "PositionRecovery { ... };"

Isso declara uma estrutura chamada “PositionRecovery”. A palavra-chave struct é usada para definir a estrutura, e as chaves { ... } delimitam os membros e métodos da estrutura. O ponto e vírgula (;) ao final da definição é obrigatório em MQL5.

  • Members

Members são variáveis definidas dentro da estrutura que armazenam dados específicos de cada instância de “PositionRecovery”.

“//--- Member 1”: Espaço reservado para uma variável, como o tamanho inicial do lote da operação ou o preço de entrada.

“//--- Member 2”: Pode representar parâmetros como o tamanho da zona de recuperação ou o estado atual da operação.

“//--- Member 3”: Dados adicionais, como o número de operações de recuperação executadas ou um sinalizador indicando a conclusão da recuperação.

Esses membros nos permitem encapsular todas as informações necessárias para rastrear e gerenciar um processo individual de recuperação.

  • Methods

Methods são funções definidas dentro da estrutura que operam sobre seus membros.

“//--- Method 1”: Um espaço reservado para um método, como o cálculo do próximo tamanho de lote da operação de recuperação ou a verificação se o alvo de recuperação foi atingido.

Ao combinar tanto dados (membros) quanto lógica (métodos), a estrutura torna-se mais versátil e autocontida. Após compreender isso, agora podemos começar a definir os membros da estrutura.

//--- Struct to track individual position recovery states
struct PositionRecovery {
   CTrade trade;                    //--- Object to handle trading operations.
   double initialLotSize;           //--- Initial lot size for this position.
   double currentLotSize;           //--- Current lot size in the recovery sequence.
   double zoneSize;                 //--- Distance in points defining the recovery zone size.
   double targetSize;               //--- Distance in points defining the profit target range.
   double multiplier;               //--- Lot size multiplier for recovery trades.
   string symbol;                   //--- Trading symbol.
   ENUM_ORDER_TYPE lastOrderType;   //--- Type of the last order (BUY or SELL).
   double lastOrderPrice;           //--- Price of the last executed order.
   double zoneHigh;                 //--- Upper boundary of the recovery zone.
   double zoneLow;                  //--- Lower boundary of the recovery zone.
   double zoneTargetHigh;           //--- Upper boundary of the target range.
   double zoneTargetLow;            //--- Lower boundary of the target range.
   bool isRecovery;                 //--- Whether the recovery is active.
   ulong tickets[];                 //--- Array to store tickets of positions associated with this recovery.
   double trailingStop;             //--- Trailing stop level
   double initialEntryPrice;        //--- Initial entry price for trailing stop calculation

   //---

};

Aqui, criamos uma struct chamada “PositionRecovery” para organizar e gerenciar todos os dados necessários para acompanhar estados individuais de recuperação de posições. Ao utilizar essa estrutura, garantimos que cada processo de recuperação seja tratado de forma independente, permitindo gerenciar múltiplos sinais de maneira eficaz.

Definimos o objeto “CTrade trade”, que utilizaremos para executar operações de negociação, como abrir, modificar e fechar ordens relacionadas a essa recuperação. Configuramos “initialLotSize” para armazenar o tamanho da primeira operação na sequência, enquanto “currentLotSize” nos ajuda a acompanhar o tamanho do lote da operação mais recente no processo de recuperação. Para controlar a estratégia de recuperação, especificamos a distância da zona de recuperação em pontos usando “zoneSize” e definimos o intervalo do alvo de lucro com “targetSize”.

Para lidar com o dimensionamento dinâmico de lotes, incluímos um “multiplier”, que será usado para calcular o tamanho do lote de cada operação de recuperação subsequente. Adicionamos “symbol” para identificar o instrumento de negociação dessa recuperação, garantindo que o EA execute operações no símbolo correto. Utilizamos o tipo de dado de enumeração ENUM_ORDER_TYPE para declarar a variável “lastOrderType”, que armazena o tipo da última ordem executada (por exemplo, BUY ou SELL), e “lastOrderPrice” para registrar seu preço de execução, ajudando a acompanhar o estado atual da recuperação. Para o monitoramento das zonas de recuperação, definimos “zoneHigh” e “zoneLow” como os limites superior e inferior da zona de recuperação, enquanto “zoneTargetHigh” e “zoneTargetLow” marcam o intervalo do alvo de lucro.

Para determinar se uma recuperação está ativa, utilizamos “isRecovery”, um sinalizador que definimos como true ou false conforme necessário. Também incluímos “tickets[]”, um array no qual armazenamos os números de ticket de todas as operações na sequência de recuperação, permitindo rastreá-las e gerenciá-las individualmente. Por fim, incluímos “trailingStop” para especificar a distância do trailing stop e “initialEntryPrice” para registrar o preço de entrada da primeira operação, que será utilizado no cálculo do trailing stop. Esses componentes nos permitem proteger dinamicamente os lucros durante a recuperação.

Após definir as variáveis membro, precisamos inicializá-las em cada instância criada, ou seja, em cada cesta. Para isso, podemos criar um método para lidar com a inicialização de forma adequada.

//--- Initialize position recovery
void Initialize(double lot, double zonePts, double targetPts, double lotMultiplier, string _symbol, ENUM_ORDER_TYPE type, double price) {
   initialLotSize = lot;             //--- Assign initial lot size.
   currentLotSize = lot;             //--- Set current lot size equal to initial lot size.
   zoneSize = zonePts * _Point;      //--- Calculate zone size in points.
   targetSize = targetPts * _Point;  //--- Calculate target size in points.
   multiplier = lotMultiplier;       //--- Assign lot size multiplier.
   symbol = _symbol;                 //--- Assign the trading symbol.
   lastOrderType = type;             //--- Set the type of the last order.
   lastOrderPrice = price;           //--- Record the price of the last executed order.
   isRecovery = false;               //--- Set recovery as inactive initially.
   ArrayResize(tickets, 0);          //--- Initialize the tickets array.
   trailingStop = 0;                 //--- Initialize trailing stop
   initialEntryPrice = price;        //--- Set initial entry price
   CalculateZones();                 //--- Calculate recovery and target zones.
}

Definimos o método “Initialize”, que é responsável por configurar todos os parâmetros necessários e inicializar o estado de uma recuperação de posição individual. Esse método garante que cada instância de recuperação seja configurada corretamente e esteja pronta para gerenciar operações de forma dinâmica com base nos valores de entrada fornecidos. Começamos atribuindo o valor “lot” a “initialLotSize”, que especifica o tamanho da primeira operação na sequência de recuperação. Ao mesmo tempo, definimos “currentLotSize” igual a “initialLotSize”, pois a primeira operação utiliza o mesmo tamanho de lote. Em seguida, calculamos o tamanho da zona de recuperação e o intervalo do alvo de lucro em pontos usando as entradas “zonePts” e “targetPts”, respectivamente, multiplicando-as pela constante “_Point” para considerar o valor do ponto do símbolo. Esses cálculos definem os limites de distância para o gerenciamento das operações de recuperação e de seus alvos.

Atribuímos o “lotMultiplier” à variável “multiplier”, que determina como o tamanho do lote aumentará nas operações de recuperação subsequentes. O símbolo de negociação é atribuído a “symbol”, garantindo que todas as operações dessa instância de recuperação sejam executadas no instrumento de mercado correto. Definimos “lastOrderType” com o parâmetro “type” fornecido e “lastOrderPrice” com “price”, registrando os detalhes da ordem mais recente. Esses valores nos ajudam a acompanhar o estado da recuperação atual. Além disso, inicializamos “isRecovery” como false, indicando que o processo de recuperação não está ativo no momento da criação.

Redimensionamos o array “tickets” para zero usando a função “ArrayResize”, limpando quaisquer dados existentes e preparando-o para armazenar os números de ticket das operações associadas a essa instância de recuperação. Para maior flexibilidade, inicializamos “trailingStop” como 0 e definimos “initialEntryPrice” como “price”, fornecendo uma base para os cálculos do trailing stop. Por fim, chamamos o método “CalculateZones”, que calcula os limites superior e inferior da zona de recuperação e do intervalo do alvo. Essa etapa garante que o EA tenha todos os dados necessários para gerenciar operações de forma eficiente. Ao utilizar o método “Initialize”, estabelecemos um ponto inicial completo e bem definido para cada processo de recuperação, assegurando que todos os parâmetros relevantes estejam configurados corretamente para um gerenciamento eficaz das operações. Em seguida, podemos prosseguir para definir a função “CalculateZones”, que é responsável por calcular os níveis do intervalo de recuperação.

//--- Calculate dynamic zones and targets
void CalculateZones() {
   if (lastOrderType == ORDER_TYPE_BUY) { //--- If the last order was a BUY...
      zoneHigh = lastOrderPrice;         //--- Set upper boundary at the last order price.
      zoneLow = zoneHigh - zoneSize;     //--- Set lower boundary below the last order price.
      zoneTargetHigh = zoneHigh + targetSize; //--- Define target range above recovery zone.
      zoneTargetLow = zoneLow - targetSize;   //--- Define target range below recovery zone.
   } else if (lastOrderType == ORDER_TYPE_SELL) { //--- If the last order was a SELL...
      zoneLow = lastOrderPrice;                //--- Set lower boundary at the last order price.
      zoneHigh = zoneLow + zoneSize;           //--- Set upper boundary above the last order price.
      zoneTargetLow = zoneLow - targetSize;    //--- Define target range below recovery zone.
      zoneTargetHigh = zoneHigh + targetSize;  //--- Define target range above recovery zone.
   }
}

Aqui, definimos o método “CalculateZones”, que calcula dinamicamente os limites da zona de recuperação e os intervalos de alvo de lucro com base no tipo da última ordem executada e em seu preço. Esse método garante que cada processo de recuperação tenha níveis claramente definidos para orientar as decisões de negociação subsequentes, permitindo que o sistema reaja adequadamente aos movimentos do mercado.

Começamos verificando “lastOrderType” para determinar se a ordem mais recente foi uma BUY ou uma SELL. Se “lastOrderType” for ORDER_TYPE_BUY, atribuímos “zoneHigh” a “lastOrderPrice”, definindo o limite superior da zona de recuperação no preço de entrada da última ordem BUY. O limite inferior, “zoneLow”, é então calculado subtraindo “zoneSize” (convertido em pontos) de “zoneHigh”. Além disso, definimos o intervalo do alvo de lucro: “zoneTargetHigh” é calculado adicionando “targetSize” a “zoneHigh”, enquanto “zoneTargetLow” é calculado subtraindo “targetSize” de “zoneLow”. Esses cálculos garantem que a zona de recuperação e o intervalo do alvo de lucro sejam posicionados em relação ao preço da ordem BUY.

Se “lastOrderType” for ORDER_TYPE_SELL, invertemos a lógica. Nesse caso, atribuímos “zoneLow” a “lastOrderPrice”, definindo o limite inferior da zona de recuperação no preço de entrada da última ordem SELL. O limite superior, “zoneHigh”, é calculado adicionando “zoneSize” a “zoneLow”. Para o intervalo do alvo de lucro, calculamos “zoneTargetLow” subtraindo “targetSize” de “zoneLow” e “zoneTargetHigh” adicionando “targetSize” a “zoneHigh”. Esses limites são definidos em relação ao preço da ordem SELL. Essas definições de níveis representariam a imagem visualizada abaixo:

VISUALIZAÇÃO DOS NÍVEIS DO SISTEMA

Após definir os níveis das zonas, podemos prosseguir para a abertura das posições. Iremos encapsular a lógica de abertura de posições em um método para facilitar o uso dentro da estrutura do código.

//--- Open a trade with comments for position type
bool OpenTrade(ENUM_ORDER_TYPE type, string comment) {
   if (type == ORDER_TYPE_BUY) { //--- For a BUY order...
      if (trade.Buy(currentLotSize, symbol, 0, 0, 0, comment)) { //--- Attempt to place a BUY trade.
         lastOrderType = ORDER_TYPE_BUY;                        //--- Update the last order type.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Record the current price.
         ArrayResize(tickets, ArraySize(tickets) + 1);          //--- Resize the tickets array.
         tickets[ArraySize(tickets) - 1] = trade.ResultOrder(); //--- Store the new ticket.
         CalculateZones();                                      //--- Recalculate zones.
         isRecovery = false;                                    //--- Ensure recovery is inactive for initial trade.
         Print("Opened BUY Position, Ticket: ", tickets[ArraySize(tickets) - 1]);
         return true;                                           //--- Return success.
      }
   } else if (type == ORDER_TYPE_SELL) { //--- For a SELL order...
      if (trade.Sell(currentLotSize, symbol, 0, 0, 0, comment)) { //--- Attempt to place a SELL trade.
         lastOrderType = ORDER_TYPE_SELL;                        //--- Update the last order type.
         lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID);  //--- Record the current price.
         ArrayResize(tickets, ArraySize(tickets) + 1);           //--- Resize the tickets array.
         tickets[ArraySize(tickets) - 1] = trade.ResultOrder();  //--- Store the new ticket.
         CalculateZones();                                       //--- Recalculate zones.
         isRecovery = false;                                     //--- Ensure recovery is inactive for initial trade.
         Print("Opened SELL Position, Ticket: ", tickets[ArraySize(tickets) - 1]);
         return true;                                            //--- Return success.
      }
   }
   return false; //--- If the trade was not placed, return false.
}

Nesta função booleana “OpenTrade”, lidamos com a lógica para abrir uma nova operação de um tipo especificado (BUY ou SELL) e gerenciar as atualizações necessárias no sistema de recuperação. Essa função garante que as operações sejam abertas corretamente e que todos os dados relacionados sejam atualizados para manter a sincronização com o processo de recuperação. Quando o parâmetro “type” é ORDER_TYPE_BUY, tentamos abrir uma operação BUY utilizando o método “trade.Buy”. O método utiliza os parâmetros “currentLotSize”, “symbol” e “comment” para executar a operação, deixando os níveis de stop loss e take profit como zero (não especificados), pois estes serão definidos dinamicamente conforme os intervalos de alvo da zona. Se a operação BUY for posicionada com sucesso, atualizamos “lastOrderType” para ORDER_TYPE_BUY, indicando o tipo da última operação, e “lastOrderPrice” é definido como o preço atual de mercado obtido por meio da função com o parâmetro “SYMBOL_BID”.

Em seguida, redimensionamos o array “tickets” usando a função ArrayResize para criar espaço para a nova operação e armazenamos o número do ticket da operação posicionada com sucesso usando “trade.ResultOrder()”. Isso garante que todas as operações relacionadas a essa instância de recuperação sejam rastreadas e armazenadas de forma eficiente. Então chamamos a função “CalculateZones” para recalcular as zonas de recuperação e de alvo com base na operação mais recente. Por fim, definimos “isRecovery” como false, pois esta é a operação inicial e não faz parte de um processo de recuperação. Uma mensagem de sucesso é impressa no log, e a função retorna true para indicar que a operação foi aberta com sucesso.

Se o parâmetro “type” for “ORDER_TYPE_SELL”, seguimos uma lógica semelhante. O método “trade.Sell” é chamado para posicionar uma operação SELL com os parâmetros especificados. Em caso de sucesso, atualizamos “lastOrderType” para ORDER_TYPE_SELL e registramos “lastOrderPrice” como o preço atual de mercado. O array “tickets” é redimensionado e o novo ticket é armazenado, da mesma forma que fizemos para uma ordem BUY. As zonas são recalculadas usando “CalculateZones”, e “isRecovery” é definido como false. Uma mensagem de sucesso é exibida, e a função retorna true.

Se a operação falhar para qualquer um dos tipos de ordem, a função retorna false, sinalizando que a operação não foi bem-sucedida. Essa estrutura garante que as operações sejam gerenciadas de forma sistemática e que todos os dados relacionados à recuperação sejam atualizados corretamente para um gerenciamento de trades contínuo e eficiente. Após as posições serem abertas e os níveis das zonas calculados, podemos então continuar a gerenciar essas zonas a cada tick para abrir as posições de recuperação quando qualquer um dos níveis for atingido.

//--- Manage zone recovery
void ManageZones() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current price.
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) { //--- If price drops below the recovery zone for a BUY...
      double previousLotSize = currentLotSize;                       //--- Store the current lot size temporarily.
      currentLotSize *= multiplier;                                 //--- Tentatively increase lot size.
      if (OpenTrade(ORDER_TYPE_SELL, "Recovery Position")) {        //--- Attempt to open a SELL recovery trade.
         isRecovery = true;                                         //--- Mark recovery as active if trade is successful.
      } else {
         currentLotSize = previousLotSize;                          //--- Revert the lot size if the trade fails.
      }
   } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) { //--- If price rises above the recovery zone for a SELL...
      double previousLotSize = currentLotSize;                       //--- Store the current lot size temporarily.
      currentLotSize *= multiplier;                                 //--- Tentatively increase lot size.
      if (OpenTrade(ORDER_TYPE_BUY, "Recovery Position")) {         //--- Attempt to open a BUY recovery trade.
         isRecovery = true;                                         //--- Mark recovery as active if trade is successful.
      } else {
         currentLotSize = previousLotSize;                          //--- Revert the lot size if the trade fails.
      }
   }
}

Declaramos uma função “ManageZones” para monitorar o preço de mercado das zonas de recuperação e tomar ações caso o preço se mova contra a operação inicial. Primeiro, obtemos o preço atual de mercado utilizando a função SymbolInfoDouble para recuperar o último preço bid. Em seguida, verificamos se o preço saiu dos limites da zona de recuperação, que é definida por “zoneLow” para uma ordem BUY e por “zoneHigh” para uma ordem SELL.

Se a última ordem foi uma BUY (indicada por “lastOrderType” == ORDER_TYPE_BUY) e o preço atual cair abaixo de “zoneLow”, aumentamos o tamanho do lote para a operação de recuperação. Armazenamos o tamanho atual do lote em “previousLotSize” e, em seguida, multiplicamos “currentLotSize” pelo “multiplier” para aumentá-lo. Depois disso, tentamos abrir uma operação SELL de recuperação utilizando a função “OpenTrade”. Se a operação de recuperação for posicionada com sucesso, definimos “isRecovery” como true para indicar que a recuperação está ativa. Se a operação falhar, revertimos o tamanho do lote para o valor original armazenado em “previousLotSize”.

De forma semelhante, se a última ordem foi uma SELL (indicada por “lastOrderType” == ORDER_TYPE_SELL) e o preço subir acima de “zoneHigh”, aplicamos a mesma lógica para abrir uma operação BUY de recuperação. O tamanho do lote é aumentado e tentamos abrir a operação BUY. Se for bem-sucedida, “isRecovery” é definido como true, mas se a operação falhar, o tamanho do lote é revertido. Isso garante que o sistema gerencie as operações de recuperação de forma eficaz, ajustando os tamanhos de posição e tomando ações corretivas com base nas condições de mercado. Por fim, precisamos fechar as posições quando o preço atingir os níveis de alvo definidos, e para isso será necessária uma função que trate dessa lógica.

//--- Check and close trades at targets
void CheckCloseAtTargets() {
   double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current price.
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) { //--- If price reaches the target for a BUY...
      ClosePositionsAtTarget();                               //--- Close positions that meet the target criteria.
   } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) { //--- If price reaches the target for a SELL...
      ClosePositionsAtTarget();                               //--- Close positions that meet the target criteria.
   }
}

Aqui, definimos uma função void “CheckCloseAtTargets”, para verificar se o preço de mercado atingiu os níveis de alvo predefinidos e fechar as posições que atenderem aos critérios de alvo. Primeiro, obtemos o preço bid atual do mercado usando “SymbolInfoDouble (symbol, SYMBOL_BID)”. Em seguida, comparamos esse preço com os níveis de alvo definidos por “zoneTargetHigh” para uma ordem BUY e por “zoneTargetLow” para uma ordem SELL.

Se a última operação foi uma BUY (indicada por “lastOrderType” == ORDER_TYPE_BUY) e o preço atual subir até ou acima de “zoneTargetHigh”, consideramos que a posição atingiu o alvo de lucro desejado. Nesse caso, chamamos a função “ClosePositionsAtTarget” para fechar quaisquer posições que atendam aos critérios de alvo. Da mesma forma, se a última ordem foi uma SELL (indicada por “lastOrderType” == ORDER_TYPE_SELL) e o preço cair até ou abaixo de “zoneTargetLow”, o sistema novamente chama “ClosePositionsAtTarget” para fechar as posições. Essa função garante que as operações sejam encerradas quando o mercado atingir o alvo de lucro designado, garantindo os ganhos e finalizando o processo de recuperação.

Para fechar as posições, utilizamos uma função chamada “ClosePositionsAtTarget” para que possamos reutilizá-la. Aqui está o trecho da função.

//--- Close positions that have reached the target
void ClosePositionsAtTarget() {
   for (int i = ArraySize(tickets) - 1; i >= 0; i--) {              //--- Iterate through all tickets.
      ulong ticket = tickets[i];                                    //--- Get the position ticket.
      int retries = 10;                                             //--- Set retry count.
      while (retries > 0) {                                         //--- Retry until successful or retries exhausted.
         if (trade.PositionClose(ticket)) {                         //--- Attempt to close the position.
            Print("CLOSED # ", ticket, " Trailed and closed: ", (trailingStop != 0));
            ArrayRemove(tickets, i);                                //--- Remove the ticket from the array on success.
            retries = 0;                                            //--- Exit the loop on success.
         } else {
            retries--;                                              //--- Decrement retries on failure.
            Sleep(100);                                             //--- Wait before retrying.
         }
      }
   }
   if (ArraySize(tickets) == 0) {                                   //--- If all tickets are closed...
      Reset();                                                      //--- Reset recovery state after closing the target positions.
   }
}

Na função “ClosePositionsAtTarget”, percorremos todas as posições abertas armazenadas no array “tickets” e tentamos fechar aquelas que atingiram os níveis de alvo. Começamos iterando pelo array “tickets” em ordem reversa para garantir que não pulemos nenhuma posição ao removê-las após o fechamento. Para cada ticket, definimos uma contagem de tentativas “retries” para garantir que, se uma posição não for fechada na primeira tentativa, o sistema tente novamente.

Para cada posição, tentamos fechá-la utilizando a função “trade.PositionClose(ticket)”. Se a posição for fechada com sucesso, exibimos uma mensagem indicando que o ticket foi fechado e se era trailing ou não, utilizando “trailingStop != 0” para verificar se um trailing stop foi aplicado. Depois que a posição é fechada, removemos o ticket do array “tickets” usando a função ArrayRemove e saímos do loop de tentativas definindo “retries” como 0. Se a posição falhar ao fechar, decrementamos o contador “retries”, aguardamos por um breve período usando a função Sleep e, em seguida, tentamos fechar a posição novamente, garantindo que não sobrecarreguemos a função.

Após tentar fechar todas as posições, verificamos se o array “tickets” está vazio usando a função ArraySize. Se todas as posições estiverem fechadas, chamamos a função “Reset” para redefinir o estado da recuperação, limpando quaisquer dados restantes relacionados à recuperação e preparando o sistema para operações futuras. Isso é tudo. No entanto, como não temos total certeza de que o mercado atingirá nossos níveis de alvo, podemos melhorar o sistema aplicando trailing nas posições que atingirem nosso lucro mínimo, em vez de esperar até que os níveis sejam alcançados. Temos essa lógica novamente em um método.

//--- Apply trailing stop logic to initial positions
void ApplyTrailingStop() {
   if (inputTrailingStopEnabled && ArraySize(tickets) == 1) { // Ensure trailing stop is enabled and there is only one position (initial position)
      ulong ticket = tickets[0]; // Get the ticket of the initial position
      double entryPrice = GetPositionEntryPrice(ticket); // Get the entry price of the position by ticket
      double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); // Get the current price
      double newTrailingStop;

      if (lastOrderType == ORDER_TYPE_BUY) {
         if (currentPrice > entryPrice + (inputMinimumProfitPts + inputTrailingStopPts) * _Point) {
            newTrailingStop = currentPrice - inputTrailingStopPts * _Point; // Calculate new trailing stop for BUY
            if (newTrailingStop > trailingStop) {
               trailingStop = newTrailingStop; // Update trailing stop if the new one is higher
               Print("Trailing BUY Position, Ticket: ", ticket, " New Trailing Stop: ", trailingStop);
            }
         }

         if (trailingStop != 0 && currentPrice <= trailingStop) {
            Print("Trailed and closing BUY Position, Ticket: ", ticket);
            ClosePositionsAtTarget(); // Close position if the price falls below the trailing stop
         }
      } else if (lastOrderType == ORDER_TYPE_SELL) {
         if (currentPrice < entryPrice - (inputMinimumProfitPts + inputTrailingStopPts) * _Point) {
            newTrailingStop = currentPrice + inputTrailingStopPts * _Point; // Calculate new trailing stop for SELL
            if (newTrailingStop < trailingStop) {
               trailingStop = newTrailingStop; // Update trailing stop if the new one is lower
               Print("Trailing SELL Position, Ticket: ", ticket, " New Trailing Stop: ", trailingStop);
            }
         }

         if (trailingStop != 0 && currentPrice >= trailingStop) {
            Print("Trailed and closing SELL Position, Ticket: ", ticket);
            ClosePositionsAtTarget(); // Close position if the price rises above the trailing stop
         }
      }
   }
}

No método void “ApplyTrailingStop” que definimos, implementamos a lógica de trailing stop para a posição inicial com base em se o trailing stop está habilitado e se há apenas uma posição ativa. Primeiro, verificamos se o recurso de trailing stop está habilitado usando “inputTrailingStopEnabled” e se há apenas uma posição aberta (garantido por “ArraySize(tickets) == 1”). Em seguida, recuperamos o ticket da posição inicial e utilizamos esse ticket para obter o preço de entrada por meio da função “GetPositionEntryPrice”. Também obtemos o preço atual de mercado utilizando a função SymbolInfoDouble.

Para uma posição BUY, verificamos se o preço atual se moveu acima do preço de entrada por uma quantidade específica (considerando tanto o lucro mínimo quanto a distância do trailing stop, calculados com “inputMinimumProfitPts + inputTrailingStopPts”) e definimos o novo trailing stop de acordo. Se o trailing stop calculado for maior que o “trailingStop” atual, atualizamos o valor do trailing stop e exibimos uma mensagem indicando o novo nível de trailing stop. Se o preço atual cair até ou abaixo do nível do trailing stop, fechamos a posição utilizando a função “ClosePositionsAtTarget”.

Para uma posição SELL, seguimos um processo semelhante, porém invertido. Verificamos se o preço atual está abaixo do preço de entrada por uma determinada quantidade e definimos o trailing stop para baixo, se necessário. Se o trailing stop calculado for menor que o “trailingStop” atual, atualizamos o trailing stop e exibimos uma mensagem indicando o novo nível. Se o preço atual subir até ou acima do trailing stop, a posição é fechada. Essa função garante que o trailing stop seja aplicado dinamicamente com base nas condições de mercado, permitindo o travamento de lucros enquanto protege a posição contra perdas significativas. Se o preço se mover favoravelmente, o trailing stop é ajustado; se o preço reverter e atingir o trailing stop, a posição é fechada.

Você pode ter notado que utilizamos uma função personalizada para obter os preços de entrada. Aqui está a lógica dessa função.

//--- Get the entry price of a position by ticket
double GetPositionEntryPrice(ulong ticket) {
   if (PositionSelectByTicket(ticket)) {
      return PositionGetDouble(POSITION_PRICE_OPEN);
   } else {
      Print("Failed to select position by ticket: ", ticket);
      return 0.0;
   }
}

Aqui, definimos a função “GetPositionEntryPrice”, na qual recuperamos o preço de entrada de uma posição usando o número de ticket fornecido. Primeiro, tentamos selecionar a posição associada ao ticket informado utilizando a função PositionSelectByTicket. Se a posição for selecionada com sucesso, recuperamos o preço de entrada da posição chamando “PositionGetDouble (POSITION_PRICE_OPEN)”, que nos fornece o preço no qual a posição foi aberta. Se a posição não puder ser selecionada (por exemplo, se o ticket for inválido ou a posição não existir mais), exibimos uma mensagem de erro indicando a falha e retornamos o valor 0.0 para indicar que o preço de entrada não pôde ser recuperado.

Agora, após abrir e fechar as posições, precisamos redefinir o sistema e remover a cesta de trades associada como um método de limpeza. Veja como tratamos essa lógica de limpeza, em uma função “Reset”.

//--- Reset recovery state
void Reset() {
   currentLotSize = inputlot; //--- Reset lot size to initial value.
   lastOrderType = -1;              //--- Clear the last order type.
   lastOrderPrice = 0.0;            //--- Reset the last order price.
   isRecovery = false;              //--- Mark recovery as inactive.
   ArrayResize(tickets, 0);         //--- Clear the tickets array.
   trailingStop = 0;                //--- Reset trailing stop
   initialEntryPrice = 0.0;         //--- Reset initial entry price
   Print("Strategy BASKET reset after closing trades.");
}

Veja como tratamos essa lógica de limpeza, em uma função “Reset”. Primeiro, redefinimos “currentLotSize” para o valor inicial definido por “inputlot”, garantindo que o tamanho do lote seja restaurado para o valor inicial definido pelo usuário. Também limpamos os detalhes da última ordem, definindo “lastOrderType” como -1 (o que indica nenhum tipo de ordem ativa) e redefinimos “lastOrderPrice” para 0.0, removendo efetivamente qualquer informação de preço de ordem anterior.

Em seguida, marcamos a recuperação como inativa definindo “isRecovery” como false, garantindo que nenhuma lógica de recuperação seja aplicada quando o reset ocorrer. Então limpamos o array “tickets” utilizando a função ArrayResize, removendo todos os tickets de posições que faziam parte do processo de recuperação anterior. Além disso, redefinimos “trailingStop” para 0 e “initialEntryPrice” para 0.0, limpando quaisquer configurações de trailing stop e valores de preço de entrada de operações anteriores. Por fim, exibimos a mensagem “Strategy BASKET reset after closing trades” para notificar que o reset foi concluído e que o estado de recuperação foi limpo. Essa função garante que o sistema esteja em um estado limpo e pronto para o próximo ciclo de negociação.

Após definir as propriedades da estrutura, estamos agora prontos para gerar sinais e adicioná-los à estrutura definida. No entanto, como será necessário gerenciar muitos sinais dinâmicos, precisaremos definir uma estrutura de array, que atuará como uma cesta principal na qual definiremos as subcestas para cada sinal gerado. Veja como fazemos isso.

//--- Dynamic list to track multiple positions
PositionRecovery recoveryArray[]; //--- Dynamic array for recovery instances.

Aqui, declaramos um array dinâmico chamado “recoveryArray”, que é projetado para rastrear e gerenciar múltiplas instâncias de recuperação de posições. O array é baseado na estrutura “PositionRecovery”, permitindo armazenar estados individuais de recuperação para múltiplas operações de forma independente. Cada elemento do array representa uma configuração distinta de recuperação, completa com todos os atributos relevantes, como tamanho de lote, limites de zona e tickets de operações associados.

Ao tornar o array dinâmico, podemos expandi-lo ou reduzi-lo conforme necessário durante a execução, utilizando funções como ArrayResize. Isso nos permite adicionar dinamicamente novas instâncias de recuperação para novos sinais de negociação ou remover recuperações concluídas, garantindo uso eficiente de memória e adaptabilidade a diferentes cenários de negociação. Essa abordagem é essencial para gerenciar múltiplas operações simultaneamente, pois permite que a lógica de recuperação de cada operação funcione de forma independente dentro de sua própria “cesta” de dados.

Após definir o array, podemos agora iniciar a lógica de geração de sinais. Precisaremos inicializar o handle do indicador no manipulador de eventos OnInit, que é chamado sempre que o programa é inicializado.

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

No manipulador de eventos OnInit, inicializamos os componentes essenciais necessários para o funcionamento do Expert Advisor (EA). Começamos criando um handle do indicador RSI usando a função iRSI, que calcula o Relative Strength Index para o símbolo e período atuais. Esse handle permite que o EA acesse dinamicamente os valores do RSI. Se a criação do handle falhar, indicada pelo valor INVALID_HANDLE, registramos uma mensagem de erro com detalhes usando a função Print e retornamos INIT_FAILED para encerrar o processo de inicialização. Em seguida, configuramos o array “rsiBuffer” como uma série temporal usando ArraySetAsSeries, garantindo que os dados estejam organizados cronologicamente para um processamento preciso. Após a inicialização bem-sucedida, exibimos uma mensagem de confirmação indicando que a estratégia está pronta e retornamos INIT_SUCCEEDED para sinalizar que o EA está pronto para operar. Em seguida, no manipulador de eventos OnDeinit, destruímos o handle para economizar recursos.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   if (rsiHandle != INVALID_HANDLE)             //--- Check if RSI handle is valid.
      IndicatorRelease(rsiHandle);              //--- Release the RSI handle.
   Print("Multi-Zone Recovery Strategy deinitialized."); //--- Log deinitialization.
}

Aqui, tratamos a limpeza e o gerenciamento de recursos do Expert Advisor (EA) quando ele é removido ou desativado. Primeiro, verificamos se o “rsiHandle” do indicador RSI é válido, garantindo que ele não seja igual a “INVALID_HANDLE”. Se o handle for válido, nós o liberamos usando a função IndicatorRelease para liberar recursos e evitar vazamentos de memória. Por fim, registramos uma mensagem usando Print para indicar que a Estratégia de Recuperação Multi-Zona foi desinicializada com sucesso. Essa função garante um encerramento limpo e organizado do programa, sem deixar recursos ou processos pendentes.

Depois disso, podemos avançar para o manipulador de eventos OnTick, que lidará com toda a lógica principal do sistema utilizando a estrutura definida anteriormente. Primeiro, precisamos recuperar os dados do indicador para podermos usá-los na análise subsequente.

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

   //---

}

Aqui, na função OnTick, tratamos a lógica executada a cada novo tick, que representa uma atualização de preço para o símbolo negociado. O primeiro passo envolve copiar os valores do indicador RSI para o array “rsiBuffer” usando a função CopyBuffer. Especificamos o “rsiHandle” para identificar o indicador RSI, definimos o índice do buffer como 0 e solicitamos dois valores a partir da barra mais recente. Se a operação falhar (ou seja, se o valor retornado for menor ou igual a 0), exibimos uma mensagem de erro usando Print para notificar o usuário sobre o problema e incluímos os detalhes do erro obtidos por meio da função GetLastError. Após registrar o erro, saímos imediatamente da função usando return. Isso garante que o restante da lógica não seja executado caso a recuperação dos dados do RSI falhe, mantendo a integridade e a estabilidade do Expert Advisor.

Se recuperarmos os dados com sucesso, podemos então utilizá-los para a lógica de geração de sinais, conforme a seguir.

datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar.
if (currentBarTime != lastBarTime) {                         //--- Check if a new bar has formed.
   lastBarTime = currentBarTime;                             //--- Update the last processed bar time.
   if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) {            //--- Check for oversold RSI crossing up.
      Print("BUY SIGNAL");
      PositionRecovery newRecovery;                          //--- Create a new recovery instance.
      newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_BUY, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
      newRecovery.OpenTrade(ORDER_TYPE_BUY, "Initial Position"); //--- Open an initial BUY position.
      ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
      recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
   } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) {      //--- Check for overbought RSI crossing down.
      Print("SELL SIGNAL");
      PositionRecovery newRecovery;                          //--- Create a new recovery instance.
      newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
      newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); //--- Open an initial SELL position.
      ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
      recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
   }
}

Aqui, focamos na detecção de novas barras e na geração de sinais de negociação com base no cruzamento do indicador em determinados níveis. Primeiro, recuperamos o tempo da barra atual usando a função iTime, que é armazenado na variável “currentBarTime”. Em seguida, comparamos “currentBarTime” com “lastBarTime” para verificar se uma nova barra foi formada. Se os dois valores forem diferentes, isso indica que uma nova barra foi formada, então atualizamos “lastBarTime” com o valor de “currentBarTime” para evitar o processamento repetido da mesma barra.

Em seguida, avaliamos as condições para sinais baseados no RSI. Se o valor do RSI em “rsiBuffer[1]” (barra anterior) for maior que 30 e o valor atual em “rsiBuffer[0]” (barra atual) for menor ou igual a 30, isso indica uma condição de sobrevenda com cruzamento para cima. Nesse caso, exibimos uma mensagem de “BUY SIGNAL” e iniciamos uma nova instância de “PositionRecovery” chamada “newRecovery”. Em seguida, chamamos o método “Initialize” de “newRecovery” para configurar os parâmetros de recuperação, incluindo “inputlot”, “inputzonesizepts”, “inputzonetragetpts”, “inputlotmultiplier”, símbolo, tipo de ordem como ORDER_TYPE_BUY e o preço bid atual obtido pela função “SymbolInfoDouble”. Após a inicialização, abrimos uma posição inicial “BUY” usando o método “OpenTrade”, passando “ORDER_TYPE_BUY” e um comentário descritivo.

Da mesma forma, se o valor do “RSI” em “rsiBuffer[1]” for menor que 70 e o valor atual em “rsiBuffer[0]” for maior ou igual a 70, isso indica uma condição de sobrecompra com cruzamento para baixo. Nesse cenário, exibimos uma mensagem de “SELL SIGNAL” e criamos uma nova instância de “PositionRecovery”. Após inicializá-la com os mesmos parâmetros, mas definindo o tipo de ordem como ORDER_TYPE_SELL, abrimos uma posição inicial “SELL” usando o método “OpenTrade”.

Por fim, para ambos os sinais “BUY” e “SELL”, adicionamos a instância inicializada de “PositionRecovery” ao “recoveryArray”. O array é redimensionado usando a função ArrayResize, e a nova instância é atribuída à última posição do array, garantindo que cada recuperação seja rastreada de forma independente. Essa lógica agora é responsável por iniciar as cestas de posições com posições iniciais e condições definidas. Para lidar com o gerenciamento das posições, precisaremos percorrer as cestas dentro da cesta principal e aplicar a lógica de gerenciamento definida na estrutura principal a cada tick. Segue a lógica:

for (int i = 0; i < ArraySize(recoveryArray); i++) { //--- Iterate through all recovery instances.
   recoveryArray[i].ManageZones();                 //--- Manage zones for each recovery instance.
   recoveryArray[i].CheckCloseAtTargets();         //--- Check and close positions at targets.
   recoveryArray[i].ApplyTrailingStop();           //--- Apply trailing stop logic to initial positions.
}

Para gerenciar as posições de forma independente, utilizamos um for loop para iterar por todas as instâncias de recuperação armazenadas no “recoveryArray”. Esse loop garante que cada instância de recuperação seja gerenciada separadamente, permitindo que o sistema mantenha controle independente sobre múltiplos cenários de recuperação. O loop começa com o índice “i” definido como 0 e continua até que todos os elementos do “recoveryArray” tenham sido processados, conforme determinado pela função ArraySize.

Dentro do loop, três métodos essenciais são chamados em cada instância de recuperação. Primeiro, o método “ManageZones” é invocado usando o dot operator, que monitora os movimentos de preço em relação às zonas de recuperação definidas. Se o preço sair dos limites da zona, esse método toma medidas tentando abrir uma posição de recuperação, ajustando dinamicamente o tamanho do lote de acordo com o multiplicador especificado.

Em seguida, o método “CheckCloseAtTargets” é executado para avaliar se o preço atingiu os níveis de alvo da instância de recuperação. Se as condições de alvo forem atendidas, esse método fecha todas as posições associadas e redefine a instância de recuperação, garantindo que os lucros sejam assegurados e que a instância esteja pronta para um novo ciclo.

Por fim, o método “ApplyTrailingStop” é aplicado, que impõe a lógica de trailing stop para a posição inicial da instância de recuperação. Esse método ajusta dinamicamente o nível do trailing stop conforme o preço se move de forma favorável, garantindo os lucros. Se o preço reverter e atingir o trailing stop, o método garante que a posição seja fechada, protegendo contra possíveis perdas.

Ao processar cada instância de recuperação dessa forma, o sistema gerencia de maneira eficaz múltiplas posições independentes, garantindo que todos os cenários de recuperação sejam tratados dinamicamente e em conformidade com as estratégias predefinidas. Para garantir que o programa esteja funcionando corretamente, nós o executamos, e este é o resultado.

EXEMPLO DE RESET DA ESTRATÉGIA

A partir da imagem, podemos ver que a estratégia foi redefinida após o fechamento de uma instância de recuperação, que é um dos principais objetivos do sistema. No entanto, o fechamento não interfere nas outras instâncias em execução, o que significa que cada instância é tratada de forma independente das demais instâncias no array. Para confirmar isso, mudamos para a aba de trades e podemos ver que existem instâncias ativas.

INSTÂNCIAS DE TRADE

A partir da imagem, podemos ver que ainda existem instâncias de recuperação em andamento, e duas já estão em modo de recuperação. Elas são perfeitamente distinguidas pelos comentários adicionados a elas na seção direita, indicando se são posições iniciais ou de recuperação. Isso confirma que atingimos com sucesso nosso objetivo, e o que resta agora é realizar o backtest do programa e analisar seu desempenho. Isso é tratado na seção seguinte.


Backtesting

Para avaliar o desempenho e a robustez do programa, primeiro precisamos simular condições históricas de mercado. Ao fazer isso, podemos determinar o quão bem o programa lida com cenários de recuperação, se ajusta aos movimentos de preço e gerencia as operações. O backtest nos fornece informações importantes sobre a lucratividade da estratégia, níveis de drawdown e gerenciamento de risco. O programa gera sinais de compra e venda com base nos limites estabelecidos (por exemplo, sobrevenda em 30 e sobrecompra em 70; exatamente como o usuário prefere), processando dados históricos de preço tick a tick para replicar condições reais de mercado. Quando um sinal é gerado, o EA inicializa uma nova instância de recuperação, executa a primeira operação e acompanha os movimentos de preço dentro das zonas de recuperação designadas. 

Testamos rigorosamente o mecanismo dinâmico de recuperação do sistema, que ajusta os tamanhos de lote usando o multiplicador e abre posições de hedge quando necessário, em diversas condições de mercado. O programa avalia os cenários de recuperação de forma independente para cada sinal, garantindo que todas as operações sejam gerenciadas de forma isolada, conforme refletido no tratamento do “recoveryArray”. Isso garante que, mesmo com múltiplas instâncias de recuperação ativas, a estratégia permaneça organizada e adaptável. Testamos o programa nos últimos 5 meses utilizando as seguintes configurações:

CONFIGURAÇÕES DO BACKTEST

Após a conclusão, temos os seguintes resultados:

Gráfico do testador de estratégia:

GRÁFICO

Relatórios do Strategy Tester:

RELATÓRIO

A partir das imagens acima, podemos ver que os gráficos são suaves, embora apresentem oscilações quando há correlação entre o saldo e a equidade, causadas pelo aumento contínuo no número de níveis de recuperação que o programa executa por instância e pela quantidade de sinais gerados. Assim, podemos limitar o número de posições de recuperação habilitando o recurso de trailing stop. Aqui estão os resultados obtidos.

RELATÓRIO_TRAILING HABILITADO

A partir da imagem, podemos ver que o número de operações é reduzido e a taxa de acerto aumenta quando o recurso de trailing stop está habilitado. Podemos limitar ainda mais o número de posições incorporando uma lógica de restrição de contagem de trades, na qual, quando já existem várias posições abertas, não consideramos a abertura de novas ordens com base nos sinais gerados. Para isso, definimos variáveis de entrada adicionais conforme a seguir:

input bool inputEnablePositionsRestriction = true; // Enable Maximum positions restriction
input int inputMaximumPositions = 11; // Maximum number of positions

Essas variáveis de entrada contêm um sinalizador para habilitar ou desabilitar a opção de restrição, e a segunda contém o número máximo de posições que podem ser iniciadas no sistema quando a restrição estiver habilitada. Em seguida, adotamos essa lógica no manipulador de eventos OnTick quando um sinal é confirmado, adicionando essa camada extra de restrição de negociação.

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

   datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar.
   if (currentBarTime != lastBarTime) {                         //--- Check if a new bar has formed.
      lastBarTime = currentBarTime;                             //--- Update the last processed bar time.
      if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) {            //--- Check for oversold RSI crossing up.
         Print("BUY SIGNAL");
         if (inputEnablePositionsRestriction == false || inputMaximumPositions > PositionsTotal()){
            PositionRecovery newRecovery;                          //--- Create a new recovery instance.
            newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_BUY, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
            newRecovery.OpenTrade(ORDER_TYPE_BUY, "Initial Position"); //--- Open an initial BUY position.
            ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
            recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
         }
         else {
            Print("FAILED: Maximum positions threshold hit!");
         }
      } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) {      //--- Check for overbought RSI crossing down.
         Print("SELL SIGNAL");
         if (inputEnablePositionsRestriction == false || inputMaximumPositions > PositionsTotal()){
            PositionRecovery newRecovery;                          //--- Create a new recovery instance.
            newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery.
            newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); //--- Open an initial SELL position.
            ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array.
            recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array.
         }
         else {
            Print("FAILED: Maximum positions threshold hit!");
         }
      }
   }

   for (int i = 0; i < ArraySize(recoveryArray); i++) { //--- Iterate through all recovery instances.
      recoveryArray[i].ManageZones();                 //--- Manage zones for each recovery instance.
      recoveryArray[i].CheckCloseAtTargets();         //--- Check and close positions at targets.
      recoveryArray[i].ApplyTrailingStop();           //--- Apply trailing stop logic to initial positions.
   }
}

Aqui, implementamos um mecanismo para gerenciar restrições de posições e controlar o número de operações que o Expert Advisor (EA) pode abrir em um determinado momento. A lógica começa avaliando se as restrições de posição estão desativadas (“inputEnablePositionsRestriction” == false) ou se o número total de posições atualmente abertas (PositionsTotal) está abaixo do máximo definido pelo usuário (“inputMaximumPositions”). Se qualquer uma dessas condições for atendida, o EA prossegue com a abertura de uma nova operação, garantindo que esteja alinhada com as preferências do usuário para negociação irrestrita ou limitada.

No entanto, se ambas as condições falharem — indicando que as restrições de posição estão habilitadas e que o número máximo permitido de posições foi atingido — o EA não abrirá uma nova operação. Em vez disso, ele registra uma mensagem de falha no terminal: “FAILED: Maximum positions threshold hit!”. Essa mensagem funciona como um mecanismo de feedback informativo, ajudando o usuário a entender por que operações adicionais não foram executadas. Destacamos as alterações na cor amarelo-claro para maior clareza. Após os testes, obtemos os seguintes resultados.

RELATÓRIO_RESTRIÇÃO DE MÁXIMO DE ORDENS

A partir da imagem, podemos ver que o número de operações é ainda mais reduzido e a taxa de acerto aumenta ainda mais. Isso confirma que alcançamos nosso objetivo de criar um sistema de recuperação multi-zona. Em uma visualização em Graphic Interchange Format (GIF), temos a seguinte simulação, confirmando o alcance do nosso objetivo.

GIF MULTI-ZONA


Conclusão

Em conclusão, este artigo ilustrou o processo de construção de um Expert Advisor MQL5 robusto, baseado em uma estratégia de Recuperação por Zonas em Múltiplos Níveis. Ao aproveitar conceitos centrais como detecção automatizada de sinais, gerenciamento dinâmico de recuperação e mecanismos de proteção de lucro como trailing stops, criamos um sistema flexível capaz de lidar com múltiplas instâncias de recuperação independentes. Os principais componentes dessa implementação incluem geração de sinais de trade, lógica de restrição de posições e tratamento eficiente tanto das estratégias de recuperação quanto de saída.

Aviso legal: Este artigo tem finalidade educacional para programação em MQL5. Embora o sistema de Recuperação por Zonas em Múltiplos Níveis apresentado forneça uma estrutura organizada para gerenciamento de operações, o comportamento do mercado é inerentemente incerto. Negociar envolve risco financeiro, e o sucesso histórico não garante resultados futuros. Testes abrangentes e um gerenciamento de risco eficaz são indispensáveis antes de implementar qualquer estratégia em mercados reais.

Ao seguir as metodologias discutidas neste guia, você pode expandir sua expertise em negociação algorítmica e aplicar esses princípios para criar sistemas de trading ainda mais sofisticados. Boa codificação, e que suas operações sejam bem-sucedidas!

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

Componente View para tabelas no paradigma MVC em MQL5: elemento gráfico básico Componente View para tabelas no paradigma MVC em MQL5: elemento gráfico básico
O artigo analisa o processo de desenvolvimento de um elemento gráfico básico para o componente View no contexto da implementação de tabelas no paradigma MVC (Model-View-Controller) na linguagem MQL5. Este é o primeiro artigo dedicado ao componente View e o terceiro da série de artigos sobre a criação de tabelas para o terminal cliente MetaTrader 5.
Integrar seu próprio LLM em EA (Parte 5): Desenvolver e testar estratégia de trading com LLMs (IV) — Testar estratégia de trading Integrar seu próprio LLM em EA (Parte 5): Desenvolver e testar estratégia de trading com LLMs (IV) — Testar estratégia de trading
Com o rápido desenvolvimento da inteligência artificial atualmente, os modelos de linguagem (LLMs) são uma parte importante da inteligência artificial, portanto devemos pensar em como integrar LLMs poderosos ao nosso trading algorítmico. Para a maioria das pessoas, é difícil ajustar esses modelos poderosos de acordo com suas necessidades, implantá-los localmente e, em seguida, aplicá-los ao trading algorítmico. Esta série de artigos adotará uma abordagem passo a passo para alcançar esse objetivo.
Análise quantitativa de tendências: coletando estatísticas em Python Análise quantitativa de tendências: coletando estatísticas em Python
O que é a análise quantitativa de tendências no mercado Forex. Coletando estatísticas sobre as tendências, sua magnitude e distribuição no par de moedas EURUSD. Como a análise quantitativa de tendências ajuda a criar um EA lucrativo.
Mecanismos de gating em aprendizado por ensemble Mecanismos de gating em aprendizado por ensemble
Neste artigo, continuamos nossa exploração de modelos ensemble discutindo o conceito de gates, especificamente como eles podem ser úteis na combinação das saídas dos modelos para aprimorar a precisão das previsões ou a generalização do modelo.