English Русский 中文 Español Deutsch 日本語
preview
Criação de EAs auto-otimizáveis em MQL5 (Parte 8): Análise de múltiplas estratégias

Criação de EAs auto-otimizáveis em MQL5 (Parte 8): Análise de múltiplas estratégias

MetaTrader 5 — Exemplos |
26 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Como traders algorítmicos, enfrentamos diversos desafios que já abordamos ao longo desta série de artigos. Por exemplo, observamos que nossos modelos estatísticos têm mais facilidade para prever os valores futuros dos indicadores técnicos do que os níveis futuros de preços.

Também analisamos as vantagens de um sistema de negociação capaz de modelar a relação entre a estratégia utilizada e o mercado em que ela é aplicada.

Nossos modelos apresentaram resultados consistentemente melhores quando substituímos a abordagem clássica de previsão direta de preços por essas tarefas alternativas. Prever preços diretamente é uma tarefa difícil, mas, ao reformular o problema, podemos superar modelos que permanecem limitados à abordagem tradicional, mesmo utilizando as mesmas ferramentas estatísticas.

Hoje, vamos explorar uma nova estratégia em potencial, baseada nas conclusões anteriores. E se desenvolvêssemos um aplicativo capaz de trabalhar com três estratégias de negociação diferentes? Será que esse aplicativo conseguiria aprender a selecionar apenas uma estratégia por vez, alternando periodicamente para a mais lucrativa, em vez de seguir as três simultaneamente? Se o aplicativo conseguir alternar entre as estratégias, conseguirá escolher de forma rentável a melhor das três estratégias que conhece?

Um aplicativo com essa capacidade pode ser mais vantajoso do que um algoritmo de negociação fixo, que segue as três estratégias simultaneamente ou utiliza uma combinação delas.

Para avaliar a eficácia do nosso modelo estatístico, primeiro precisamos estabelecer um desempenho de referência que ele deverá superar.

Vamos combinar três estratégias de negociação independentes: a estratégia de continuação da tendência por cruzamento de médias móveis (Moving Average Crossover Continuation Strategy), a estratégia de momentum do Índice de Força Relativa (Relative Strength Index Momentum Strategy) e a estratégia de rompimento de tendência baseada no indicador Williams %R (Williams Percent Range Trend Breakout Strategy). Vamos examinar cada uma delas em mais detalhes.

Neste artigo, apresentaremos alguns recursos avançados do terminal MetaTrader 5, com destaque para o teste forward. O teste forward é diferente do teste com dados históricos. Explicaremos essas diferenças mais adiante.

O teste forward fornece mais informações do que um simples teste com dados históricos, especialmente quando combinado com um otimizador que gera novos conjuntos de parâmetros para testar a estratégia. Isso nos permite identificar com maior confiabilidade configurações lucrativas para nossa estratégia de negociação. No MetaTrader 5, essa funcionalidade avançada é implementada por meio dos otimizadores genéticos rápido e lento (Fast and Slow Genetic Optimizers).

Ao combinar esses poderosos recursos de teste de estratégias com os sólidos princípios de projeto orientado a objetos que estamos explorando nesta série, vamos desenvolver uma alternativa potencialmente competitiva aos nossos modelos estatísticos e testar seu funcionamento.


Introdução ao MQL5

Nesta etapa, vamos abordar o desafio de combinar diferentes estratégias de maneira eficiente, buscando a melhor forma de integrá-las em uma única estratégia. Soluções programadas em código são raras, especialmente quando várias estratégias são utilizadas simultaneamente.

Combinar diferentes estratégias é uma tarefa interessante que exige criatividade. No entanto, também precisamos minimizar o risco de efeitos colaterais inesperados.

Os traders costumam utilizar diferentes estratégias ao mesmo tempo. Por exemplo, uma estratégia pode ser responsável por abrir posições, enquanto outra determina o momento de fechá-las. Cada estratégia se concentra em uma parte específica do problema. Queremos reproduzir essa abordagem humana e, ao mesmo tempo, demonstrar como utilizar o testador de estratégias do MetaTrader 5 para encontrar as configurações ideais.

Para combinar as estratégias de forma confiável, vamos encapsular cada estratégia em sua própria classe. Cada classe deverá ser testada para confirmar seu correto funcionamento. Todas as nossas estratégias terão como base uma classe comum, chamada Parent. Essa classe reunirá métodos compartilhados, como a atualização de parâmetros e a verificação de sinais de compra ou venda.

Cada classe derivada redefinirá o que considera um sinal de compra ou de venda. Para isso, declararemos os métodos comuns como virtuais, permitindo que cada estratégia os sobrescreva com segurança.

Já temos os conhecimentos necessários para começar a desenvolver nossa primeira classe: a classe base das estratégias.

Em MQL5, toda declaração de classe começa com a palavra-chave class, seguida pelo nome da classe. Normalmente, o arquivo recebe o mesmo nome da classe.

Alguns métodos serão declarados virtuais para indicar ao compilador que podem ser sobrescritos pelas classes derivadas. Dessa forma, cada estratégia poderá implementar seu próprio comportamento para esses métodos, sem comprometer a estrutura compartilhada.

//+------------------------------------------------------------------+
//|                                                     Strategy.mqh |
//|                                               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"

class Strategy
  {
private:
                     int  buffer_size;
                     bool buy;
                     bool sell;
public:
                     //--- Class constructors and destructors
                     Strategy(void);
                    ~Strategy(void);
                    
                    //--- Check if we have any valid trading signals from our strategy
                    virtual bool BuySignal(void);
                    virtual bool SellSignal(void);
                    
                    //--- Update the technical indicators in our strategy
                    virtual bool Update(void);
                    
                    //--- Get the size of the technical indicator buffers
                            int  GetIndicatorBufferSize(void);
  };

Nosso construtor padrão define os valores iniciais comuns a todas as instâncias de uma estratégia.

//+------------------------------------------------------------------+
//| The only way to create an object of the class                    |
//+------------------------------------------------------------------+
Strategy::Strategy(void)
  {
      //--- Upon initialization, both flags should be false 
      buy = false;
      sell = false;
      buffer_size = 10;
  }

Também precisaremos de alguns métodos auxiliares, conhecidos como getters e setters. Para começar, vamos definir um método que retorna o tamanho atual do buffer configurado para os indicadores utilizados em nossa estratégia.

//+------------------------------------------------------------------+
//| The size of our indicator buffer                                 |
//+------------------------------------------------------------------+
int Strategy::GetIndicatorBufferSize(void)
   {
      int res = buffer_size;
      return(res);
   }

Além disso, cada estratégia deverá ter dois métodos: um para verificar a existência de sinais de compra e outro para verificar a existência de sinais de venda. Cada classe derivada deverá implementar as regras que determinam a geração desses sinais. Caso contrário, o método da classe base sempre retornará false e orientará o usuário a sobrescrever o método na classe derivada.

//+------------------------------------------------------------------+
//| Check if our strategy is giving us any buy signals               |
//+------------------------------------------------------------------+
bool Strategy::BuySignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }
   
//+------------------------------------------------------------------+
//| Check if our strategy is giving us any sell signals              |
//+------------------------------------------------------------------+
bool Strategy::SellSignal(void)
   {
      //--- The user is intended to overwrite the function in the child class
      //--- Otherwise, failing to do so will always return false as a safety feature
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

Atualizar o objeto da estratégia significa atualizar todos os parâmetros da estratégia utilizados nas operações de negociação.

//+------------------------------------------------------------------+
//| Update our strategy parameters                                   |
//+------------------------------------------------------------------+
bool Strategy::Update(void)
   {
      //--- The user is intended to overwrite the function in the child class
      Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class");
      return(false);
   }

Por enquanto, o destrutor da classe está vazio.

//+------------------------------------------------------------------+
//| The class destructor is currently empty                          |
//+------------------------------------------------------------------+
Strategy::~Strategy(void)
  {
  }
//+------------------------------------------------------------------+

Vamos começar definindo o corpo da nossa classe, chamada OpenCloseMACrossover. Essa estratégia utiliza dois indicadores de média móvel com o mesmo período, calculados respectivamente sobre os preços de abertura e de fechamento. Observe que os métodos declarados virtuais na classe base também foram declarados virtuais na classe derivada.

Os sinais de venda são gerados quando a média móvel dos preços de abertura está acima da média móvel dos preços de fechamento. Caso contrário, é gerado um sinal de compra. A lógica é simples: quando a média dos preços de fechamento supera a média dos preços de abertura, podemos interpretar o movimento como altista, indicando que uma tendência forte nessa direção pode continuar.

//+------------------------------------------------------------------+
//|                                         OpenCloseMACrossover.mqh |
//|                                               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"

#include<VolatilityDoctor\Strategies\Parent\Strategy.mqh>
#include<VolatilityDoctor\Indicators\MA.mqh>

class OpenCloseMACrossover : public Strategy
  {
private:
                     //--- Create 2 moving average instances
                     MA *ma_array[2];

public:
                     //---- Class constructors and destructor
                     OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode);
                     ~OpenCloseMACrossover();
                     
                     //--- Class methods
                     virtual bool Update(void);
                     virtual bool BuySignal(void);
                     virtual bool SellSignal(void);
                    
  };

Como já definimos as regras da estratégia de negociação, implementar os métodos que verificam essas condições será uma tarefa simples.

//+------------------------------------------------------------------+
//| Check For a Buy Signal                                           |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::BuySignal(void)
   {
      //--- Our buy signal is generated if the close moving average is above the open.
      return(ma_array[0].GetCurrentReading()>ma_array[1].GetCurrentReading());
   }

//+------------------------------------------------------------------+
//| Check For a Sell Signal                                          |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::SellSignal(void)
   {
      //--- Our sell signal is generated if the open moving average is above the close.
      return(ma_array[0].GetCurrentReading()<ma_array[1].GetCurrentReading());
   }

Nosso método de atualização chama as funções de atualização dos indicadores que implementamos na classe SingleBufferIndicator. Essas funções exigem que o tamanho do buffer seja informado como parâmetro. Para obter esse valor, utilizamos o método criado na classe base, acessando-o com o operador de resolução de escopo "::", na chamada Strategy::GetIndicatorBufferSize(). Ao final da atualização, verificamos se os valores atualizados são iguais a zero e, em seguida, devolvemos o controle ao código que chamou o método.

//+------------------------------------------------------------------+
//| Our update method                                                |  
//+------------------------------------------------------------------+
bool OpenCloseMACrossover::Update(void)
   {
      //--- Copy indicator readings 
      //--- We will always get the buffer size from the parent class
      ma_array[0].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      ma_array[1].SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      
      //--- Make sure neither of the indicator values equal 0
      if((ma_array[0].GetCurrentReading() * ma_array[1].GetCurrentReading()) != 0) return(true);
      
      //--- If one/both indicator values equal 0, something went wrong.
      return(false);
   }

O construtor da classe cria dinamicamente duas instâncias do indicador de média móvel e armazena esses ponteiros em um array de ponteiros para objetos do nosso tipo personalizado de indicador de média móvel.

//+------------------------------------------------------------------+
//| Our class constructor                                            |
//+------------------------------------------------------------------+
OpenCloseMACrossover::OpenCloseMACrossover(string symbol,ENUM_TIMEFRAMES time_frame,int period,int shift,ENUM_MA_METHOD ma_mode)
  {
      //--- Create two instances of our moving average indiator objects
      ma_array[0] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_CLOSE);
      ma_array[1] = new MA(symbol,time_frame,period,shift,ma_mode,PRICE_OPEN);
      
      //--- Give feedback
      Print("Strategy class loaded correctly");
  }

O destrutor da classe libera os objetos criados dinamicamente, permitindo gerenciar o consumo de memória.

//+------------------------------------------------------------------+
//| Our class destructor                                             |
//+------------------------------------------------------------------+
OpenCloseMACrossover::~OpenCloseMACrossover()
  {
   //--- Delete the custom objects we made
   delete ma_array[0];
   delete ma_array[1];
   
   //--- Give feedback
   Print("Strategy deinitialized correctly. Goodbye");
  }
//+------------------------------------------------------------------+

Agora vamos testar nossa primeira classe de estratégia. Lembre-se de que nosso aplicativo final terá três etapas e três estratégias diferentes. Portanto, seguindo boas práticas de desenvolvimento, devemos testar cada classe isoladamente, comparando-a com uma implementação equivalente cujas regras estejam definidas diretamente no código. O teste será considerado bem-sucedido se as duas implementações produzirem os mesmos resultados no teste com dados históricos do mesmo período. Essa verificação pode nos poupar horas de depuração no futuro.

Primeiro, vamos definir as constantes do sistema que manteremos iguais nos dois testes. Se as duas implementações forem equivalentes, deverão ser iniciadas no mesmo momento, abrir o mesmo número de posições e apresentar a mesma proporção entre operações de compra e venda. A repetição dessas constantes nos dois testes é intencional, pois elas controlam os parâmetros da estratégia.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.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"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

Em seguida, vamos carregar nossas dependências.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Time\Time.mqh>

Depois, precisaremos de algumas variáveis globais para monitorar os indicadores técnicos e medir por quanto tempo nossa posição permanece aberta.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade    Trade;
TradeInfo *TradeInformation;
Time      *TradeTime;

//--- System Types
double ma_open[],ma_close[];
int    ma_open_handler,ma_close_handler;
intn    position_timer; 

Após inicializar nosso sistema, vamos carregar os indicadores técnicos e verificar se estão corretos.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Our technical indicators
   ma_close_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_CLOSE);
   ma_open_handler = iMA(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_OPEN);

//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);


//--- Safety checks
   if(ma_close_handler == INVALID_HANDLE)
      return(false);
   if(ma_open_handler == INVALID_HANDLE)
      return(false);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

Quando o sistema deixar de ser utilizado, vamos liberar os indicadores técnicos carregados e excluir os objetos dinâmicos criados durante a configuração do sistema.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the indicators and dynamic objects
   IndicatorRelease(ma_close_handler);
   IndicatorRelease(ma_open_handler);

   delete TradeTime;
   delete TradeInformation;

  }
//--- End of Deinit Scope

Sempre que o terminal receber novas cotações, primeiro verificamos se uma nova vela foi formada. Em caso afirmativo, atualizamos os indicadores técnicos. Em seguida, verificamos se há posições abertas. Se não houver, procuramos um sinal de negociação. Caso exista uma posição aberta, aguardamos o término do prazo definido para manter a posição aberta antes de fechá-la.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {

      //--- Update our technical indicators
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

Nossa função de atualização armazena os valores atuais dos indicadores nos arrays que criamos para seus respectivos buffers.

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Call the CopyBuffer method to get updated indicator values
   CopyBuffer(ma_close_handler,0,0,1,ma_close);
   CopyBuffer(ma_open_handler,0,0,1,ma_open);
  }
//--- End of Update Scope

A função de verificação de sinais avalia as condições de negociação que definimos anteriormente: para considerar o movimento dos preços altista, a média móvel dos preços de fechamento deve estar acima da média móvel dos preços de abertura.

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(ma_close[0] > ma_open[0])
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short
   else
      if(ma_close[0] < ma_open[0])
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }

  }
//--- End of CheckSignal Scope

Por fim, ao final do programa, remova as definições das variáveis de sistema que você criou.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

Agora, vamos estabelecer um desempenho de referência para nossa classe.

Fig. 1. Parâmetros iniciais de teste para o desempenho de referência

O próximo passo é definir as datas de início e término do nosso teste com dados históricos. Se quisermos acompanhar a sequência dos eventos, vamos selecionar um intervalo de um dia.

Fig. 2. Datas de teste que utilizaremos como referência

A curva de patrimônio líquido (equity) gerada pela versão da estratégia implementada diretamente no código deverá ser reproduzida pela classe que desenvolvemos, desde que ambas utilizem os mesmos parâmetros. Vamos verificar se nossa classe foi implementada corretamente.

Fig. 3. Visualização da curva de patrimônio líquido gerada pela estratégia de referência

Os resultados estatísticos dos dois testes não serão exatamente iguais. Vale lembrar que nossos testes com dados históricos simulam atrasos aleatórios e ruído sistemático. Portanto, esperamos obter resultados muito próximos, mas não necessariamente idênticos.

Fig. 4. Resultados detalhados do teste com a versão da estratégia de cruzamento de médias móveis implementada diretamente no código

Nesta versão do teste da classe, manteremos as constantes do sistema exatamente como foram definidas, para garantir a consistência da comparação.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.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"

//+------------------------------------------------------------------+
//| Define system constants                                          |
//+------------------------------------------------------------------+
#define MA_TYPE        MODE_EMA
#define MA_PERIOD      10
#define MA_TIME_FRAME  PERIOD_D1
#define MA_SHIFT       0
#define HOLDING_PERIOD 5

Em seguida, vamos carregar nossas dependências.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>
#include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>

Também precisaremos declarar uma nova variável global para armazenar a instância da nossa estratégia.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+

//--- Custom Types
CTrade               Trade;
TradeInfo            *TradeInformation;
Time                 *TradeTime;
OpenCloseMACrossover *MACross;

//--- System Types
int    position_timer;

No geral, o aplicativo permanece praticamente inalterado. Nosso objetivo é isolar o efeito dos sinais gerados pela classe. Por isso, boa parte do código já deverá ser familiar ao leitor que acompanhou nosso primeiro teste.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),MA_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),MA_TIME_FRAME);
   MACross          = new OpenCloseMACrossover(Symbol(),MA_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete MACross;
  }
//--- End of Deinit Scope

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {
      //--- Update strategy
      Update();

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Reset the position timer
         position_timer = 0;

         //--- Check for a trading signal
         CheckSignal();
        }

      //--- Otherwise
      else
        {
         //--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());

         //--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
//--- End of OnTick Scope

A única mudança nos métodos de atualização dos parâmetros da estratégia e detecção de sinais de negociação é que agora eles utilizam a classe que criamos, em vez de implementar essa lógica diretamente no código.

//+------------------------------------------------------------------+
//| Update our technical indicators                                  |
//+------------------------------------------------------------------+
void Update(void)
  {
//--- Update the strategy
   MACross.Update();
  }
//--- End of Update Scope

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when the close moving average is above the open
   if(MACross.BuySignal())
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short
   else
      if(MACross.SellSignal())
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }
  }
//--- End of CheckSignal Scope

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_SHIFT
#undef MA_TIME_FRAME
#undef MA_TYPE
#undef HOLDING_PERIOD
//+------------------------------------------------------------------+

Agora estamos prontos para testar nossa classe de estratégia de cruzamento de médias móveis em MQL5 (MQL5 MA Crossover). Primeiro, vamos carregar o EA que utiliza essa classe e configurar o mesmo período de teste com dados históricos utilizado no primeiro teste.

Fig. 5. Datas iniciais que utilizaremos para avaliar nossa estratégia. Essas datas serão afetadas pelo teste forward que realizaremos posteriormente

Certifique-se de que as configurações selecionadas sejam iguais às utilizadas no primeiro teste.

Fig. 6. Selecionaremos Real ticks (ticks reais) e Random delay (atraso aleatório) para realizar testes com dados históricos confiáveis

Os resultados detalhados dos testes da classe e da estratégia implementada diretamente no código são praticamente idênticos: ambas realizaram 127 operações, com a mesma proporção entre compras e vendas, e apresentaram índices de Sharpe muito próximos.

Fig. 7. As estatísticas detalhadas obtidas com nossa classe Strategy correspondem aos resultados da estratégia de negociação implementada diretamente no código

A curva de patrimônio líquido (equity) obtida durante os testes é muito semelhante à apresentada na Figura 3. Portanto, as duas implementações produziram resultados equivalentes, conforme esperado.

Fig. 8. A curva de patrimônio líquido obtida com nossa classe corresponde à da estratégia implementada diretamente no código

Agora que confirmamos que nossa classe foi implementada corretamente, podemos começar a buscar os parâmetros ideais da estratégia.

Primeiro, precisaremos substituir a maioria das constantes do sistema por parâmetros configuráveis pelo usuário. Isso permitirá que nosso otimizador genético ajuste automaticamente os parâmetros da estratégia. Dessa forma, manteremos uma única definição do sistema.

//+------------------------------------------------------------------+
//|                                                   MSA Test 1.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_SHIFT 0

//+------------------------------------------------------------------+
//| User Inputs                                                      |
//+------------------------------------------------------------------+
input   group          "Strategy Parameters"
input   int             MA_PERIOD      =        10;//Moving Average Period
input   int             HOLDING_PERIOD =         5;//Position Holding Period
input   ENUM_TIMEFRAMES MA_TIME_FRAME  = PERIOD_D1;//Moving Average Time Frame
input   ENUM_MA_METHOD  MA_TYPE        =  MODE_EMA;//Moving Average Type

O restante do sistema permanece praticamente inalterado. Portanto, vamos aproveitar para entender as diferenças entre o teste com dados históricos e o teste forward no MetaTrader 5.

Em resumo, o teste com dados históricos (backtest) avalia o desempenho de uma estratégia de negociação com base no histórico do mercado. No entanto, podemos ir além dessa análise convencional. Ao dividir os dados em duas partes, podemos utilizar a primeira para otimizar os parâmetros da estratégia e, em seguida, avaliar os parâmetros encontrados com os dados restantes.

Essa é a principal vantagem do teste forward. Não queremos apenas encontrar configurações que produzam bons resultados, mas também avaliar sua estabilidade.

Portanto, configure o parâmetro Forward como 1/2, para utilizar 50% dos dados na otimização e a outra metade nos testes.

Fig. 9. Configure o campo Forward como 1/2 para utilizar metade dos dados na otimização e a outra metade nos testes

O terminal MetaTrader 5 oferece diferentes métodos de otimização. Vamos selecionar Fast genetic-based algorithm (algoritmo genético rápido), pois exige relativamente poucos recursos computacionais e, ao mesmo tempo, oferece resultados confiáveis.

Fig. 10. Precisamos de um otimizador para gerar novos parâmetros da estratégia após cada teste

Ao iniciar a otimização, você verá um gráfico de dispersão dos resultados, semelhante ao apresentado na Figura 11 abaixo. Esse gráfico permite visualizar o desempenho obtido em cada iteração da otimização. O otimizador utiliza apenas os resultados da primeira metade dos dados para identificar combinações de parâmetros mais promissoras, que poderão ser testadas na próxima iteração.

Fig. 11. Configuramos o otimizador genético rápido para melhorar o índice de Sharpe da nossa estratégia. Cada ponto representa os resultados de uma iteração do teste

Podemos clicar com o botão direito do mouse sobre esse gráfico de dispersão para acessar diferentes opções de visualização. É possível alterar os eixos exibidos ou até mesmo visualizar os resultados em um gráfico tridimensional.

Fig. 12. O menu de contexto permite alterar os eixos do gráfico e explorar diferentes relações entre os dados

Explorar diferentes formas de visualização dos dados pode nos ajudar a identificar padrões na relação entre nossa estratégia e o mercado escolhido. Por exemplo, os resultados parecem indicar que a rentabilidade da estratégia aumenta à medida que utilizamos timeframes maiores.

Fig. 13. Podemos observar que timeframes maiores ajudam a obter melhores índices de Sharpe, conforme mostrado no histograma tridimensional

O testador de estratégias também apresenta resultados detalhados para cada combinação de parâmetros de entrada avaliada. Observe que, na parte inferior da tabela, há um painel que permite alternar entre as seções Back test e Forward.

Fig. 14. O MetaTrader 5 também apresenta uma análise detalhada de todos os parâmetros da estratégia testados

Ao clicar com o botão direito do mouse nessa tabela, será exibido um menu de contexto com diversas opções úteis, como escolher quais colunas devem aparecer ou até mesmo exportar os resultados do teste forward para um arquivo.

Fig. 15. O menu de contexto da tabela de resultados permite exportar os dados para o formato XML para análises posteriores

Podemos examinar separadamente os resultados do teste com dados históricos e do teste forward. Os resultados do teste com dados históricos são apresentados na Figura 16, abaixo. Vale lembrar que nosso principal interesse está nos resultados do teste forward e nas diferenças em relação aos resultados obtidos com os dados históricos.

Fig. 16. Resultados estatísticos detalhados do teste com dados históricos obtidos com nosso otimizador genético

Os resultados do teste forward são muito semelhantes aos do teste com dados históricos. Isso é um bom indicativo da estabilidade potencial da estratégia. Por outro lado, se os melhores resultados do teste forward entrarem em contradição com os resultados do teste com dados históricos, isso pode indicar que a estratégia apresenta um comportamento instável.

Fig. 17. Estes são os resultados que mais nos interessam: os resultados do teste forward

Também temos acesso à curva de patrimônio líquido (equity) obtida nos dois testes. A longa linha vertical no centro do gráfico marca a divisão entre o teste com dados históricos e o teste forward.

Fig. 18. Observe que ambas as curvas de patrimônio líquido, obtidas durante a otimização e o teste forward, apresentam tendências positivas

Por fim, estes são os parâmetros ideais da estratégia que nosso otimizador genético nos ajudou a encontrar.

Fig. 19. As melhores configurações obtidas com nosso algoritmo genético de busca


Conclusão

Neste artigo, demonstramos a utilidade do testador de estratégias do MetaTrader 5. Mesmo os leitores que não pretendem desenvolver suas próprias classes encontrarão exemplos práticos de como incorporar os princípios da programação orientada a objetos (POO) disponíveis no MQL5 ao processo de desenvolvimento. O artigo também incentiva a adoção de técnicas avançadas de desenvolvimento para criar e testar classes confiáveis.

Além disso, ao aplicar os princípios de projeto orientado a objetos que exploramos, os leitores poderão desenvolver suas próprias estratégias de maneira confiável e testá-las facilmente para encontrar os parâmetros de entrada ideais em diferentes símbolos e timeframes. No próximo artigo, vamos combinar estratégias baseadas no RSI e em médias móveis.

Nome do arquivo Descrição do arquivo
MSA Test 1 Baseline.mq5 Implementação da estratégia de cruzamento diretamente no código, utilizada como referência para os resultados que nossa classe deveria reproduzir.
MSA Test 1 Class.mq5 Arquivo que demonstra como testar nossa classe de estratégia baseada em médias móveis.
MSA Test 1.mq5 EA utilizado para encontrar os parâmetros ideais da estratégia com o testador de estratégias do MetaTrader 5.
OpenCloseMACrossover.mqh Classe que implementa nossa estratégia baseada em médias móveis.
Strategy.mqh Classe base de todas as nossas estratégias.
MSA Test 1.ex5 Versão compilada do nosso EA.

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

Arquivos anexados |
MSA_Test_1.mq5 (5.28 KB)
Strategy.mqh (3.98 KB)
MSA_Test_1.ex5 (44.16 KB)
Recursos do Assistente MQL5 que você precisa conhecer (Parte 70): Uso dos padrões SAR e RVI com uma rede neural cujos kernels crescem exponencialmente Recursos do Assistente MQL5 que você precisa conhecer (Parte 70): Uso dos padrões SAR e RVI com uma rede neural cujos kernels crescem exponencialmente
No artigo anterior, apresentamos uma combinação dos indicadores SAR e RVI. Neste artigo, veremos como aprimorá-la com técnicas de machine learning. Juntos, o SAR e o RVI combinam informações de tendência e momentum. Nossa abordagem utiliza uma rede neural convolucional (convolutional neural network) com um kernel exponencial (Exponential kernel) para definir o tamanho dos kernels e o número de canais da rede, utilizada para aprimorar as previsões baseadas nesses dois indicadores. Como de costume, essa abordagem é implementada em um arquivo personalizado da classe de sinais (signal class), que interage com o Assistente MQL5 para gerar um EA.
Criação de um painel administrativo de negociação em MQL5 (Parte XII): Integração de uma calculadora Forex Criação de um painel administrativo de negociação em MQL5 (Parte XII): Integração de uma calculadora Forex
O cálculo preciso das principais métricas de negociação é uma parte essencial da rotina de qualquer trader. Neste artigo, veremos como integrar uma ferramenta poderosa, a calculadora Forex, ao painel de gerenciamento de operações, ampliando ainda mais as funcionalidades do nosso sistema administrativo para traders, composto por vários painéis. Calcular corretamente o risco, o tamanho da posição e o lucro potencial é fundamental para a execução de operações. A nova funcionalidade busca agilizar esses cálculos e facilitar sua realização diretamente no painel. Ao longo do artigo, exploraremos a aplicação prática do MQL5 no desenvolvimento de painéis de negociação avançados.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Dominando os Registros de Log (Parte 7): Como Exibir Logs Diretamente no Gráfico Dominando os Registros de Log (Parte 7): Como Exibir Logs Diretamente no Gráfico
Aprenda a exibir logs diretamente no gráfico do MetaTrader de forma organizada, com molduras, títulos e atualização com deslocamento automático. Neste artigo, mostraremos como criar um sistema visual de logs utilizando MQL5, ideal para acompanhar em tempo real o que seu robô está fazendo.