Automatização de estratégias de negociação em MQL5 (Parte 22): Desenvolvimento de um sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes
Introdução
No artigo anterior (Parte 21), exploramos uma estratégia de negociação baseada em uma rede neural, aprimorada com taxas de aprendizado adaptativas para aumentar a precisão das previsões dos movimentos do mercado em MetaQuotes Language 5 (MQL5). Nesta Parte 22, nosso foco será o desenvolvimento de um sistema de recuperação por zonas (Zone Recovery System), integrado a uma estratégia de negociação a favor da tendência que combina o indicador Envelopes com o índice de força relativa (RSI) para automatizar operações e gerenciar perdas com eficiência. Abordaremos os seguintes tópicos:
- Entendendo a arquitetura do sistema de recuperação por zonas aplicado à negociação a favor da tendência
- Implementação em MQL5
- Backtesting
- Conclusão
Ao final, você terá um sistema de negociação robusto, desenvolvido em MQL5 para operar em condições dinâmicas de mercado, pronto para implementação e testes.
Entendendo a arquitetura da estratégia de tendência com recuperação por zonas
A recuperação por zonas (zone recovery) é uma estratégia de negociação que busca transformar perdas potenciais em lucro por meio da abertura de operações adicionais quando o mercado se move contra a posição inicial, com o objetivo de encerrar o conjunto de operações com lucro ou, pelo menos, no ponto de equilíbrio. Imagine que você compre um par de moedas esperando uma valorização, mas o preço comece a cair. É nesse momento que entra em ação a recuperação por zonas: estabelecemos uma faixa de preços, ou "zona", na qual abrimos operações na direção oposta para compensar as perdas caso o preço volte a subir. Nosso objetivo é desenvolver um sistema automatizado em MetaQuotes Language 5 (MQL5) que aplique esse conceito ao mercado de câmbio, buscando manter os riscos baixos e maximizar os lucros.
Utilizaremos dois indicadores técnicos para identificar os melhores momentos de entrada no mercado. Um deles avaliará a força dos movimentos de preço, permitindo abrir operações apenas quando houver um movimento direcional forte e evitando sinais fracos ou pouco claros. O outro, o Envelopes, traça duas bandas que delimitam um canal em torno de uma média móvel dos preços, indicando quando os preços sobem ou caem excessivamente e sinalizando possíveis pontos de reação do preço favoráveis à entrada no mercado. Em conjunto, esses indicadores ajudarão a identificar oportunidades de negociação com maior probabilidade de sucesso, quando o preço apresentar sinais de mudança de direção para retomar o movimento a favor da tendência predominante.
Veja como planejamos integrar todos esses elementos: começaremos abrindo uma operação quando os indicadores sinalizarem uma mudança de direção do preço, por exemplo, quando ele atingir uma das bandas do Envelopes com forte impulso. Se o mercado se mover contra nossa posição, ativaremos a recuperação por zonas, abrindo operações na direção oposta dentro de uma faixa de preços previamente definida, cuja amplitude será cuidadosamente ajustada para equilibrar o risco e a capacidade de recuperação das perdas. Também limitaremos o número de operações para evitar a abertura excessiva de posições e manter a disciplina da estratégia. Essa abordagem permitirá aproveitar as oportunidades oferecidas pelas tendências e, ao mesmo tempo, contar com um mecanismo de proteção caso o mercado não se comporte como esperado, adaptando-se tanto a condições de maior volatilidade quanto a períodos de maior estabilidade. A seguir, apresentamos o plano de implementação.

Implementação em MQL5
Para desenvolver o programa em MQL5, abra o MetaEditor, acesse o "Navegador", selecione a pasta Indicators, clique em "Criar" e siga as instruções para criar o arquivo. Começaremos declarando algumas variáveis de entrada que permitirão ajustar facilmente os principais parâmetros do programa.
//+------------------------------------------------------------------+ //| Envelopes Trend Bounce with Zone Recovery EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade/Trade.mqh> //--- Include trade library enum TradingLotSizeOptions { FIXED_LOTSIZE, UNFIXED_LOTSIZE }; //--- Define lot size options input group "======= EA GENERAL SETTINGS =======" input TradingLotSizeOptions lotOption = UNFIXED_LOTSIZE; // Lot Size Option input double initialLotSize = 0.01; // Initial Lot Size input double riskPercentage = 1.0; // Risk Percentage (%) input int riskPoints = 300; // Risk Points input int magicNumber = 123456789; // Magic Number input int maxOrders = 1; // Maximum Initial Positions input double zoneTargetPoints = 600; // Zone Target Points input double zoneSizePoints = 300; // Zone Size Points input bool restrictMaxOrders = true; // Apply Maximum Orders Restriction
Nesta etapa, estabeleceremos a base do nosso sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes em MQL5, definindo os componentes necessários e os parâmetros configuráveis pelo usuário. Primeiro, incluiremos a biblioteca <Trade/Trade.mqh>, que disponibiliza a classe CTrade para executar operações de negociação, como abrir e fechar posições. Essa biblioteca é fundamental, pois fornece ao EA os recursos necessários para interagir com o mercado, especialmente para enviar ordens. A seguir, mostramos como abrir o arquivo.

Em seguida, definimos o tipo enumerado TradingLotSizeOptions com dois valores: FIXED_LOTSIZE e UNFIXED_LOTSIZE. Isso permite que o usuário escolha entre um tamanho de lote fixo e um tamanho calculado dinamicamente com base nos parâmetros de risco, oferecendo flexibilidade para dimensionar as posições de acordo com diferentes estilos de negociação. Depois, configuramos os parâmetros de entrada do grupo EA GENERAL SETTINGS (configurações gerais do EA), que podem ser alterados pelo usuário na plataforma MetaTrader 5.
O parâmetro lotOption, definido por padrão como UNFIXED_LOTSIZE, determina se o EA utilizará um tamanho de lote fixo ou calculado com base no risco. O parâmetro initialLotSize (0.01) estabelece o tamanho do lote para operações com volume fixo, enquanto riskPercentage (1.0%) e riskPoints (300) definem, respectivamente, o percentual do saldo da conta destinado ao risco e a distância do stop-loss em pontos, utilizados no cálculo dinâmico do tamanho do lote. Essas configurações determinam o risco assumido em cada operação, permitindo adequar o funcionamento do EA à tolerância ao risco do usuário.
Atribuiremos um magicNumber exclusivo (123456789) para identificar as operações do nosso EA e diferenciá-las das demais operações realizadas na mesma conta. Os parâmetros de entrada maxOrders (1) e restrictMaxOrders (true) limitam o número de posições iniciais, impedindo que o EA abra posições em excesso simultaneamente. Por fim, zoneTargetPoints (600) e zoneSizePoints (300) definem, respectivamente, o objetivo de lucro e a amplitude da zona de recuperação, ambos em pontos, estabelecendo os limites utilizados pela estratégia. Após a compilação, obtemos o seguinte resultado.

Após carregar os dados de entrada, podemos começar a definir a lógica central do sistema. Primeiro, declararemos algumas estruturas e classes, pois adotaremos a programação orientada a objetos (OOP).
class MarketZoneTrader { private: //--- Trade State Definition enum TradeState { INACTIVE, RUNNING, TERMINATING }; //--- Define trade lifecycle states //--- Data Structures struct TradeMetrics { bool operationSuccess; //--- Track operation success double totalVolume; //--- Sum closed trade volumes double netProfitLoss; //--- Accumulate profit/loss }; struct ZoneBoundaries { double zoneHigh; //--- Upper recovery zone boundary double zoneLow; //--- Lower recovery zone boundary double zoneTargetHigh; //--- Upper profit target double zoneTargetLow; //--- Lower profit target }; struct TradeConfig { string marketSymbol; //--- Trading symbol double openPrice; //--- Position entry price double initialVolume; //--- Initial trade volume long tradeIdentifier; //--- Magic number string tradeLabel; //--- Trade comment ulong activeTickets[]; //--- Active position tickets ENUM_ORDER_TYPE direction; //--- Trade direction double zoneProfitSpan; //--- Profit target range double zoneRecoverySpan; //--- Recovery zone range double accumulatedBuyVolume; //--- Total buy volume double accumulatedSellVolume; //--- Total sell volume TradeState currentState; //--- Current trade state }; struct LossTracker { double tradeLossTracker; //--- Track cumulative profit/loss }; };
Nesta etapa, definiremos a estrutura principal do nosso sistema de negociação a favor da tendência com o indicador Envelopes em MQL5, criando a classe MarketZoneTrader e concentrando-nos em sua seção privada, que contém os estados de negociação e as estruturas de dados. Essa arquitetura permitirá estruturar os componentes essenciais para gerenciar operações, acompanhar a recuperação por zonas e monitorar o desempenho da estratégia. Começaremos definindo a classe MarketZoneTrader, que servirá de base para o nosso EA, encapsulando a lógica da estratégia de negociação.
Na seção privada, adicionaremos o tipo enumerado TradeState com três estados: INACTIVE (inativo), RUNNING (em execução) e TERMINATING (em encerramento). Esses estados permitem acompanhar o ciclo de vida das operações, identificando se o EA está aguardando, gerenciando posições ativamente ou encerrando posições. Esse controle é fundamental para coordenar a execução da estratégia, especialmente em ações como a abertura de operações de recuperação e o encerramento de posições.
Em seguida, criaremos a estrutura TradeMetrics para armazenar os principais dados sobre os resultados das operações. Ela incluirá o campo operationSuccess, que indica se uma operação de negociação foi executada com sucesso, como o fechamento de uma posição; o campo totalVolume, que acumula o volume das operações encerradas; e o campo netProfitLoss, que registra o lucro ou prejuízo líquido acumulado dessas operações. Essa estrutura permitirá avaliar os resultados da estratégia e acompanhar seus indicadores de desempenho durante a recuperação ou o encerramento das posições.
Depois, definiremos a estrutura ZoneBoundaries, responsável por armazenar os níveis de preço utilizados na estratégia de recuperação por zonas. As variáveis zoneHigh e zoneLow representam os limites superior e inferior da zona de recuperação, nos quais abrimos operações na direção oposta para compensar as perdas. Já os parâmetros zoneTargetHigh e zoneTargetLow estabelecem os níveis-alvo de lucro acima e abaixo da zona, indicando quando as posições podem ser encerradas com lucro. Esses limites são fundamentais para a estratégia, pois determinam os pontos em que devemos iniciar as operações de recuperação ou encerrar as posições. A seguir, apresentamos uma representação visual dessas zonas para facilitar a compreensão da finalidade dessa estrutura.

Os parâmetros de configuração das operações são armazenados na estrutura TradeConfig. Ela inclui marketSymbol, que identifica o par de moedas, openPrice, que armazena o preço de entrada, e initialVolume, que define o volume inicial da operação. O campo tradeIdentifier armazena nosso número mágico exclusivo, enquanto tradeLabel adiciona um comentário para identificar a operação. O array activeTickets permite acompanhar as posições abertas, e direction indica se a operação é de compra ou de venda. Também utilizamos zoneProfitSpan e zoneRecoverySpan para definir, em unidades de preço, as distâncias correspondentes ao objetivo de lucro e à zona de recuperação, além de accumulatedBuyVolume e accumulatedSellVolume para acompanhar o volume total das operações de compra e venda, respectivamente. Por fim, currentState, baseado no tipo enumerado TradeState, acompanha o estado atual da negociação, integrando esses componentes.
Por último, adicionamos a estrutura LossTracker, que contém apenas a variável tradeLossTracker, responsável por acompanhar o lucro ou prejuízo acumulado de todas as operações. Isso nos permite avaliar o impacto financeiro das medidas de recuperação e ajustar a estratégia caso as perdas se tornem excessivas. Em seguida, podemos declarar alguns atributos adicionais da classe para armazenar informações menos relevantes, mas ainda necessárias ao gerenciamento das operações.
//--- Member Variables TradeConfig m_tradeConfig; //--- Store trade configuration ZoneBoundaries m_zoneBounds; //--- Store zone boundaries LossTracker m_lossTracker; //--- Track profit/loss string m_lastError; //--- Store error message int m_errorStatus; //--- Store error code CTrade m_tradeExecutor; //--- Manage trade execution int m_handleRsi; //--- RSI indicator handle int m_handleEnvUpper; //--- Upper Envelopes handle int m_handleEnvLower; //--- Lower Envelopes handle double m_rsiBuffer[]; //--- RSI data buffer double m_envUpperBandBuffer[]; //--- Upper Envelopes buffer double m_envLowerBandBuffer[]; //--- Lower Envelopes buffer TradingLotSizeOptions m_lotOption; //--- Lot size option double m_initialLotSize; //--- Fixed lot size double m_riskPercentage; //--- Risk percentage int m_riskPoints; //--- Risk points int m_maxOrders; //--- Maximum positions bool m_restrictMaxOrders; //--- Position restriction flag double m_zoneTargetPoints; //--- Profit target points double m_zoneSizePoints; //--- Recovery zone points
Definiremos os principais atributos na seção privada da classe MarketZoneTrader para gerenciar as configurações de negociação, a recuperação por zonas e os dados dos indicadores. Utilizaremos m_tradeConfig (estrutura TradeConfig) para armazenar informações das operações, como símbolo e direção; m_zoneBounds (estrutura ZoneBoundaries) para armazenar os limites da zona de recuperação e os preços-alvo de lucro; e m_lossTracker (estrutura LossTracker) para acompanhar os lucros e prejuízos. As variáveis m_lastError (string) e m_errorStatus (inteiro) serão utilizadas para registrar erros, enquanto o objeto m_tradeExecutor (classe CTrade) ficará responsável pela execução das operações de negociação.
Os handles dos indicadores m_handleRsi, m_handleEnvUpper e m_handleEnvLower permitem acessar os dados do RSI e do Envelopes. Os arrays m_rsiBuffer, m_envUpperBandBuffer e m_envLowerBandBuffer armazenam os valores desses indicadores. Os parâmetros de entrada serão armazenados nas variáveis m_lotOption (TradingLotSizeOptions), m_initialLotSize, m_riskPercentage, m_riskPoints, m_maxOrders, m_restrictMaxOrders, m_zoneTargetPoints e m_zoneSizePoints, utilizadas para controlar o tamanho dos lotes, os limites de posições e as dimensões das zonas. Esses atributos fornecem a base necessária para gerenciar as operações e os indicadores, preparando a implementação da lógica de negociação nas próximas etapas. Agora, precisamos definir algumas funções auxiliares que serão utilizadas com frequência no programa.
//--- Error Handling void logError(string message, int code) { //--- Error Logging Start m_lastError = message; //--- Store error message m_errorStatus = code; //--- Store error code Print("Error: ", message); //--- Log error to Experts tab //--- Error Logging End } //--- Market Data Access double getMarketVolumeStep() { //--- Volume Step Retrieval Start return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_VOLUME_STEP); //--- Retrieve broker's volume step //--- Volume Step Retrieval End } double getMarketAsk() { //--- Ask Price Retrieval Start return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_ASK); //--- Retrieve ask price //--- Ask Price Retrieval End } double getMarketBid() { //--- Bid Price Retrieval Start return SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_BID); //--- Retrieve bid price //--- Bid Price Retrieval End }
Nesta etapa, adicionaremos funções auxiliares essenciais para o tratamento de erros e o acesso aos dados de mercado. A função logError armazena message em m_lastError e code em m_errorStatus, além de exibir a mensagem por meio de Print na aba "Experts", facilitando a depuração. A função getMarketVolumeStep utiliza SymbolInfoDouble com SYMBOL_VOLUME_STEP para obter o incremento mínimo de volume permitido pela corretora para o símbolo m_tradeConfig.marketSymbol, permitindo ajustar o volume das operações às regras de negociação. As funções getMarketAsk e getMarketBid consultam, respectivamente, as cotações ask e bid por meio de SymbolInfoDouble, utilizando SYMBOL_ASK e SYMBOL_BID para obter os preços necessários à execução das operações.
Agora podemos definir as principais funções responsáveis pela execução das operações de negociação. Começaremos pelas rotinas mais simples, que permitirão inicializar operações, armazenar as ordens para acompanhamento e monitoramento e encerrar posições.
//--- Trade Initialization bool configureTrade(ulong ticket) { //--- Trade Configuration Start if (!PositionSelectByTicket(ticket)) { //--- Select position by ticket logError("Failed to select ticket " + IntegerToString(ticket), INIT_FAILED); //--- Log selection failure return false; //--- Return failure } m_tradeConfig.marketSymbol = PositionGetString(POSITION_SYMBOL); //--- Set symbol m_tradeConfig.tradeLabel = __FILE__; //--- Set trade comment m_tradeConfig.tradeIdentifier = PositionGetInteger(POSITION_MAGIC); //--- Set magic number m_tradeConfig.direction = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); //--- Set direction m_tradeConfig.openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Set entry price m_tradeConfig.initialVolume = PositionGetDouble(POSITION_VOLUME); //--- Set initial volume m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number for executor return true; //--- Return success //--- Trade Configuration End } //--- Trade Ticket Management void storeTradeTicket(ulong ticket) { //--- Ticket Storage Start int ticketCount = ArraySize(m_tradeConfig.activeTickets); //--- Get ticket count ArrayResize(m_tradeConfig.activeTickets, ticketCount + 1); //--- Resize ticket array m_tradeConfig.activeTickets[ticketCount] = ticket; //--- Store ticket //--- Ticket Storage End } //--- Trade Execution ulong openMarketTrade(ENUM_ORDER_TYPE tradeDirection, double tradeVolume, double price) { //--- Trade Opening Start ulong ticket = 0; //--- Initialize ticket if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, tradeDirection, tradeVolume, price, 0, 0, m_tradeConfig.tradeLabel)) { //--- Open position ticket = m_tradeExecutor.ResultOrder(); //--- Get ticket } else { Print("Failed to open trade: Direction=", EnumToString(tradeDirection), ", Volume=", tradeVolume); //--- Log failure } return ticket; //--- Return ticket //--- Trade Opening End } //--- Trade Closure void closeActiveTrades(TradeMetrics &metrics) { //--- Trade Closure Start for (int i = ArraySize(m_tradeConfig.activeTickets) - 1; i >= 0; i--) { //--- Iterate tickets in reverse if (m_tradeConfig.activeTickets[i] > 0) { //--- Check valid ticket if (m_tradeExecutor.PositionClose(m_tradeConfig.activeTickets[i])) { //--- Close position m_tradeConfig.activeTickets[i] = 0; //--- Clear ticket metrics.totalVolume += m_tradeExecutor.ResultVolume(); //--- Accumulate volume if ((ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE) == ORDER_TYPE_BUY) { //--- Check buy position metrics.netProfitLoss += m_tradeExecutor.ResultVolume() * (m_tradeExecutor.ResultPrice() - PositionGetDouble(POSITION_PRICE_OPEN)); //--- Calculate buy profit } else { //--- Handle sell position metrics.netProfitLoss += m_tradeExecutor.ResultVolume() * (PositionGetDouble(POSITION_PRICE_OPEN) - m_tradeExecutor.ResultPrice()); //--- Calculate sell profit } } else { metrics.operationSuccess = false; //--- Mark failure Print("Failed to close ticket: ", m_tradeConfig.activeTickets[i]); //--- Log failure } } } //--- Trade Closure End } //--- Bar Detection bool isNewBar() { //--- New Bar Detection Start static datetime previousTime = 0; //--- Store previous bar time datetime currentTime = iTime(m_tradeConfig.marketSymbol, Period(), 0); //--- Get current bar time bool result = (currentTime != previousTime); //--- Check for new bar previousTime = currentTime; //--- Update previous time return result; //--- Return new bar status //--- New Bar Detection End }
Nesta etapa, aprofundaremos a lógica central do programa, implementando funções para abrir operações, acompanhar posições, executar ordens, encerrar operações e determinar quando executar cada ação. Começaremos criando a função configureTrade, que preparará os dados da operação associada a um ticket específico. Primeiro, tentaremos selecionar a posição com PositionSelectByTicket. Se a seleção falhar, registraremos o erro por meio de logError e retornaremos false. Caso seja bem-sucedida, preencheremos m_tradeConfig com os dados da posição: obteremos marketSymbol usando PositionGetString, definiremos tradeLabel como FILE e recuperaremos tradeIdentifier e direction com PositionGetInteger, convertendo este último para ENUM_ORDER_TYPE. Em seguida, obteremos openPrice e initialVolume com PositionGetDouble e configuraremos o número mágico em m_tradeExecutor por meio de SetExpertMagicNumber, deixando o objeto preparado para executar operações.
Depois, criaremos a função storeTradeTicket para manter o registro das posições abertas. Consultaremos o tamanho do array m_tradeConfig.activeTickets com ArraySize, aumentaremos seu tamanho em um elemento usando ArrayResize e armazenaremos o novo ticket na posição adicionada, permitindo acompanhar quais posições estão abertas. Em seguida, desenvolveremos a função openMarketTrade para executar operações a mercado. Chamaremos m_tradeExecutor.PositionOpen, passando os parâmetros tradeDirection, tradeVolume e price, além dos dados de m_tradeConfig. Se a abertura for bem-sucedida, atribuiremos a ticket o valor obtido por ResultOrder. Caso contrário, registraremos o erro com Print, permitindo identificar eventuais falhas na execução das operações.
Na sequência, implementaremos o fechamento das posições com a função closeActiveTrades. Percorreremos o array m_tradeConfig.activeTickets em ordem inversa, utilizando m_tradeExecutor.PositionClose para encerrar cada posição associada a um ticket válido. Quando o fechamento for bem-sucedido, adicionaremos ResultVolume a metrics.totalVolume e calcularemos metrics.netProfitLoss com auxílio das funções PositionGetInteger e PositionGetDouble, consultando a direção e os dados da posição. Se alguma operação falhar, definiremos metrics.operationSuccess como false e registraremos a ocorrência com Print, permitindo acompanhar o resultado de cada tentativa de fechamento.
Por fim, adicionaremos a função isNewBar, que permitirá limitar a execução de operações a uma vez por barra, reduzindo o consumo de recursos. Utilizaremos iTime para obter o horário de abertura da barra atual do símbolo m_tradeConfig.marketSymbol, compararemos esse valor com previousTime e atualizaremos a variável quando houver uma diferença. Dessa forma, poderemos identificar o surgimento de uma nova barra e verificar os sinais de negociação. Para completar essa etapa, ainda precisaremos de uma função para calcular o volume das operações e outra para abrir posições.
//--- Lot Size Calculation double calculateLotSize(double riskPercent, int riskPips) { //--- Lot Size Calculation Start double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * riskPercent / 100; //--- Calculate risk amount double tickSize = SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_TRADE_TICK_SIZE); //--- Get tick size double tickValue = SymbolInfoDouble(m_tradeConfig.marketSymbol, SYMBOL_TRADE_TICK_VALUE); //--- Get tick value if (tickSize == 0 || tickValue == 0) { //--- Validate tick data Print("Invalid tick size or value"); //--- Log invalid data return -1; //--- Return invalid lot } double lotValue = (riskPips * _Point) / tickSize * tickValue; //--- Calculate lot value if (lotValue == 0) { //--- Validate lot value Print("Invalid lot value"); //--- Log invalid lot return -1; //--- Return invalid lot } return NormalizeDouble(riskMoney / lotValue, 2); //--- Return normalized lot size //--- Lot Size Calculation End } //--- Order Execution int openOrder(ENUM_ORDER_TYPE orderType, double stopLoss, double takeProfit) { //--- Order Opening Start int ticket; //--- Initialize ticket double openPrice; //--- Initialize open price if (orderType == ORDER_TYPE_BUY) { //--- Check buy order openPrice = NormalizeDouble(getMarketAsk(), Digits()); //--- Set buy price } else if (orderType == ORDER_TYPE_SELL) { //--- Check sell order openPrice = NormalizeDouble(getMarketBid(), Digits()); //--- Set sell price } else { Print("Invalid order type"); //--- Log invalid type return -1; //--- Return invalid ticket } double lotSize = 0; //--- Initialize lot size if (m_lotOption == FIXED_LOTSIZE) { //--- Check fixed lot lotSize = m_initialLotSize; //--- Use fixed lot size } else if (m_lotOption == UNFIXED_LOTSIZE) { //--- Check dynamic lot lotSize = calculateLotSize(m_riskPercentage, m_riskPoints); //--- Calculate risk-based lot } if (lotSize <= 0) { //--- Validate lot size Print("Invalid lot size: ", lotSize); //--- Log invalid lot return -1; //--- Return invalid ticket } if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, 0, 0, __FILE__)) { //--- Open position ticket = (int)m_tradeExecutor.ResultOrder(); //--- Get ticket Print("New trade opened: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize); //--- Log success } else { ticket = -1; //--- Set invalid ticket Print("Failed to open order: Type=", EnumToString(orderType), ", Volume=", lotSize); //--- Log failure } return ticket; //--- Return ticket //--- Order Opening End }
Começaremos pela função calculateLotSize, que determina o tamanho do lote com base nos parâmetros de risco. Primeiro, calcularemos riskMoney, aplicando riskPercent ao saldo da conta obtido por meio de AccountInfoDouble com ACCOUNT_BALANCE. Em seguida, obteremos tickSize e tickValue para m_tradeConfig.marketSymbol utilizando SymbolInfoDouble com SYMBOL_TRADE_TICK_SIZE e SYMBOL_TRADE_TICK_VALUE. Se algum desses valores for igual a zero, registraremos o erro com Print e retornaremos -1 para evitar cálculos inválidos. Depois, calcularemos lotValue utilizando riskPips, _Point, tickSize e tickValue. Se o resultado for zero, registraremos outro erro e retornaremos -1. Por fim, retornaremos o tamanho do lote normalizado com NormalizeDouble para duas casas decimais, buscando adequá-lo às exigências da corretora.
Em seguida, criaremos a função openOrder para abrir operações. Inicializaremos ticket e openPrice e verificaremos o valor de orderType. Para ORDER_TYPE_BUY, obteremos o preço de compra com getMarketAsk e o normalizaremos utilizando NormalizeDouble com Digits. Para ORDER_TYPE_SELL, utilizaremos getMarketBid. Se orderType contiver um valor inválido, exibiremos uma mensagem com Print e retornaremos -1. Determinaremos lotSize de acordo com m_lotOption: para FIXED_LOTSIZE, utilizaremos m_initialLotSize; para UNFIXED_LOTSIZE, chamaremos calculateLotSize com os parâmetros m_riskPercentage e m_riskPoints. Se lotSize for inválido, registraremos o erro com Print e retornaremos -1. Depois, abriremos a posição por meio de m_tradeExecutor.PositionOpen, passando m_tradeConfig.marketSymbol, orderType, lotSize, openPrice e FILE como comentário. Se a abertura for bem-sucedida, atribuiremos a ticket o valor retornado por ResultOrder e exibiremos o resultado com Print. Caso contrário, definiremos ticket como -1 e exibiremos uma mensagem de erro. Por fim, retornaremos o valor de ticket.
Depois disso, precisaremos inicializar os valores do sistema. Poderíamos utilizar uma função específica para essa finalidade, mas, para simplificar, recorreremos ao construtor da classe. É recomendável declará-lo na seção pública para que possa ser acessado pelas demais partes do programa. Também definiremos o destrutor.
public: //--- Constructor MarketZoneTrader(TradingLotSizeOptions lotOpt, double initLot, double riskPct, int riskPts, int maxOrds, bool restrictOrds, double targetPts, double sizePts) { //--- Constructor Start m_tradeConfig.currentState = INACTIVE; //--- Set initial state ArrayResize(m_tradeConfig.activeTickets, 0); //--- Initialize ticket array m_tradeConfig.zoneProfitSpan = targetPts * _Point; //--- Set profit target m_tradeConfig.zoneRecoverySpan = sizePts * _Point; //--- Set recovery zone m_lossTracker.tradeLossTracker = 0.0; //--- Initialize loss tracker m_lotOption = lotOpt; //--- Set lot size option m_initialLotSize = initLot; //--- Set initial lot m_riskPercentage = riskPct; //--- Set risk percentage m_riskPoints = riskPts; //--- Set risk points m_maxOrders = maxOrds; //--- Set max positions m_restrictMaxOrders = restrictOrds; //--- Set restriction flag m_zoneTargetPoints = targetPts; //--- Set target points m_zoneSizePoints = sizePts; //--- Set zone points m_tradeConfig.marketSymbol = _Symbol; //--- Set symbol m_tradeConfig.tradeIdentifier = magicNumber; //--- Set magic number //--- Constructor End } //--- Destructor ~MarketZoneTrader() { //--- Destructor Start cleanup(); //--- Release resources //--- Destructor End }
Prosseguiremos com a implementação do construtor e do destrutor da classe MarketZoneTrader em sua seção pública. Começaremos pelo construtor MarketZoneTrader, que recebe os parâmetros lotOpt, initLot, riskPct, riskPts, maxOrds, restrictOrds, targetPts e sizePts. Inicializaremos o estado de negociação definindo m_tradeConfig.currentState como INACTIVE, indicando que não há operações ativas. Em seguida, esvaziaremos o array m_tradeConfig.activeTickets utilizando ArrayResize com tamanho zero, preparando-o para armazenar novos tickets. Calcularemos m_tradeConfig.zoneProfitSpan e m_tradeConfig.zoneRecoverySpan multiplicando targetPts e sizePts, respectivamente, por _Point. Dessa forma, definiremos as distâncias correspondentes ao objetivo de lucro e à zona de recuperação em unidades de preço. Também inicializaremos m_lossTracker.tradeLossTracker com 0.0 para começar a acompanhar os lucros e prejuízos sem valores acumulados anteriormente.
Em seguida, atribuiremos os parâmetros de entrada aos atributos correspondentes da classe: lotOpt a m_lotOption, initLot a m_initialLotSize, riskPct a m_riskPercentage, riskPts a m_riskPoints, maxOrds a m_maxOrders, restrictOrds a m_restrictMaxOrders, targetPts a m_zoneTargetPoints e sizePts a m_zoneSizePoints. Definiremos m_tradeConfig.marketSymbol como _Symbol para operar o símbolo do gráfico atual e atribuiremos magicNumber a m_tradeConfig.tradeIdentifier para identificar exclusivamente as operações do EA. Com essa configuração, o EA utilizará os parâmetros definidos pelo usuário e estará preparado para iniciar as operações.
Em seguida, definiremos o destrutor ~MarketZoneTrader para liberar os recursos utilizados pela classe. Chamaremos a função cleanup para liberar todos os recursos alocados, como os handles dos indicadores, garantindo que o EA seja encerrado corretamente, sem vazamentos de memória. Vale lembrar que o construtor e o destrutor têm o mesmo nome da classe, mas o destrutor é precedido por um til (~). Sua função é realizar a limpeza necessária quando o objeto é destruído.
//--- Cleanup void cleanup() { //--- Cleanup Start IndicatorRelease(m_handleRsi); //--- Release RSI handle ArrayFree(m_rsiBuffer); //--- Free RSI buffer IndicatorRelease(m_handleEnvUpper); //--- Release upper Envelopes handle ArrayFree(m_envUpperBandBuffer); //--- Free upper Envelopes buffer IndicatorRelease(m_handleEnvLower); //--- Release lower Envelopes handle ArrayFree(m_envLowerBandBuffer); //--- Free lower Envelopes buffer //--- Cleanup End }
Utilizaremos IndicatorRelease para liberar os handles dos indicadores e ArrayFree para liberar os arrays utilizados no armazenamento de dados. Como estamos tratando dos indicadores, também definiremos a função de inicialização que será chamada quando o programa for iniciado.
//--- Getters TradeState getCurrentState() { //--- Get Current State Start return m_tradeConfig.currentState; //--- Return trade state //--- Get Current State End } double getZoneTargetHigh() { //--- Get Target High Start return m_zoneBounds.zoneTargetHigh; //--- Return profit target high //--- Get Target High End } double getZoneTargetLow() { //--- Get Target Low Start return m_zoneBounds.zoneTargetLow; //--- Return profit target low //--- Get Target Low End } double getZoneHigh() { //--- Get Zone High Start return m_zoneBounds.zoneHigh; //--- Return recovery zone high //--- Get Zone High End } double getZoneLow() { //--- Get Zone Low Start return m_zoneBounds.zoneLow; //--- Return recovery zone low //--- Get Zone Low End } //--- Initialization int initialize() { //--- Initialization Start m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number int totalPositions = PositionsTotal(); //--- Get total positions for (int i = 0; i < totalPositions; i++) { //--- Iterate positions ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket)) { //--- Select position if (PositionGetString(POSITION_SYMBOL) == m_tradeConfig.marketSymbol && PositionGetInteger(POSITION_MAGIC) == m_tradeConfig.tradeIdentifier) { //--- Check symbol and magic if (activateTrade(ticket)) { //--- Activate position Print("Existing position activated: Ticket=", ticket); //--- Log activation } else { Print("Failed to activate existing position: Ticket=", ticket); //--- Log failure } } } } m_handleRsi = iRSI(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 8, PRICE_CLOSE); //--- Initialize RSI if (m_handleRsi == INVALID_HANDLE) { //--- Check RSI Print("Failed to initialize RSI indicator"); //--- Log failure return INIT_FAILED; //--- Return failure } m_handleEnvUpper = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1); //--- Initialize upper Envelopes if (m_handleEnvUpper == INVALID_HANDLE) { //--- Check upper Envelopes Print("Failed to initialize upper Envelopes indicator"); //--- Log failure return INIT_FAILED; //--- Return failure } m_handleEnvLower = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4); //--- Initialize lower Envelopes if (m_handleEnvLower == INVALID_HANDLE) { //--- Check lower Envelopes Print("Failed to initialize lower Envelopes indicator"); //--- Log failure return INIT_FAILED; //--- Return failure } ArraySetAsSeries(m_rsiBuffer, true); //--- Set RSI buffer ArraySetAsSeries(m_envUpperBandBuffer, true); //--- Set upper Envelopes buffer ArraySetAsSeries(m_envLowerBandBuffer, true); //--- Set lower Envelopes buffer Print("EA initialized successfully"); //--- Log success return INIT_SUCCEEDED; //--- Return success //--- Initialization End }
Começaremos criando funções getter simples para acessar os principais dados de negociação. A função getCurrentState retornará m_tradeConfig.currentState, permitindo verificar se o sistema está no estado INACTIVE, RUNNING ou TERMINATING. Em seguida, criaremos getZoneTargetHigh e getZoneTargetLow para obter os valores de m_zoneBounds.zoneTargetHigh e m_zoneBounds.zoneTargetLow, que representam os preços-alvo de lucro das operações. Depois, adicionaremos getZoneHigh e getZoneLow para acessar m_zoneBounds.zoneHigh e m_zoneBounds.zoneLow, correspondentes aos limites da zona de recuperação.
Na sequência, criaremos a função initialize para configurar nosso EA. Primeiro, utilizaremos SetExpertMagicNumber para atribuir m_tradeConfig.tradeIdentifier a m_tradeExecutor, permitindo identificar as operações executadas pelo EA. Depois, verificaremos as posições existentes com PositionsTotal e percorreremos cada uma delas em um laço, obtendo os respectivos tickets por meio de PositionGetTicket. Se PositionSelectByTicket selecionar a posição com sucesso e ela corresponder a m_tradeConfig.marketSymbol e m_tradeConfig.tradeIdentifier, conforme os valores obtidos com PositionGetString e PositionGetInteger, chamaremos activateTrade para assumir seu gerenciamento. Registraremos o sucesso ou a falha dessa operação com Print.
Em seguida, configuraremos os indicadores. Criaremos o handle do RSI com iRSI para m_tradeConfig.marketSymbol, utilizando um período de 8 barras, o timeframe atual e PRICE_CLOSE. Se m_handleRsi for igual a INVALID_HANDLE, registraremos o erro com Print e retornaremos INIT_FAILED. Depois, inicializaremos os indicadores Envelopes: criaremos m_handleEnvUpper com iEnvelopes, utilizando uma média móvel simples de 150 períodos, desvio de 0,1 e PRICE_CLOSE; para m_handleEnvLower, utilizaremos um período de 95 barras e desvio de 1,4. Se algum dos handles for igual a INVALID_HANDLE, registraremos o erro e retornaremos INIT_FAILED. Por fim, configuraremos m_rsiBuffer, m_envUpperBandBuffer e m_envLowerBandBuffer como arrays de séries temporais por meio de ArraySetAsSeries, registraremos a inicialização bem-sucedida com Print e retornaremos INIT_SUCCEEDED. Agora podemos chamar essa função no manipulador de eventos OnInit, mas primeiro precisaremos criar uma instância da classe.
//--- Global Instance MarketZoneTrader *trader = NULL; //--- Declare trader instance
Nesta etapa, declararemos um ponteiro global para a classe MarketZoneTrader, que permitirá acessar uma única instância do nosso sistema de negociação. Criaremos a variável trader como um ponteiro para MarketZoneTrader e a inicializaremos com NULL. Dessa forma, poderemos utilizar a mesma instância em todo o EA para gerenciar as operações de negociação, incluindo a inicialização de operações, a execução de ordens e o gerenciamento da recuperação por zonas. Ao inicializar trader com NULL, deixaremos o ponteiro preparado para receber uma instância da classe posteriormente, evitando que o programa tente acessar o objeto antes de sua criação e configuração. Agora podemos chamar a função de inicialização.
int OnInit() { //--- EA Initialization Start trader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints, zoneSizePoints); //--- Create trader instance return trader.initialize(); //--- Initialize EA //--- EA Initialization End }
No manipulador de eventos OnInit, começaremos criando uma instância da classe MarketZoneTrader e atribuindo-a ao ponteiro global trader. Passaremos os parâmetros de entrada definidos pelo usuário, lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints e zoneSizePoints, ao construtor para configurar o sistema de negociação. Em seguida, chamaremos a função initialize por meio de trader para inicializar o EA, incluindo a identificação das operações, a verificação das posições existentes e a inicialização dos indicadores. Por fim, retornaremos o resultado dessa função, indicando se a inicialização foi bem-sucedida. Essa rotina prepara o EA para iniciar as operações com as configurações especificadas. Após a compilação, obtemos o seguinte resultado.

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

A imagem mostra que alguns objetos não foram destruídos, causando um vazamento de memória. Para resolver esse problema, precisamos liberar os objetos que permanecem alocados. Para isso, utilizaremos a seguinte lógica.
void OnDeinit(const int reason) { //--- EA Deinitialization Start if (trader != NULL) { //--- Check trader existence delete trader; //--- Delete trader trader = NULL; //--- Clear pointer Print("EA deinitialized"); //--- Log deinitialization } //--- EA Deinitialization End }
No manipulador de eventos OnDeinit, começaremos verificando se o ponteiro trader é diferente de NULL, confirmando que existe uma instância de MarketZoneTrader a ser destruída. Nesse caso, utilizaremos o operador delete para destruir o objeto apontado por trader e liberar a memória alocada, evitando vazamentos de memória. Em seguida, definiremos trader como NULL para impedir acessos acidentais à memória já liberada. Por fim, exibiremos uma mensagem com Print para confirmar a desinicialização do EA. Essa rotina permitirá encerrar o EA corretamente, liberando os recursos utilizados. Agora podemos prosseguir com a implementação da lógica principal de avaliação dos sinais de negociação e gerenciamento das posições abertas. Para isso, precisaremos de algumas funções auxiliares.
//--- Position Management bool activateTrade(ulong ticket) { //--- Position Activation Start m_tradeConfig.currentState = INACTIVE; //--- Set state to inactive ArrayResize(m_tradeConfig.activeTickets, 0); //--- Clear tickets m_lossTracker.tradeLossTracker = 0.0; //--- Reset loss tracker if (!configureTrade(ticket)) { //--- Configure trade return false; //--- Return failure } storeTradeTicket(ticket); //--- Store ticket if (m_tradeConfig.direction == ORDER_TYPE_BUY) { //--- Handle buy position m_zoneBounds.zoneHigh = m_tradeConfig.openPrice; //--- Set zone high m_zoneBounds.zoneLow = m_zoneBounds.zoneHigh - m_tradeConfig.zoneRecoverySpan; //--- Set zone low m_tradeConfig.accumulatedBuyVolume = m_tradeConfig.initialVolume; //--- Set buy volume m_tradeConfig.accumulatedSellVolume = 0.0; //--- Reset sell volume } else { //--- Handle sell position m_zoneBounds.zoneLow = m_tradeConfig.openPrice; //--- Set zone low m_zoneBounds.zoneHigh = m_zoneBounds.zoneLow + m_tradeConfig.zoneRecoverySpan; //--- Set zone high m_tradeConfig.accumulatedSellVolume = m_tradeConfig.initialVolume; //--- Set sell volume m_tradeConfig.accumulatedBuyVolume = 0.0; //--- Reset buy volume } m_zoneBounds.zoneTargetHigh = m_zoneBounds.zoneHigh + m_tradeConfig.zoneProfitSpan; //--- Set target high m_zoneBounds.zoneTargetLow = m_zoneBounds.zoneLow - m_tradeConfig.zoneProfitSpan; //--- Set target low m_tradeConfig.currentState = RUNNING; //--- Set state to running return true; //--- Return success //--- Position Activation End } //--- Tick Processing void processTick() { //--- Tick Processing Start double askPrice = NormalizeDouble(getMarketAsk(), Digits()); //--- Get ask price double bidPrice = NormalizeDouble(getMarketBid(), Digits()); //--- Get bid price if (!isNewBar()) return; //--- Exit if not new bar if (!CopyBuffer(m_handleRsi, 0, 0, 3, m_rsiBuffer)) { //--- Load RSI data Print("Error loading RSI data. Reverting."); //--- Log RSI failure return; //--- Exit } if (!CopyBuffer(m_handleEnvUpper, 0, 0, 3, m_envUpperBandBuffer)) { //--- Load upper Envelopes Print("Error loading upper envelopes data. Reverting."); //--- Log failure return; //--- Exit } if (!CopyBuffer(m_handleEnvLower, 1, 0, 3, m_envLowerBandBuffer)) { //--- Load lower Envelopes Print("Error loading lower envelopes data. Reverting."); //--- Log failure return; //--- Exit } int ticket = 0; //--- Initialize ticket const int rsiOverbought = 70; //--- Set RSI overbought level const int rsiOversold = 30; //--- Set RSI oversold level if (m_rsiBuffer[1] < rsiOversold && m_rsiBuffer[2] > rsiOversold && m_rsiBuffer[0] < rsiOversold) { //--- Check buy signal if (askPrice > m_envUpperBandBuffer[0]) { //--- Confirm price above upper Envelopes if (!m_restrictMaxOrders || PositionsTotal() < m_maxOrders) { //--- Check position limit ticket = openOrder(ORDER_TYPE_BUY, 0, 0); //--- Open buy order } } } else if (m_rsiBuffer[1] > rsiOverbought && m_rsiBuffer[2] < rsiOverbought && m_rsiBuffer[0] > rsiOverbought) { //--- Check sell signal if (bidPrice < m_envLowerBandBuffer[0]) { //--- Confirm price below lower Envelopes if (!m_restrictMaxOrders || PositionsTotal() < m_maxOrders) { //--- Check position limit ticket = openOrder(ORDER_TYPE_SELL, 0, 0); //--- Open sell order } } } if (ticket > 0) { //--- Check if trade opened if (activateTrade(ticket)) { //--- Activate position Print("New position activated: Ticket=", ticket); //--- Log activation } else { Print("Failed to activate new position: Ticket=", ticket); //--- Log failure } } //--- Tick Processing End }
Nesta etapa, continuaremos o desenvolvimento do programa implementando as funções activateTrade e processTick na classe MarketZoneTrader para gerenciar posições e processar os ticks do mercado. Começaremos pela função activateTrade, que ativa uma operação a partir de um ticket específico. Primeiro, definiremos m_tradeConfig.currentState como INACTIVE e esvaziaremos o array m_tradeConfig.activeTickets utilizando ArrayResize para limpar a lista de tickets. Também redefiniremos m_lossTracker.tradeLossTracker como 0.0 e, em seguida, chamaremos configureTrade com o ticket informado. Se a configuração falhar, retornaremos false. Caso contrário, armazenaremos o ticket com storeTradeTicket. Para uma operação de compra, quando m_tradeConfig.direction for igual a ORDER_TYPE_BUY, definiremos m_zoneBounds.zoneHigh como m_tradeConfig.openPrice e calcularemos m_zoneBounds.zoneLow subtraindo m_tradeConfig.zoneRecoverySpan desse preço. Também atribuiremos m_tradeConfig.initialVolume a m_tradeConfig.accumulatedBuyVolume e reinicializaremos m_tradeConfig.accumulatedSellVolume.
Para uma operação de venda, definiremos m_zoneBounds.zoneLow como m_tradeConfig.openPrice e calcularemos m_zoneBounds.zoneHigh adicionando m_tradeConfig.zoneRecoverySpan ao preço de abertura. Também ajustaremos os volumes acumulados de compra e venda conforme a direção da operação. Em seguida, calcularemos m_zoneBounds.zoneTargetHigh e m_zoneBounds.zoneTargetLow com base em m_tradeConfig.zoneProfitSpan, alteraremos m_tradeConfig.currentState para RUNNING e retornaremos true.
Depois, criaremos a função processTick para processar os ticks do mercado. Obteremos askPrice e bidPrice por meio de getMarketAsk e getMarketBid, normalizando os preços com NormalizeDouble e Digits. Se isNewBar retornar false, encerraremos o processamento para economizar recursos. Em seguida, utilizaremos CopyBuffer para copiar os dados de m_handleRsi para m_rsiBuffer, de m_handleEnvUpper para m_envUpperBandBuffer e de m_handleEnvLower para m_envLowerBandBuffer. Se alguma dessas operações falhar, registraremos o erro com Print e interromperemos o processamento. Para avaliar os sinais de negociação, definiremos rsiOverought como 70 e rsiOversold como 30.
Se m_rsiBuffer indicar sobrevenda e askPrice estiver acima da banda superior representada por m_envUpperBandBuffer, utilizaremos openOrder para abrir uma operação de compra, desde que m_restrictMaxOrders seja false ou PositionsTotal seja inferior a m_maxOrders. Quando o RSI indicar sobrecompra e bidPrice estiver abaixo da banda inferior representada por m_envLowerBandBuffer, abriremos uma operação de venda. Se a abertura retornar um ticket válido, chamaremos activateTrade e registraremos o resultado no log. Agora podemos chamar processTick no manipulador de eventos OnTick para avaliar os sinais de negociação e inicializar as posições.
void OnTick() { //--- Tick Handling Start if (trader != NULL) { //--- Check trader existence trader.processTick(); //--- Process tick } //--- Tick Handling End }
No manipulador de eventos OnTick, começaremos verificando se o ponteiro trader, que referencia nossa instância da classe MarketZoneTrader, é diferente de NULL, confirmando que o sistema de negociação foi inicializado. Se o objeto existir, chamaremos trader.processTick para processar os ticks recebidos, avaliar as posições, verificar os sinais dos indicadores e executar operações quando necessário. Após a compilação, obtemos o seguinte resultado.

Como podemos observar na imagem, identificamos e avaliamos um sinal de negociação e abrimos uma posição de compra. Agora precisamos implementar o gerenciamento das posições abertas. Para manter a modularidade do código, distribuiremos essa lógica em funções específicas.
//--- Market Tick Evaluation void evaluateMarketTick() { //--- Tick Evaluation Start if (m_tradeConfig.currentState == INACTIVE) return; //--- Exit if inactive if (m_tradeConfig.currentState == TERMINATING) { //--- Check terminating state finalizePosition(); //--- Finalize position return; //--- Exit } }
Nesta etapa, implementaremos a função evaluateMarketTick na classe MarketZoneTrader para avaliar as condições de mercado enquanto houver operações ativas. Primeiro, verificaremos se m_tradeConfig.currentState é igual a INACTIVE. Nesse caso, encerraremos imediatamente a execução da função para evitar processamento desnecessário quando não houver operações ativas. Em seguida, verificaremos se m_tradeConfig.currentState é igual a TERMINATING. Se essa condição for atendida, chamaremos finalizePosition para fechar todas as posições abertas e concluir o ciclo de negociação, encerrando a execução da função logo depois. A seguir, apresentamos a função responsável pelo fechamento das operações.
//--- Position Finalization bool finalizePosition() { //--- Position Finalization Start m_tradeConfig.currentState = TERMINATING; //--- Set terminating state TradeMetrics metrics = {true, 0.0, 0.0}; //--- Initialize metrics closeActiveTrades(metrics); //--- Close all trades if (metrics.operationSuccess) { //--- Check success ArrayResize(m_tradeConfig.activeTickets, 0); //--- Clear tickets m_tradeConfig.currentState = INACTIVE; //--- Set inactive state Print("Position closed successfully"); //--- Log success } else { Print("Failed to close position"); //--- Log failure } return metrics.operationSuccess; //--- Return status //--- Position Finalization End }
Para começar, definiremos m_tradeConfig.currentState como TERMINATING, indicando que o ciclo de negociação está sendo encerrado. Isso evita que a rotina de gerenciamento continue sendo executada durante o fechamento das posições. Em seguida, inicializaremos uma instância da estrutura TradeMetrics chamada metrics, definindo operationSuccess como true, totalVolume como 0.0 e netProfitLoss como 0.0 para acompanhar os resultados do fechamento das operações. Chamaremos closeActiveTrades com metrics para encerrar todas as posições abertas registradas em m_tradeConfig.activeTickets. Se metrics.operationSuccess permanecer true, esvaziaremos o array m_tradeConfig.activeTickets utilizando ArrayResize para limpar a lista de tickets, definiremos m_tradeConfig.currentState como INACTIVE para indicar que o sistema está inativo e registraremos a conclusão bem-sucedida com Print.
Se ocorrer alguma falha no fechamento, registraremos o erro com Print. Por fim, retornaremos metrics.operationSuccess para indicar se o procedimento foi concluído com sucesso. Caso não seja necessário encerrar posições nesta etapa, poderemos prosseguir com a avaliação dos preços em relação aos limites da zona de recuperação e aos níveis-alvo. Começaremos pelas posições de compra.
double currentPrice; //--- Initialize price if (m_tradeConfig.direction == ORDER_TYPE_BUY) { //--- Handle buy position currentPrice = getMarketBid(); //--- Get bid price if (currentPrice > m_zoneBounds.zoneTargetHigh) { //--- Check profit target Print("Closing position: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh); //--- Log closure finalizePosition(); //--- Close position return; //--- Exit } else if (currentPrice < m_zoneBounds.zoneLow) { //--- Check recovery trigger Print("Triggering recovery trade: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow); //--- Log recovery triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice); //--- Open sell recovery } }
Em seguida, implementaremos a lógica de gerenciamento das posições de compra na função evaluateMarketTick da classe MarketZoneTrader. Primeiro, declararemos a variável currentPrice para armazenar a cotação atual. Se m_tradeConfig.direction for igual a ORDER_TYPE_BUY, obteremos o preço bid por meio de getMarketBid e o atribuiremos a currentPrice, pois essa é a cotação utilizada para encerrar uma posição de compra. Depois, verificaremos se currentPrice ultrapassou m_zoneBounds.zoneTargetHigh. Se isso ocorrer, registraremos o sinal de fechamento com Print, exibindo o preço bid e o nível-alvo, chamaremos finalizePosition para encerrar a operação e sairemos da função com return.
Se currentPrice cair abaixo de m_zoneBounds.zoneLow, registraremos o sinal de recuperação com Print e chamaremos triggerRecoveryTrade, passando ORDER_TYPE_SELL e currentPrice para abrir uma operação de venda e reduzir as perdas. Essa lógica permite encerrar posições de compra quando o preço atinge o objetivo de lucro ou iniciar a recuperação quando o mercado se move contra a posição, tornando a estratégia mais flexível. A seguir, apresentamos a lógica da função responsável pela abertura das operações de recuperação.
//--- Recovery Trade Handling void triggerRecoveryTrade(ENUM_ORDER_TYPE tradeDirection, double price) { //--- Recovery Trade Start TradeMetrics metrics = {true, 0.0, 0.0}; //--- Initialize metrics closeActiveTrades(metrics); //--- Close existing trades for (int i = 0; i < 10 && !metrics.operationSuccess; i++) { //--- Retry closure Sleep(1000); //--- Wait 1 second metrics.operationSuccess = true; //--- Reset success flag closeActiveTrades(metrics); //--- Retry closure } m_lossTracker.tradeLossTracker += metrics.netProfitLoss; //--- Update loss tracker if (m_lossTracker.tradeLossTracker > 0 && metrics.operationSuccess) { //--- Check positive profit Print("Closing position due to positive profit: ", m_lossTracker.tradeLossTracker); //--- Log closure finalizePosition(); //--- Close position m_lossTracker.tradeLossTracker = 0.0; //--- Reset loss tracker return; //--- Exit } double tradeSize = determineRecoverySize(tradeDirection); //--- Calculate trade size ulong ticket = openMarketTrade(tradeDirection, tradeSize, price); //--- Open recovery trade if (ticket > 0) { //--- Check if trade opened storeTradeTicket(ticket); //--- Store ticket m_tradeConfig.direction = tradeDirection; //--- Update direction if (tradeDirection == ORDER_TYPE_BUY) m_tradeConfig.accumulatedBuyVolume += tradeSize; //--- Update buy volume else m_tradeConfig.accumulatedSellVolume += tradeSize; //--- Update sell volume Print("Recovery trade opened: Ticket=", ticket, ", Direction=", EnumToString(tradeDirection), ", Volume=", tradeSize); //--- Log recovery trade } //--- Recovery Trade End } //--- Recovery Size Calculation double determineRecoverySize(ENUM_ORDER_TYPE tradeDirection) { //--- Recovery Size Calculation Start double tradeSize = -m_lossTracker.tradeLossTracker / m_tradeConfig.zoneProfitSpan; //--- Calculate lot size tradeSize = MathCeil(tradeSize / getMarketVolumeStep()) * getMarketVolumeStep(); //--- Round to volume step return tradeSize; //--- Return trade size //--- Recovery Size Calculation End }
Para tratar as situações em que o movimento do mercado exige o início da recuperação, implementaremos a função triggerRecoveryTrade, responsável por gerenciar as operações de recuperação quando o preço se move contra nossa posição. Primeiro, inicializaremos uma instância da estrutura TradeMetrics chamada metrics, definindo operationSuccess como true, totalVolume como 0.0 e netProfitLoss como 0.0. Em seguida, chamaremos closeActiveTrades com metrics para encerrar as posições existentes. Se metrics.operationSuccess for false, repetiremos a tentativa de fechamento até 10 vezes, aguardando um segundo entre as tentativas com Sleep e reinicializando operationSuccess antes de cada nova tentativa.
Atualizaremos m_lossTracker.tradeLossTracker adicionando metrics.netProfitLoss ao valor acumulado. Se m_lossTracker.tradeLossTracker for positivo e metrics.operationSuccess for true, registraremos o fechamento com Print, chamaremos finalizePosition, redefiniremos m_lossTracker.tradeLossTracker como 0.0 e sairemos da função com return. Caso contrário, calcularemos o volume da operação de recuperação, tradeSize, utilizando determineRecoverySize com tradeDirection. Em seguida, abriremos uma nova operação por meio de openMarketTrade, passando os parâmetros tradeDirection, tradeSize e price.
Se a função retornar um ticket válido, nós o armazenaremos com storeTradeTicket, atualizaremos m_tradeConfig.direction e ajustaremos m_tradeConfig.accumulatedBuyVolume ou m_tradeConfig.accumulatedSellVolume conforme tradeDirection. Também registraremos a operação com Print, utilizando EnumToString. Em seguida, criaremos a função determineRecoverySize para calcular o tamanho do lote das operações de recuperação. Calcularemos tradeSize dividindo o valor negativo de m_lossTracker.tradeLossTracker por m_tradeConfig.zoneProfitSpan, determinando o volume necessário para compensar as perdas. Depois, ajustaremos tradeSize ao incremento de volume permitido pela corretora, arredondando para cima com MathCeil e utilizando getMarketVolumeStep, e retornaremos o resultado. Com isso, teremos implementado a lógica de recuperação e poderemos prosseguir com o gerenciamento das posições de venda. Como essa lógica é inversa à utilizada para as posições de compra, não precisaremos nos deter nos mesmos detalhes. Ao final, a implementação completa terá a seguinte estrutura.
//--- Market Tick Evaluation void evaluateMarketTick() { //--- Tick Evaluation Start if (m_tradeConfig.currentState == INACTIVE) return; //--- Exit if inactive if (m_tradeConfig.currentState == TERMINATING) { //--- Check terminating state finalizePosition(); //--- Finalize position return; //--- Exit } double currentPrice; //--- Initialize price if (m_tradeConfig.direction == ORDER_TYPE_BUY) { //--- Handle buy position currentPrice = getMarketBid(); //--- Get bid price if (currentPrice > m_zoneBounds.zoneTargetHigh) { //--- Check profit target Print("Closing position: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh); //--- Log closure finalizePosition(); //--- Close position return; //--- Exit } else if (currentPrice < m_zoneBounds.zoneLow) { //--- Check recovery trigger Print("Triggering recovery trade: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow); //--- Log recovery triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice); //--- Open sell recovery } } else if (m_tradeConfig.direction == ORDER_TYPE_SELL) { //--- Handle sell position currentPrice = getMarketAsk(); //--- Get ask price if (currentPrice < m_zoneBounds.zoneTargetLow) { //--- Check profit target Print("Closing position: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow); //--- Log closure finalizePosition(); //--- Close position return; //--- Exit } else if (currentPrice > m_zoneBounds.zoneHigh) { //--- Check recovery trigger Print("Triggering recovery trade: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh); //--- Log recovery triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice); //--- Open buy recovery } } //--- Tick Evaluation End }
Agora, a função permite gerenciar a recuperação em ambas as direções de negociação. Após a compilação, obtemos o seguinte resultado.

Como podemos observar na imagem, conseguimos gerenciar as posições abertas a partir de sinais de reação do preço no contexto da tendência. Resta testar o programa com dados históricos.
Backtesting
Após realizar um backtesting detalhado, obtivemos os seguintes resultados.
Gráfico do backtesting:

Relatório do backtesting:

Conclusão
Desenvolvemos um programa robusto em MQL5 que implementa um sistema de recuperação por zonas para negociação a favor da tendência com o indicador Envelopes, combinando o índice de força relativa (RSI) e o Envelopes para identificar oportunidades de negociação e gerenciar perdas por meio de zonas de recuperação bem definidas. Adotamos a programação orientada a objetos (OOP) e utilizamos componentes como a classe MarketZoneTrader, as estruturas TradeConfig e ZoneBoundaries e as funções processTick e triggerRecoveryTrade. Com isso, criamos um sistema flexível, que pode ser adaptado a diferentes condições de mercado por meio do ajuste de parâmetros como zoneTargetPoints e riskPercentage.
Aviso: Este artigo tem finalidade exclusivamente educacional. A negociação nos mercados financeiros envolve riscos significativos, e a volatilidade do mercado pode causar perdas. Teste cuidadosamente o programa com dados históricos e avalie e monitore todos os riscos envolvidos antes de utilizá-lo em operações reais.
Com base nos conhecimentos adquiridos neste artigo, você poderá aprimorar o sistema de recuperação por zonas ou adaptar sua lógica para desenvolver novas estratégias de negociação, ampliando seus conhecimentos sobre trading algorítmico. Boas operações!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18720
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Arbitragem estatística com ações cointegradas (Parte 1): testes de cointegração de Engle-Granger e Johansen
Está chegando o novo MetaTrader 5 e MQL5
Implementação em MQL5 de módulos úteis de outras linguagens (Parte 1): Criação de uma biblioteca SQLite3 semelhante à do Python
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Muito obrigado 🙏
De nada. Obrigado