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

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

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

Nesta série de artigos, exploramos diferentes abordagens para combinar estratégias de trading em um único conjunto integrado. Nosso objetivo é superar as limitações de cada estratégia individual, combinando-as em uma solução mais robusta e poderosa.

No artigo anterior, criamos uma superclasse que serve de base para todas as nossas estratégias de trading. Com ela, implementamos nossa primeira estratégia em MQL5, baseada no cruzamento de médias móveis. Em seguida, comparamos nossa classe flexível com uma implementação equivalente, na qual a lógica da estratégia foi definida diretamente no código, para verificar se ambas apresentavam o mesmo desempenho.

Com o testador de estratégias do MetaTrader 5, conseguimos identificar combinações de parâmetros que produziram bons resultados para essa estratégia. Encontrar bons parâmetros manualmente pode ser uma tarefa difícil. É por isso que o otimizador genético do MetaTrader 5 é uma ferramenta tão valiosa: ele automatiza essa busca, economizando tempo e esforço.

Também utilizamos testes forward para selecionar, entre os resultados obtidos, os conjuntos de parâmetros que apresentaram maior estabilidade. Dessa forma, confirmamos que nossa implementação estava correta e era eficiente.

Hoje, vamos avançar mais um passo. Criaremos uma segunda estratégia, baseada no Índice de Força Relativa (RSI), e depois a combinaremos com a estratégia de cruzamento de médias móveis. Com essa combinação, pretendemos desenvolver uma estratégia de conjunto mais robusta e potencialmente mais lucrativa. Também utilizaremos o testador de estratégias do MetaTrader 5 para otimizar essa nova estratégia combinada. Antes, porém, precisamos abordar um conceito fundamental: a redução do número de parâmetros.

À medida que adicionamos novos componentes à estratégia, o número de parâmetros pode crescer rapidamente. Quando há componentes demais, a otimização se torna difícil e, em alguns casos, praticamente inviável. Por isso, é fundamental limitar o número de parâmetros sempre que possível. Uma solução frequente consiste em manter determinados parâmetros com valores fixos, concentrando a otimização naqueles que têm maior influência sobre os resultados.

Para efeito de comparação, nossa estratégia original de médias móveis alcançou um índice de Sharpe de 1,29 nos testes forward, com um lucro de US$ 133,51 em 101 operações. Já a nova estratégia baseada no RSI, que desenvolveremos neste artigo, atingiu um índice de Sharpe de 2,68 e um lucro de US$ 214,08, realizando apenas 52 operações.

Isso significa que a nova estratégia não apenas gerou um lucro maior, mas também conseguiu esse resultado com menos operações, menor risco de mercado e menor exposição ao mercado. É exatamente esse tipo de eficiência que buscamos ao desenvolver estratégias de trading.

Ao explorar os recursos do testador de estratégias e utilizá-lo de forma criteriosa, podemos identificar configurações de parâmetros mais lucrativas. No entanto, é importante manter expectativas realistas. O testador de estratégias do MetaTrader 5 não é capaz de corrigir, por si só, uma estratégia mal concebida. Se uma estratégia apresentar falhas fundamentais, nenhuma otimização será capaz de torná-la lucrativa.

Mesmo com uma boa estratégia, a otimização não garante resultados melhores, mas, quando aplicada corretamente, pode aumentar significativamente nossas chances de sucesso. Com uma base sólida e as ferramentas adequadas, podemos explorar ao máximo o potencial de desempenho de nossas estratégias.


Primeiros passos em MQL5

Vamos começar criando uma classe para nossa estratégia baseada na linha central do RSI. O primeiro passo é incluir as dependências necessárias para essa classe. A primeira delas é a classe RSI, que desenvolvemos para nossos indicadores de buffer único. Também precisaremos da superclasse que serve de base para todas as nossas estratégias, conhecida como classe-pai.

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

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <VolatilityDoctor\Strategies\Parent\Strategy.mqh>

Com as dependências incluídas, podemos definir a classe da estratégia baseada na linha central do RSI e seus membros. Observe que essa classe herda da classe base de nossas estratégias, o que indicamos pelo uso de dois-pontos na declaração de herança da classe. Em seguida, definimos os membros específicos da nossa classe de estratégia baseada no RSI. Como podemos observar, precisamos declarar apenas alguns membros. Vale lembrar também que todos os métodos virtuais definidos na classe-pai devem ser declarados novamente como virtuais na classe RSI Midpoint.

class RSIMidPoint : public Strategy
  {
private:
                     //--- The instance of the RSI used in this strategy
                     RSI *my_rsi;

public:
                     //--- Class constructor 
                     RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price);
                     
                     //--- Class destructor
                    ~RSIMidPoint();
                    
                    //--- Class overrides
                    virtual bool Update(void);
                    virtual bool BuySignal(void);
                    virtual bool SellSignal(void);
  };
 

Agora, vamos examinar como implementar cada método de acordo com as necessidades da nossa estratégia baseada no RSI. Esse método deve apenas atualizar o valor do RSI e verificar se ele é diferente de zero. Se for, consideramos que a atualização ocorreu corretamente; caso contrário, significa que houve algum problema.

//+------------------------------------------------------------------+
//| Our strategy update method                                       |
//+------------------------------------------------------------------+
bool RSIMidPoint::Update(void)
   {
      //--- Set the indicator value
      my_rsi.SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
      
      //--- Check readings are valid
      if(my_rsi.GetCurrentReading() != 0) return(true);
      
      //--- Something went wrong
      return(false);
   }  

Em seguida, vamos definir as condições para gerar os sinais de compra e venda. Os sinais de compra são gerados quando o valor do RSI está abaixo de 50, enquanto os sinais de venda são gerados quando o RSI está acima de 50.

//+------------------------------------------------------------------+
//| Check for our buy signal                                         |
//+------------------------------------------------------------------+
bool RSIMidPoint::BuySignal(void)
   {
      //--- Buy signals when the RSI is below 50
      return(my_rsi.GetCurrentReading()<50);
   }

//+------------------------------------------------------------------+
//| Check for our sell signal                                        |
//+------------------------------------------------------------------+
bool RSIMidPoint::SellSignal(void)
   {
      //--- Sell signals when the RSI is above 50
      return(my_rsi.GetCurrentReading()>50);
   }

Por fim, precisamos implementar o construtor e o destrutor da classe. O construtor receberá os parâmetros necessários para configurar nosso indicador RSI. Mais especificamente, o usuário deverá informar o símbolo, o período gráfico (timeframe), o período de cálculo do RSI e o preço aplicado. Com essas informações, inicializamos os parâmetros correspondentes e criamos uma nova instância do nosso indicador RSI.

É importante destacar que essa instância não corresponde ao indicador RSI nativo do MetaTrader 5. Trata-se de uma classe personalizada que desenvolvemos e que oferece diversas funcionalidades adicionais que utilizaremos ao longo do projeto. Ainda assim, a funcionalidade geral permanece a mesma, e os leitores que desejarem poderão implementar alguns desses métodos do zero.

//+------------------------------------------------------------------+
//| Our class constructor                                            |
//+------------------------------------------------------------------+
RSIMidPoint::RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price)
  {
   my_rsi = new RSI(user_symbol,user_timeframe,user_period,user_price);
   Print("RSI-Mid-Point Strategy Loaded.");
  }

Por fim, no destrutor da nossa classe, excluímos o objeto RSI personalizado que criamos, utilizando seu ponteiro.

//+------------------------------------------------------------------+
//| Our class destructor                                             |
//+------------------------------------------------------------------+
RSIMidPoint::~RSIMidPoint()
  {
   delete my_rsi;
  }
//+------------------------------------------------------------------+

Agora que definimos a classe que encapsula nossa estratégia baseada na linha central do RSI, precisamos verificar se ela foi implementada corretamente. Para isso, começaremos estabelecendo uma referência de desempenho com uma versão da estratégia cuja lógica foi definida diretamente no código. Assim, poderemos verificar se nossa classe reproduz os mesmos indicadores de desempenho da implementação de referência.

Se as duas estratégias forem equivalentes, deverão apresentar a mesma rentabilidade e os mesmos indicadores estatísticos quando testadas no mesmo período. Portanto, para estabelecer essa referência, começaremos definindo as constantes do sistema necessárias, como o preço aplicado ao cálculo do RSI, o período do indicador e o período gráfico (timeframe). Essas constantes deverão permanecer fixas em ambos os testes, garantindo uma comparação em condições equivalentes.

//+------------------------------------------------------------------+
//|                                          MSA Test 2 Baseline.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 RSI_PRICE        PRICE_CLOSE
#define RSI_PERIOD       15
#define RSI_TIME_FRAME   PERIOD_D1
#define HOLDING_PERIOD   5

Em seguida, incluiremos as dependências necessárias. Neste caso, precisaremos de apenas três bibliotecas.

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

Também precisaremos declarar algumas variáveis globais. Observe que podemos agrupá-las em duas categorias, de acordo com seus tipos: definidos pelo usuário e predefinidos pelo sistema. Os tipos definidos pelo usuário são criados pelo próprio programador, enquanto os tipos predefinidos estão disponíveis em qualquer instalação do MetaTrader 5, como double e float. Essa forma de organizar as variáveis globais facilita a manutenção do código.

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

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

//--- System Types
double rsi[],ma_close[];
int    rsi_handler;
int    position_timer;

A seguir, vamos implementar a inicialização do nosso EA. Durante a inicialização, criaremos diretamente um identificador (handle) para o indicador RSI e instanciaremos dinamicamente alguns objetos de nossas classes personalizadas. Esses objetos serão utilizados, entre outras funções, para acompanhar o tempo e obter informações importantes para as operações, como o volume mínimo de negociação permitido, expresso em lotes. Depois disso, verificaremos se o identificador do RSI foi criado corretamente.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Our technical indicator
   rsi_handler = iRSI(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);

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


//--- Safety checks
   if(rsi_handler == INVALID_HANDLE)
      return(false);
   
//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope

Quando nosso Expert Advisor deixar de ser executado, liberaremos os recursos do indicador que não será mais utilizado e excluiremos os objetos dinâmicos que criamos.

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

   delete TradeTime;
   delete TradeInformation;

  }
//--- End of Deinit Scope

Na função OnTick, começaremos verificando se uma nova vela diária foi formada, utilizando uma biblioteca que desenvolvemos especificamente para essa finalidade. Se houver uma nova vela, atualizaremos nossos indicadores técnicos e verificaremos se existem posições abertas. Se não houver posições abertas, garantiremos que o contador de tempo da posição tenha sido reiniciado e, em seguida, verificaremos se existe algum sinal de negociação. Caso contrário, se houver posições abertas, verificaremos se alguma delas atingiu o tempo máximo de permanência e precisa ser encerrada. Se esse limite ainda não tiver sido atingido, manteremos a posição aberta.

//+------------------------------------------------------------------+
//| 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

Também precisamos examinar a implementação de alguns dos métodos que mencionamos anteriormente. Por exemplo, o método Update simplesmente chama a função CopyBuffer, que utiliza o identificador do RSI para copiar os valores do indicador para o array de destino. Copiamos apenas o valor atual do RSI, pois não precisamos dos valores anteriores.

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

Da mesma forma, o método CheckSignal apenas verifica se existem sinais de negociação de acordo com as condições que definimos anteriormente: geramos um sinal de compra quando o RSI está abaixo de 50 e um sinal de venda quando está acima de 50.

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Buy signals when the RSI is below 50
   if(rsi[0] < 50)
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Sell signals when the RSI is above 50
   else
      if(rsi[0] > 50)
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }

  }
//--- End of CheckSignal Scope

Por fim, padronizaremos as constantes do sistema definidas no início do código.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef RSI_PRICE        
#undef RSI_PERIOD       
#undef RSI_TIME_FRAME   
#undef HOLDING_PERIOD   
//+------------------------------------------------------------------+

Para iniciar nosso backtest, começaremos selecionando o período de treinamento. O teste abrangerá o intervalo de 1º de janeiro de 2022 a 1º de maio de 2025 e será realizado no período gráfico diário.

Figura 1: Datas de backtest selecionadas para nossas medições de referência.

Para tornar os resultados da nossa simulação mais confiáveis, definiremos o parâmetro "Delays" como "Random delay" (atraso aleatório). Essa configuração simula atrasos na execução das ordens, derrapagem de preços (slippage) e outros fatores presentes nas condições reais de negociação.

Figura 2: Configurações de teste utilizadas em nosso backtest.

A curva de capital apresentada na Figura 3, abaixo, servirá como referência para verificar se nossa classe de estratégia baseada no RSI reproduz os resultados esperados.

Figura 3: Curva de capital produzida pela versão da nossa estratégia de trading com lógica definida diretamente no código.

Também podemos examinar as estatísticas detalhadas obtidas com nossa estratégia de referência. Nosso objetivo é que a implementação baseada em classes apresente exatamente os mesmos indicadores estatísticos quando testada no mesmo período, confirmando assim a confiabilidade da implementação.


Figura 4: Estatísticas detalhadas de desempenho da nossa estratégia de trading.

Agora, precisamos repetir o teste para verificar se nossa classe de estratégia RSI foi implementada corretamente. Para isso, manteremos praticamente inalterados os elementos globais definidos anteriormente, como as constantes do sistema e as variáveis globais. Entre as principais alterações no programa está a criação de uma instância da nossa classe de estratégia durante a inicialização do EA.

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

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

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

//--- System Types
int    position_timer;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),RSI_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME);
   RSIMid           = new RSIMidPoint(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);

//--- Everything was fine
   return(INIT_SUCCEEDED);
  }
//--- End of OnInit Scope
Além disso, precisaremos atualizar a função OnDeinit para garantir que o novo objeto seja excluído corretamente.
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the dynamic objects
   delete TradeTime;
   delete TradeInformation;
   delete RSIMid;
  }
//--- End of Deinit Scope
Por fim, verificaremos se o método CheckSignal está sendo chamado da forma adequada.
//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {
//--- Long positions when RSI is below 50
   if(RSIMid.BuySignal())
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
      return;
     }

//--- Otherwise short positions when the RSI is above 50
   else
      if(RSIMid.SellSignal())
        {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
         return;
        }
  }
//--- End of CheckSignal Scope

Com essas alterações, testaremos nossa classe de estratégia RSI no mesmo período utilizado no backtest de referência.

Figura 5: Seleção das datas de teste para nossa versão da estratégia RSI implementada por meio de uma classe.

Ambos os testes foram executados com a opção «Random delay» (atraso aleatório), conforme mostrado na Figura 2, e produziram curvas de capital semelhantes com as mesmas configurações de backtest.

Figura 6: Curva de capital obtida com a versão baseada em classes da nossa estratégia de trading, correspondente ao resultado de referência produzido pelo código com lógica definida diretamente.

Além disso, uma análise detalhada revelou que os dois testes apresentaram indicadores estatísticos idênticos. Isso confirma que conseguimos implementar nossa classe corretamente, sem erros identificados na implementação da classe. Portanto, podemos avançar para a combinação das classes de estratégia de médias móveis e do Índice de Força Relativa (RSI).

Figura 7: As estatísticas detalhadas que analisam o desempenho da versão da nossa estratégia de negociação implementada com base em classes correspondem aos resultados de referência.

Agora que confirmamos que a implementação baseada em classes e a versão com a lógica definida diretamente no código produzem os mesmos resultados, podemos combinar a estratégia de cruzamento de médias móveis com a estratégia baseada no RSI para criar uma nova estratégia de conjunto (ensemble). Antes disso, precisamos selecionar alguns parâmetros que permanecerão fixos. É importante controlar o crescimento do espaço de parâmetros, pois um número excessivo de combinações pode tornar uma otimização eficaz praticamente inviável.

//+------------------------------------------------------------------+
//|                                                   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                                                 |
//+------------------------------------------------------------------+
//--- Fix any parameters that can afford to remain fixed
#define MA_SHIFT         0
#define MA_TYPE          MODE_EMA
#define RSI_PRICE        PRICE_CLOSE

Além disso, agora que executaremos duas estratégias simultaneamente, precisamos definir como elas irão interagir em MQL5. Para isso, podemos utilizar uma enumeração (enum), que permite representar diferentes modos de funcionamento relacionados entre si.

Assim, criamos uma enumeração chamada Strategy Mode, com três configurações possíveis. A primeira, identificada pelo valor 0, corresponde ao modo de votação. Nesse modo, uma operação só é aberta quando as estratégias de médias móveis e RSI indicam a mesma direção. Em outras palavras, para abrir uma posição comprada, ambas as estratégias precisam gerar um sinal de compra.

No primeiro modo alternativo, a estratégia baseada no RSI fica responsável pelas posições compradas, enquanto a estratégia de médias móveis cuida das posições vendidas. O terceiro modo inverte essa distribuição: a estratégia de médias móveis fica responsável pelas posições compradas, e a estratégia baseada no RSI, pelas posições vendidas.

//+------------------------------------------------------------------+
//| User defined enumerator                                          |
//+------------------------------------------------------------------+

enum STRATEGY_MODE
  {
   MODE_ONE   = 0, //Voting Policy
   MODE_TWO   = 1, //RSI Buy & MA Sell
   MODE_THREE = 2  //MA Sell & RSI Buy
  };
Ao organizar nossa estratégia dessa forma, conseguimos reduzir o número total de parâmetros de entrada para apenas cinco, tornando a otimização mais fácil de gerenciar. Em seguida, incluímos as dependências apresentadas anteriormente e inicializamos as variáveis que já utilizamos ao longo do desenvolvimento.

//+------------------------------------------------------------------+
//| User Inputs                                                      |
//+------------------------------------------------------------------+
input   group          "Moving Average Strategy Parameters"
input   int             MA_PERIOD                       =        10;//Moving Average Period


input   group          "RSI Strategy Parameters"
input   int             RSI_PERIOD                      =         15;//RSI Period

input   group          "Global Strategy Parameters"
input   ENUM_TIMEFRAMES STRATEGY_TIME_FRAME             = PERIOD_D1;//Strategy Timeframe
input   int             HOLDING_PERIOD                  =         5;//Position Maturity Period
input   STRATEGY_MODE   USER_MODE                       =         0;//Operation Mode For Our Strategy

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

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

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

//--- System Types
int                  position_timer;

Na função de inicialização, criamos as instâncias das mesmas classes que utilizamos nas etapas anteriores. Quando o EA é encerrado, garantimos que todos os objetos criados dinamicamente sejam excluídos corretamente.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),STRATEGY_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),STRATEGY_TIME_FRAME);
   MACross          = new OpenCloseMACrossover(Symbol(),STRATEGY_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE);
   RSIMid           = new RSIMidPoint(Symbol(),STRATEGY_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
//--- 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;
   delete RSIMid;
  }
//--- End of Deinit Scope

Na função OnTick, mantemos a mesma lógica utilizada anteriormente. Como já examinamos seu funcionamento em detalhes, não reproduziremos seu código nesta seção. O método Update recebeu uma pequena alteração, pois agora chama dois métodos de atualização distintos, um para cada estratégia. Já o método CheckSignal passou pelas mudanças mais significativas: antes de iniciar qualquer operação, ele precisa identificar qual modo de estratégia está selecionado.

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

//+------------------------------------------------------------------+
//| Check for a trading signal using our cross-over strategy         |
//+------------------------------------------------------------------+
void CheckSignal(void)
  {

//--- Both Stratetgies Should Cast The Same Vote
   if(USER_MODE == 0)
     {
      //--- Long positions when the close moving average is above the open
      if(MACross.BuySignal() && RSIMid.BuySignal())
        {
         Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
         return;
        }

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

//--- RSI Opens All Long Positions & The Moving Average Opens Short Positions
   else
      if(USER_MODE == 1)
        {

         if(RSIMid.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;
              }
        }

      //--- RSI Opens All Short Positions & The Moving Average Opens Long Positions
      else
         if(USER_MODE == 2)
           {

            if(MACross.BuySignal())
              {
               Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,"");
               return;
              }

            //--- Otherwise short
            else
               if(RSIMid.SellSignal())
                 {
                  Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,"");
                  return;
                 }
           }

  }
//--- End of CheckSignal Scope

Por fim, ao final do código, removemos as definições de todas as constantes do sistema, concluindo a estrutura do programa de forma adequada.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_SHIFT
#undef RSI_PRICE
#undef MA_TYPE
//+------------------------------------------------------------------+

Agora, vamos procurar as combinações de parâmetros que produzam os melhores resultados para nosso EA. Selecione o programa que acabamos de criar e defina a opção "Forward" como "1/2". Com essa configuração, o otimizador genético utilizará metade dos dados históricos disponíveis para buscar os melhores parâmetros. A outra metade ficará oculta para o otimizador e será utilizada nos testes forward para avaliar as configurações consideradas promissoras pelo otimizador.

Figura 8: Configuração do teste forward.

Depois de selecionar «Random delay» (atraso aleatório), como fizemos nos dois testes anteriores, precisamos também escolher o método de otimização. Se você tiver maior capacidade de processamento disponível, poderá experimentar outras opções oferecidas pelo testador de estratégias, como «Slow Complete» (otimização completa lenta).

Figura 9: Selecione a opção "Fast Genetic Based Algorithm" no campo "Optimization".

Selecione a aba "Inputs", na parte inferior da tela, para abrir o painel de configuração dos parâmetros da nossa estratégia que serão testados. Marque a caixa de seleção ao lado de cada parâmetro e defina os valores inicial e final do intervalo de busca. Observe que o valor do passo também merece atenção. Se escolhermos um passo de 1, obteremos uma busca bastante detalhada, mas o número total de combinações a serem testadas poderá se tornar extremamente elevado. Em geral, é recomendável utilizar passos de 2, 4, 5 ou até mesmo 10 quando o espaço de busca for muito amplo.

Figura 10: Seleção dos intervalos de busca dos parâmetros.

Ao executar o teste, os resultados dos testes forward serão apresentados em um gráfico de dispersão, no qual cada ponto representa um teste completo realizado com uma combinação específica de parâmetros da estratégia.

Figura 11: Podemos visualizar os resultados obtidos com o testador de estratégias em um gráfico de dispersão bidimensional, com o lucro no eixo Y.

Nossa estratégia permaneceu lucrativa mesmo fora da amostra de treinamento utilizada pelo algoritmo genético para selecionar essas configurações. Esse é um sinal positivo de que a estratégia poderá manter um desempenho estável no futuro, caso decidamos utilizá-la em operações com capital real.

Figura 12: A curva de capital produzida pela nossa estratégia de trading apresenta uma evolução positiva fora da amostra.

Nosso novo sistema apresentou uma rentabilidade significativamente maior do que a versão anterior. Também podemos observar claramente que o modo de votação parece ser o mais estável.

Figura 13: O desempenho do nosso sistema melhorou após a inclusão de uma estratégia adicional.

Esses foram os melhores resultados que havíamos obtido com os dados dos testes forward durante nossa análise inicial. Nosso novo sistema superou o desempenho da versão anterior, que utilizava apenas uma estratégia.

Figura 14: Melhores resultados obtidos quando nosso sistema utilizava apenas uma estratégia.



Conclusão

Neste artigo, mostramos diversas vantagens de aplicar os princípios da programação orientada a objetos (POO) em MQL5, aliando esses princípios à criatividade humana. Demonstramos que existem várias maneiras de integrar diferentes estratégias em um único sistema. A programação orientada a objetos é uma ferramenta poderosa e versátil para esse tipo de desenvolvimento, pois proporciona um desenvolvimento estruturado, controlado e previsível.

Com base nos conceitos fundamentais apresentados aqui, os leitores já podem aprimorar e otimizar suas próprias estratégias de trading. Utilizando o testador de estratégias integrado ao MetaTrader 5, é possível testar diferentes configurações, avaliar os resultados e aperfeiçoar gradualmente as soluções desenvolvidas.

Ao longo desta análise, também destacamos a importância de limitar o número de parâmetros de entrada de uma estratégia. Quanto maior o número de parâmetros, mais difícil se torna realizar uma otimização eficiente. Embora seja possível transferir parte da carga computacional para os serviços da MQL5 Cloud Network, manter uma estrutura simples costuma contribuir para melhores resultados e estratégias mais robustas.

Ao aplicar os princípios apresentados neste artigo, os leitores terão os conhecimentos práticos necessários para desenvolver seus próprios sistemas de trading baseados em estratégias de conjunto (ensemble). Esperamos contar com sua participação nos próximos artigos, nos quais apresentaremos o terceiro nível da nossa estratégia de trading. Essa nova etapa nos permitirá estabelecer uma base sólida para o desenvolvimento de futuros modelos estatísticos.

Nome do arquivo Descrição do arquivo
MSA Test 2 Baseline.mq5 Versão de teste da nossa estratégia de trading baseada no RSI, com a lógica definida diretamente no código.
MSA Test 2 Class.mq5 Versão de teste da nossa classe de estratégia RSI.
MSA Test 2.ex5 Versão compilada da nossa estratégia de conjunto (ensemble).
MSA Test 2.mq5 Código-fonte da aplicação de trading de conjunto (ensemble) que desenvolvemos utilizando médias móveis (MA) e RSI.
RSIMidPoint.mqh Classe MQL5 da nossa estratégia baseada na linha central do RSI.

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

Arquivos anexados |
MSA_Test_2.ex5 (47.2 KB)
MSA_Test_2.mq5 (7.64 KB)
RSIMidPoint.mqh (3.51 KB)
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.
Desenvolvimento de ferramentas para análise de Price Action (Parte 28): Ferramenta para operar rompimentos da faixa de abertura Desenvolvimento de ferramentas para análise de Price Action (Parte 28): Ferramenta para operar rompimentos da faixa de abertura
No início de cada sessão de negociação, a direção do mercado muitas vezes só fica clara quando o preço rompe os limites da faixa de abertura. Neste artigo, veremos como desenvolver um EA em MQL5 capaz de detectar e analisar automaticamente esses rompimentos, fornecendo sinais oportunos baseados em dados para embasar entradas intradiárias com maior confiança.
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.
Aprendizado de Máquina e Data Science (Parte 43): Identificação de padrões ocultos em indicadores com modelos de misturas gaussianas latentes (LGMM) Aprendizado de Máquina e Data Science (Parte 43): Identificação de padrões ocultos em indicadores com modelos de misturas gaussianas latentes (LGMM)
Você já teve a sensação de que existe algo mais por trás de um gráfico, algum padrão que ainda não conseguiu identificar? Como se houvesse um código secreto que, uma vez decifrado, permitiria saber exatamente para onde os preços estão indo? Apresento o LGMM, uma ferramenta capaz de detectar padrões ocultos no mercado. Esse modelo de aprendizado de máquina ajuda a identificar regularidades que normalmente passam despercebidas.