English Русский 中文 Español Deutsch 日本語
preview
Reimaginando Estratégias Clássicas (Parte 13): Minimizando o Atraso em Cruzamentos de Médias Móveis

Reimaginando Estratégias Clássicas (Parte 13): Minimizando o Atraso em Cruzamentos de Médias Móveis

MetaTrader 5Exemplos |
96 3
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Em nossas discussões anteriores, adotamos diferentes perspectivas sobre como podemos maximizar nossa eficiência ao usar estratégias de cruzamento de médias móveis. Para resumir brevemente, conduzimos anteriormente um exercício com mais de 200 símbolos diferentes e observamos que nosso computador parece aprender a prever valores futuros de médias móveis melhor do que tende a prever níveis futuros de preços corretamente, forneci um link rápido para o artigo, aqui. Ampliamos essa ideia e modelamos 2 médias móveis, com o objetivo único de prever cruzamentos mais cedo do que participantes casuais do mercado e ajustar nossas operações de acordo. Incluí um link para o segundo artigo também, disponível facilmente ao seu alcance, aqui

Nossa discussão de hoje estenderá novamente a estratégia original, para minimizar o atraso inerente à estratégia de negociação. As estratégias tradicionais de cruzamento exigem que exista um intervalo temporal entre os 2 períodos das médias móveis. Entretanto, nos afastaremos dessa escola de pensamento convencional e usaremos o mesmo período em ambas as médias móveis.

Neste ponto, alguns leitores podem estar se questionando se isso ainda pode ser qualificado como uma estratégia de cruzamento de médias móveis, pois como suas médias móveis irão se cruzar se ambas compartilham o mesmo período? A resposta é surpreendentemente simples: aplicamos uma média móvel no preço de abertura e outra no preço de fechamento, respectivamente.
Quando a média móvel da abertura está acima da média móvel do fechamento, os níveis de preço estão terminando mais baixos do que começaram. Isso é idêntico a saber que os níveis de preço estão caindo. E o inverso é verdadeiro quando a média móvel do fechamento está acima da de abertura.
Um período comum em ambas as médias móveis é incomum; no entanto, selecionei-o para nosso exercício de hoje para demonstrar ao leitor uma possível forma de encerrar quaisquer críticas sobre o atraso associado aos cruzamentos de médias móveis.

Antes de mergulharmos no segmento técnico de nossa discussão, este artigo pode ser lido como um exemplo de uma classe generalizada de estratégias de negociação que pode ser facilmente estendida a muitos outros indicadores técnicos. Por exemplo, o Índice de Força Relativa também pode ser aplicado separadamente ao preço de abertura, fechamento, máxima ou mínima, e observaremos que o RSI acompanhando o preço de abertura tende a ficar acima do RSI acompanhando o preço de fechamento quando os mercados estão em tendência de baixa.


Visão Geral do Nosso Backtest

Para apreciarmos a relevância de nossa discussão de hoje, primeiro estabeleceremos um benchmark criado pela estratégia tradicional de cruzamento. Em seguida, compararemos o desempenho legado com o que podemos alcançar usando nossa versão reimaginada da estratégia.

Selecionei o par EURUSD para nossa discussão de hoje. O EURUSD é o par de moedas mais negociado do mundo. É significativamente mais volátil do que a maioria dos pares de moedas e geralmente não é uma boa escolha para estratégias simples baseadas em cruzamentos. Como já discutimos anteriormente, focaremos no período diário. Faremos o backtest de nossa estratégia em aproximadamente 4 anos de dados históricos, de 1º de janeiro de 2020 até 24 de dezembro de 2024; o período do backtest está destacado abaixo na Fig. 1.

Fig 1: Visualizando nosso período de backtest de 4 anos do EURUSD em nosso terminal MetaTrader 5 usando o período Mensal

Embora estratégias tradicionais de cruzamento sejam intuitivas e fundamentadas em princípios razoavelmente sólidos, essas estratégias frequentemente exigem otimizações intermináveis para garantir eficácia. Além disso, os “períodos certos” para usar nos indicadores lento e rápido não são imediatamente óbvios e podem mudar drasticamente. 

Para recapitular, a estratégia original é baseada nas interseções criadas por 2 médias móveis, ambas acompanhando o preço de fechamento do mesmo ativo, mas com períodos diferentes. Quando a média móvel com o período mais curto está por cima, interpretamos isso como um sinal de que os níveis de preço estiveram em tendência de alta e provavelmente continuarão subindo. O oposto se aplica quando a média móvel com o período mais longo está por cima; interpretamos isso como um sinal baixista. Um exemplo ilustrado é fornecido na Fig. 2 a seguir.

Fig 2: Um exemplo da estratégia tradicional de cruzamento de médias móveis em ação; a linha amarela é a média móvel rápida e a branca é a lenta

Na Fig. 2 acima, selecionamos aleatoriamente um período que mostra as limitações da estratégia clássica. À esquerda da linha vertical no gráfico, você observará que a ação do preço ficou presa em um intervalo por aproximadamente 2 meses. Essa ação letárgica gera sinais de negociação que são rapidamente revertidos e provavelmente não lucrativos. No entanto, após esse período de desempenho fraco, finalmente vimos os níveis de preço se afastarem em uma verdadeira tendência à direita da linha vertical. Estratégias tradicionais de cruzamento funcionam melhor em condições de mercado em tendência. No entanto, a estratégia proposta neste artigo lida com esses problemas de maneira elegante.



Estabelecendo um Benchmark

Nosso aplicativo de negociação pode ser conceituado como 4 componentes principais que trabalharão juntos para nos ajudar a negociar:

Feature Descrição
Constantes do Sistema Ajudar-nos a isolar as melhorias trazidas pelas mudanças que estamos fazendo na lógica de negociação de nosso aplicativo.
Variáveis Globais Responsável por acompanhar valores de indicadores, preços atuais de mercado e potencialmente mais informações necessárias.
Organizadores de Eventos Executar várias tarefas no momento apropriado para atender ao nosso objetivo de negociar cruzamentos de médias móveis de forma eficaz.
Funções Personalizadas Cada função personalizada em nosso sistema tem uma tarefa específica delegada a ela, e todas juntas as funções ajudam coletivamente a alcançar nosso objetivo.

A versão de benchmark de nossa aplicação será minimalista em sua implementação. Nossa primeira tarefa é configurar constantes do sistema que permanecerão fixas nos dois testes que realizaremos. Nossas constantes do sistema são importantes para fazer comparações justas entre diferentes estratégias de negociação e evitam que mudemos acidentalmente configurações que não deveriam ser alteradas entre os testes, como o tamanho do nosso stop loss, que deve ser constante em ambos os testes para garantir uma comparação justa.

//+------------------------------------------------------------------+
//|                                               Channel And MA.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define TF_1           PERIOD_D1 //--- Our main time frame
#define TF_2           PERIOD_H1 //--- Our lower time frame
#define ATR_PERIOD     14        //--- The period for our ATR
#define ATR_MULTIPLE   3         //--- How wide should our stops be?
#define VOL            0.01      //--- Trading volume

Também definiremos algumas variáveis globais importantes que usaremos para buscar valores de indicadores e obter preços atuais de mercado.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    trade;
int    ma_f_handler,ma_s_handler,atr_handler;
double ma_f[],ma_s[],atr[];
double bid,ask;
double o,h,l,c;
double original_sl;

Usaremos a biblioteca de trade para gerenciar nossas posições. 

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

Em MQL5, expert advisors são construídos a partir de manipuladores de eventos. Existem muitos tipos diferentes de eventos que acontecem no terminal MetaTrader 5. Esses eventos podem ser disparados por ações tomadas pelo usuário ou quando novos preços são cotados. Cada evento é emparelhado com um manipulador de eventos que é chamado cada vez que o evento é disparado. Portanto, projetei nossa aplicação de forma que cada manipulador de eventos tenha sua própria função designada, que será chamada por sua vez para executar as tarefas necessárias para uma estratégia de cruzamento de médias móveis.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release our indicators
   release();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update system variables
   update();
  }
//+------------------------------------------------------------------+

A função setup é chamada quando nosso sistema é iniciado. O manipulador OnInit será chamado quando a aplicação de negociação for aplicada pela primeira vez a um gráfico, e ele repassará a cadeia de comando para nossa função setup personalizada, que aplicará nossos indicadores técnicos para nós.

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);

   ma_f_handler = iMA(Symbol(),TF_1,10,0,MODE_EMA,PRICE_CLOSE);
   ma_s_handler = iMA(Symbol(),TF_1,60,0,MODE_EMA,PRICE_CLOSE);
  }

Se não estivermos mais usando nossa aplicação de negociação, o manipulador de eventos OnDeinit é chamado, e ele chamará nossa função release, que liberará os recursos do sistema que estavam sendo consumidos anteriormente pelos nossos indicadores técnicos.

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_f_handler);
   IndicatorRelease(ma_s_handler);
  }

Sempre que novos preços de mercado são cotados, o manipulador OnTick será chamado, e ele, por sua vez, chamará a função update para armazenar as novas informações de mercado disponíveis. Depois disso, se não tivermos posições abertas, procuraremos uma oportunidade de negociação. Caso contrário, gerenciaremos as posições que temos abertas.

/+------------------------------------------------------------------+
//| Update system variables                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Update the system
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),TF_2,0);
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(ma_s_handler,0,0,1,ma_s);
      CopyBuffer(ma_f_handler,0,0,1,ma_f);

      o  = iOpen(Symbol(),TF_1,0);
      h  = iHigh(Symbol(),TF_1,0);
      l  = iLow(Symbol(),TF_1,0);
      c  = iClose(Symbol(),TF_1,0);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

      if(PositionsTotal() == 0)
         find_position();
      if(PositionsTotal() > 0)
         manage_position();
     }
  }

Nossas regras para entrar em posições são diretas e já foram explicadas em detalhes acima. Entramos em posições longas se nossa média móvel rápida estiver acima da média móvel lenta, e se o oposto for verdadeiro, assumiremos posições curtas.

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_s[0] > ma_f[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_s[0] < ma_f[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

Por fim, nosso stop loss será ajustado dinamicamente usando o indicador Average True Range. Adicionaremos um múltiplo fixo da leitura do ATR acima e abaixo do nosso preço de entrada para marcar nossos níveis de stop loss e take profit. Além disso, também adicionaremos a leitura média do ATR dos últimos 90 dias (1 ciclo de negócios); nossa intenção ao fazer isso é contabilizar os níveis recentes de volatilidade no mercado. Por fim, usaremos um operador ternário para ajustar os níveis de take profit e stop loss. Nossa regra é que os stops só devem ser atualizados se a nova posição for mais lucrativa do que a antiga. Operadores ternários nos permitem expressar essa lógica de forma compacta. Além disso, operadores ternários também nos dão flexibilidade para ajustar facilmente take profit e stop loss independentemente um do outro.

//+------------------------------------------------------------------+
//| Manage our positions                                             |
//+------------------------------------------------------------------+
void manage_position(void)
  {
//--- Select the position
   if(PositionSelect(Symbol()))
     {
      //--- Get ready to update the SL/TP
      double initial_sl  = PositionGetDouble(POSITION_SL);
      double initial_tp  = PositionGetDouble(POSITION_TP);
      //--- Calculate the average ATR move
      vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90);
      double buy_sl      = (ask - ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double sell_sl     = (bid + ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double buy_tp      = (ask + ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double sell_tp     = (bid - ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double new_sl      = ((trade == 1) && (initial_sl <  buy_sl)) ? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl);
      double new_tp      = ((trade == 1) && (initial_tp <  buy_tp)) ? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp);

      if(initial_sl == 0 && initial_tp == 0)
        {
         if(trade == 1)
           {
            original_sl = buy_sl;
            Trade.PositionModify(Symbol(),buy_sl,buy_tp);
           }

         if(trade == -1)
           {
            original_sl = sell_sl;
            Trade.PositionModify(Symbol(),sell_sl,sell_tp);
           }

        }
      //--- Update the position
      else
         if((initial_sl * initial_tp) != 0)
           {
            Trade.PositionModify(Symbol(),new_sl,new_tp);
           }
     }
  }
//+------------------------------------------------------------------+

Quando colocamos tudo junto, nosso código atual se apresenta assim até o momento.

//+------------------------------------------------------------------+
//|                                               Channel And MA.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| This version off the application is mean reverting               |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define TF_1           PERIOD_D1 //--- Our main time frame
#define TF_2           PERIOD_H1 //--- Our lower time frame
#define ATR_PERIOD     14        //--- The period for our ATR
#define ATR_MULTIPLE   3         //--- How wide should our stops be?
#define VOL            0.01         //--- Trading volume

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    trade;
int    ma_f_handler,ma_s_handler,atr_handler;
double ma_f[],ma_s[],atr[];
double bid,ask;
double o,h,l,c;
double original_sl;

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release our indicators
   release();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update system variables
   update();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);

   ma_f_handler = iMA(Symbol(),TF_1,10,0,MODE_EMA,PRICE_CLOSE);
   ma_s_handler = iMA(Symbol(),TF_1,60,0,MODE_EMA,PRICE_CLOSE);
  }

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_f_handler);
   IndicatorRelease(ma_s_handler);
  }

//+------------------------------------------------------------------+
//| Update system variables                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Update the system
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),TF_2,0);
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(ma_s_handler,0,0,1,ma_s);
      CopyBuffer(ma_f_handler,0,0,1,ma_f);

      o  = iOpen(Symbol(),TF_1,0);
      h  = iHigh(Symbol(),TF_1,0);
      l  = iLow(Symbol(),TF_1,0);
      c  = iClose(Symbol(),TF_1,0);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

      if(PositionsTotal() == 0)
         find_position();
      if(PositionsTotal() > 0)
         manage_position();
     }
  }

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_s[0] > ma_f[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_s[0] < ma_f[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

//+------------------------------------------------------------------+
//| Manage our positions                                             |
//+------------------------------------------------------------------+
void manage_position(void)
  {
//--- Select the position
   if(PositionSelect(Symbol()))
     {
      //--- Get ready to update the SL/TP
      double initial_sl  = PositionGetDouble(POSITION_SL);
      double initial_tp  = PositionGetDouble(POSITION_TP);
      //--- Calculate the average ATR move
      vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90);
      double buy_sl      = (ask - ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double sell_sl     = (bid + ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double buy_tp      = (ask + ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double sell_tp     = (bid - ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double new_sl      = ((trade == 1) && (initial_sl <  buy_sl)) ? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl);
      double new_tp      = ((trade == 1) && (initial_tp <  buy_tp)) ? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp);

      if(initial_sl == 0 && initial_tp == 0)
        {
         if(trade == 1)
           {
            original_sl = buy_sl;
            Trade.PositionModify(Symbol(),buy_sl,buy_tp);
           }

         if(trade == -1)
           {
            original_sl = sell_sl;
            Trade.PositionModify(Symbol(),sell_sl,sell_tp);
           }

        }
      //--- Update the position
      else
         if((initial_sl * initial_tp) != 0)
           {
            Trade.PositionModify(Symbol(),new_sl,new_tp);
           }
     }
  }
//+------------------------------------------------------------------+

Como nossa estratégia atual não emprega IA ou qualquer técnica de ajuste de curva, podemos realizar um backtest simples sem nos preocuparmos com overfitting aos dados disponíveis. Além disso, não temos parâmetros de entrada que precisem ser ajustados. Portanto, configuramos “Forward” para “No”, porque não precisamos empregar as capacidades de forward testing do terminal MetaTrader 5 agora.

Fig 3: Selecionando as datas para nosso backtest

Além disso, é uma boa prática testar suas aplicações de negociação sob o ambiente mais estressante que você puder simular. Portanto, selecionamos o modo “Random delay” para nosso backtest de hoje, com nossa modelagem configurada para usar ticks reais. Ao usar ticks reais, o tempo necessário para baixar dados históricos pode ser maior do que ao usar outros modos, como “Open prices only”. No entanto, os resultados tendem a ser mais próximos dos resultados reais em ticks ao vivo.

Nosso segundo conjunto de configurações

Fig 4: Nosso segundo conjunto de configurações para nosso backtest de cruzamento do EURUSD

Quando analisamos os resultados obtidos usando a estratégia simples de cruzamento, podemos rapidamente ver possíveis problemas que enfrentaríamos se seguíssemos a estratégia em sua forma original. Observe que desde o início do backtest até julho de 2022, nossa estratégia trabalhou duro apenas para empatar. Este é um período de drawdown que corresponde a quase metade do nosso backtest, ou 2 anos. Isso é indesejável e não é uma característica do tipo de estratégia na qual podemos confiar para negociar nosso dinheiro sem supervisão.

Fig 5: Analisando nossa curva de lucro e prejuízo produzida ao seguir a estratégia de cruzamento de médias móveis

Nossa estratégia em sua forma original é pouco lucrativa e perde cerca de 61% de todas as negociações realizadas. Isso nos dá expectativas negativas em relação à estratégia, e nosso pessimismo é ainda validado pelo fato de que nosso índice de Sharpe está muito próximo de 0. Mas observe o quanto podemos melhorar drasticamente nossa estratégia fazendo alguns ajustes simples na lógica de negociação empregada. 

Fig 6: Um resumo detalhado de nosso desempenho usando a estratégia legada de cruzamento de médias móveis



Melhorando a Estratégia Original

Na Fig. 7 abaixo, forneci uma ilustração visual de nossa nova estratégia sugerida de cruzamento. A linha azul e a verde são médias móveis de período 5 acompanhando o preço de fechamento (azul) e de abertura (verde). Observe que quando a média móvel azul está acima da verde, os níveis de preço estavam subindo. Além disso, preste atenção em quão responsivos são nossos cruzamentos às mudanças nos níveis de preço. Estratégias tradicionais de cruzamento levam um tempo variável para refletir qualquer mudança na tendência do mercado. No entanto, quando seguimos os níveis de preço usando nossa nova estratégia, podemos rapidamente observar mudanças na tendência e até períodos de consolidação, nos quais nossas 2 médias móveis continuam se cruzando, mas sem fazer progresso real.

Fig 7: Visualizando nossa nova estratégia de cruzamento no gráfico diário do EURUSD

Até agora, nosso sistema conseguiu negociar com lucro, mas podemos melhorar. A única mudança que faremos em nosso sistema é alterar as condições sob as quais nossas posições são acionadas:

Change Descrição
Trading
Rules
Nossas regras tradicionais compram quando a média móvel rápida está acima da lenta. Em vez disso, agora compraremos quando nossa média móvel da abertura estiver acima da média móvel do fechamento.

Para realizar a melhoria desejada, precisamos fazer algumas alterações na forma original de nossa estratégia de negociação atual:

Change Descrição
Variáveis ​​adicionais do sistema Precisaremos de uma nova variável de sistema responsável por definir o período da média móvel da abertura e do fechamento.
Novas variáveis ​​globais Novas variáveis globais serão criadas para acompanhar as novas informações às quais estamos prestando atenção.
Modificar funções personalizadas Algumas das funções personalizadas que construímos até agora precisam ser estendidas à luz do novo design de sistema que estamos seguindo.

Em grande parte, todas as outras partes do nosso sistema serão preservadas. Queremos isolar as melhorias resultantes da mudança em nossa perspectiva sobre cruzamentos de médias móveis. Portanto, para alcançar nosso objetivo, começaremos criando uma nova constante de sistema para fixar o período de nossas médias móveis.

//--- Omitted code that has not changed
#define MA_PERIOD      2         //--- The period for our moving average following the close

Em seguida, precisamos definir novas variáveis globais para as novas informações que estamos acompanhando. Agora estamos criando manipuladores de médias móveis para cada um dos 4 preços cotados (Abertura, Máxima, Mínima e Fechamento).

//--- Omitted code that have not changed
int    ma_h_handler,ma_l_handler,ma_c_handler,ma_o_handler;
double ma_h[],ma_l[],ma_c[],ma_o[];

Quando nossa aplicação de negociação estiver sendo iniciada, precisaremos carregar alguns indicadores adicionais além daqueles com os quais já estamos familiarizados.

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);
   ma_h_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   ma_c_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_OPEN);
  }

O mesmo se aplica à nossa função personalizada responsável por remover nosso expert advisor; ela será estendida para acomodar os novos indicadores que introduzimos no sistema.

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_h_handler);
   IndicatorRelease(ma_l_handler);
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }

Por fim, as condições que usaremos para abrir nossas operações devem ser atualizadas para que estejam alinhadas com nossa nova lógica de negociação.

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_o[0] > ma_c[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_o[0] < ma_c[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

Agora vejamos qual diferença isso faz no nosso resultado final. Primeiro configuraremos nossa nova aplicação de negociação para operar no mesmo período que usamos em nosso primeiro teste.

Fig 8: Configurando nosso novo e aprimorado algoritmo de negociação para operar no mesmo período utilizado em nosso primeiro teste

Além disso, queremos realizar ambos os backtests sob condições idênticas para garantir que nosso teste seja imparcial. Caso contrário, se uma estratégia receber vantagem injusta, a integridade de nossos testes até agora seria comprometida.

Fig 9: Queremos garantir que as condições de teste sejam idênticas em ambos os testes para permitir uma comparação justa

Já podemos ver uma grande melhoria em relação aos resultados iniciais que obtivemos. Passamos os primeiros 2 anos de nosso backtest inicial tentando empatar. No entanto, com nossa nova estratégia de negociação, rompemos essa limitação e fomos lucrativos ao longo de todos os 4 anos. 

Fig 10: Nossa nova estratégia de negociação rompeu o período improdutivo de operações que seria desafiador resolver usando estratégias tradicionais de cruzamento

Em nosso backtest inicial, nosso sistema fez 41 operações no total e em nosso último backtest fizemos 42 operações no total. Portanto, nosso novo sistema está assumindo mais risco do que a forma antiga de negociar. Porque, se permitirmos que mais tempo passe, a diferença entre eles pode continuar crescendo. No entanto, embora nosso novo sistema pareça realizar mais operações do que o antigo, nosso lucro total no sistema antigo foi de US$ 59,57, e agora nosso lucro total mais que dobrou para US$ 125,36. Lembre-se de que, por enquanto, limitamos nosso sistema de negociação a abrir 1 operação no lote mínimo. Além disso, em nosso primeiro sistema nossa perda bruta foi de US$ 410,02 e com nossa nova estratégia nossa perda bruta caiu para US$ 330,74. 

Ao projetarmos sistemas, precisamos pensar em termos de trade-offs. Nosso novo sistema está apresentando melhor desempenho. No entanto, devemos observar que o tamanho de nosso lucro médio caiu de US$ 29,35 para US$ 22,81. Isso ocorre porque, ocasionalmente, nosso novo sistema perderá operações das quais nosso sistema antigo obteve lucro. No entanto, esse arrependimento ocasional pode ser justificável considerando os ganhos de desempenho que obtemos. 

Nosso índice de Sharpe aumentou de 0.18 em nosso primeiro teste, para 0.5 em nosso teste atual. Este é um bom sinal que indica que estamos utilizando melhor nosso capital. Além disso, nossa proporção de operações perdedoras caiu de 60.98% no primeiro teste para um novo mínimo de 52.38%.

Fig 11: Uma análise detalhada do desempenho de nossa nova estratégia de negociação



Conclusão

A maioria dos membros de nossa comunidade tende a ser composta por desenvolvedores independentes, trabalhando em seus projetos sozinhos. Para nossos colegas nessas posições, acredito que algoritmos simples, como o que sugerimos aqui, podem ser soluções mais práticas. É mais fácil de manter, desenvolver e expandir. Gerenciar uma base de código grande e complexa como desenvolvedor solo não é uma tarefa fácil, mesmo para programadores experientes. E se você é novo em nossa comunidade de negociação algorítmica, então essa estratégia pode ser especialmente útil para você. Se você gostou de ler este artigo, então junte-se a nós em nossa próxima discussão, onde tentaremos superar os melhores resultados que produzimos hoje. 

Nome do Arquivo Descrição
Abrir e fechar MA Cross Este arquivo contém nossa nova versão reimaginada da estratégia de cruzamento de médias móveis.Cruzamento MA tradicional
Cruzamento MA tradicional Este arquivo contém a implementação clássica de cruzamentos de médias móveis.

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

Últimos Comentários | Ir para discussão (3)
linfo2
linfo2 | 24 jan. 2025 em 19:16
Obrigado pelo código e pelas ideias, gostei da forma como você estruturou o código. Você me apresentou o tipo de dados Vector, que eu nunca tinha usado antes.
  vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90); atr_mean.Mean())
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 2 fev. 2025 em 03:33
linfo2 #:
Obrigado pelo código e pelas ideias, gostei da forma como você estruturou o código. Você me apresentou o tipo de dados Vector, que eu nunca tinha usado antes.
Obrigado, Niel, meu objetivo é combinar simplicidade com rigor técnico. É bom saber que isso está valendo a pena.

E, sim, a classe de vetor é um divisor de águas, pois ela realmente oferece mais funcionalidades do que as que usamos no dia a dia.

Estou realmente ansioso para aprender a usar a classe Matrix, pois ela nos permite criar modelos lineares em apenas uma chamada de função.
Celestine Nwakaeze
Celestine Nwakaeze | 13 set. 2025 em 10:39
Muito obrigado por essa maravilhosa forma de estruturação de código. Como iniciante, este artigo levou meu aprendizado longe. Mais graxa em seu cotovelo. Deus o abençoe por mim. Muito obrigado.
Gerenciamento de riscos (Parte 5): Integração do sistema de gerenciamento de riscos ao EA Gerenciamento de riscos (Parte 5): Integração do sistema de gerenciamento de riscos ao EA
Neste artigo, implementaremos o sistema de gerenciamento de risco desenvolvido em publicações anteriores e adicionaremos o indicador Order Blocks apresentado em outros artigos. Além disso, será realizado um backtest para comparar os resultados com a aplicação do sistema de gerenciamento de risco e para avaliar o impacto do risco dinâmico.
Desenvolvimento de um sistema de monitoramento de entradas de swing (EA) Desenvolvimento de um sistema de monitoramento de entradas de swing (EA)
À medida que o ano se aproxima do fim, traders de longo prazo costumam refletir sobre o histórico do mercado para analisar seu comportamento e tendências, visando projetar potenciais movimentos futuros. Neste artigo, exploraremos o desenvolvimento de um Expert Advisor (EA) de monitoramento de entradas de longo prazo usando MQL5. O objetivo é abordar o desafio das oportunidades de negociação de longo prazo perdidas devido ao trading manual e à ausência de sistemas automatizados de monitoramento. Usaremos um dos pares mais negociados como exemplo para estruturar e desenvolver nossa solução de forma eficaz.
Redes neurais em trading: Detecção de anomalias no domínio da frequência (Conclusão) Redes neurais em trading: Detecção de anomalias no domínio da frequência (Conclusão)
Damos continuidade ao trabalho de implementação das abordagens do framework CATCH, que combina a transformada de Fourier e o mecanismo de patching em frequência, possibilitando a detecção precisa de anomalias de mercado. Nesta etapa, concluímos a realização da nossa própria versão das abordagens propostas e conduziremos testes com os novos modelos utilizando dados históricos reais.
Automatização de estratégias de trading com MQL5 (Parte 13): Criação de um algoritmo de negociação para o padrão "Cabeça e Ombros" Automatização de estratégias de trading com MQL5 (Parte 13): Criação de um algoritmo de negociação para o padrão "Cabeça e Ombros"
Neste artigo, automatizaremos o padrão "Cabeça e Ombros" em MQL5. Analisaremos sua arquitetura, implementaremos um EA para sua detecção e negociação, e testaremos os resultados no histórico. Esse processo revela um algoritmo de negociação prático, que pode ser aprimorado.