English Русский 中文 Español Deutsch 日本語
preview
Construindo Expert Advisors Autootimizáveis em MQL5 (Parte 6): Regras de Trading Autoajustáveis (II)

Construindo Expert Advisors Autootimizáveis em MQL5 (Parte 6): Regras de Trading Autoajustáveis (II)

MetaTrader 5Exemplos |
30 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Em nossa última discussão sobre regras de trading auto-adaptativas, vinculada aqui, consideramos os problemas enfrentados por um trader algorítmico ao tentar seguir as melhores práticas sobre como utilizar o indicador RSI.

Descobrimos que os resultados padronizados nem sempre são gerados pelo indicador, dependendo de vários fatores, como o período, o timeframe e também o mercado específico em questão.

Para resolver esse problema, postulamos que traders algorítmicos poderiam, em vez disso, estudar o intervalo real do indicador, de modo que possam reajustar o ponto médio do indicador para o meio de seu intervalo observado, e não de seu intervalo total possível. Fazendo isso, obtemos algumas garantias sobre a geração de sinais de trading que não conseguimos obter a partir das regras tradicionais do RSI. Ganhamos controle adicional sobre o novo sinal ao registrar um desvio médio a partir do ponto médio, e registrar apenas sinais gerados por múltiplos do desvio médio.

Agora avançaremos além de nossa tentativa inicial de construir uma solução prática. Há várias melhorias que podemos fazer em relação à nossa última tentativa. A melhoria essencial que buscamos é a capacidade de tentar estimar o valor dos níveis de RSI escolhidos. 

Em nossa última discussão, simplesmente assumimos que desvios significativamente maiores do que o desvio médio poderiam tender a ser mais lucrativos. No entanto, não tentamos medir se isso era verdade. Não fizemos nenhuma tentativa de quantificar o valor dos novos níveis que estamos propondo e compará-los com o valor dos níveis tradicionais, 70 e 30.

Além disso, nossa última discussão considerou o caso em que o período do RSI era fixo. Essa suposição simplificadora tornou nossa estrutura mais fácil de entender. Hoje, passamos a tratar do problema oposto: o caso em que o usuário não sabe qual período deve usar. Em sua forma atual, a classe completa fica assim.



Visualizando o Problema

Caso tenhamos novos leitores se juntando a nós, na Fig 1 abaixo, anexamos uma captura de tela do gráfico diário do EURUSD com um RSI de período 2. 

Fig 1

Fig 1: Visualizando nosso RSI e a qualidade dos sinais gerados por um período curto.

Abaixo da Fig 1, aplicamos um RSI de período 70 à mesma seção do gráfico na Fig 2. Podemos observar que o sinal do RSI está lentamente se tornando uma linha plana centrada ao longo do nível 50 do RSI. Que comentários poderíamos fazer ao comparar as duas imagens? Bem, um comentário digno de nota é que, ao longo do período do EURUSD capturado em ambas as figuras, a taxa de câmbio simplesmente caiu, do nível de preço 1.121 em 18 de setembro de 2024 para mínimas de 1.051 em 2 de dezembro de 2024. No entanto, o RSI de período 2 mudou de níveis com muita frequência ao longo do mesmo período, e o RSI de período 70 não mudou de níveis de forma alguma.

Isso significa que os traders devem ficar para sempre limitados a usar apenas uma faixa estreita de períodos ao empregar o RSI? O que será necessário para projetarmos algoritmos que selecionem automaticamente um bom período de RSI, sem intervenção humana? Além disso, como podemos escrever algoritmos que nos ajudem a encontrar bons níveis de trading, independentemente do período com o qual começamos inicialmente?

Fig 2

Fig 2: Visualizando nosso indicador RSI operando com um período longo.


Começando no MQL5

Há muitas maneiras de abordar esse problema. Poderíamos usar bibliotecas em Python para gerar leituras de RSI com diferentes períodos e otimizar nosso período e níveis de RSI dessa forma. No entanto, isso introduz possíveis desvantagens. A maior limitação pode potencialmente vir de pequenas diferenças na implementação do cálculo desses valores de indicadores técnicos. 

Para evitar isso, implementaremos nossa solução em MQL5. Ao construir uma classe RSI, podemos rapidamente registrar múltiplas instâncias de valores de RSI em um único arquivo CSV e usar esses valores para realizar nossa análise numérica em Python a fim de estimar um período ótimo de RSI e níveis alternativos para usar além de 70 e 30.

Começaremos criando um script que nos permita primeiro recuperar manualmente valores de RSI e calcular a variação nos níveis de RSI. Em seguida, criaremos uma classe que encapsula a funcionalidade de que precisamos. Queremos criar uma grade de leituras de RSI com períodos incrementando em passos de 5, de 5 até 70. Mas antes de alcançarmos isso, precisamos implementar e testar nossa classe. 

Construir a classe em um script nos permitirá testar rapidamente a saída da classe em relação à saída padrão obtida manualmente a partir do indicador. Se tivermos especificado bem a classe, a saída gerada por ambos os métodos deverá ser a mesma. Isso nos dará uma classe útil para gerar 14 indicadores de RSI com diferentes períodos, ao mesmo tempo em que acompanhando a variação de cada instância do RSI para qualquer outro símbolo que desejemos negociar.

Dado o uso que queremos fazer dessa classe RSI, faz sentido começar garantindo que a classe tenha um mecanismo que nos impeça de tentar ler o valor do indicador antes de termos configurado o buffer adequadamente. Começaremos construindo essa parte da nossa classe primeiro. Precisamos declarar membros privados da nossa classe. Esses flags booleanos privados nos impedirão de ler valores de RSI antes de copiarmos a partir do buffer do indicador.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- The RSI class will manage our indicator settings and provide useful transformations we need
class RSI
  {
   //--- Private members
private:
   //--- Have the indicator values been copied to the buffer?
   bool              indicator_values_initialized;
   bool              indicator_differenced_values_initialized;

Também incluí um método que retorna strings para informar ao usuário o que está acontecendo dentro do objeto e como resolver problemas. O método recebe um parâmetro inteiro que informa em que parte do código o erro foi gerado. Assim, as soluções normalmente são fáceis de sugerir como uma mensagem impressa no terminal.

//--- Give the user feedback
string            user_feedback(int flag)
  {
   string message;

   //--- Check if the RSI indicator loaded correctly
   if(flag == 0)
     {
      //--- Check the indicator was loaded correctly
      if(IsValid())
         message = "RSI Indicator Class Loaded Correcrtly \n";
      return(message);
      //--- Something went wrong
      message = "Error loading RSI Indicator: [ERROR] " + (string) GetLastError();
      return(message);
     }

   //--- User tried getting indicator values before setting them
   if(flag == 1)
     {
      message = "Please set the indicator values before trying to fetch them from memory";
      return(message);
     }

   //--- We sueccessfully set our differenced indicator values
   if(flag == 2)
     {
      message = "Succesfully set differenced indicator values.";
      return(message);
     }

   //--- Failed  to set our differenced indicator values
   if(flag == 3)
     {
      message = "Failed to set our differenced indicator values: [ERROR] " + (string) GetLastError();
      return(message);
     }

   //--- The user is trying to retrieve an index beyond the buffer size and must update the buffer size first
   if(flag == 4)
     {
      message = "The user is attempting to use call an index beyond the buffer size, update the buffer size first";
      return(message);
     }

   //--- No feedback
   else
      return("");
  }

Agora definiremos os membros protegidos da nossa classe. Esses membros constituirão as partes móveis necessárias para inicializar uma instância da classe iRSI() e interagir com o buffer do indicador.

   //--- Protected members
protected:
   //--- The handler for our RSI
   int               rsi_handler;
   //--- The Symbol our RSI should be applied on
   string            rsi_symbol;
   //--- Our RSI period
   int               rsi_period;
   //--- How far into the future we wish to forecast
   int               forecast_horizon;
   //--- The buffer for our RSI indicator
   double            rsi_reading[];
   vector            rsi_differenced_values;
   //--- The current size of the buffer the user last requested
   int               rsi_buffer_size;
   int               rsi_differenced_buffer_size;
   //--- The time frame our RSI should be applied on
   ENUM_TIMEFRAMES   rsi_time_frame;
   //--- The price should the RSI be applied on
   ENUM_APPLIED_PRICE rsi_price;

Seguindo para os membros públicos da classe. A primeira função que precisaremos nos informa se o manipulador do indicador é válido. Se o manipulador do indicador não estiver configurado corretamente, podemos informar o usuário imediatamente.

//--- Now, we can define public members:
public:

   //--- Check if our indicator handler is valid
   bool              IsValid(void)
     {
      return((this.rsi_handler != INVALID_HANDLE));
     }

Nosso construtor padrão criará um objeto da classe RSI configurado para o EURUSD no gráfico diário, com período de 5 dias. Para garantir que esta seja a escolha pretendida do usuário, nossa classe imprime qual mercado e período está sendo utilizado. Além disso, o construtor padrão informa explicitamente ao usuário que a instância atual do objeto RSI foi criada pelo construtor padrão.

//--- Our default constructor
void              RSI(void):
                  indicator_values_initialized(false),
                  rsi_symbol("EURUSD"),
                  rsi_time_frame(PERIOD_D1),
                  rsi_period(5),
                  rsi_price(PRICE_CLOSE),
                  rsi_handler(iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price))
  {
   //--- Give the user feedback on initilization
   Print(user_feedback(0));
   //--- Remind the user they called the default constructor
   Print("Default Constructor Called: ",__FUNCSIG__," ",&this);
  }

Caso contrário, esperamos que o usuário chame o construtor paramétrico para o objeto RSI e especifique todos os parâmetros necessários.

//--- Parametric constructor
   void              RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price)
     {
      indicator_values_initialized = false;
      rsi_symbol                   = user_symbol;
      rsi_time_frame               = user_time_frame;
      rsi_period                   = user_period;
      rsi_price                    = user_price;
      rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
      //--- Give the user feedback on initilization
      Print(user_feedback(0));
     }

Também precisaremos de um destrutor para liberar os recursos do sistema que não são mais necessários, permitindo que façamos a limpeza adequada.

//--- Destructor
void             ~RSI(void)
  {
   //--- Free up resources we don't need and reset our flags
   if(IndicatorRelease(rsi_handler))
     {
      indicator_differenced_values_initialized = false;
      indicator_values_initialized = false;
      Print("RSI System logging off");
     }
  }

Agora, os métodos necessários para interagir com o buffer do indicador são o componente chave da nossa classe. Solicitamos ao usuário que especifique quantos valores devem ser copiados do buffer e se os valores devem ser organizados como séries. Em seguida, testamos para garantir que os valores de RSI não estejam retornando como nulos antes do término da chamada do método.

//--- Copy readings for our RSI indicator
bool              SetIndicatorValues(int buffer_size,bool set_as_series)
  {
   rsi_buffer_size = buffer_size;
   CopyBuffer(this.rsi_handler,0,0,buffer_size,rsi_reading);
   if(set_as_series)
      ArraySetAsSeries(this.rsi_reading,true);
   indicator_values_initialized = true;

   //--- Did something go wrong?
   vector rsi_test;
   rsi_test.CopyIndicatorBuffer(rsi_handler,0,0,buffer_size);
   if(rsi_test.Sum() == 0)
      return(false);

   //--- Everything went fine.
   return(true);
  }

Uma função simples para obter a leitura atual do RSI.

//--- Get the current RSI reading
double            GetCurrentReading(void)
  {
   double temp[];
   CopyBuffer(this.rsi_handler,0,0,1,temp);
   return(temp[0]);
  }

Também podemos precisar buscar valores em um índice específico, não apenas o valor mais recente. Essa função, GetReadingAt(), fornece essa utilidade para nós. A função primeiro verifica se não estamos tentando ultrapassar o tamanho do buffer que copiamos do indicador. Se usada corretamente, a função retornará a leitura do indicador no índice especificado. Caso contrário, uma mensagem de erro será exibida.

//--- Get a specific RSI reading
double            GetReadingAt(int index)
  {
   //--- Is the user trying to call indexes beyond the buffer?
   if(index > rsi_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

   //--- Get the reading at the specified index
   if((indicator_values_initialized) && (index < rsi_buffer_size))
      return(rsi_reading[index]);

   //--- User is trying to get values that were not set prior
   else
    {
      Print(user_feedback(1));
      return(-1e10);
     }
  }

Nosso interesse também está na variação dos valores do RSI. Não é suficiente termos acesso apenas à leitura atual do RSI. Também queremos acesso à mudança nos níveis de RSI que ocorre em qualquer tamanho de janela arbitrário que especificarmos. Como antes, simplesmente chamamos as funções CopyBuffer para o usuário nos bastidores para calcular o crescimento nos níveis de RSI, mas a classe também inclui uma verificação adicional para garantir que a saída do cálculo não seja um vetor de 0 antes de retornar a resposta encontrada ao usuário.

//--- Let's set the conditions for our differenced data
 bool              SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series)
   {
    //--- Internal variables
    rsi_differenced_buffer_size = buffer_size;
    rsi_differenced_values = vector::Zeros(rsi_differenced_buffer_size);

    //--- Prepare to record the differences in our RSI readings
    double temp_buffer[];
    int fetch = (rsi_differenced_buffer_size + (2 * differencing_period));
    CopyBuffer(rsi_handler,0,0,fetch,temp_buffer);
    if(set_as_series)
       ArraySetAsSeries(temp_buffer,true);

    //--- Fill in our values iteratively
    for(int i = rsi_differenced_buffer_size;i > 1; i--)
      {
       rsi_differenced_values[i-1] = temp_buffer[i-1] - temp_buffer[i-1+differencing_period];
      }

    //--- If the norm of a vector is 0, the vector is empty!
    if(rsi_differenced_values.Norm(VECTOR_NORM_P) != 0)
      {
       Print(user_feedback(2));
       indicator_differenced_values_initialized = true;
       return(true);
      }

    indicator_differenced_values_initialized = false;
    Print(user_feedback(3));
    return(false);
   }

Por fim, precisamos de um método para obter o valor diferenciado do RSI em um índice específico. Novamente, nossa função garante que o usuário não esteja tentando acessar além do intervalo do buffer copiado. Nesse caso, o usuário deve primeiro atualizar o tamanho do buffer e, em seguida, copiar o valor do índice desejado adequadamente.

//--- Get a differenced value at a specific index
double            GetDifferencedReadingAt(int index)
  {
   //--- Make sure we're not trying to call values beyond our index
   if(index > rsi_differenced_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

   //--- Make sure our values have been set
   if(!indicator_differenced_values_initialized)
     {

      //--- The user is trying to use values before they were set in memory
      Print(user_feedback(1));
      return(-1e10);
     }

   //--- Return the differenced value of our indicator at a specific index
   if((indicator_differenced_values_initialized) && (index < rsi_differenced_buffer_size))
      return(rsi_differenced_values[index]);

   //--- Something went wrong.
   return(-1e10);
  }
};

Construir o restante do nosso teste é direto. Controlaremos manualmente uma instância semelhante do indicador RSI, inicializada com as mesmas configurações. Se escrevermos ambas as leituras no mesmo arquivo, devemos observar informações duplicadas. Caso contrário, teremos cometido um erro na implementação da classe.

//--- How far we want to forecast
#define HORIZON 10

//--- Our handlers for our indicators
int rsi_5_handle;

//--- Data structures to store the readings from our indicators
double rsi_5_reading[];

//--- File name
string file_name = Symbol() + " Testing RSI Class.csv";

//--- Amount of data requested
input int size = 3000;

Para o restante do nosso script, precisamos apenas inicializar nossa classe RSI e configurá-la com os mesmos parâmetros que usaremos com uma versão duplicada, porém controlada manualmente, do RSI.

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Testing the RSI Class
//--- Initialize the class
   RSI my_rsi(Symbol(),PERIOD_CURRENT,5,PRICE_CLOSE);
   my_rsi.SetIndicatorValues(size,true);
   my_rsi.SetDifferencedIndicatorValues(size,10,true);

//---Setup our technical indicators
   rsi_5_handle  = iRSI(Symbol(),PERIOD_CURRENT,5,PRICE_CLOSE);
   int fetch = size + (2 * HORIZON);

//---Set the values as series
   CopyBuffer(rsi_5_handle,0,0,fetch,rsi_5_reading);
   ArraySetAsSeries(rsi_5_reading,true);

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","RSI 5","RSI 5 Class","RSI 5 Difference","RSI 5 Class Difference");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   rsi_5_reading[i],
                   my_rsi.GetReadingAt(i),
                   rsi_5_reading[i]  - rsi_5_reading[i + HORIZON],
                   my_rsi.GetDifferencedReadingAt(i)
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+
#undef  HORIZON

Se nossa classe tiver sido implementada corretamente, nosso script de teste produzirá um arquivo intitulado "EURUSD Testing RSI Class" que conterá leituras de RSI duplicadas. Como pode ser visto na Fig 3, nossa classe RSI passou no teste. Essa classe nos economiza tempo, evitando a necessidade de implementar os mesmos métodos repetidamente em vários projetos. Podemos simplesmente importar nossa classe RSI e chamar os métodos de que precisamos.

Fig 3: Nossa classe RSI passou no teste, utilizá-la é idêntico a trabalhar manualmente com a classe do indicador RSI.

Agora que estamos confiantes na implementação da classe, vamos escrevê-la em um arquivo include dedicado, para que possamos compartilhar a classe com nosso Expert Advisor e quaisquer outros exercícios que realizarmos no futuro que possam exigir o mesmo conjunto de funcionalidades. Quando completamente composta, é assim que nossa classe se apresenta em sua forma atual.

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

//+------------------------------------------------------------------+
//| This class will provide us with usefull functionality            |
//+------------------------------------------------------------------+
class RSI
  {
private:
   //--- Have the indicator values been copied to the buffer?
   bool              indicator_values_initialized;
   bool              indicator_differenced_values_initialized;

   //--- Give the user feedback
   string            user_feedback(int flag);

protected:
   //--- The handler for our RSI
   int               rsi_handler;
   //--- The Symbol our RSI should be applied on
   string            rsi_symbol;
   //--- Our RSI period
   int               rsi_period;
   //--- How far into the future we wish to forecast
   int               forecast_horizon;
   //--- The buffer for our RSI indicator
   double            rsi_reading[];
   vector            rsi_differenced_values;
   //--- The current size of the buffer the user last requested
   int               rsi_buffer_size;
   int               rsi_differenced_buffer_size;
   //--- The time frame our RSI should be applied on
   ENUM_TIMEFRAMES   rsi_time_frame;
   //--- The price should the RSI be applied on
   ENUM_APPLIED_PRICE rsi_price;

public:
                     RSI();
                     RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price);
                    ~RSI();
   bool              SetIndicatorValues(int buffer_size,bool set_as_series);
   bool              IsValid(void);
   double            GetCurrentReading(void);
   double            GetReadingAt(int index);
   bool              SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series);
   double            GetDifferencedReadingAt(int index);
  };
//+------------------------------------------------------------------+
//| Our default constructor for our RSI class                        |
//+------------------------------------------------------------------+
void RSI::RSI()
  {
   indicator_values_initialized = false;
   rsi_symbol                   = "EURUSD";
   rsi_time_frame               = PERIOD_D1;
   rsi_period                   = 5;
   rsi_price                    = PRICE_CLOSE;
   rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
//--- Give the user feedback on initilization
   Print(user_feedback(0));
//--- Remind the user they called the default constructor
   Print("Default Constructor Called: ",__FUNCSIG__," ",&this);
  }

//+------------------------------------------------------------------+
//| Our parametric constructor for our RSI class                     |
//+------------------------------------------------------------------+
void RSI::RSI(string user_symbol,ENUM_TIMEFRAMES user_time_frame,int user_period,ENUM_APPLIED_PRICE user_price)
  {
   indicator_values_initialized = false;
   rsi_symbol                   = user_symbol;
   rsi_time_frame               = user_time_frame;
   rsi_period                   = user_period;
   rsi_price                    = user_price;
   rsi_handler                  = iRSI(rsi_symbol,rsi_time_frame,rsi_period,rsi_price);
//--- Give the user feedback on initilization
   Print(user_feedback(0));
  }

//+------------------------------------------------------------------+
//| Our destructor for our RSI class                                 |
//+------------------------------------------------------------------+
void RSI::~RSI()
  {
//--- Free up resources we don't need and reset our flags
   if(IndicatorRelease(rsi_handler))
     {
      indicator_differenced_values_initialized = false;
      indicator_values_initialized = false;
      Print(user_feedback(5));
     }
  }

//+------------------------------------------------------------------+
//| Get our current reading from the RSI indicator                   |
//+------------------------------------------------------------------+
double RSI::GetCurrentReading(void)
  {
   double temp[];
   CopyBuffer(this.rsi_handler,0,0,1,temp);
   return(temp[0]);
  }

//+------------------------------------------------------------------+
//| Set our indicator values and our buffer size                     |
//+------------------------------------------------------------------+
bool              RSI::SetIndicatorValues(int buffer_size,bool set_as_series)
  {
   rsi_buffer_size = buffer_size;
   CopyBuffer(this.rsi_handler,0,0,buffer_size,rsi_reading);
   if(set_as_series)
      ArraySetAsSeries(this.rsi_reading,true);
   indicator_values_initialized = true;

//--- Did something go wrong?
   vector rsi_test;
   rsi_test.CopyIndicatorBuffer(rsi_handler,0,0,buffer_size);
   if(rsi_test.Sum() == 0)
      return(false);

//--- Everything went fine.
   return(true);
  }
//+--------------------------------------------------------------+
//| Let's set the conditions for our differenced data            |
//+--------------------------------------------------------------+
bool              RSI::SetDifferencedIndicatorValues(int buffer_size,int differencing_period,bool set_as_series)
  {
//--- Internal variables
   rsi_differenced_buffer_size = buffer_size;
   rsi_differenced_values = vector::Zeros(rsi_differenced_buffer_size);

//--- Prepare to record the differences in our RSI readings
   double temp_buffer[];
   int fetch = (rsi_differenced_buffer_size + (2 * differencing_period));
   CopyBuffer(rsi_handler,0,0,fetch,temp_buffer);
   if(set_as_series)
      ArraySetAsSeries(temp_buffer,true);

//--- Fill in our values iteratively
   for(int i = rsi_differenced_buffer_size;i > 1; i--)
     {
      rsi_differenced_values[i-1] = temp_buffer[i-1] - temp_buffer[i-1+differencing_period];
     }

//--- If the norm of a vector is 0, the vector is empty!
   if(rsi_differenced_values.Norm(VECTOR_NORM_P) != 0)
     {
      Print(user_feedback(2));
      indicator_differenced_values_initialized = true;
      return(true);
     }

   indicator_differenced_values_initialized = false;
   Print(user_feedback(3));
   return(false);
  }

 //--- Get a differenced value at a specific index
   double            RSI::GetDifferencedReadingAt(int index)
     {
      //--- Make sure we're not trying to call values beyond our index
      if(index > rsi_differenced_buffer_size)
        {
         Print(user_feedback(4));
         return(-1e10);
        }

      //--- Make sure our values have been set
      if(!indicator_differenced_values_initialized)
        {

         //--- The user is trying to use values before they were set in memory
         Print(user_feedback(1));
         return(-1e10);
        }

      //--- Return the differenced value of our indicator at a specific index
      if((indicator_differenced_values_initialized) && (index < rsi_differenced_buffer_size))
         return(rsi_differenced_values[index]);

      //--- Something went wrong.
      return(-1e10);
     }

//+------------------------------------------------------------------+
//| Get a reading at a specific index from our RSI buffer            |
//+------------------------------------------------------------------+
double            RSI::GetReadingAt(int index)
  {
//--- Is the user trying to call indexes beyond the buffer?
   if(index > rsi_buffer_size)
     {
      Print(user_feedback(4));
      return(-1e10);
     }

//--- Get the reading at the specified index
   if((indicator_values_initialized) && (index < rsi_buffer_size))
      return(rsi_reading[index]);

//--- User is trying to get values that were not set prior
   else
     {
      Print(user_feedback(1));
      return(-1e10);
     }
  }

//+------------------------------------------------------------------+
//| Check if our indicator handler is valid                          |
//+------------------------------------------------------------------+
bool RSI::IsValid(void)
  {
   return((this.rsi_handler != INVALID_HANDLE));
  }

//+------------------------------------------------------------------+
//| Give the user feedback on the actions he is performing           |
//+------------------------------------------------------------------+
string RSI::user_feedback(int flag)
  {
   string message;

//--- Check if the RSI indicator loaded correctly
   if(flag == 0)
     {
      //--- Check the indicator was loaded correctly
      if(IsValid())
         message = "RSI Indicator Class Loaded Correcrtly \nSymbol: " + (string) rsi_symbol + "\nPeriod: " + (string) rsi_period;
      return(message);
      //--- Something went wrong
      message = "Error loading RSI Indicator: [ERROR] " + (string) GetLastError();
      return(message);
     }

//--- User tried getting indicator values before setting them
   if(flag == 1)
     {
      message = "Please set the indicator values before trying to fetch them from memory, call SetIndicatorValues()";
      return(message);
     }

//--- We sueccessfully set our differenced indicator values
   if(flag == 2)
     {
      message = "Succesfully set differenced indicator values.";
      return(message);
     }

//--- Failed  to set our differenced indicator values
   if(flag == 3)
     {
      message = "Failed to set our differenced indicator values: [ERROR] " + (string) GetLastError();
      return(message);
     }

//--- The user is trying to retrieve an index beyond the buffer size and must update the buffer size first
   if(flag == 4)
     {
      message = "The user is attempting to use call an index beyond the buffer size, update the buffer size first";
      return(message);
     }

//--- The class has been deactivated by the user
   if(flag == 5)
     {
      message = "Goodbye.";
      return(message);
     }

//--- No feedback
   else
      return("");
  }
//+------------------------------------------------------------------+

Agora vamos obter os dados de mercado de que precisamos usando uma coleção de instâncias da nossa classe RSI. Armazenaremos ponteiros para cada instância da nossa classe em um array do tipo personalizado que definimos. O MQL5 nos permite gerar objetos automaticamente conforme necessário. No entanto, essa flexibilidade vem ao custo de sempre precisarmos limpar os recursos posteriormente para evitar problemas relacionados a vazamento de memória.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define HORIZON 10

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
RSI *my_rsi_array[14];
string file_name = Symbol() + " RSI Algorithmic Input Selection.csv";

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- How much data should we store in our indicator buffer?
   int fetch = size + (2 * HORIZON);

//--- Store pointers to our RSI objects
   for(int i = 0; i <= 13; i++)
     {
      //--- Create an RSI object
      my_rsi_array[i] = new RSI(Symbol(),PERIOD_CURRENT,((i+1) * 5),PRICE_CLOSE);
      //--- Set the RSI buffers
      my_rsi_array[i].SetIndicatorValues(fetch,true);
      my_rsi_array[i].SetDifferencedIndicatorValues(fetch,HORIZON,true);
     }

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","True Close","Open","High","Low","Close","RSI 5","RSI 10","RSI 15","RSI 20","RSI 25","RSI 30","RSI 35","RSI 40","RSI 45","RSI 50","RSI 55","RSI 60","RSI 65","RSI 70","Diff RSI 5","Diff RSI 10","Diff RSI 15","Diff RSI 20","Diff RSI 25","Diff RSI 30","Diff RSI 35","Diff RSI 40","Diff RSI 45","Diff RSI 50","Diff RSI 55","Diff RSI 60","Diff RSI 65","Diff RSI 70");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iLow(_Symbol,PERIOD_CURRENT,i) - iLow(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   iClose(_Symbol,PERIOD_CURRENT,i) - iClose(Symbol(),PERIOD_CURRENT,i + HORIZON),
                   my_rsi_array[0].GetReadingAt(i),
                   my_rsi_array[1].GetReadingAt(i),
                   my_rsi_array[2].GetReadingAt(i),
                   my_rsi_array[3].GetReadingAt(i),
                   my_rsi_array[4].GetReadingAt(i),
                   my_rsi_array[5].GetReadingAt(i),
                   my_rsi_array[6].GetReadingAt(i),
                   my_rsi_array[7].GetReadingAt(i),
                   my_rsi_array[8].GetReadingAt(i),
                   my_rsi_array[9].GetReadingAt(i),
                   my_rsi_array[10].GetReadingAt(i),
                   my_rsi_array[11].GetReadingAt(i),
                   my_rsi_array[12].GetReadingAt(i),
                   my_rsi_array[13].GetReadingAt(i),
                   my_rsi_array[0].GetDifferencedReadingAt(i),
                   my_rsi_array[1].GetDifferencedReadingAt(i),
                   my_rsi_array[2].GetDifferencedReadingAt(i),
                   my_rsi_array[3].GetDifferencedReadingAt(i),
                   my_rsi_array[4].GetDifferencedReadingAt(i),
                   my_rsi_array[5].GetDifferencedReadingAt(i),
                   my_rsi_array[6].GetDifferencedReadingAt(i),
                   my_rsi_array[7].GetDifferencedReadingAt(i),
                   my_rsi_array[8].GetDifferencedReadingAt(i),
                   my_rsi_array[9].GetDifferencedReadingAt(i),
                   my_rsi_array[10].GetDifferencedReadingAt(i),
                   my_rsi_array[11].GetDifferencedReadingAt(i),
                   my_rsi_array[12].GetDifferencedReadingAt(i),
                   my_rsi_array[13].GetDifferencedReadingAt(i)
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);

//--- Delete our RSI object pointers
   for(int i = 0; i <= 13; i++)
     {
      delete my_rsi_array[i];
     }
  }
//+------------------------------------------------------------------+
#undef HORIZON


Analisando os Dados em Python

Agora podemos começar a analisar os dados que coletamos do nosso Terminal MetaTrader 5. Nosso primeiro passo será carregar as bibliotecas padrão de que precisamos.

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import plotly

Agora leia os dados de mercado. Além disso, vamos criar um indicador para denotar se o nível de preço atual é maior ou menor do que o preço registrado há 10 dias. Lembre-se de que 10 dias é o mesmo período que utilizamos em nosso script para calcular a variação nos níveis de RSI.

#Let's read in our market data
data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
data['Bull'] = np.NaN

data.loc[data['True Close'] > data['True Close'].shift(10),'Bull'] = 1
data.loc[data['True Close'] < data['True Close'].shift(10),'Bull'] = 0

data.dropna(inplace=True)
data.reset_index(inplace=True,drop=True)

Também precisamos calcular os retornos reais do mercado.

#Estimate the market returns
#Define our forecast horizon
HORIZON = 10
data['Target'] = 0

data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.loc[data['Return'] > 0,'Target'] = 1

#Drop missing values
data.dropna(inplace=True)

Mais importante ainda, precisamos excluir todos os dados que se sobrepõem ao nosso período de backtest.

#No cheating boys.
_ = data.iloc[((-365 * 3) + 95):,:]
data = data.iloc[:((-365 * 3) + 95),:]
data

Fig 4: Nosso conjunto de dados não contém mais nenhuma das datas que se sobrepõem ao nosso período de backtest.

Podemos visualizar a distribuição dos retornos de mercado de 10 dias do EURUSD e rapidamente perceber que os retornos estão concentrados em torno de 0. Essa forma geral da distribuição não é surpresa e não é exclusiva do par EURUSD.

plt.title('Distribution of EURUSD 10 Day Returns')
plt.grid()
sns.histplot(data['Return'],color='black')

Fig 5: Visualizando a distribuição dos retornos de 10 dias do EURUSD.

Este exercício nos oferece uma oportunidade única de visualizar a diferença entre a distribuição dos níveis de RSI em um período curto e em um período longo. As linhas verticais tracejadas em vermelho marcam os níveis padronizados de 30 e 70. As barras pretas marcam a distribuição dos níveis de RSI quando o período é definido como 5. Podemos ver que o RSI de período 5 gerará muitos sinais além dos níveis padronizados. No entanto, as barras brancas representam a distribuição dos níveis de RSI quando o período é definido como 70. Podemos visualizar que quase nenhum sinal será gerado. É essa mudança na forma da distribuição que torna desafiador para traders algorítmicos sempre seguirem as "melhores práticas" ao utilizar um indicador.

plt.title('Comapring The Distribution of RSI Changes Across Different RSI Periods')
sns.histplot(data['RSI 5'],color='black')
sns.histplot(data['RSI 70'],color='white')
plt.xlabel('RSI Level')
plt.legend(['RSI 5','RSI 70'])
plt.axvline(30,color='red',linestyle='--')
plt.axvline(70,color='red',linestyle='--')
plt.grid()

Fig 6: Comparando a distribuição dos níveis de RSI em diferentes períodos de RSI.

Criar um gráfico de dispersão com a variação de 10 períodos no RSI de período 60 tanto no eixo x quanto no eixo y nos permite visualizar se existe uma relação entre a variação do indicador e o alvo. Parece que uma variação de 10 níveis de RSI pode ser um sinal de trading razoável para buscar posições vendidas se a leitura do RSI cair 10 níveis. Ou entrar em posições compradas se o nível do RSI aumentar em 10.

plt.title('Scatter Plot of 10 Day Change in 50 Period RSI & EURUSD 10 Day Return')
sns.scatterplot(data,y='Diff RSI 60',x='Diff RSI 60',hue='Target')
plt.xlabel('50 Period RSI')
plt.ylabel('50 Period RSI')
plt.grid()
plt.axvline(-10,color='red',linestyle='--')
plt.axvline(10,color='red',linestyle='--')

Fig 7: Visualizando a relação entre a variação no RSI de período 60 e o alvo.

Tentar combinar os sinais gerados por variações no RSI com diferentes períodos pode parecer uma ideia razoável. No entanto, parece contribuir pouco para uma melhor separação das nossas duas classes de interesse.

plt.title('Scatter Plot of 10 Day Change in 5 Period RSI & EURUSD 10 Day Return')
sns.scatterplot(data,y='Diff RSI 60',x='Diff RSI 5',hue='Target')
plt.xlabel('5 Period RSI')
plt.ylabel('5 Period RSI')
plt.grid()


Fig 8: Parece que simplesmente usar indicadores RSI com diferentes períodos é uma fonte fraca de confirmação.

Temos 14 indicadores RSI diferentes para escolher. Em vez de executar 14 backtests para decidir qual período pode nos servir melhor, podemos estimar o período ótimo avaliando o desempenho de um modelo treinado com todos os 14 indicadores RSI como entradas e, em seguida, avaliando a importância das variáveis que o modelo estatístico aprendeu a partir dos dados com os quais foi treinado. 

Observe que sempre temos a opção de empregar modelos estatísticos para precisão preditiva ou para interpretações e insights. Hoje estamos realizando o segundo caso. Ajustaremos um modelo Ridge nas diferenças de todos os 14 indicadores RSI como entradas. O modelo Ridge possui seus próprios parâmetros de ajuste. Portanto, realizaremos uma busca em grade sobre o espaço de entrada para o modelo estatístico Ridge. Em particular, estaremos ajustando os parâmetros:

  • Alpha: O modelo Ridge requer uma penalização adicionada para controlar os coeficientes do modelo. 
  • Tolerance: Determina a menor mudança necessária para definir condições de parada / outras condições para sub-rotinas, dependendo do solucionador selecionado pelo usuário.

Para nossa discussão, utilizaremos um modelo Ridge com o solucionador 'sparse_cg'. O leitor também pode considerar ajustar o modelo, se desejar.

O modelo Ridge é particularmente útil para nós porque ele reduz seus coeficientes em direção a 0 para diminuir a perda do modelo. Portanto, buscaremos um amplo espaço de configurações iniciais para nosso modelo e, em seguida, focaremos nas configurações que produziram o menor erro. A configuração dos pesos do modelo em seu melhor desempenho pode nos informar rapidamente qual período de RSI foi mais relevante para o modelo. Em nosso exemplo específico, foi a variação de 10 períodos no RSI de período 55 que obteve os maiores coeficientes no melhor modelo.

#Set the max levels we wish to check
ALPHA_LEVELS = 10
TOL_LEVELS   = 10

#DataFrame labels
r_c = 'TOL_LEVEL_'
r_r = 'ALHPA_LEVEL_'

results_columns = []
results_rows = []

for c in range(TOL_LEVELS):
    n_c = r_c + str(c)
    n_r = r_r + str(c)
    results_columns.append(n_c)
    results_rows.append(n_r)

#Create a DataFrame to store our results
results = pd.DataFrame(columns=results_columns,index=results_rows)

#Cross validate our model
for i in range(TOL_LEVELS):
    tol = 10 ** (-i)
    error = []
    for j in range(ALPHA_LEVELS):
        #Set alpha
        alpha = 10 ** (-j)
        
        #Its good practice to generally check the 0 case
        if(i == 0 & j == 0):
            model = Ridge(alpha=j,tol=i,solver='sparse_cg')

        #Otherwise use a float
        model = Ridge(alpha=alpha,tol=tol,solver='sparse_cg')

        #Store the error levels
        error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Diff RSI 5',
       'Diff RSI 10', 'Diff RSI 15', 'Diff RSI 20', 'Diff RSI 25',
       'Diff RSI 30', 'Diff RSI 35', 'Diff RSI 40', 'Diff RSI 45',
       'Diff RSI 50', 'Diff RSI 55', 'Diff RSI 60', 'Diff RSI 65',
       'Diff RSI 70',]],data['Return'],cv=tscv))))
    
    #Record the error levels
    results.iloc[:,i] = error
    
results
    

A tabela abaixo resume nossos resultados. Observamos que nossas menores taxas de erro foram obtidas quando os parâmetros iniciais do modelo foram ambos definidos como 0.

Parâmetro de Ajuste  Erro do Modelo
ALHPA_LEVEL_0     0.053509  
ALHPA_LEVEL_1     0.056245
ALHPA_LEVEL_2     0.060158
ALHPA_LEVEL_3     0.062230
ALHPA_LEVEL_4     0.061521
ALHPA_LEVEL_5     0.064312
ALHPA_LEVEL_6     0.073248
ALHPA_LEVEL_7     0.079310
ALHPA_LEVEL_8    0.081914
ALHPA_LEVEL_9     0.085171

Também é possível visualizar nossos resultados usando um gráfico de contorno. Queremos usar modelos na região do gráfico associada a baixo erro, as regiões em azul. Esses são nossos modelos de melhor desempenho até o momento. Agora vamos visualizar o tamanho de cada coeficiente no modelo. O maior coeficiente será atribuído à entrada da qual o modelo mais dependeu.

import plotly.graph_objects as go

fig = go.Figure(data =
    go.Contour(
        z=results,
        colorscale='bluered'
    ))

fig.update_layout(
    width = 600,
    height = 400,
    title='Contour Plot Of Our Error Forecasting EURUSD Using Grid Search '
)

fig.show()

Fig 9: Encontramos configurações de entrada ótimas para nosso modelo Ridge prevendo o retorno de 10 dias do EURUSD.

Ao plotar os dados visualmente, podemos ver rapidamente que o coeficiente associado ao RSI de período 55 recebeu o maior valor absoluto. Isso nos dá alguma confiança para restringir nosso foco a essa configuração específica de RSI.

#Let's visualize the importance of each column
model = Ridge(alpha=0,tol=0,solver='sparse_cg')

model.fit(data.loc[:,['Diff RSI 5',
       'Diff RSI 10', 'Diff RSI 15', 'Diff RSI 20', 'Diff RSI 25',
       'Diff RSI 30', 'Diff RSI 35', 'Diff RSI 40', 'Diff RSI 45',
       'Diff RSI 50', 'Diff RSI 55', 'Diff RSI 60', 'Diff RSI 65',
       'Diff RSI 70',]],data['Return'])

#Clearly our model relied on the 25 Period RSI the most, from all the data it had available at training
sns.barplot(np.abs(model.coef_),color='black')
plt.title('Rleative Feature Importance')
plt.ylabel('Coefficient Value')
plt.xlabel('Coefficient Index')
plt.grid()

Fig 10: O coeficiente associado à diferença no RSI de período 55 recebeu o maior valor.

Agora que identificamos nosso período de interesse, vamos também avaliar como o erro do nosso modelo muda à medida que percorremos níveis crescentes de RSI em passos de 10. Criaremos 3 colunas adicionais em nosso dataframe. A Coluna 1 terá valor 1 se a leitura do RSI for maior que o primeiro valor que queremos verificar. Caso contrário, se a leitura do RSI for menor que um determinado valor que desejamos verificar, a Coluna 2 terá valor 1. Em qualquer outro caso, a Coluna 3 terá valor 1.

def objective(x):
    data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
    data['0'] = 0
    data['1'] = 0
    data['2'] = 0
    HORIZON = 10
    data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
    data.dropna(subset=['Return'],inplace=True)
    data.iloc[data['Diff RSI 55'] > x[0],12] = 1
    data.iloc[data['Diff RSI 55'] < x[1],13] = 1
    data.iloc[(data['Diff RSI 55'] < x[0]) & (data['RSI 55'] > x[1]),14] = 1
    #Calculate or RMSE When using those levels
    model = Ridge(alpha=0,tol=0,solver='sparse_cg')
    error = np.mean(np.abs(cross_val_score(model,data.iloc[:,12:15],data['Return'],cv=tscv)))
    return(error)

Vamos avaliar o erro produzido pelo nosso modelo se definirmos 0 como nosso nível crítico.

#Bad rules for using the RSI
objective([0,0])

0.026897725573317266

Se inserirmos os níveis 70 e 30 em nossa função, nosso erro aumenta. Realizaremos uma busca em grade em passos de 10 para encontrar variações nos níveis de RSI mais adequadas para o RSI de período 55. Nossos resultados mostraram que o nível ótimo de variação parece estar próximo de 10 níveis de RSI.

#Bad rules for using the RSI
objective([70,30])

0.031258730612736006

LEVELS  = 10
results = []

for i in np.arange(0,(LEVELS)):
    results.append(objective([i * 10,-(i * 10)]))

plt.plot(results,color='black')
plt.ylabel('Error Rate')
plt.xlabel('Change in RSI as multiples of 10')
plt.grid()
plt.scatter(results.index(min(results)),min(results),color='red')
plt.title('Measuring The Strength of Changes In RSI Levels')

Fig 11: Visualizando o nível ótimo de variação em nosso indicador RSI.

Vamos agora realizar outra busca mais refinada, no intervalo de variações de RSI entre 0 e 20. Após uma análise mais detalhada, observamos que o ótimo real parece estar no valor 9. No entanto, não realizamos esse tipo de exercício para ajustar perfeitamente os dados históricos, isso é chamado de overfitting e é uma má prática. Nosso objetivo não é realizar um exercício de ajuste de curva. Em vez de adotar o valor ótimo exatamente onde ele aparece em nossa análise dos dados históricos, reconhecemos que a localização do ótimo pode mudar e, em vez disso, buscamos estar dentro de uma certa fração de um desvio padrão em relação ao valor ótimo em ambos os lados, para servir como nossos intervalos de confiança.

LEVELS  = 20
coef = 0.5
results = []

for i in np.arange(0,(LEVELS),1):
    results.append(objective([i,-(i)]))

plt.plot(results)
plt.ylabel('Error Rate')
plt.xlabel('Change in RSI')
plt.grid()
plt.scatter(results.index(min(results)),min(results),color='red')
plt.title('Measuring The Strength of Changes In RSI Levels')


plt.axvline(results.index(min(results)),color='red')
plt.axvline(results.index(min(results)) - (coef * np.std(data['Diff RSI 55'])),linestyle='--',color='red')
plt.axvline(results.index(min(results)) + (coef * np.std(data['Diff RSI 55'])),linestyle='--',color='red')

Fig 12: Visualizando a taxa de erro associada à definição de diferentes limiares para variações nos níveis de RSI.

Podemos visualizar a região que acreditamos ser ótima sobreposta à distribuição histórica das variações no RSI.

sns.histplot(data['Diff RSI 55'],color='black')
coef = 0.5
plt.axvline((results.index(min(results))),linestyle='--',color='red')
plt.axvline(results.index(min(results)) - (coef * np.std(data['Diff RSI 55'])),color='red')
plt.axvline(results.index(min(results)) + (coef * np.std(data['Diff RSI 55'])),color='red')
plt.axvline(-(results.index(min(results))),linestyle='--',color='red')
plt.axvline(-(results.index(min(results)) - (coef * np.std(data['Diff RSI 55']))),color='red')
plt.axvline(-(results.index(min(results)) + (coef * np.std(data['Diff RSI 55']))),color='red')
plt.title("Visualizing our Optimal Point in The Distribution")

Fig 13: Visualizando as regiões ótimas que selecionamos para nossos sinais de trading com RSI.

Vamos obter os valores da nossa estimativa de bons intervalos de confiança; estes servirão como os valores críticos em nosso Expert Advisor que acionam sinais de compra e venda.

results.index(min(results)) + ( coef * np.std(data['Diff RSI 55']))

10.822857254027287

E nosso limite inferior.

results.index(min(results)) - (coef * np.std(data['Diff RSI 55']))

7.177142745972713

Vamos obter uma explicação do nosso modelo Ridge para interpretar o indicador RSI de uma forma que talvez não tivéssemos considerado intuitivamente.

def explanation(x):
    data = pd.read_csv("EURUSD RSI Algorithmic Input Selection.csv")
    data['0'] = 0
    data['1'] = 0
    data['2'] = 0
    HORIZON = 10
    data['Return'] = data['True Close'].shift(-HORIZON) - data['True Close']
    data.dropna(subset=['Return'],inplace=True)
    data.iloc[data['Diff RSI 55'] > x[0],12] = 1
    data.iloc[data['Diff RSI 55'] < x[1],13] = 1
    data.iloc[(data['Diff RSI 55'] < x[0]) & (data['RSI 55'] > x[1]),14] = 1
    #Calculate or RMSE When using those levels
    model = Ridge(alpha=0,tol=0,solver='sparse_cg')
    model.fit(data.iloc[:,12:15],data['Return'])
    return(model.coef_.copy())

Observamos que, quando o indicador RSI varia mais do que 9, nosso valor ótimo, nosso modelo aprendeu coeficientes positivos, o que implica que devemos entrar em posições compradas. Caso contrário, o modelo afirma que devemos vender em quaisquer outras condições. 

opt = 9

print(explanation([opt,-opt]))

[ 1.97234840e-04 -1.64215118e-04 -7.55222156e-05]


Construindo Nosso Expert Advisor

Assumindo que nosso conhecimento do passado é um bom modelo do futuro, podemos construir uma aplicação para negociar o EURUSD de forma lucrativa usando o que aprendemos sobre o mercado? Para começar, primeiro definiremos constantes importantes do sistema que precisaremos ao longo do programa e em quaisquer outras versões que possamos construir.

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.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 RSI_PERIOD 55
#define RSI_TIME_FRAME PERIOD_D1
#define SYSTEM_TIME_FRAME PERIOD_D1
#define RSI_PRICE  PRICE_CLOSE
#define RSI_BUFFER_SIZE 20
#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)

Vamos carregar nossas bibliotecas.

//+------------------------------------------------------------------+
//| Load our RSI library                                             |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <Trade\Trade.mqh>

Precisaremos de algumas variáveis globais.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
RSI    rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
double last_value;
int    count;
int    ma_o_handler,ma_c_handler;
double ma_o[],ma_c[];
double trade_sl;

Nossos manipuladores de eventos chamarão cada um seu próprio método dedicado para lidar com os subprocessos necessários para concluir suas tarefas.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

A função de atualização atualiza todas as variáveis do sistema e verifica se devemos abrir uma posição ou gerenciar posições abertas.

//+------------------------------------------------------------------+
//| Update our system variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_c_handler,0,0,1,ma_c);
      CopyBuffer(ma_o_handler,0,0,1,ma_o);

      if((count == 0) && (PositionsTotal() == 0))
        {
         rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true);
         last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);
         count = 1;
        }

      if(PositionsTotal() == 0)
         check_signal();

      if(PositionsTotal() > 0)
         manage_setup();
     }
  }

Nossas posições precisam ter seus stop losses constantemente ajustados para garantir que reduzamos nossos níveis de risco sempre que possível. 

//+------------------------------------------------------------------+
//| Manage our open trades                                           |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl);
      double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl);
      //--- Buy setup
      if((current_tp > current_sl) && (new_sl < current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);

      //--- Sell setup
      if((current_tp < current_sl) && (new_sl > current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);
     }
  }

Nossa função de configuração é responsável por iniciar o sistema desde o zero. Ela preparará os indicadores de que precisamos e redefinirá nossos contadores.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_OPEN);
   count        = 0;
   last_value   = 0;
   trade_sl     = 1.5e-2;
  }

Por fim, nossas regras de trading foram geradas graças aos valores dos coeficientes que nosso modelo aprendeu a partir dos dados de treinamento. Esta é a última função que definiremos antes de removermos as variáveis do sistema que criamos no início do programa.

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   double current_reading = rsi_55.GetCurrentReading();
   Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading));
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double cp_lb = 7.17;
   double cp_ub = 10.82;

   if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb))))
     {
      if(ma_o[0] > ma_c[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl));
            count = 0;
           }
        }
     }

   if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub))
     {
      if(ma_c[0] < ma_o[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
            count = 0;
           }
        }
     }
  }
//+------------------------------------------------------------------+

#undef RSI_BUFFER_SIZE
#undef RSI_PERIOD
#undef RSI_PRICE
#undef RSI_TIME_FRAME
#undef SYSTEM_TIME_FRAME
#undef TRADING_VOLUME
//+------------------------------------------------------------------+

Vamos agora iniciar o backtest do sistema de trading. Lembre-se de que na Fig 4 excluímos todos os dados que se sobrepunham ao nosso backtest. Portanto, isso pode servir como uma boa aproximação de como nossa estratégia pode se comportar em tempo real. Realizaremos um backtest de 3 anos da nossa estratégia em dados diários, de 1 de janeiro de 2022 até março de 2025.

Nossas datas

Fig 14: As datas que utilizaremos para o backtest da estratégia de trading.

Para obter os melhores resultados, sempre usamos a opção "Cada tick baseado em ticks reais", pois ela oferece a simulação mais precisa das condições passadas do mercado, com base nos ticks históricos coletados pelo Terminal MetaTrader 5 junto à sua corretora.

Fig 15: As condições sob as quais realizamos nossos testes são muito importantes e alteram a lucratividade da nossa estratégia.

A curva de equity produzida pelo nosso testador de estratégia parece promissora. Mas vamos interpretar as estatísticas detalhadas juntos para obter uma visão completa do desempenho da estratégia.

Fig 16: A curva de equity produzida pela estratégia de trading EURUSD com RSI de período 55.

Executar nossa estratégia produziu as seguintes estatísticas em nosso testador:

  • Índice de Sharpe: 0,92
  • Retorno Esperado: 2,49
  • Lucro Líquido Total: US$ 151,87
  • Operações Lucrativas: 57,38%

No entanto, observe que, das 61 operações totais realizadas, apenas 4 foram operações de compra. Por que nossa estratégia está tão desproporcionalmente enviesada para vendas? Como podemos corrigir esse viés? Vamos observar novamente nosso gráfico EURUSD e tentar entender isso juntos.

Nosso backtest

Fig 17: As estatísticas detalhadas do desempenho histórico do nosso Expert Advisor na taxa de câmbio diária EURUSD.


Melhorando Nosso Expert Advisor

Na Fig 18, anexei uma captura de tela da taxa de câmbio mensal do EURUSD. As duas linhas verticais vermelhas marcam o início do ano de 2009 e o fim do ano de 2021, respectivamente. A linha vertical verde representa o início dos dados de treinamento que usamos para nosso modelo estatístico. É possível perceber rapidamente por que o modelo aprendeu um viés para posições vendidas, dado o prolongado movimento de baixa que começou em 2008.

Fig 18: Entendendo por que nosso modelo aprendeu um viés tão baixista.

Nem sempre precisamos de mais dados históricos para tentar corrigir isso. Em vez disso, podemos ajustar um modelo mais flexível do que o modelo Ridge com o qual começamos. O modelo mais robusto então fornecerá à nossa estratégia sinais adicionais de compra.

É possível fornecer ao nosso Expert Advisor uma estimativa razoável da probabilidade de que a taxa de câmbio suba nos próximos 10 dias. Podemos treinar um Random Forest Regressor para prever a probabilidade de observarmos um movimento de preço altista. Quando a probabilidade esperada exceder 0.5, nosso Expert Advisor entrará em uma posição comprada. Caso contrário, seguiremos a estratégia que aprendemos com nosso modelo Ridge.


Modelando Probabilidades em Python

Para começar nossas correções, primeiro importaremos algumas bibliotecas.

from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestRegressor

Em seguida, definiremos nossas variáveis dependentes e independentes.

#Independent variable
X = data[['Diff RSI 55']]
#Dependent variable
y = data['Target']

Ajustar o modelo.

model = RandomForestRegressor()
model.fit(X,y)

Preparar para exportar para ONNX.

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Definir os formatos de entrada.

inital_params = [("float_input",FloatTensorType([1,1]))]

Criar o protótipo ONNX e salvá-lo em disco.

onnx_proto = convert_sklearn(model=model,initial_types=inital_params,target_opset=12)
onnx.save(onnx_proto,"EURUSD Diff RSI 55 D1 1 1.onnx")

Você também pode visualizar seu modelo usando a biblioteca netron, para garantir que os atributos corretos de entrada e saída foram atribuídos ao seu modelo ONNX.

import netron
netron.start("EURUSD Diff RSI 55 D1 1 1.onnx")

Esta é uma representação gráfica do nosso modelo Random Forest Regressor e dos atributos que ele possui. O Netron também pode ser usado para visualizar redes neurais e muitos outros tipos de modelos ONNX.

Modelo ONNX

Fig 19: Visualizando nosso modelo Random Forest Regressor em ONNX.

Os formatos de entrada e saída foram especificados corretamente pelo ONNX, então agora podemos prosseguir para aplicar o Random Forest Regressor para ajudar nosso Expert Advisor a prever a probabilidade de ocorrência de um movimento de preço altista nos próximos 10 dias.

Detalhes do Modelo ONNX

Fig 20: Os detalhes do nosso modelo ONNX correspondem às especificações esperadas que queríamos verificar.


Melhorando Nosso Expert Advisor

Agora que exportamos um modelo probabilístico do mercado EURUSD, podemos importar nosso modelo ONNX para dentro do nosso Expert Advisor.

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.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 resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Diff RSI 55 D1 1 1.onnx" as uchar onnx_model_buffer[];

Também precisaremos de alguns novos macros especificando o formato do nosso modelo ONNX.

#define ONNX_INPUTS 1
#define ONNX_OUTPUTS 1

Além disso, o modelo ONNX merece algumas variáveis globais, pois podemos precisar delas rapidamente em várias partes do nosso código.

long   onnx_model;
vectorf onnx_output(1);
vectorf onnx_input(1);

No trecho de código abaixo, excluímos partes da base de código que não foram alteradas e mostramos apenas as mudanças feitas para inicializar o modelo ONNX e definir os formatos de seus parâmetros.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our technical indicators
  ...

//--- Create our ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      return(false);
     }

//--- Define the I/O signature
   ulong onnx_param[] =  {1,1};

   if(!OnnxSetInputShape(onnx_model,0,onnx_param))
      return(false);

   if(!OnnxSetOutputShape(onnx_model,0,onnx_param))
      return(false);

   return(true);
  }

Além disso, nossa verificação de trading válido foi reduzida para destacar apenas o código adicional inserido, evitando duplicação do mesmo código.

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
   onnx_input[0] = (float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - 1);

//--- Our Random forest model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output))
      Comment("Failed to obtain a forecast from our model!");

   else
     {
      if(onnx_output[0] > 0.5)
         if(ma_o[0] < ma_c[0])
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
      Print("Model Bullish Probabilty: ",onnx_output);
     }
}

No geral, esta é a aparência da segunda versão da nossa estratégia de trading quando totalmente composta.

//+------------------------------------------------------------------+
//|                                  Algorithmic Input Selection.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 resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Diff RSI 55 D1 1 1.onnx" as uchar onnx_model_buffer[];

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define RSI_PERIOD 55
#define RSI_TIME_FRAME PERIOD_D1
#define SYSTEM_TIME_FRAME PERIOD_D1
#define RSI_PRICE  PRICE_CLOSE
#define RSI_BUFFER_SIZE 20
#define TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)
#define ONNX_INPUTS 1
#define ONNX_OUTPUTS 1
#define HORIZON 10

//+------------------------------------------------------------------+
//| Load our RSI library                                             |
//+------------------------------------------------------------------+
#include <VolatilityDoctor\Indicators\RSI.mqh>
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CTrade Trade;
RSI    rsi_55(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
double last_value;
int    count;
int    ma_o_handler,ma_c_handler;
double ma_o[],ma_c[];
double trade_sl;
long   onnx_model;
vectorf onnx_output(1);
vectorf onnx_input(1);

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Update our system variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_c_handler,0,0,1,ma_c);
      CopyBuffer(ma_o_handler,0,0,1,ma_o);

      if((count == 0) && (PositionsTotal() == 0))
        {
         rsi_55.SetIndicatorValues(RSI_BUFFER_SIZE,true);
         rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
         last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);
         count = 1;
        }

      if(PositionsTotal() == 0)
         check_signal();

      if(PositionsTotal() > 0)
         manage_setup();
     }
  }

//+------------------------------------------------------------------+
//| Manage our open trades                                           |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double new_sl = (current_tp > current_sl) ? (bid-trade_sl) : (ask+trade_sl);
      double new_tp = (current_tp < current_sl) ? (bid+trade_sl) : (ask-trade_sl);
      //--- Buy setup
      if((current_tp > current_sl) && (new_sl < current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);

      //--- Sell setup
      if((current_tp < current_sl) && (new_sl > current_sl))
         Trade.PositionModify(Symbol(),new_sl,new_tp);
     }
  }

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Setup our technical indicators
   ma_c_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),SYSTEM_TIME_FRAME,2,0,MODE_EMA,PRICE_OPEN);
   count        = 0;
   last_value   = 0;
   trade_sl     = 1.5e-2;

//--- Create our ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DATA_TYPE_FLOAT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      return(false);
     }

//--- Define the I/O signature
   ulong onnx_param[] =  {1,1};

   if(!OnnxSetInputShape(onnx_model,0,onnx_param))
      return(false);

   if(!OnnxSetOutputShape(onnx_model,0,onnx_param))
      return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Check if we have a trading setup                                 |
//+------------------------------------------------------------------+
void check_signal(void)
  {
   rsi_55.SetDifferencedIndicatorValues(RSI_BUFFER_SIZE,HORIZON,true);
   last_value = rsi_55.GetReadingAt(RSI_BUFFER_SIZE - 1);

   double current_reading = rsi_55.GetCurrentReading();
   Comment("Last Reading: ",last_value,"\nDifference in Reading: ",(last_value - current_reading));
   double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
   double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double cp_lb = 7.17;
   double cp_ub = 10.82;
   onnx_input[0] = (float) rsi_55.GetDifferencedReadingAt(RSI_BUFFER_SIZE - 1);

//--- Our Random forest model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_input,onnx_output))
      Comment("Failed to obtain a forecast from our model!");

   else
     {
      if(onnx_output[0] > 0.5)
         if(ma_o[0] < ma_c[0])
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
      Print("Model Bullish Probabilty: ",onnx_output);
     }

//--- The trading rules we learned from our Ridge Regression Model
//--- Ridge Regression Sell
   if((((last_value - current_reading) <= -(cp_lb)) && ((last_value - current_reading) > (cp_ub)))|| ((((last_value - current_reading) > -(cp_lb))) && ((last_value - current_reading) < (cp_lb))))
     {
      if(ma_o[0] > ma_c[0])
        {
         if(PositionsTotal() == 0)
           {
            Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask+trade_sl),(ask-trade_sl));
            count = 0;
           }
        }
     }
//--- Ridge Regression Buy
   else
      if(((last_value - current_reading) >= (cp_lb)) < ((last_value - current_reading) < cp_ub))
        {
         if(ma_c[0] < ma_o[0])
           {
            if(PositionsTotal() == 0)
              {
               Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid-trade_sl),(bid+trade_sl));
               count = 0;
              }
           }
        }
  }
//+------------------------------------------------------------------+

#undef RSI_BUFFER_SIZE
#undef RSI_PERIOD
#undef RSI_PRICE
#undef RSI_TIME_FRAME
#undef SYSTEM_TIME_FRAME
#undef TRADING_VOLUME
#undef ONNX_INPUTS
#undef ONNX_OUTPUTS
//+------------------------------------------------------------------+

Testaremos a estratégia sob as mesmas condições destacadas nas Fig 14 e Fig 15. Aqui estão os resultados obtidos com a segunda versão da nossa estratégia de trading. A curva de equity produzida pela segunda versão parece bastante semelhante à primeira curva de equity que produzimos. 

Fig 21: Visualizando a lucratividade da nossa segunda estratégia de trading. 

É somente ao observarmos as estatísticas detalhadas que as diferenças entre as duas estratégias começam a aparecer. Nosso índice de Sharpe e o retorno esperado diminuíram. Nossa nova estratégia executou 85 operações, o que representa 39% a mais do que as 61 operações da estratégia inicial. Além disso, o número total de posições compradas aumentou de apenas 4 no teste inicial para 42 no segundo teste. Isso representa um aumento de 950%. Assim, ao considerarmos que o nível adicional de risco assumido é significativo e, ainda assim, nossas estatísticas de precisão e lucratividade caíram apenas marginalmente, começamos a desenvolver expectativas positivas quanto ao uso da estratégia. No teste anterior, 57.38% das operações foram lucrativas, e agora 56.47%, o que representa uma redução na precisão de aproximadamente 1.59%.

Fig 22: Estatísticas detalhadas do desempenho da versão revisada da nossa estratégia de trading.



Conclusão

Após a leitura deste artigo, o leitor compreende como pode empregar técnicas de busca em grade juntamente com modelos estatísticos para ajudá-lo a selecionar um período ótimo para indicadores, sem precisar realizar múltiplos backtests e testar manualmente cada período possível. Além disso, o leitor também aprendeu uma forma de estimar e comparar o valor de novos níveis de RSI que deseja operar com o valor dos níveis tradicionais 70 e 30, permitindo operar em condições adversas de mercado com maior confiança em sua capacidade.

Nome do Arquivo Descrição
Algorithmic Inputs Selection.ipynb O notebook Jupyter que utilizamos para realizar a análise numérica usando Python.
EURUSD Testing RSI Class.mql5 O script MQL5 que utilizamos para testar nossa implementação da classe RSI personalizada.
EURUSD RSI Algorithmic Input Selection.mql5 O script que utilizamos para obter dados históricos de mercado.
Algorithmic Input Selection.mql5 Nossa versão inicial da estratégia de trading que construímos.
Algorithmic Input Selection 2.mql5 Nossa versão refinada da estratégia de trading que corrigiu o viés aprendido pela estratégia inicial.

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

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.
Superando as limitações do aprendizado de máquina (Parte 2): falta de reprodutibilidade Superando as limitações do aprendizado de máquina (Parte 2): falta de reprodutibilidade
O artigo examina por que os resultados de trading podem variar significativamente entre corretoras, mesmo usando a mesma estratégia e o mesmo símbolo financeiro, devido à precificação descentralizada e às divergências nos dados. Este artigo ajuda os desenvolvedores MQL5 a entender por que seus produtos podem receber avaliações mistas no MQL5 Marketplace e incentiva os desenvolvedores a adaptar suas abordagens a corretoras específicas para garantir resultados transparentes e reproduzíveis. Se amplamente adotada, essa pode se tornar uma prática recomendada importante e bastante especializada, capaz de beneficiar nossa comunidade.
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.
Automatizando Estratégias de Trading em MQL5 (Parte 12): Implementação da Estratégia Mitigation Order Blocks (MOB) Automatizando Estratégias de Trading em MQL5 (Parte 12): Implementação da Estratégia Mitigation Order Blocks (MOB)
Neste artigo, construímos um sistema de trading em MQL5 que automatiza a detecção de order blocks para trading Smart Money. Descrevemos as regras da estratégia, implementamos a lógica em MQL5 e integramos o gerenciamento de risco para uma execução eficaz das operações. Por fim, realizamos o backtest do sistema para avaliar seu desempenho e refiná-lo para obter resultados ideais.