Construa EAs auto-otimizáveis em MQL5 (Parte 3): Acompanhamento dinâmico de tendência e retorno à média
As estratégias de trading algorítmico baseadas em médias móveis se destacam entre a maioria das estratégias de trading por sua capacidade de manter nossos sistemas alinhados às tendências de longo prazo do mercado. Infelizmente, quando os mercados oscilam e deixam de seguir uma tendência de longo prazo, nossas outrora confiáveis estratégias de acompanhamento de tendência acabam nos prejudicando mais do que ajudando. Compreender como os mercados alternam entre movimentos tendenciais e laterais pode nos ajudar significativamente a usar de forma mais eficaz as estratégias baseadas em médias móveis.
Geralmente, os traders tendem a classificar os mercados como estando em um intervalo ou em tendência antes de decidir quais estratégias aplicar em um determinado dia de trading. Há poucas pesquisas dedicadas à transição entre esses dois regimes. Em vez de considerar os mercados como um ambiente estático que existe em apenas um dos dois estados, queremos imaginar os mercados financeiros como um ambiente dinâmico que alterna constantemente entre esses dois estados possíveis, sem se fixar em nenhum deles. Nosso objetivo aqui é desenvolver uma única estratégia de trading dinâmica, capaz de identificar de forma autônoma quando o regime de mercado subjacente muda de tendencial para lateral e vice-versa.
Essa estratégia dinâmica, esperamos, substituirá a tradicional abordagem que pressupõe o uso de uma estratégia específica para cada condição de mercado. A estratégia proposta é baseada no cálculo de um canal de trading com base no deslocamento progressivo (roll forward). A ideia principal da estratégia é que existe um limite que separa os movimentos de tendência dos movimentos dentro de um intervalo. E, observando onde os níveis de preço se encontram em relação a esse limite, podemos realizar operações mais fundamentadas. As fronteiras superior e inferior do canal são calculadas somando (limite superior) e subtraindo (limite inferior) um múltiplo do valor do ATR à média do indicador de média móvel. Discutiremos essa estratégia em detalhes nas próximas partes do artigo.
No entanto, o leitor deve compreender que o canal é calculado dinamicamente todos os dias utilizando os indicadores técnicos padrão incluídos no MetaTrader 5. Nossa estratégia inicial era uma simples estratégia de acompanhamento de tendência, que abria posições longas sempre que os níveis de preço fechavam acima da média móvel de 100 períodos, e posições curtas no caso contrário. Após a abertura das operações, estas eram posteriormente gerenciadas utilizando stop loss e take profit fixos. Com base em um teste histórico de 4 anos com dados M1 do par EURUSD, apenas 52% das operações executadas com a nossa estratégia inicial foram lucrativas. As regras dinâmicas baseadas em canais que propusemos aumentaram a proporção de operações vencedoras para 86% durante o mesmo período de 4 anos no timeframe M1, sem o uso de qualquer método de ajuste de curva ou IA.
Os resultados demonstram que o esforço investido em compreender melhor como avaliar a fronteira foi justificado. Ao reduzir nossa necessidade de classificar categoricamente os mercados dentro de moldes bem definidos e, em vez disso, tentar acompanhar o ritmo natural do mercado, descobrimos que nosso sistema de trading conseguiu atingir resultados impressionantes em termos de operações lucrativas. Além disso, considerando o formato simples adotado para nosso estilo de codificação, o leitor poderá facilmente expandir o modelo fornecido para incluir sua visão única de mercado.
Visão geral da estratégia de negociação
Como explicamos na introdução, estratégias de trading baseadas em médias móveis são particularmente populares entre os traders, pois nos permitem manter-nos alinhados às tendências de longo prazo do mercado. Um exemplo bastante notável desse princípio em ação é mostrado na figura 1 abaixo. O screenshot foi feito a partir do gráfico diário do par EURUSD e demonstra uma tendência de alta no mercado que começou no final de novembro de 2016 e se estendeu até abril de 2018. Um resultado impressionante por qualquer métrica. Observe que o indicador de média móvel também confirmou que os níveis de preço estavam em uma prolongada tendência ascendente.
Podemos ver que nossas posições longas poderiam ser abertas sempre que os níveis de preço fechassem acima da média móvel, enquanto nossas posições curtas poderiam ser abertas sempre que os níveis de preço fechassem abaixo da média móvel.

Fig. 1. Exemplo de nosso sistema de acompanhamento de tendência definido pela média móvel em ação
O exemplo mostrado na figura 1 demonstra as principais vantagens das estratégias baseadas em médias móveis quando aplicadas a mercados com tendência. No entanto, quando o mercado está desprovido de qualquer tendência de longo prazo, como nas condições ilustradas na figura 2, o simples acompanhamento de tendência deixa de ser eficaz.

Fig. 2. Exemplo de um período prolongado de perdas ao usar sistemas de acompanhamento de tendência
A estratégia proposta neste artigo é capaz de lidar excepcionalmente bem com ambas as condições de mercado, sem a necessidade de utilizar indicadores adicionais ou uma lógica de trading complexa para determinar quando e como alterar a estratégia. Portanto, o que é necessário fazer para que nossa simples estratégia de trading baseada em média móvel se torne dinâmica e autorregulável?
Iniciando o trabalho em MQL5
Nosso sistema de trading é composto por várias partes distintas:
| Elemento do sistema | Objetivo proposto |
|---|---|
| Constantes do sistema | São constantes ocultas do usuário final, destinadas a manter o comportamento consistente do sistema durante ambos os testes em histórico, evitando vieses ou alterações não intencionais que possam comprometer a lógica de trading. |
| Bibliotecas | Em nossa aplicação, importamos apenas a biblioteca de trading, para simplificar a abertura e o gerenciamento de posições. |
| Variáveis globais | Essas variáveis são usadas para armazenar valores de indicadores, níveis de preço e outros dados do sistema, que serão compartilhados entre as diferentes partes do sistema. |
| Manipuladores de eventos do sistema | Funções como OnTick() são manipuladores de eventos do sistema que nos ajudam a executar nossas tarefas de forma organizada. |
| Funções personalizadas | São funções adaptadas às nossas necessidades específicas para seguir com sucesso a tendência definida pela média móvel. |
Para começar, definiremos nossas constantes do sistema. Observe que não modificaremos esses valores em nenhuma das versões posteriores de nossa aplicação de trading.
//+------------------------------------------------------------------+ //| Dynamic Moving Average Strategy.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define MA_PRICE PRICE_CLOSE //Moving average applied price #define MA_MODE MODE_EMA //Moving average type #define MA_SHIFT 0 //Moving average shift #define ATR_PERIOD 14 //Period for our ATR #define TF_1 PERIOD_D1 //Hihger order timeframe #define TRADING_MA_PERIOD 100 //Moving average period #define SL_WIDTH 1.5 //How wide should the stop loss be? #define CURRENT_VOL 0.1 //Trading volume
Em seguida, importaremos uma das bibliotecas mais utilizadas na API MQL5 — a biblioteca de trading. Isso é necessário para facilitar o gerenciamento de nossas posições.
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Também precisaremos criar variáveis globais para armazenar os preços de mercado e os valores dos indicadores técnicos.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_handler,trading_ma_handler,atr_handler; double ma[],atr[],trading_ma[]; double ask,bid;
O corpo da nossa aplicação MQL5 consiste em manipuladores de eventos. Esses manipuladores são chamados sempre que ocorre um novo evento em nosso terminal, como a chegada de novos preços ou a remoção da aplicação do gráfico. Alguns eventos são iniciados pelo usuário final e outros pelo servidor de trading. O manipulador OnInit() é acionado quando o usuário executa nossa aplicação de trading. Ele chamará uma função especial que desenvolvemos para inicializar nossas variáveis globais.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); }
Quando o usuário final remove o aplicativo de trading do gráfico, o manipulador OnDeinit() é chamado. Usaremos esse manipulador para liberar os recursos de memória do sistema que não estão mais sendo utilizados.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- IndicatorRelease(atr_handler); IndicatorRelease(ma_handler); IndicatorRelease(trading_ma_handler); }
O manipulador OnTick() é acionado pelo servidor do trader, e não pelo usuário final. Ele é chamado sempre que recebemos uma atualização de preço. Chamaremos funções específicas para atualizar nossos indicadores técnicos e registrar as novas informações de preço; em seguida, se não houver posições abertas, tentaremos abrir uma nova posição.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); if(PositionsTotal() == 0) find_setup(); } //+------------------------------------------------------------------+
Nossas regras de abertura de posição na estratégia de acompanhamento de tendência são simples de entender. Se os níveis de preço estiverem acima da média móvel de 100 períodos, abriremos posições longas com stop loss fixo. Otherwise, if price levels are beneath the moving average, we will open short positions.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //Buy on rallies if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0]) Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * SL_WIDTH)),(bid + (atr[0] * SL_WIDTH)),""); if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0]) Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * SL_WIDTH)),(ask - (atr[0] * SL_WIDTH)),""); }
A função chamada pelo manipulador OnInit() é responsável por configurar nossos indicadores técnicos.
//+------------------------------------------------------------------+ //| Setup our global variables | //+------------------------------------------------------------------+ void setup(void) { atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); trading_ma_handler = iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE); }
Nossa função Update() será chamada pelo manipulador OnTick() para atualizar nossas variáveis globais.
//+------------------------------------------------------------------+ //| Update | //+------------------------------------------------------------------+ void update(void) { static datetime time_stamp; datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); if(time_stamp != current_time) { time_stamp = current_time; CopyBuffer(atr_handler,0,0,1,atr); CopyBuffer(trading_ma_handler,0,0,1,trading_ma); } } //+------------------------------------------------------------------+
Abaixo está mostrado o estado atual do nosso aplicativo de trading na sua forma atual.
//+------------------------------------------------------------------+ //| Dynamic Moving Average Strategy.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define MA_PRICE PRICE_CLOSE //Moving average applied price #define MA_MODE MODE_EMA //Moving average type #define MA_SHIFT 0 //Moving average shift #define ATR_PERIOD 14 //Period for our ATR #define TF_1 PERIOD_D1 //Hihger order timeframe #define TRADING_MA_PERIOD 100 //Moving average period #define SL_WIDTH 1.5 //How wide should the stop loss be? #define CURRENT_VOL 0.1 //Trading volume //+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_handler,trading_ma_handler,atr_handler; double ma[],atr[],trading_ma[]; double ask,bid; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- IndicatorRelease(atr_handler); IndicatorRelease(ma_handler); IndicatorRelease(trading_ma_handler); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); if(PositionsTotal() == 0) find_setup(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //Buy on rallies if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0]) Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * SL_WIDTH)),(bid + (atr[0] * SL_WIDTH)),""); if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0]) Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * SL_WIDTH)),(ask - (atr[0] * SL_WIDTH))); } //+------------------------------------------------------------------+ //| Setup our global variables | //+------------------------------------------------------------------+ void setup(void) { atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); trading_ma_handler = iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE); } //+------------------------------------------------------------------+ //| Update | //+------------------------------------------------------------------+ void update(void) { static datetime time_stamp; datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); if(time_stamp != current_time) { time_stamp = current_time; CopyBuffer(atr_handler,0,0,1,atr); CopyBuffer(trading_ma_handler,0,0,1,trading_ma); } } //+------------------------------------------------------------------+
Valor de referência
Vamos observar o desempenho do nosso aplicativo de acompanhamento de tendência em dados históricos M1, no período de quarta-feira, 1º de janeiro de 2020, até segunda-feira, 6 de janeiro de 2025. Utilizaremos o par EURUSD para o teste histórico. Observe que não ajustamos nenhum parâmetro da estratégia, portanto a configuração “Forward” está definida como “Nenhum”.

Fig. 3. Datas do nosso teste histórico
Também configuraremos nossa simulação com base em ticks reais, para reproduzir o mais fielmente possível as condições reais de trading em nosso testador. Além disso, o uso de um atraso aleatório nos dá uma ideia de como nosso sistema se comportará em situações de estresse. Vale lembrar que a latência durante o trading em tempo real pode variar, portanto é natural que queiramos permanecer próximos das condições nas quais esperamos que nosso sistema opere.

Fig. 4. Condições do nosso teste histórico
Esta é a curva de capital resultante obtida com nossa estratégia de trading ao longo de um teste histórico de 5 anos. Observe a diferença entre o saldo máximo e o saldo final da conta. Os resultados mostram claramente que nossa estratégia é instável e tende a perder dinheiro quase tão facilmente quanto a obter lucro. Nossas sequências de ganhos, ao utilizar essa estratégia, eram seguidas por períodos de rebaixamento que duravam tanto quanto os períodos lucrativos. Esses longos períodos de queda sustentada provavelmente estão associados a momentos em que o mercado não seguia uma tendência de longo prazo, e nossa estratégia ingênua de trading não conseguia reagir adequadamente a tais condições de mercado.

Fig. 5. Nossa curva de capital obtida da versão inicial da nossa estratégia
Ao analisar em detalhe os resultados do nosso teste histórico, podemos ver que nossa estratégia foi lucrativa durante o período de 5 anos, o que nos encoraja a tentar melhorá-la. No entanto, a proporção entre operações lucrativas e perdedoras é de quase 50/50, o que é indesejável. Queremos filtrar as operações perdedoras para alcançar uma taxa mais alta de acertos. Em média, tivemos 2 ganhos consecutivos e 2 perdas consecutivas. Isso confirma nossa observação de que o sistema tem a mesma probabilidade de gerar lucro quanto de causar perda de capital. Um sistema assim não pode ser confiado para operar de forma autônoma.

Fig. 6. Relatório detalhado do nosso teste histórico
Análise da solução proposta
A análise dos resultados do teste histórico inicial nos permite discutir detalhadamente o sistema proposto. Sabemos que nosso sistema é lucrativo quando os mercados seguem uma tendência, mas perde dinheiro quando o mercado não está em tendência. Portanto, como podemos projetar um sistema que seja capaz de decidir autonomamente se o mercado está ou não em um regime de tendência?
Compreender como os mercados alternam entre esses dois regimes ajudará nosso sistema a utilizar de forma mais eficaz o indicador de média móvel. Nos dias em que o sistema identificar que o mercado provavelmente estará operando dentro de uma faixa limitada, ele abrirá operações contrárias à tendência definida pela média móvel. Caso contrário, ele abrirá operações seguindo a tendência presumida.
Em outras palavras, se nosso algoritmo determinar que o ativo negociado provavelmente está em um regime de faixa, então sempre que os níveis de preço fecharem acima da média móvel, venderemos em vez de comprar. No entanto, se o algoritmo identificar que o ativo está em um regime de tendência, quando os níveis de preço fecharem acima da média móvel, faremos uma compra. Essencialmente, o mesmo evento de mercado será interpretado e tratado de forma diferente, dependendo do regime em que acreditamos que o mercado se encontra.
Então, agora resta apenas uma pergunta — como podemos determinar em qual regime o mercado se encontra? A estratégia que propomos consiste em dividir os níveis de preço em 4 zonas discretas. A ideia central da estratégia é bastante intuitiva:
- Regime de tendência — o mercado só pode realmente se mover em tendência quando está na Zona 1 ou na Zona 4.
- Regime de faixa — o mercado só pode realmente estar em consolidação quando se encontra na Zona 2 ou na Zona 3.

Fig. 7. Exemplo simples de nosso sistema de negociação zonal
Vamos dar uma olhada em cada zona, começando pela Zona 1. Quando os níveis de preço estão na Zona 1, interpretamos isso como um sentimento altista do mercado e procuraremos oportunidades de compra sempre que os níveis de preço fecharem acima da média móvel de 100 períodos. A largura do nosso take profit e do stop loss será mantida conforme o teste histórico inicial. Observe que buscaremos apenas oportunidades de longo prazo para seguir as tendências de alta enquanto estivermos na Zona 1. Não abriremos posições curtas enquanto os níveis de preço permanecerem na Zona 1!

Fig. 8. Explicação da importância da Zona 1
Se os níveis de preço caírem da Zona 1 para a Zona 2, nosso sentimento de mercado muda. Já não acreditamos que haverá tendências reais enquanto os preços permanecerem na Zona 2. Em vez disso, entendemos que, na Zona 2, os níveis de preço tendem a oscilar em torno da faixa média que separa a Zona 2 da Zona 3. Procuraremos exclusivamente oportunidades de venda na Zona 2, pois assumimos que os níveis de preço tenderão a cair em direção à faixa média.
Se o nível de preço subir acima da média móvel de 100 períodos enquanto estivermos na Zona 2, venderemos, mantendo a mesma configuração de stop loss usada na estratégia inicial. No entanto, a posição do nosso take profit precisará ser ajustada. Colocaremos nosso take profit na faixa média que separa a Zona 2 da Zona 3, pois presumimos que é ali que os níveis de preço tenderão a se estabilizar enquanto estivermos dentro da Zona 2. Observe que não abriremos posições longas enquanto estivermos na Zona 2. Cada zona permite apenas um tipo de operação.

Fig. 9. Compreendendo como nossa estratégia de trading evolui à medida que passa pelas quatro zonas de mercado que definimos
Espero que, neste ponto, o leitor já tenha começado a perceber um certo padrão, e o restante do conjunto de regras deva parecer intuitivo. Vamos fazer um pequeno exercício para garantir que estamos em sintonia: se o nível de preço cair para a Zona 3, que tipo de posição você acha que devemos abrir? Espero que tenha pensado “posições longas”.
E onde colocaremos nosso take profit? Espero que o leitor agora entenda intuitivamente que, quando estivermos na Zona 2 ou na Zona 3, nosso take profit será colocado na faixa média que separa a Zona 2 da Zona 3.
Em outras palavras, quando estivermos na Zona 3, abriremos posições longas apenas quando os níveis de preço fecharem abaixo da média móvel de 100 períodos. Nosso take profit será colocado na faixa média, e o stop loss terá o mesmo tamanho usado na estratégia original. Enquanto estivermos na Zona 3, não abriremos posições curtas.

Fig. 10. As zonas 2 e 3 são consideradas zonas de retorno ao valor médio.
Por fim, quando os níveis de preço estiverem na Zona 4, procuraremos apenas oportunidades de abrir posições curtas. Entraremos vendido sempre que o nível de preço cair abaixo da nossa média móvel de 100 períodos. A largura do nosso take profit e do stop loss será idêntica à utilizada na nossa estratégia base. Não abriremos posições longas enquanto os níveis de preço permanecerem na Zona 4.

Fig. 11. A Zona 4 é o lugar onde acreditamos que se formarão tendências de baixa. Portanto, não abriremos posições longas na Zona 4.
Para implementar a estratégia das quatro zonas, precisaremos fazer algumas alterações na estratégia original de acompanhamento de tendência:
| Nova proposta de modificação | Objetivo proposto |
|---|---|
| Novas variáveis do sistema | Precisaremos de novas variáveis do sistema para obter controle sobre o canal que vamos criar. |
| Criação de parâmetros de entrada do usuário | Embora o usuário não possa controlar todos os aspectos do canal, alguns parâmetros — como a largura do canal e o número de faixas utilizadas em seu cálculo — poderão ser ajustados por ele. |
| Novas variáveis globais | Serão criadas novas variáveis globais relacionadas ao canal, que ajudarão a aprimorar a lógica de trading da nossa aplicação. |
| Alterações em nossas funções personalizadas | Criaremos novas funções dentro do nosso aplicativo de trading e modificaremos algumas das funções existentes para implementar a lógica de trading descrita. |
Os detalhes sobre o cálculo do canal serão discutidos à medida que avançarmos.
Implementação da solução em MQL5
Começaremos definindo as constantes do sistema que nos ajudarão a calcular o canal. Para esse cálculo, primeiro precisamos aplicar o indicador de média móvel em um timeframe superior ao que pretendemos operar. Assim, em nosso exemplo, queremos operar no M1. Aplicaremos uma média móvel de 20 períodos no timeframe diário e a utilizaremos para calcular nosso canal. Se calculássemos o canal no mesmo timeframe em que operamos, perceberíamos que o canal se moveria de forma excessivamente volátil, o que impediria a criação de uma estratégia de trading estável.
#define MA_PERIOD 20 //Moving Average period for calculating the mid point of our range zone
Em seguida, precisamos definir as variáveis globais que irão acompanhar o canal. Em particular, nosso objetivo é saber onde se encontra a fronteira da Zona 2 (limite superior) e da Zona 3 (limite inferior). Além disso, queremos determinar com precisão onde está a fronteira entre a Zona 2 e a Zona 3 (limite médio). A Zona 1 é definida pela fronteira superior e não possui limite máximo. Da mesma forma, a Zona 4 começa onde termina a Zona 3 e não possui limite inferior.
double range_zone_mid,range_zone_ub,range_zone_lb; int active_zone;
Agora, definiremos os parâmetros do canal que permitiremos ao usuário final ajustar. Por exemplo, o período de cálculo baseado em dados históricos e a largura do canal que o usuário deseja utilizar. É importante oferecer ao usuário a possibilidade de controlar a largura do canal, já que ela está diretamente relacionada ao nível de risco da conta. Para nossa demonstração, simplesmente faremos com que a largura do canal seja equivalente a duas vezes o valor do ATR. A maioria dos mercados tende a se mover em torno de 1 ATR por dia, e devemos lembrar que não queremos que nosso canal oscile em excesso.
//+------------------------------------------------------------------+ //| User inputs | //+------------------------------------------------------------------+ input group "Technical Analysis" input double atr_multiple = 2 ; //ATR Multiple input int bars_used = 30; //How Many Bars should we use to calculate the channel?
Precisamos de uma função que determine em qual zona estamos atualmente. A lógica de identificação das zonas já foi explicada em detalhes anteriormente.
//+------------------------------------------------------------------+ //| Get our current active zone | //+------------------------------------------------------------------+ void get_active_zone(void) { if(iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_ub) { active_zone = 1; return; } if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_ub) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_mid)) { active_zone = 2; return; } if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_mid) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_lb)) { active_zone = 3; return; } if(iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_lb) { active_zone = 4; return; } } //+------------------------------------------------------------------+
Também precisamos atualizar nossa função de configuração. Ignoraremos las partes da função que não sofreram alterações. Estamos introduzindo apenas um novo indicador técnico. O primeiro indicador técnico que aplicamos foi a média móvel de 100 períodos no timeframe M1. Nosso novo indicador é uma média móvel de 20 períodos aplicada ao timeframe diário.
//+------------------------------------------------------------------+ //| Setup our global variables | //+------------------------------------------------------------------+ void setup(void) { //We have omitted parts of the code that have not changed ma_handler = iMA(Symbol(),TF_1,MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE); }
Em seguida, devemos fazer alterações na nossa função de atualização. Deliberadamente omitimos as partes da função que permanecem inalteradas para concentrar-nos nas novas seções. Começamos inicializando o vetor com zeros. Depois, utilizando nosso novo vetor, copiamos a quantidade de barras necessárias para os cálculos a partir da nova média móvel aplicada ao timeframe diário na etapa anterior.
Então, tomamos o valor médio da média móvel diária de 20 períodos — este será o valor da faixa média que separa a Zona 2 da Zona 3. As fronteiras das Zonas 2 e 3 serão calculadas somando (Zona 2) e subtraindo (Zona 3) um múltiplo do valor do ATR a partir do ponto médio calculado com base na média móvel de 20 períodos.
//+------------------------------------------------------------------+ //| Update | //+------------------------------------------------------------------+ void update(void) { static datetime time_stamp; datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); if(time_stamp != current_time) { //Omitted parts of the function that remained unchanged vector ma_average = vector::Zeros(1); ma_average.CopyIndicatorBuffer(ma_handler,0,1,bars_used); range_zone_mid = ma_average.Mean(); range_zone_ub = (range_zone_mid + (atr[0] * atr_multiple)); range_zone_lb = (range_zone_mid - (atr[0] * atr_multiple)); get_active_zone(); Comment("Zone: ",active_zone); ObjectDelete(0,"RANGE HIGH"); ObjectDelete(0,"RANGE LOW"); ObjectDelete(0,"RANGE MID"); ObjectCreate(0,"RANGE MID",OBJ_HLINE,0,0,range_zone_mid); ObjectCreate(0,"RANGE LOW",OBJ_HLINE,0,0,range_zone_lb); ObjectCreate(0,"RANGE HIGH",OBJ_HLINE,0,0,range_zone_ub); } }
A última modificação que precisamos fazer diz respeito à forma como determinamos nossas condições de entrada. A lógica de trading que fundamenta a abertura de posições já foi discutida em detalhes, portanto esse trecho de código deve ser claro para o leitor. Resumindo a ideia principal: acompanharemos a tendência apenas nas Zonas 1 e 4. Isso significa que, se o preço fechar acima da média móvel de 100 períodos na Zona 1, faremos uma compra. Caso contrário, se estivermos na Zona 2 ou na Zona 3, operaremos contra a tendência — ou seja, se o preço fechar acima da média móvel de 100 períodos na Zona 2, venderemos em vez de comprar, como fazíamos na Zona 1.
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { // Follow the trend if(active_zone == 1) { //Buy on rallies if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0]) Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),(bid + (atr[0] * SL_WIDTH)),""); } // Go against the trend if(active_zone == 2) { //Sell on rallies if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0]) Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * 1.5)),range_zone_mid); } // Go against the trend if(active_zone == 3) { //Buy the dip if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0]) Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),range_zone_mid,""); } // Follow the trend if(active_zone == 4) { //Sell the dip if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0]) Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * atr_multiple)),(ask - (atr[0] * SL_WIDTH))); } }
Se juntarmos tudo, é assim que a versão atualizada da nossa estratégia de trading se apresenta.
//+------------------------------------------------------------------+ //| Dynamic Moving Average Strategy.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define MA_PRICE PRICE_CLOSE //Moving average shift #define MA_MODE MODE_EMA //Moving average shift #define MA_SHIFT 0 //Moving average shift #define ATR_PERIOD 14 //Period for our ATR #define TF_1 PERIOD_D1 //Hihger order timeframe #define MA_PERIOD 20 //Moving Average period for calculating the mid point of our range zone #define TRADING_MA_PERIOD 100 //Moving average period #define SL_WIDTH 1.5 //How wide should the stop loss be? #define CURRENT_VOL 0.1 //Trading volume //+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_handler,trading_ma_handler,atr_handler; double ma[],atr[],trading_ma[]; double range_zone_mid,range_zone_ub,range_zone_lb; double ask,bid; int active_zone; //+------------------------------------------------------------------+ //| User inputs | //+------------------------------------------------------------------+ input group "Technical Analysis" input double atr_multiple = 1; //ATR Multiple input int bars_used = 30; //How Many Bars should we use to calculate the channel? //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- IndicatorRelease(ma_handler); IndicatorRelease(atr_handler); IndicatorRelease(trading_ma_handler); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); if(PositionsTotal() == 0) find_setup(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { // Follow the trend if(active_zone == 1) { //Buy on rallies if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0]) Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),(bid + (atr[0] * SL_WIDTH)),""); } // Go against the trend if(active_zone == 2) { //Sell on rallies if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0]) Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * 1.5)),range_zone_mid); } // Go against the trend if(active_zone == 3) { //Buy the dip if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0]) Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),range_zone_mid,""); } // Follow the trend if(active_zone == 4) { //Sell the dip if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0]) Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * atr_multiple)),(ask - (atr[0] * SL_WIDTH))); } } //+------------------------------------------------------------------+ //| Setup our global variables | //+------------------------------------------------------------------+ void setup(void) { atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); trading_ma_handler = iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE); ma_handler = iMA(Symbol(),TF_1,MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE); } //+------------------------------------------------------------------+ //| Update | //+------------------------------------------------------------------+ void update(void) { static datetime time_stamp; datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); if(time_stamp != current_time) { time_stamp = current_time; CopyBuffer(ma_handler,0,0,1,ma); CopyBuffer(atr_handler,0,0,1,atr); CopyBuffer(trading_ma_handler,0,0,1,trading_ma); vector ma_average = vector::Zeros(1); ma_average.CopyIndicatorBuffer(ma_handler,0,1,bars_used); range_zone_mid = ma_average.Mean(); range_zone_ub = (range_zone_mid + (atr[0] * atr_multiple)); range_zone_lb = (range_zone_mid - (atr[0] * atr_multiple)); get_active_zone(); Comment("Zone: ",active_zone); ObjectDelete(0,"RANGE HIGH"); ObjectDelete(0,"RANGE LOW"); ObjectDelete(0,"RANGE MID"); ObjectCreate(0,"RANGE MID",OBJ_HLINE,0,0,range_zone_mid); ObjectCreate(0,"RANGE LOW",OBJ_HLINE,0,0,range_zone_lb); ObjectCreate(0,"RANGE HIGH",OBJ_HLINE,0,0,range_zone_ub); } } //+------------------------------------------------------------------+ //| Get our current active zone | //+------------------------------------------------------------------+ void get_active_zone(void) { if(iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_ub) { active_zone = 1; return; } if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_ub) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_mid)) { active_zone = 2; return; } if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_mid) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_lb)) { active_zone = 3; return; } if(iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_lb) { active_zone = 4; return; } } //+------------------------------------------------------------------+
Vamos verificar se essas modificações nos proporcionam o controle desejado. Definiremos o período durante o qual será realizado o teste histórico.

Fig. 12. O período do nosso teste histórico coincide com o período inicial utilizado
Da mesma forma, ajustaremos as condições do backtest para que correspondam ao nosso teste anterior.

Fig. 13. Certifique-se de que ambas as estratégias sejam testadas nas mesmas condições
Nossa nova estratégia possui dois parâmetros de entrada. O primeiro controla a largura do canal, e o segundo define o número de faixas utilizadas para o cálculo do canal.

Fig. 14. Elementos de controle que nosso usuário final pode ajustar
A nova curva de capital, gerada por nossa estratégia revisada, apresenta períodos de perda, como qualquer estratégia, mas observe como ela se recupera rapidamente deles. Ela não fica estagnada, como nossa estratégia anterior. Pode haver uma operação perdedora, mas o sistema se ajusta rapidamente ao mercado. Isso é evidenciado pelo fato de que os períodos de perda são seguidos por períodos de trading lucrativo.

Fig. 15. A curva de capital obtida com nossa nova estratégia de trading
Ao analisar em detalhe os resultados da nossa nova estratégia, observamos que a proporção de operações vencedoras aumentou de 52% para 86%, enquanto a proporção de operações perdedoras diminuiu de 47% para 13%. Nosso lucro médio é menor que nossa perda média, mas é importante lembrar que nossos stop loss e take profit são fixos; esse problema pode ser resolvido mantendo o stop loss fixo quando ocorrem perdas e permitindo que ele varie quando obtemos lucros. Além disso, nossa média de ganhos consecutivos aumentou de 2 para 9, enquanto a média de perdas consecutivas caiu de 2 para 1. Nossa estratégia inicial realizou 300 operações. Nossa nova estratégia executou um total de 1301 operações. Isso significa que a nova abordagem realiza mais operações e vence com maior frequência.

Fig. 16. Uma análise detalhada do desempenho da nossa nova estratégia
Considerações finais
Na discussão de hoje, começamos com uma estratégia ingênua de acompanhamento de tendência e a evoluímos para uma abordagem mais fundamentada, utilizando os dados históricos disponíveis em nosso terminal MetaTrader 5. Teoricamente, poderíamos implementar essa estratégia com papel e caneta, mas, felizmente para nós, o API MQL5 simplifica todo o processo. Desde o cálculo rápido de métricas estatísticas por meio de funções vetoriais até a execução otimizada das operações — a maior parte do trabalho complexo é realizada automaticamente pelo nosso aplicativo no MetaTrader 5. Em futuros artigos, exploraremos maneiras de aumentar ainda mais a proporção de operações lucrativas e de aprimorar o controle sobre o tamanho das operações perdedoras.
| Arquivo anexo | Descrição |
|---|---|
| Estratégia de referência de média móvel | Implementação inicial da nossa estratégia de acompanhamento de tendência |
| Estratégia de média móvel dinâmica | Nova estratégia dinâmica proposta. |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16856
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.
Aplicação de métodos de ensemble para tarefas de classificação em MQL5
Simplificando a negociação com base em notícias (Parte 6): Executando trades (III)
Dominando registros de log (Parte 2): Formatação dos logs
Simulação de mercado: Position View (XIX)
- 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