Criação de EAs auto-otimizáveis em MQL5 (Parte 8): Análise de múltiplas estratégias
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
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.
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
Criação de um painel administrativo de negociação em MQL5 (Parte XII): Integração de uma calculadora Forex
Está chegando o novo MetaTrader 5 e MQL5
Dominando os Registros de Log (Parte 7): Como Exibir Logs Diretamente no Gráfico
- 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