English Русский 中文 Español Deutsch 日本語
preview
Criação de EAs que se auto-otimizam em MQL5 (Parte 9): Cruzamento duplo de médias móveis

Criação de EAs que se auto-otimizam em MQL5 (Parte 9): Cruzamento duplo de médias móveis

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

Ao longo desta série de artigos, exploramos diferentes abordagens para reduzir o atraso dos sinais na estratégia clássica de cruzamento de médias móveis.

Nossas primeiras tentativas envolveram o uso de ferramentas de modelagem estatística para antecipar os cruzamentos de médias móveis. Obtivemos alguns avanços nessa direção e constatamos que, em determinadas condições de mercado, a previsão desses cruzamentos pode ser mais precisa do que a previsão direta dos preços. Posteriormente, identificamos outra maneira de reduzir ainda mais esse atraso. A ideia consiste em utilizar o mesmo período para as duas médias móveis, fazendo com que os cruzamentos ocorram pela aplicação de uma média ao preço de abertura e da outra ao preço de fechamento. Essa abordagem alternativa se mostrou eficaz, permitindo reduzir ainda mais o atraso sem recorrer a modelos complexos, apenas mantendo o mesmo período e utilizando preços diferentes no cálculo dos dois indicadores.

Neste artigo, exploraremos outra abordagem, ainda não analisada nesta série. Como acontece com muitos problemas da vida e da matemática, existem várias maneiras de resolver uma mesma questão, cada uma com suas vantagens e desvantagens. Ao comparar essas alternativas, buscamos entender até que ponto podemos controlar o atraso dos sinais do sistema.

Desta vez, vamos implementar o que chamo de estratégia de cruzamento duplo de médias móveis. Como mostra a Figura 1, a estratégia clássica de cruzamento de médias móveis normalmente utiliza duas médias de períodos diferentes em um único timeframe. Na abordagem discutida anteriormente, ambas as médias utilizavam o mesmo período fixo. Agora, retomaremos parcialmente o modelo clássico, permitindo que os indicadores tenham períodos diferentes.

O problema da abordagem original é que os sinais de entrada muitas vezes só são confirmados quando o movimento do preço já começou. Isso pode resultar em entradas atrasadas ou oportunidades de negociação perdidas.

Figura 1: Visualização da nossa estratégia de cruzamento de médias móveis no timeframe diário.

A abordagem proposta aqui não é totalmente nova. Na verdade, traders discricionários, que operam manualmente, já utilizam uma lógica semelhante há bastante tempo. A ideia principal é começar observando os cruzamentos em um timeframe superior, como o gráfico diário mostrado na Figura 1. No entanto, não entramos no mercado assim que esse sinal aparece. Em vez disso, após identificar um cruzamento no timeframe superior, passamos para um timeframe inferior, como o M30, mostrado na Figura 2, e procuramos sinais de cruzamento na mesma direção do sinal observado no timeframe superior.

É comum ouvir entre os traders a seguinte recomendação: "Opere na direção do timeframe superior." Na maioria das nossas discussões sobre trading algorítmico, aplicamos a estratégia em um único timeframe. Neste caso, porém, utilizamos a mesma estratégia em dois timeframes: primeiro no superior e depois no inferior. O timeframe superior define a direção principal das operações do dia, enquanto o inferior é utilizado para identificar pontos de entrada alinhados a essa direção. Essa é a essência da estratégia de cruzamento duplo. Primeiro determinamos a direção no timeframe superior e depois buscamos oportunidades de entrada no inferior, seguindo essa mesma direção. Com isso, procuramos reduzir o atraso dos sinais gerados no timeframe superior.

Com a lógica básica definida, podemos passar à implementação da estratégia para avaliar sua eficácia na prática. Antes de escrever o código, porém, precisamos analisar cuidadosamente os diversos componentes dessa abordagem. Uma das principais questões é como definir as condições de entrada. Suponha que ocorra um cruzamento de alta no timeframe superior. Nesse caso, temos duas opções para operar no timeframe inferior:

  1. Entrada contra a tendência: aguardar um cruzamento de baixa no timeframe inferior e abrir uma posição na direção oposta à desse cruzamento, na expectativa de que, até o fim do dia, o movimento no timeframe inferior volte a se alinhar à direção de alta do timeframe superior.
  2. Entrada a favor da tendência: aguardar a formação de um cruzamento de alta no timeframe inferior, na mesma direção do sinal observado no timeframe superior.

Essas duas abordagens representam lógicas diferentes para definir as entradas nessa estratégia. As regras de saída acrescentam ainda mais complexidade e possibilidades, cada uma com suas vantagens e limitações. Por exemplo, podemos encerrar a posição quando os sinais do timeframe inferior deixam de estar alinhados com a direção indicada pelo timeframe superior. Outra opção é sair apenas quando o próprio sinal do timeframe superior mudar. Assim, se o dia começar com um sinal de alta no gráfico diário, a posição será mantida até que esse sinal se transforme em um sinal de baixa.

Como podemos observar, existem diversas maneiras de definir as entradas e saídas com esse método. Em vez de escolher a melhor combinação apenas com base no raciocínio lógico, precisamos recorrer a um otimizador genético. Com base nos dados de mercado, ele nos permitirá identificar qual combinação dessas abordagens pode ser mais lucrativa.

Figura 2: Visualização da nossa estratégia de cruzamento de médias móveis no timeframe inferior (M30).

O primeiro passo será definir algumas constantes do sistema que permanecerão inalteradas durante o desenvolvimento da aplicação. Para simplificar, vamos fixar os timeframes: o gráfico diário será o timeframe superior, o M15 será o inferior e o H4 será utilizado para calcular o nível de stop-loss.

Mais adiante, poderemos transformar essas constantes em parâmetros configuráveis, permitindo que o otimizador genético explore diferentes combinações para encontrar os melhores pontos de entrada. Por enquanto, manteremos esses valores fixos para simplificar o desenvolvimento e começar a testar a estratégia mais rapidamente.
//+------------------------------------------------------------------+
//|                                             Double Crossover.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
//--- System time frames
#define TF_1            PERIOD_D1
#define TF_2            PERIOD_M15
#define TF_3            PERIOD_H4
#define LOT_MULTILPLE   1
    

Também precisamos definir algumas enumerações personalizadas para representar os diferentes modos de operação da estratégia. Por exemplo, a aplicação poderá funcionar no modo de seguimento de tendência ou no modo de reversão à média, e uma enumeração específica permitirá ao usuário alternar entre eles. Da mesma forma, criaremos outra enumeração para definir em qual timeframe serão avaliadas as condições de fechamento das posições: no inferior ou no superior.

//+------------------------------------------------------------------+
//| Custom enumerations                                              |
//+------------------------------------------------------------------+
//--- What trading style should we follow when opening our positions, trend following or mean reverting?
enum STRATEGY_MODES  {
     TREND = 0,             //Trend Following Mode
     MEAN_REVERTING = 1     //Mean Reverting Mode
};

//--- Which time frame should we consult, when determining if we should close our position?
enum CLOSING_TIME_FRAME {
     HIGHER_TIME_CLOSE = 0, //Close on higher time frames
     LOWER_TIME_CLOSE = 1   //Close on lower time frames
};

Nossos parâmetros de entrada são relativamente simples. Definimos um valor de período para o timeframe superior e, em seguida, estabelecemos a diferença entre os períodos da primeira e da segunda média móvel. Dessa forma, garantimos que o otimizador genético selecione uma diferença de pelo menos uma unidade, impedindo que as duas médias tenham períodos iguais.

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "Technical Indicators"
input int ma_1_period = 10;   //Higher Time Frame Period
input int ma_1_gap    = 20;   //Higher Time Frame Period Gap

input int ma_2_period = 10;   //Lower Time Frame Period
input int ma_2_gap    = 20;   //Lower Time Frame Period Gap

input group "Strategy Settings"
input STRATEGY_MODES     strategy_mode = 0;  //Strategy Operation Mode
input CLOSING_TIME_FRAME closing_tf    = 0;  //Strategy Closing Timeframe

A aplicação precisará de uma quantidade moderada de variáveis globais, incluindo as relacionadas aos indicadores técnicos e aos objetos utilizados para acompanhar os preços atuais do mercado.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int      ma_c_1_handle,ma_c_2_handle,ma_c_3_handle,ma_c_4_handle;
double   ma_c_1[],ma_c_2[],ma_c_3[],ma_c_4[];
double   volume_min;
double   bid,ask;
int      state;

A única dependência externa será a biblioteca Trade, utilizada para abrir e fechar posições conforme necessário.

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

Durante a inicialização, configuramos os indicadores técnicos de acordo com os parâmetros definidos pelo usuário. Também redefinimos o estado do sistema para -1, indicando que não há posições abertas, e armazenamos o volume mínimo permitido para negociação no mercado.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   volume_min     = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   ma_c_2_handle  = iMA(Symbol(),TF_1,ma_1_period,0,MODE_SMA,PRICE_CLOSE);
   ma_c_1_handle  = iMA(Symbol(),TF_1,(ma_1_period + ma_1_gap),0,MODE_SMA,PRICE_CLOSE);
   ma_c_4_handle  = iMA(Symbol(),TF_2,ma_2_period,0,MODE_SMA,PRICE_CLOSE);
   ma_c_3_handle  = iMA(Symbol(),TF_2,(ma_2_period + ma_2_gap),0,MODE_SMA,PRICE_CLOSE);
   state          = -1;
//---
   return(INIT_SUCCEEDED);
  }

Quando a aplicação deixa de ser utilizada, liberamos os recursos alocados aos indicadores para economizar memória.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_c_1_handle);
   IndicatorRelease(ma_c_2_handle);
   IndicatorRelease(ma_c_3_handle);
   IndicatorRelease(ma_c_4_handle);
  }

Sempre que recebemos novos dados de preço, verificamos se uma nova vela foi formada no timeframe inferior, neste caso, M15. Quando isso acontece, atualizamos o estado interno do sistema.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   datetime current_time = iTime(Symbol(),TF_2,0);
   static datetime time_stamp;

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      update();
     }
  }
//+------------------------------------------------------------------+

Um dos principais componentes da estratégia é a função responsável por identificar os sinais de negociação. Ela recebe o parâmetro padding, que determina a distância entre o preço de entrada e o nível de stop-loss das posições. Esse valor é calculado pela função que a chama, com base nas amplitudes históricas dos preços de mercado. Como medida de segurança, a função começa verificando se não há nenhuma posição aberta, evitando assim o excesso de operações.

Se ocorrer um cruzamento de alta no timeframe superior e a estratégia estiver operando no modo de seguimento de tendência, a função procurará um cruzamento de alta correspondente no timeframe inferior. Já no modo de reversão à média, o sistema buscará o sinal oposto, ou seja, um cruzamento de baixa, e abrirá uma posição na direção contrária a esse sinal. A mesma lógica se aplica às entradas de baixa: quando ocorre um cruzamento de baixa no timeframe superior, procuramos o sinal correspondente no inferior, de acordo com o modo de operação selecionado.

//+------------------------------------------------------------------+
//| Find a trading signal                                            |
//+------------------------------------------------------------------+
void find_setup(double padding)
  {
   if(PositionsTotal() == 0)
     {
      //--- Reset the system state
      state = -1;

      //--- Bullish on the higher time frame
      if(ma_c_1[0] > ma_c_2[0])
        {
         //--- Trend following mode
         if((ma_c_3[0] > ma_c_4[0]) && (strategy_mode == 0))
           {
            Trade.Buy(volume_min,Symbol(),ask,(bid - padding),0,"");
            state = 1;
           }
         //--- Mean reverting mode
         if((ma_c_3[0] < ma_c_4[0]) && (strategy_mode == 1))
           {
            Trade.Buy(volume_min,Symbol(),ask,(bid - padding),0,"");
            state = 1;
           }
        }

      //--- Bearish on the higher time frame
      if(ma_c_1[0] < ma_c_2[0])
        {
         //--- Trend following mode
         if((ma_c_3[0] < ma_c_4[0]) && (strategy_mode == 0))
           {
            Trade.Sell(volume_min,Symbol(),bid,(ask + padding),0,"");
            state = 0;
           }
         //--- Mean reverting mode
         if((ma_c_3[0] > ma_c_4[0]) && (strategy_mode == 1))
           {
            Trade.Sell(volume_min,Symbol(),bid,(ask + padding),0,"");
            state = 0;
           }
        }
     }
  }

Após a abertura de uma posição, uma função específica de gestão de posições é acionada. Essa função também opera em dois modos, dependendo de utilizarmos os sinais do timeframe inferior ou superior para encerrar as operações. O estado do sistema é atualizado quando uma posição é aberta e indica a direção da operação. Por exemplo, o valor zero significa que uma posição vendida foi aberta. Se, em seguida, a primeira média móvel ficar acima da segunda, indicando um impulso de alta no gráfico diário, a posição será encerrada caso as saídas sejam gerenciadas com base no timeframe superior. Se utilizarmos o timeframe inferior, aguardaremos um cruzamento correspondente nesse timeframe. Ainda não é possível determinar qual dessas alternativas oferece melhores resultados, por isso precisamos testar ambas.

//+------------------------------------------------------------------+
//| Manage our open positions                                        |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   if(closing_tf == 0)
     {
      if((state ==0) && (ma_c_1[0] > ma_c_2[0]))
         Trade.PositionClose(Symbol());
      if((state ==1) && (ma_c_1[0] < ma_c_2[0]))
         Trade.PositionClose(Symbol());
     }

   else
      if(closing_tf == 1)
        {
         if((state ==0) && (ma_c_3[0] > ma_c_4[0]))
            Trade.PositionClose(Symbol());
         if((state ==1) && (ma_c_3[0] < ma_c_4[0]))
            Trade.PositionClose(Symbol());
        }
  }

Por fim, precisamos de uma função para atualizar as principais variáveis do sistema, como os preços Bid e Ask atuais. Também acompanharemos as máximas e mínimas das últimas 10 velas em um terceiro timeframe, utilizado para avaliar o risco. Neste exemplo, usamos o H4, que fica entre o M15 e o gráfico diário. Essa posição intermediária o torna adequado para avaliar o risco de mercado e definir níveis de stop-loss que não fiquem nem muito próximos nem muito distantes do preço de entrada.

//+------------------------------------------------------------------+
//| Update our technical indicators and positions                    |
//+------------------------------------------------------------------+
void update(void)
  {
   
   //Update technical indicators and market readings
   CopyBuffer(ma_c_2_handle,0,0,1,ma_c_2);
   CopyBuffer(ma_c_1_handle,0,0,1,ma_c_1);
   CopyBuffer(ma_c_4_handle,0,0,1,ma_c_4);
   CopyBuffer(ma_c_3_handle,0,0,1,ma_c_3);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   vector high = vector::Zeros(10);
   vector low = vector::Zeros(10);
   low.CopyRates(Symbol(),TF_3,COPY_RATES_LOW,0,10);
   high.CopyRates(Symbol(),TF_3,COPY_RATES_HIGH,0,10);
   vector var = high - low;
   double padding = var.Mean();

   //Find an open position
   if(PositionsTotal() == 0)
      find_setup(padding);

   //Manage our open positions
   else
      if(PositionsTotal() > 0)
         manage_setup();
  }
//+------------------------------------------------------------------+

Para iniciar o backtest, primeiro precisamos selecionar o EA que desenvolvemos, double crossover EX5. Em seguida, escolhemos o instrumento EURUSD, o timeframe M1 e o período de teste de janeiro de 2020 até o momento atual, totalizando cinco anos. Reservaremos metade dos dados disponíveis para o teste forward, para tornar a avaliação dos resultados mais realista.

Figura 3: Seleção do nosso aplicativo de negociação e do período de treinamento.

Para simular as condições reais de mercado, configuraremos atrasos aleatórios e utilizaremos ticks reais como modo de modelagem. Vale lembrar que a otimização será realizada com um algoritmo genético rápido.

Figura 4: Seleção das condições de simulação do mercado.

Em seguida, precisamos selecionar os parâmetros de entrada da estratégia que serão otimizados, conforme discutimos anteriormente. Entre eles estão os períodos utilizados nos cruzamentos das médias móveis e as diferenças entre esses períodos, tanto no timeframe superior quanto no inferior. Também habilitaremos os dois modos de operação para a otimização: seguimento de tendência e reversão à média. Por fim, poderemos configurar a estratégia para encerrar as posições com base nos sinais do timeframe superior ou do inferior. Todos esses parâmetros serão utilizados pelo otimizador genético para identificar as melhores configurações.

Figura 5: Seleção dos parâmetros da estratégia para otimização por meio de um algoritmo genético.

Os resultados do backtest são promissores. Como podemos observar nos resultados da otimização, a maioria das estratégias apresentou lucro, especialmente no modo de reversão à média. No entanto, ao analisar os resultados do teste forward, constatamos uma situação diferente: a maioria das estratégias lucrativas utiliza o modo de seguimento de tendência, ao contrário do que foi observado no backtest.

Figura 6: Resultados do backtest do nosso teste inicial.

Ainda mais preocupante é o fato de que, ao examinar os resultados do teste forward com mais atenção, constatamos que os resultados de nenhuma das estratégias do backtest coincidiram plenamente com os do teste forward. Em outras palavras, quase nenhuma das estratégias com bom desempenho no teste forward também foi lucrativa no backtest.

Figura 7: Os resultados do teste forward do experimento inicial mostram que a maioria das estratégias não é estável.

Precisei filtrar manualmente os resultados do teste forward para encontrar configurações que fossem lucrativas em ambos os testes. Esse é justamente um indicador de estabilidade: a estratégia deve apresentar lucro tanto no backtest quanto no teste forward. O fato de poucas configurações atenderem a esse critério me levou a buscar novas melhorias.

Figura 8: Apenas uma pequena parcela das estratégias identificadas pelo otimizador genético apresentou lucro em ambos os testes — um sinal negativo.


Melhorias adicionais

Uma melhoria importante seria permitir que o otimizador genético escolhesse o timeframe utilizado para calcular o nível de stop-loss e os parâmetros de risco. Além disso, deixaremos que o otimizador determine quantas barras históricas devem ser consideradas no cálculo do stop-loss. Na primeira versão, partimos do pressuposto de que 10 barras no timeframe H4 seriam suficientes. Agora, porém, permitiremos que o otimizador ajuste esse parâmetro para verificar se isso melhora os resultados gerais.
//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "Money Management Settings"
input ENUM_TIMEFRAMES TF_3 =    PERIOD_H4; //Risk Time Frame
input int HISTORICAL_BARS  =           10; //Historical bars for risk calculation

Também precisamos modificar o código. Na versão anterior, o método update era responsável por calcular, para cada posição, a distância entre o preço de entrada e o nível de stop-loss (padding). Nesta nova versão, esse cálculo ficará a cargo de uma função específica, pois queremos implementar um stop-loss móvel que acompanhe as posições lucrativas.

//+------------------------------------------------------------------+
//| Get the stop loss size to use                                    |
//+------------------------------------------------------------------+
double get_padding(void)
  {
   vector high = vector::Zeros(10);
   vector low = vector::Zeros(10);
   low.CopyRates(Symbol(),TF_3,COPY_RATES_LOW,0,HISTORICAL_BARS);
   high.CopyRates(Symbol(),TF_3,COPY_RATES_HIGH,0,HISTORICAL_BARS);
   vector var = high - low;
   double padding = var.Mean();
   return(padding);
  }

O método update será alterado para refletir essa mudança. A distância do stop-loss (padding) passará a ser calculada pelo método get_padding. A parte do método update que antes era responsável por procurar posições agora chamará a função encarregada de identificar um setup de negociação, além de outro método responsável por gerenciar as posições abertas.

//+------------------------------------------------------------------+
//| Update our technical indicators and positions                    |
//+------------------------------------------------------------------+
void update(void)
  {

//Update technical indicators and market readings
   CopyBuffer(ma_c_2_handle,0,0,1,ma_c_2);
   CopyBuffer(ma_c_1_handle,0,0,1,ma_c_1);
   CopyBuffer(ma_c_4_handle,0,0,1,ma_c_4);
   CopyBuffer(ma_c_3_handle,0,0,1,ma_c_3);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double padding = get_padding();

//Find an open position
   if(PositionsTotal() == 0)
      find_setup(padding);

//Manage our open positions
   else
      if(PositionsTotal() > 0)
         manage_setup();
  }
//+------------------------------------------------------------------+

O método de gestão de posições sempre verificará se o novo nível de stop-loss proposto é mais vantajoso do que o atual. Se for, o stop-loss será atualizado; caso contrário, o nível existente será mantido.

//+------------------------------------------------------------------+
//| Manage our open positions                                        |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
  //Does the position exist?
  if(PositionSelect(Symbol()))
   {
      //Get the current stop loss
      double current_sl = PositionGetDouble(POSITION_SL);
      double padding = get_padding();
      double new_sl;
      
   
      //Sell position
      if((state == 0))
      {
         new_sl = (ask + padding);
         
         if(new_sl < current_sl) Trade.PositionModify(Symbol(),new_sl,0);
      }

      //Buy position
      if((state == 1))
      {
         new_sl = (bid - padding);
         
         if(new_sl > current_sl) Trade.PositionModify(Symbol(),new_sl,0);
      }
  
  
   if(closing_tf == 0)
     {
      if((state ==0) && (ma_c_1[0] > ma_c_2[0]))
         Trade.PositionClose(Symbol());
      if((state ==1) && (ma_c_1[0] < ma_c_2[0]))
         Trade.PositionClose(Symbol());
     }

   else
      if(closing_tf == 1)
        {
         if((state ==0) && (ma_c_3[0] > ma_c_4[0]))
            Trade.PositionClose(Symbol());
         if((state ==1) && (ma_c_3[0] < ma_c_4[0]))
            Trade.PositionClose(Symbol());
        }
   }
  }

Para realizar os testes, selecionei uma configuração que havia sido lucrativa tanto no backtest quanto no teste forward. Em seguida, mantive os demais parâmetros fixos e permiti que o otimizador genético buscasse configurações de risco mais eficientes.

Figura 9: Tentativa de melhorar nossos resultados iniciais. Apesar de permitirmos que o otimizador genético controlasse os parâmetros de risco, os novos resultados continuaram sem apresentar lucro em ambos os testes.

Infelizmente, encontramos novamente o mesmo problema. Ao otimizar os parâmetros de risco, nossa aplicação apresentou lucro apenas no teste forward, sem conseguir obter resultados positivos no backtest.

Figura 10: Os novos resultados continuaram sem apresentar lucro em ambos os testes.



Conclusão

Esse experimento nos trouxe aprendizados importantes. Com a estratégia de cruzamento de duas médias móveis, vimos que é possível controlar o grau de atraso dos sinais, embora as consequências desses ajustes nem sempre sejam evidentes de imediato. Talvez valha a pena executar novamente a otimização, considerando todos os parâmetros disponíveis ao mesmo tempo. É possível que ajustar apenas um parâmetro enquanto mantemos os demais fixos não seja a melhor abordagem. Otimizar todos os parâmetros simultaneamente pode produzir resultados mais estáveis.

Em nossa próxima discussão, após concluir um ciclo completo de otimização, desenvolveremos modelos estatísticos com base nas configurações mais lucrativas. Isso poderá nos ajudar a reduzir ainda mais o atraso dos sinais. Por enquanto, porém, já estabelecemos uma base sólida. É importante lembrar que a otimização não oferece garantias e que a inteligência artificial não substitui desenvolvedores dedicados. Precisamos repetir a otimização até que o otimizador genético identifique grupos de estratégias lucrativas tanto no backtest quanto no teste forward. Caso contrário, é provável que a otimização tenha sido prematura.

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

Arquivos anexados |
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 75): Utilização do Awesome Oscillator e do canal Envelopes Recursos do Assistente MQL5 que você precisa conhecer (Parte 75): Utilização do Awesome Oscillator e do canal Envelopes
O Awesome Oscillator de Bill Williams e o canal Envelopes (Envelopes Channel) são dois indicadores que podem se complementar em um EA desenvolvido em MQL5. Utilizamos o Awesome Oscillator para identificar tendências, enquanto o canal Envelopes ajuda a determinar níveis de suporte e resistência. Como de costume, recorremos ao Assistente MQL5 para desenvolver padrões de sinal e testar o potencial dessa combinação de indicadores.
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.
Implementação em MQL5 de módulos práticos de outras linguagens (Parte 02): criação de uma biblioteca REQUESTS como a do Python Implementação em MQL5 de módulos práticos de outras linguagens (Parte 02): criação de uma biblioteca REQUESTS como a do Python
Neste artigo, vamos apresentar a implementação de um módulo semelhante ao requests do Python, com o objetivo de simplificar o envio de requisições web e o recebimento de respostas no MetaTrader 5 usando MQL5.