
Desenvolvendo uma estratégia Martingale de Recuperação de Zona em MQL5
Introdução
Neste artigo, vamos criar o advisor especializado (EA) para a estratégia de negociação forex Martingale de recuperação de zona em MetaQuotes Language 5 (MQL5) para MetaTrader 5 (MT5), passo a passo. A estratégia Martingale de recuperação de zona é uma estratégia comum que visa contrapor posições perdedoras, abrindo posições opostas com um volume de negociação ligeiramente maior, que cancela as posições perdedoras. Basicamente, é uma estratégia de acompanhamento de tendência que não se preocupa com a direção do mercado, na esperança de que, em algum momento, o mercado esteja em tendência de baixa ou alta e, eventualmente, certos alvos sejam atingidos. Junte-se a nós enquanto não apenas discutimos, mas também automatizamos este sistema em MQL5.
Esta jornada abordará os seguintes tópicos:
- Definição da estratégia de Recuperação de Zona
- Descrição da estratégia de Recuperação de Zona
- Implementação no MQL5
- Resultados do backtest:
- Conclusão
Definição da estratégia de Recuperação de Zona
A estratégia de negociação de recuperação de zona é um método sofisticado, usado principalmente no mercado forex para controlar e reduzir perdas. Determinar faixas de preço precisas que um negociador antecipa que o mercado irá flutuar é a ideia central deste método. O trader lança uma série de contra-negociações para construir uma zona de recuperação quando uma negociação se move desfavoravelmente e atinge uma zona de perda predeterminada, em vez de fechar a posição com prejuízo. Idealmente, essas negociações devem ser feitas de forma que um movimento de volta para a zona de recuperação permita fechar a posição como um todo no ponto de equilíbrio ou até mesmo com lucro.
Esta técnica é focada principalmente em hedging e na redução média do preço. Quando o mercado vai contra a primeira negociação, o trader cria uma posição oposta de tamanho igual para atuar como hedge. Se o mercado continuar se movendo para baixo, novas negociações são abertas em intervalos cruciais para reduzir a média do preço de entrada. As negociações são projetadas de forma que o lucro agregado das negociações de recuperação possa igualar as perdas da negociação inicial quando o mercado retornar à média. Essa abordagem exige uma estratégia de gestão de risco bem definida, pois a possibilidade de acumular posições pode resultar em requisitos de margem elevados e exposição a condições de mercado voláteis.
A capacidade da estratégia de recuperação de zona de transformar uma transação perdedora em uma bem-sucedida, sem exigir previsão precisa da direção do mercado, é uma de suas principais vantagens. Ela permite que os traders lucrem com a turbulência e reversões do mercado, convertendo tendências desfavoráveis em oportunidades de recuperação.
O método de negociação de recuperação de zona é mais adequado para traders experientes, com um entendimento aprofundado de estratégias de gestão de risco e dinâmicas de mercado. É uma ferramenta poderosa no arsenal de um trader, especialmente útil em mercados voláteis com flutuações de preços frequentes. Embora tenha a capacidade de se recuperar de negociações fracassadas e gerar lucro, sua complexidade e os riscos associados exigem planejamento cuidadoso e execução calculada.
Descrição da estratégia de Recuperação de Zona
Com base em uma análise de mercado, o método de negociação de recuperação de zona começa com a abertura de uma posição inicial. Imagine um trader que abre uma posição de compra porque acredita que o mercado vai subir. O primeiro objetivo é lucrar com a tendência de alta. O trader fecha a posição e assegura os ganhos se o mercado se mover positivamente e o preço subir para um objetivo de lucro predeterminado. O trader pode lucrar com os movimentos favoráveis do mercado com essa estratégia simples, sem precisar usar outras mais complexas.
Por outro lado, o método de recuperação de zona inicia seu mecanismo de mitigação de perdas se o mercado se mover contra a posição longa inicial e atingir um ponto de perda predeterminado. Nesse momento, o trader abre uma posição de venda com um tamanho de lote maior, geralmente o dobro do tamanho da posição longa anterior, em vez de fechar a posição de compra com prejuízo. Ao compensar as perdas da primeira negociação com os possíveis ganhos da nova posição, essa contra-negociação busca estabelecer uma cobertura. Usando a oscilação inerente do mercado, a estratégia antecipa uma reversão ou, pelo menos, uma estabilização dentro de um intervalo específico.
O trader fica de olho na nova posição de venda à medida que o mercado avança. O impacto combinado das posições de compra inicial e de venda maior resulta, idealmente, em uma situação de equilíbrio ou lucro se o mercado continuar a cair e atingir outro ponto predeterminado. Depois disso, o trader pode fechar ambas as posições, usando os ganhos da negociação maior para compensar as perdas da primeira. Para garantir que toda a posição possa ser fechada com lucro dentro da zona de recuperação, essa estratégia exige cálculos e timing precisos.
Implementação no MQL5
Para criar um advisor especializado em MQL5 voltado para visualizar os níveis de recuperação de zona, tipicamente quatro, precisaremos primeiro definir os níveis. Fazemos isso definindo-os o mais cedo possível, pois serão cruciais para visualizar o sistema de negociação. Isso é alcançado usando a palavra-chave "#define", que é uma diretiva interna no MQL5 usada para atribuir nomes mnemônicos a constantes. Isso é mostrado abaixo:
#define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name
Aqui, definimos as constantes dos nossos limites de zona: ZONE_H como 'ZH' (Zona Alta), ZONE_L como 'ZL' (Zona Baixa), ZONE_T_H como 'ZTH' (Alvo da Zona Alta) e ZONE_T_L como 'ZTL' (Alvo da Zona Baixa). Essas constantes representam os respectivos níveis em nosso sistema.
Após nossas definições, precisaremos abrir posições de negociação. A maneira mais fácil de abrir posições é incluir uma instância de negociação, geralmente conseguida pela inclusão de outro arquivo dedicado à abertura de posições. Usamos a diretiva include para incluir a biblioteca de negociação, que contém funções para operações de negociação.
#include <Trade/Trade.mqh>
CTrade obj_trade;
Primeiro, usamos os sinais de menor e maior para indicar que o arquivo que queremos incluir está contido na pasta include e fornecemos a pasta Trade, seguida por uma barra normal ou barra invertida, e então o nome do arquivo alvo, neste caso, "Trade.MQH". CTrade é uma classe para manipulação de operações de negociação, e obj_trade é uma instância dessa classe, tipicamente um objeto ponteiro criado a partir da classe CTrade para fornecer acesso às variáveis membro da classe.
Depois, precisamos de alguma lógica de controle para gerar sinais de abertura de posições. No nosso caso, usamos o RSI (Índice de Força Relativa), mas você pode usar qualquer outro conforme julgar adequado.
int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars
O rsi_handle armazena a referência para o indicador RSI (Índice de Força Relativa), que é inicializado na função OnInit, permitindo que o EA recupere os valores do RSI. O array rsiData armazena esses valores de RSI, obtidos usando CopyBuffer, e é usado para determinar sinais de negociação com base em limites de RSI. A variável totalBars mantém o controle do número total de barras no gráfico, garantindo que a lógica de negociação seja executada apenas uma vez por nova barra, evitando execuções múltiplas dentro de uma única barra. Juntas, essas variáveis permitem que o EA gere sinais de negociação com base nos valores do RSI, mantendo o timing de execução apropriado.
Finalmente, após definir os valores do indicador, definimos os níveis de geração de sinal do indicador e os níveis de zona.
double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price
Novamente, definimos duas variáveis do tipo double, overBoughtLevel e overSoldLevel, e as inicializamos em 70 e 30, respectivamente. Esses servem como nossos níveis extremos para a produção de sinais. Além disso, definimos quatro variáveis adicionais do tipo double, zoneHigh, zoneLow, zoneTargetHigh e zoneTargetLow, e as inicializamos em zero. Elas armazenarão nossos níveis de configuração de recuperação mais tarde no código.
Até este ponto, definimos todas as variáveis globais, que são cruciais para o sistema. Agora podemos prosseguir livremente para o manipulador de eventos OnInit, que é chamado sempre que o advisor especializado é inicializado. É nesta instância que precisamos inicializar o identificador do indicador, de onde copiaremos os dados para análises futuras. Para inicializar o indicador, usamos a função embutida para retornar seu identificador fornecendo os parâmetros corretos.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); }
No manipulador de eventos de desinicialização do expert, precisamos liberar os dados do indicador e também liberar os dados armazenados.. Fazemos isso para liberar o indicador da memória do computador para economizar recursos, já que ele não será mais utilizado, e, caso seja, os identificadores do indicador e os dados serão recriados na inicialização do advisor especializado.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array }
Em seguida, passamos para o manipulador de eventos OnTick, que é uma função chamada a cada tick, que é uma mudança na cotação de preço. Esta é novamente nossa função ou seção central, pois contém todos os trechos de código cruciais para uma implementação bem-sucedida da estratégia de negociação. Como vamos abrir posições, precisamos definir nossos preços de oferta e demanda para que possamos usar seus valores mais atuais para fins de análise.
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
Definir os preços de oferta e demanda usando as informações do símbolo como um tipo de dado double nos ajuda a obter as cotações de preços mais recentes para uma análise mais aprofundada. A função que usamos é uma função de sobrecarga que contém duas variantes, mas usamos a primeira que aceita apenas dois parâmetros. O primeiro parâmetro é o símbolo; usamos _Symbol para recuperar automaticamente o símbolo no gráfico atual e SYMBOL_ASK para obter a enumeração do tipo double.
Depois de obter as cotações de preços mais recentes, tecnicamente geradas a cada tick, estamos prontos. Continuamos apenas definindo o intervalo de zona e o alvo da zona para o advisor especializado. Nós os definimos como variáveis do tipo double e os multiplicamos pela variável _Point, que contém o tamanho do ponto do símbolo atual na moeda da cotação.
double zoneRange = 200*_Point; double zoneTarget = 400*_Point;
Finalmente, precisamos de variáveis utilitárias que garantam que abramos posições de recuperação corretamente. Neste caso, definimos quatro delas. LastDirection irá armazenar o tipo da última posição aberta para garantir que alternemos entre ordens de compra e venda. Intencionamos que a venda seja -1 e a compra seja +1, apenas um valor arbitrário que pode ser alterado ou implementado conforme a preferência. O lote de recuperação é então utilizado e inicializado para zero, cuja função será armazenar nossos pontos de recuperação calculados para que não percamos o controle dos volumes das próximas negociações, desde que um sistema de recuperação de zona ainda esteja em jogo. Ainda assim, temos duas variáveis booleanas, isBuyDone e isSellDone, para tipicamente armazenar flags para nos auxiliar a não abrir várias posições de uma vez. Note que todas as nossas variáveis são estáticas para garantir que não sejam atualizadas a menos que as atualizemos individualmente. Isso ocorre porque as variáveis locais declaradas com a palavra-chave static mantêm seus valores durante toda a vida útil da função. Assim, a cada próxima chamada da função OnTick, nossas variáveis locais conterão os valores que tinham durante a chamada anterior.
static int lastDirection = 0; //-1 = sell, 1 = buy static double recovery_lot = 0.0; static bool isBuyDone = false, isSellDone = false;
Depois de definir todas as variáveis utilitárias, procedemos para abrir posições, a partir das quais implementaremos posteriormente a lógica de recuperação de zona. Nosso objetivo é alcançar isso utilizando o indicador RSI, que pode ser totalmente modificado conforme as preferências do usuário; portanto, você pode simplesmente aplicar sua técnica de entrada. Agora, para economizar recursos, queremos obter os dados do indicador em cada candlestick e não em cada tick. Isso é alcançado da seguinte forma:
int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars;
Aqui, definimos uma variável inteira chamada bars e a inicializamos com o número de barras atuais no gráfico, conseguido através do uso da função embutida iBars, que aceita dois argumentos: um símbolo e um período. Em seguida, verificamos se o número de barras anteriormente definido é igual ao das barras atuais, e, se for, isso significa que ainda estamos na barra atual, e então retornamos, o que significa que interrompemos a operação e retornamos o controle para o programa chamador. Caso contrário, se as duas variáveis não coincidirem, significa que passamos para um novo candlestick e podemos continuar. Assim, atualizamos o valor de totalBars para o número atual de barras para que, no próximo tick, tenhamos um valor atualizado da variável totalBars.
Para que a recuperação de zona tenha efeito e abra apenas uma posição e a gerencie, precisamos abrir uma posição por instância. Portanto, se o número de posições for maior que um, não precisamos adicionar outras posições, e apenas retornamos cedo. Isso é demonstrado novamente abaixo.
Se não retornarmos para este ponto, significa que ainda não temos nenhuma posição, e podemos prosseguir para abrir uma. Assim, copiamos os dados do identificador do indicador e os armazenamos no array de dados do indicador para análise posterior. Isso é realizado através da função de cópia de buffer.
if (PositionsTotal() > 0) return;
if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return;
Como visualizado, a função de cópia de buffer é uma função de sobrecarga que retorna um inteiro. Por questões de segurança, usamos a declaração if para verificar se ela retorna os dados solicitados e, se não, não temos dados suficientes para retornar, pois uma análise posterior não pode ser feita. No entanto, vejamos o que a função faz. Ela contém cinco argumentos. Primeiro, o identificador do indicador de onde copiar os dados, e o segundo é o número do buffer do indicador; neste caso, é 0, mas pode variar inteiramente com base no indicador que você está usando. Terceiro é a posição inicial, ou o índice da barra para copiar os dados. Aqui usamos 1 para indicar que começamos na barra antes da barra atual no gráfico. O quarto é a contagem—o número de contagens de dados a serem armazenadas. Dois são suficientes para nós aqui, pois não fazemos uma análise detalhada. Finalmente, fornecemos o array alvo para o armazenamento dos dados recuperados.
Após recuperar os dados do identificador do indicador, procedemos então para usar os dados para fins de negociação ou, melhor, para a geração de sinais. Primeiro, procuramos por sinais de compra. Conseguimos isso usando uma declaração if e abrindo uma posição de compra.
if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel){ obj_trade.Buy(0.01);
Se os dados no índice 1 do array armazenado forem menores que o nível de sobrevendido definido e os dados no índice 0 forem maiores que o nível de sobrevendido, isso significa que tivemos um cruzamento entre a linha do RSI e o nível de sobrevendido, indicando que temos um sinal de compra. Então, usamos o objeto de negociação e o operador ponto para acessar o método de compra contido na classe CTrade. Neste caso, abrimos uma posição de compra com volume de 0,01 e ignoramos os demais parâmetros, como stop loss e take profit, pois não os usaremos, visto que isso faria nosso sistema não funcionar conforme o esperado, já que implementamos a estratégia de recuperação de zona, que não precisa fechar as posições nos níveis de stop loss.
No entanto, para definir os níveis de recuperação de zona, precisaremos do ticket da posição para que possamos acessar suas propriedades. Para obter o ticket, usamos o resultado da ordem da posição previamente aberta. Depois de obter o ticket, verificamos se ele é maior que 0, indicando que foi bem-sucedida a abertura da posição, e então selecionamos a posição pelo ticket. Se pudermos selecioná-la pelo ticket, podemos então obter as propriedades da posição, mas estamos interessados apenas no preço de abertura. A partir do preço de abertura, configuramos as posições como segue: o preço de abertura da posição de compra é nossa zona alta, e para obter a zona baixa, subtraímos o intervalo da zona da zona alta. Para os alvos das zonas alta e baixa, apenas somamos o alvo da zona à zona alta e subtraímos o alvo da zona da zona baixa, respectivamente. Finalmente, apenas normalizamos os valores para os dígitos do símbolo para maior precisão, e estamos concluídos.
ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0){ if (PositionSelectByTicket(pos_ticket)){ double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice,_Digits); zoneLow = NormalizeDouble(zoneHigh - zoneRange,_Digits); zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget,_Digits); zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget,_Digits);
Até este ponto, podemos configurar os níveis, mas eles não são visíveis no gráfico. Para alcançar isso, criamos uma função que usaremos para visualizar os quatro níveis definidos no gráfico.
drawZoneLevel(ZONE_H,zoneHigh,clrGreen,2); drawZoneLevel(ZONE_L,zoneLow,clrRed,2); drawZoneLevel(ZONE_T_H,zoneTargetHigh,clrBlue,3); drawZoneLevel(ZONE_T_L,zoneTargetLow,clrBlue,3);
Criamos uma função simples do tipo void que aceita quatro parâmetros de entrada ou argumentos, a saber, levelName, price, CLR e width. Usamos a função embutida ObjectCreate para criar uma linha horizontal que se estende por todo o comprimento do gráfico, anexando-a ao horário e preço fornecidos. Finalmente, usamos o ObjectSetInteger para definir a cor do objeto para exclusividade e a largura para melhor visibilidade ajustável.
void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width }
Finalmente, definimos a última direção para o valor 1 para mostrar que abrimos uma posição de compra, configuramos o próximo volume de recuperação como o volume inicial multiplicado por uma constante multiplicadora, neste caso, 2, o que significa que dobramos o volume, e, por último, configuramos a flag isBuyDone para true e isSellDone para false.
lastDirection = 1; recovery_lot = 0.01*2; isBuyDone = true; isSellDone = false;
O código completo para abrir a posição e configurar os níveis de recuperação de zona é mostrado abaixo.
//--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } }
Para abrir a posição de venda e configurar os níveis de recuperação de zona, a lógica de controle permanece a mesma, mas com condições inversas, conforme mostrado abaixo.
else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
Aqui, verificamos se as condições de sinal de venda são atendidas e, se sim, abrimos uma posição de venda instantaneamente. Depois, obtemos seu ticket e usamos o ticket para recuperar o preço de abertura da posição, que usamos para definir os níveis de recuperação de zona. Como se trata de uma posição de venda, seu preço se torna a zona baixa, e para obter a zona alta, basta somar o intervalo da zona à zona alta. Da mesma forma, somamos o alvo da zona à zona alta para obter o alvo da zona alta e subtraímos o alvo da zona da zona baixa para obter o alvo da zona baixa. Para a visualização, novamente desenhamos os quatro níveis usando as funções. Por fim, apenas configuramos nossas variáveis utilitárias.
Até este ponto, conseguimos abrir as posições com base no sinal apresentado e configurar o sistema de recuperação de zona. Aqui está o código completo que possibilita isso.
void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars; if (PositionsTotal() > 0) return; if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } }
Podemos configurar o sistema de recuperação de zona, mas precisamos monitorá-lo e garantir que saímos dele assim que atingirmos os níveis-alvo ou abrirmos as posições correspondentes ao atingirmos os níveis pré-definidos. Teremos que fazer isso a cada tick e, portanto, a lógica precisa ser implementada antes da restrição do loop de candlestick. Primeiro, verifiquemos a condição em que os preços atingem os níveis-alvo e, posteriormente, a condição em que os preços atingem os níveis de zona. Então, vamos lá.
if (zoneTargetHigh > 0 && zoneTargetLow > 0){ if (bid > zoneTargetHigh || bid < zoneTargetLow){ obj_trade.PositionClose(_Symbol); deleteZoneLevels(); ... } }
Aqui, verificamos se o sistema de recuperação de zona está configurado, tendo a lógica de que os níveis-alvo estão acima de zero, o que significa que já temos um sistema em funcionamento. Portanto, se esse for o caso, verificamos se o preço de oferta está acima do alvo alto ou abaixo do alvo baixo, uma indicação de que podemos confortavelmente encerrar o sistema de recuperação de zona, pois seu objetivo foi alcançado. Assim, deletamos os níveis de zona através da função deleteZoneLevels. A função que usamos é do tipo void, já que não precisamos retornar nada, e a função embutida ObjectDelete é implementada para deletar os níveis, aceitando dois argumentos: o índice do gráfico e o nome do objeto.
void deleteZoneLevels(){ ObjectDelete(0,ZONE_H); ObjectDelete(0,ZONE_L); ObjectDelete(0,ZONE_T_H); ObjectDelete(0,ZONE_T_L); }
Para fechar as posições, como pode haver várias neste ponto, usamos um loop que considera todas elas e depois as deleta individualmente. Isso é realizado através do código abaixo.
for (int i = PositionsTotal()-1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ obj_trade.PositionClose(ticket); } } }
Depois de fechar todas as posições e deletar os níveis, redefinimos o sistema para o padrão, que não possui nenhum sistema de recuperação de zona.
//closed all, reset all zoneHigh=0;zoneLow=0;zoneTargetHigh=0;zoneTargetLow=0; lastDirection=0; recovery_lot = 0;
Isso é alcançado definindo os níveis de zona e alvos para zero, além da última direção e do lote de recuperação. Essas são variáveis estáticas, e por isso precisamos redefini-las manualmente. Para as variáveis dinâmicas, não há necessidade, pois elas geralmente são atualizadas automaticamente.
O código completo responsável por encerrar o sistema de recuperação após o cumprimento de seus objetivos é mostrado abaixo:
//--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } }
Prosseguindo para abrir posições de recuperação, precisaremos verificar se o sistema ainda está em funcionamento, conforme mostrado pela instância em que os níveis de zona estão acima de zero. Se sim, configuramos isso declarando uma variável lots_rec, que simplesmente usamos para armazenar nossos lotes de recuperação. Depois, normalizamos para 3 casas decimais para precisão, já que a conta de negociação que estamos usando é uma conta de microlote. Esse valor pode mudar de acordo com o tipo de conta que você está usando. Por exemplo, se você estiver usando uma conta padrão, seu lote mínimo é 1, e, portanto, seu valor será 0, para eliminar as casas decimais. .A maioria possui 2 casas decimais, mas você pode ter um tipo de conta de 0,001, e, portanto, seu valor é 3 para arredondar os lotes para as 3 casas decimais mais próximas.
if (zoneHigh > 0 && zoneLow > 0){ double lots_Rec = 0; lots_Rec = NormalizeDouble(recovery_lot,2); ... }
Em seguida, verificamos se o preço de oferta está acima da zona alta e, se a flag isBuyDone anterior for falsa ou o valor da última direção for menor que zero, abrimos uma posição de recuperação de compra. Depois de abrir a posição, definimos o lastDirection para 1, significando que a posição anteriormente aberta é uma compra, calculamos os lotes de recuperação e os armazenamos na variável recovery_lot para uso na próxima chamada de posição de recuperação e, em seguida, definimos a flag isBuyDone para true e isSellDone para false, indicando que uma posição de compra já foi aberta.
if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } }
Caso contrário, se o preço de oferta estiver abaixo da zona baixa, abrimos a posição de recuperação de venda, respectivamente, conforme mostrado.
else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
O código completo agora responsável por abrir as posições de recuperação é mostrado abaixo:
//--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
Este é o marco que alcançamos até este ponto.
O código completo necessário para automatizar o sistema de recuperação de zona é mostrado abaixo:
//+------------------------------------------------------------------+ //| MARTINGALE EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- Define utility variables for later use #define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name //--- Include trade instance class #include <Trade/Trade.mqh> // Include the trade class for trading functions CTrade obj_trade; // Create an instance of the CTrade class for trading operations //--- Declare variables to hold indicator data int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Retrieve the current Ask and Bid prices double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double zoneRange = 200 * _Point; // Define the range for the zones double zoneTarget = 400 * _Point; // Define the target range for the zones //--- Variables to track trading status static int lastDirection = 0; // -1 = sell, 1 = buy static double recovery_lot = 0.0; // Lot size for recovery trades static bool isBuyDone = false, isSellDone = false; // Flags to track trade completion //--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } } //--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } //--- Update bars and check for new bars int bars = iBars(_Symbol, PERIOD_CURRENT); if (totalBars == bars) return; // Exit if no new bars totalBars = bars; // Update the total number of bars //--- Exit if there are open positions if (PositionsTotal() > 0) return; //--- Copy RSI data and check for oversold/overbought conditions if (!CopyBuffer(rsi_handle, 0, 1, 2, rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } } //+------------------------------------------------------------------+ //| FUNCTION TO DRAW HORIZONTAL ZONE LINES | //+------------------------------------------------------------------+ void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width } //+------------------------------------------------------------------+ //| FUNCTION TO DELETE DRAWN ZONE LINES | //+------------------------------------------------------------------+ void deleteZoneLevels() { ObjectDelete(0, ZONE_H); // Delete the high zone line ObjectDelete(0, ZONE_L); // Delete the low zone line ObjectDelete(0, ZONE_T_H); // Delete the target high zone line ObjectDelete(0, ZONE_T_L); // Delete the target low zone line }
Até este ponto, automatizamos com sucesso o sistema de negociação forex de recuperação de zona conforme pretendido, e prosseguiremos para testá-lo e verificar seu desempenho, conforme mostrado abaixo, para confirmar se atende aos seus objetivos designados.
Resultados do backtest:
Após o teste no testador de estratégias, os resultados estão abaixo.
gráfico
Resultados:
Conclusão
Neste artigo, analisamos os passos básicos que precisam ser implementados para a automação da famosa estratégia Martingale de recuperação de zona em MQL5. Fornecemos a definição básica e a descrição da estratégia e mostramos como ela pode ser implementada em MQL5. Os traders agora podem usar o conhecimento apresentado para desenvolver sistemas de recuperação de zona mais complexos, que podem, posteriormente, ser otimizados para produzir melhores resultados no final.
Aviso Legal: Este código destina-se apenas a ajudar você a entender os conceitos básicos de criação de um sistema de negociação forex de recuperação de zona, e os resultados demonstrados não garantem desempenho futuro. Portanto, implemente cuidadosamente o conhecimento para criar e otimizar seus sistemas para se adequar aos seus estilos de negociação.
O artigo contém todas as etapas de forma periódica para a criação do sistema. Esperamos que você ache útil e um passo em direção à criação de um sistema de recuperação de zona melhor e totalmente otimizado. Os anexos dos arquivos necessários são fornecidos para apresentar os exemplos usados para demonstrar esses exemplos. Você deve ser capaz de estudar o código e aplicá-lo à sua estratégia específica para obter resultados ótimos
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15067
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.






- 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