English Русский
preview
Desenvolvendo um EA multimoeda (Parte 24): Conectando uma nova estratégia (I)

Desenvolvendo um EA multimoeda (Parte 24): Conectando uma nova estratégia (I)

MetaTrader 5Testador |
180 0
Yuriy Bykov
Yuriy Bykov

Introdução

No artigo anterior, demos continuidade ao desenvolvimento do sistema de otimização automática de estratégias de trading no MetaTrader 5. O núcleo é o banco de dados de otimização, que contém informações sobre projetos de otimização. Para criar esses projetos, foi escrito um script de criação de projeto. Embora o script tenha sido escrito para criar o projeto de otimização de uma estratégia de trading específica (SimpleVolumes), ele pode ser usado como modelo para outras estratégias.

Criamos a possibilidade de exportação automática dos grupos de estratégias de trading selecionados na última etapa do projeto. A exportação é feita para um banco de dados separado, chamado banco de dados do expert. Esse banco de dados pode ser utilizado pelo EA final para atualizar as configurações das estratégias de trading, sem a necessidade de recompilação. Isso permite simular, no testador, o funcionamento do EA em um intervalo de tempo no qual podem aparecer vários resultados de otimização dos projetos.

Além disso, finalmente migramos para uma estrutura organizada de arquivos do projeto, dividindo todos os arquivos em duas partes. A primeira parte, chamada Biblioteca Advisor, foi movida para a pasta MQL5/Include, e as demais permaneceram na pasta de trabalho dentro de MQL5/Experts. Na biblioteca, estão todos os arquivos que garantem o funcionamento do sistema de otimização automática, independentemente dos tipos de estratégias de trading otimizadas. Na pasta de trabalho do projeto, ficaram os EAs das etapas, o EA final e o script de criação do projeto de otimização. 

No entanto, por enquanto, a estratégia de trading modelo SimpleVolumes foi deixada na biblioteca, pois naquele momento era mais importante verificar o funcionamento do mecanismo de atualização automática dos parâmetros das estratégias no EA final. A origem do arquivo com o código-fonte da estratégia de trading no momento da compilação não era algo tão relevante.

Agora, vamos imaginar que queremos implementar uma nova estratégia de trading e conectá-la ao sistema de otimização automática, criando os EAs das etapas e o EA final para ela. O que precisaremos fazer para isso?


Traçando o caminho

Primeiro, vamos escolher uma estratégia simples e implementá-la em código, já pensando em usá-la com a nossa biblioteca Advisor. Colocaremos o código na pasta de trabalho do projeto. Quando a estratégia estiver pronta, geraremos o EA da primeira etapa, que será usado para otimizar os parâmetros das instâncias individuais dessa estratégia. Nesse ponto, enfrentaremos algumas dificuldades relacionadas à necessidade de separar o código da biblioteca do código do projeto.

Os EAs da segunda e da terceira etapas podemos usar praticamente os mesmos que foram escritos na parte anterior, já que o código da parte de biblioteca não contém referências às classes das estratégias de trading utilizadas. Já no código da pasta de trabalho do projeto será necessário apenas adicionar o comando de inclusão do arquivo da nova estratégia.

Para a nova estratégia, também será necessário fazer algumas modificações no script do EA para a criação do projeto no banco de dados de otimização. No mínimo, as mudanças afetarão o modelo dos parâmetros de entrada do EA na primeira etapa, pois a composição desses parâmetros será diferente na nova estratégia de trading.

Após modificarmos o EA de criação do projeto no banco de dados de otimização, será possível executá-lo. Então, o banco de dados de otimização será criado e as tarefas de otimização necessárias para esse projeto serão adicionadas a ele. Então, será possível iniciar o pipeline de otimização automática e aguardar o término da execução. Esse é um processo relativamente longo. Sua duração dependerá do intervalo de tempo escolhido para a otimização (quanto maior o intervalo, mais demorado será o processo), da complexidade da estratégia de trading (quanto mais complexa, mais tempo levará) e, claro, da quantidade de agentes de teste disponíveis para realizar a otimização (quanto mais agentes, mais rápido o processo será).

O último passo será executar o EA final ou rodá-lo no testador de estratégias para avaliar os resultados da otimização.

Vamos começar!


A estratégia SimpleCandles

Criaremos na pasta MQL5/Experts uma nova pasta para o projeto. Vamos chamá-la, por exemplo, Article.17277. Vale a pena fazer uma ressalva desde já para evitar confusão no futuro. Usaremos o termo "projeto" em dois sentidos. Em um caso, estaremos nos referindo apenas à pasta com os arquivos dos EAs que serão utilizados para a otimização automática de uma estratégia de trading específica. No código desses EAs, serão usados arquivos incluídos da biblioteca Advisor. Nesse contexto, "projeto" se refere apenas à pasta de trabalho dentro da pasta de experts do terminal. No outro, a palavra "projeto" significará a estrutura de dados criada no banco de dados de otimização que descreve as tarefas que devem ser executadas automaticamente para gerar resultados a serem usados no EA final, destinado a operar em conta real. Nesse contexto, projeto é, essencialmente, o conteúdo do banco de dados de otimização antes mesmo do início da otimização.

Agora estamos falando de projeto no primeiro sentido. Então, na pasta de trabalho do projeto vamos criar uma subpasta chamada Strategies. Nela colocaremos os arquivos das diferentes estratégias de trading. Por enquanto, criaremos apenas uma nova estratégia.

Vamos repetir o caminho feito na Parte 1 ao desenvolver a estratégia de trading SimpleVolumes. Também começaremos com a formulação da ideia de trading.  

Suponhamos que, quando um determinado símbolo apresenta várias velas consecutivas na mesma direção, a probabilidade de a próxima vela ter direção oposta aumenta um pouco. Nesse caso, se abrirmos uma posição no sentido contrário logo após essas velas, talvez consigamos obter lucro.

Tentemos transformar essa ideia em uma estratégia. Para isso, precisamos formular um conjunto de regras de abertura e fechamento de posições que não contenha parâmetros desconhecidos. Esse conjunto de regras deve permitir que, em qualquer momento da execução da estratégia, seja possível determinar se deve haver posições abertas e, em caso afirmativo, quais exatamente.

Primeiro, vamos definir claramente o conceito de direção da vela. Vamos chamar a vela de direcionada para cima quando o preço de fechamento for maior que o de abertura. A vela cujo preço de fechamento for menor que o de abertura será chamada de direcionada para baixo. Como queremos avaliar a direção de várias velas consecutivas passadas, o conceito de direção da vela será aplicado apenas às velas já fechadas. Daí concluímos que o momento possível de abertura de posição surgirá com o início de uma nova barra, ou seja, com o aparecimento de uma nova vela.

Certo, já definimos de alguma forma os momentos de abertura de posição, mas e quanto ao fechamento? Vamos usar a opção mais simples: no momento da abertura da posição serão definidos níveis de StopLoss e TakeProfit, nos quais a posição será encerrada.

Agora podemos descrever nossa estratégia da seguinte forma:

O sinal para abertura de posição será a situação em que, no início de uma nova barra (vela), todas as velas anteriores tenham se direcionado para o mesmo lado (para cima ou para baixo). Se as velas anteriores estavam direcionadas para cima, abriremos uma posição de venda. Caso contrário, abrimos uma posição de compra. 

Cada posição terá níveis de StopLoss e TakeProfit e será encerrada apenas quando esses níveis forem atingidos. Se já houver uma posição aberta e surgir novamente um sinal para abertura de posição, poderão ser abertas posições adicionais, desde que a quantidade não seja excessiva.

Isso já é uma descrição mais detalhada, mas ainda não completa. Portanto, vamos reler e destacar todos os pontos em que algo não está claro. Nesses pontos será necessário dar explicações adicionais. 

Eis as questões que surgiram:

  • "...de várias velas anteriores..." — Várias velas — quantas exatamente?
  • "...podem ser abertas posições adicionais..." — Quantas posições no total podem ser abertas?
  • "...possui níveis de StopLoss e TakeProfit..." — Que valores usar para eles, como calculá-los?

Várias velas, quantas exatamente? Essa é a questão mais simples: essa quantidade será apenas um dos parâmetros da estratégia, que poderá ser ajustada para encontrar o valor mais adequado. Esse número provavelmente não será muito grande, já que, ao observar os gráficos, percebe-se que longas sequências de velas consecutivas na mesma direção são raras.

Quantas posições no total podem ser abertas? Isso também pode ser transformado em um parâmetro da estratégia e otimizado para encontrar os melhores valores. 

Que valores usar para StopLoss e TakeProfit, e como calculá-los? Essa já é uma questão um pouco mais complexa, mas, no caso mais simples, também podemos resolvê-la da mesma forma que a anterior: tornando as grandezas StopLoss e TakeProfit em pontos como parâmetros da estratégia. Ao abrir a posição, recuaremos do preço de abertura o número de pontos definido nesses parâmetros, em cada direção necessária. No entanto, é possível aplicar uma abordagem um pouco mais sofisticada: definir esses parâmetros não em pontos fixos, mas em percentuais de um valor médio da volatilidade do preço do instrumento de trading (símbolo), expressa em pontos. Isso levanta a questão seguinte.

Como encontrar esse valor de volatilidade? Existem muitas maneiras de fazer isso. Podemos, por exemplo, usar o indicador de volatilidade pronto ATR (Average True Range) ou inventar e implementar nosso próprio método de cálculo da volatilidade. Provavelmente, alguns dos parâmetros nesses cálculos seriam o número de períodos em que as oscilações de preço do instrumento de negociação são analisadas e a duração de cada período. Ao adicionarmos essas grandezas como parâmetros da estratégia, podemos calcular a volatilidade com base nelas. 

Como não impusemos a restrição de que as posições seguintes devam ser abertas na mesma direção da primeira, podem surgir situações em que a estratégia de trading mantenha posições em direções opostas. Em uma implementação convencional, seríamos obrigados a limitar o uso dessa estratégia apenas a contas que possuem contabilização independente de posições ("hedging"). No entanto, com o uso de posições virtuais, essa limitação deixa de existir.

Quando tudo estiver claro, listaremos todas as variáveis mencionadas como parâmetros da estratégia. Precisamos levar em conta que, para obter o sinal de abertura de posições, será necessário escolher o símbolo e o timeframe que observaremos. Assim, chegamos à seguinte descrição:

O EA é executado em um determinado símbolo e período (timeframe)

Definimos os parâmetros de entrada:

  • Símbolo
  • Timeframe para contagem das velas na mesma direção
  • Quantidade de velas na mesma direção (signalSeqLen)
  • Período ATR (periodATR)
  • Stop Loss (em pontos ou % do ATR) (stopLevel)
  • Take Profit (em pontos ou % do ATR) (takeLevel)
  • Quantidade máxima de posições abertas simultaneamente (maxCountOfOrders)
  • Tamanho das posições

Quando surge uma nova barra, verificamos as direções das últimas velas fechadas signalSeqLen.

Se as direções forem iguais e o número de posições abertas for menor que maxCountOfOrders, então:

  • Calculamos o StopLoss e o TakeProfit. Se periodATR = 0, simplesmente recuamos a partir do preço atual pelo número de pontos definido nos parâmetros stopLevel e takeLevel. Se periodATR > 0, calculamos o valor do ATR usando o parâmetro periodATR no timeframe diário. A partir do preço atual, recuamos os valores ATR * stopLevel e ATR * takeLevel.

  • Abrimos posição SELL se as velas estavam direcionadas para cima e posição BUY se as velas estavam direcionadas para baixo. No momento da abertura configuramos os níveis de StopLoss e TakeProfit calculados anteriormente.

Essa descrição já é suficiente para começarmos a implementação. Questões que surgirem ao longo do caminho serão resolvidas no processo.

Vale também destacar que, ao descrever a estratégia, em nenhum momento especificamos o tamanho das posições abertas. Sim, formalmente adicionamos esse parâmetro à lista, mas considerando que a estratégia será usada dentro do sistema de otimização automática, podemos simplesmente usar o lote mínimo para os testes. No processo de otimização automática serão selecionados os multiplicadores adequados do tamanho das posições, que garantirão a retração máxima de 10% em todo o intervalo de teste. Portanto, não precisaremos definir manualmente os tamanhos das posições em nenhum lugar.


Implementação da estratégia

Vamos usar a classe existente CSimpleVolumesStrategy e criar, com base nela, a classe CSimpleCandlesStrategy. Ela deve ser declarada como herdeira da classe CVirtualStrategy. Listaremos os parâmetros necessários da estratégia como campos da classe, lembrando que a nova classe também herdará alguns campos e métodos de suas classes superiores.

//+------------------------------------------------------------------+
//| Trading strategy using unidirectional candlesticks               |
//+------------------------------------------------------------------+
class CSimpleCandlesStrategy : public CVirtualStrategy {
protected:
   string            m_symbol;            // Symbol (trading instrument)
   ENUM_TIMEFRAMES   m_timeframe;         // Chart period (timeframe)

   //---  Open signal parameters
   int               m_signalSeqLen;      // Number of unidirectional candles
   int               m_periodATR;         // ATR period

   //---  Position parameters
   double            m_stopLevel;         // Stop Loss (in points or % ATR)
   double            m_takeLevel;         // Take Profit (in points or % ATR)

   //---  Money management parameters
   int               m_maxCountOfOrders;  // Max number of simultaneously open positions

   CSymbolInfo       *m_symbolInfo;       // Object for getting information about the symbol properties

  // ...   

public:
   // Constructor
                     CSimpleCandlesStrategy(string p_params);
   
   virtual string    operator~() override;   // Convert object to string
   virtual void      Tick() override;        // OnTick event handler
};

Para centralizar a obtenção de informações sobre as propriedades do instrumento de trading (símbolo), vamos adicionar ao conjunto de campos da classe um ponteiro para o objeto da classe CSymbolInfo

A classe da nossa nova estratégia de trading também é descendente da classe CFactorable. Dessa forma, poderemos implementar no novo classe um construtor que leia os valores dos parâmetros a partir de uma string de inicialização, utilizando os métodos de leitura já implementados na classe CFactorable. Se durante a leitura não ocorrerem erros, o método IsValid() retornará verdadeiro.

Para trabalhar com posições virtuais, na classe-pai CVirtualStrategy foi declarado o array m_orders, destinado a armazenar ponteiros para objetos da classe CVirtualOrder, ou seja, posições virtuais. Portanto, no construtor pediremos a criação de tantos objetos de posições virtuais quanto especificado no parâmetro m_maxCountOfOrders, e esses objetos serão colocados em nosso array m_orders. Essa tarefa será realizada pelo método estático CVirtualReceiver::Get().

Como nossa estratégia abrirá posições apenas na abertura de uma nova barra no timeframe definido, criaremos um objeto para verificar o evento de chegada de uma nova barra para o respectivo símbolo e timeframe.

E, por fim, o que precisaremos fazer no construtor é solicitar ao monitor de símbolos que crie para o nosso símbolo um objeto de informações da classe CSymbolInfo.

O código completo do construtor ficará assim:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSimpleCandlesStrategy::CSimpleCandlesStrategy(string p_params) {
   // Read parameters from the initialization string
   m_params = p_params;
   m_symbol = ReadString(p_params);
   m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params);
   m_signalSeqLen = (int) ReadLong(p_params);
   m_periodATR = (int) ReadLong(p_params);
   m_stopLevel = ReadDouble(p_params);
   m_takeLevel = ReadDouble(p_params);
   m_maxCountOfOrders = (int) ReadLong(p_params);

   if(IsValid()) {
      // Request the required number of objects for virtual positions
      CVirtualReceiver::Get(&this, m_orders, m_maxCountOfOrders);

      // Add tracking a new bar on the required timeframe
      IsNewBar(m_symbol, m_timeframe);
      
      // Create an information object for the desired symbol
      m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol];
   }
}

Em seguida, precisamos implementar o operador abstrato virtual til (~), que retorna a string de inicialização do objeto da estratégia. Sua implementação é padrão:

//+------------------------------------------------------------------+
//| Convert an object to a string                                    |
//+------------------------------------------------------------------+
string CSimpleCandlesStrategy::operator~() {
   return StringFormat("%s(%s)", typename(this), m_params);
}

Outro método virtual obrigatório a ser implementado é o de processamento de ticks Tick(). Com ele, verificamos a ocorrência de uma nova barra e também se a quantidade de posições abertas ainda não atingiu o valor máximo. Se essas condições forem atendidas, verificamos a presença de um sinal de abertura. Se houver, abriremos a posição correspondente à direção detectada. Os demais métodos que adicionaremos à classe desempenharão papéis auxiliares. 

//+------------------------------------------------------------------+
//| "Tick" event handler function                                    |
//+------------------------------------------------------------------+
void CSimpleCandlesStrategy::Tick() override {
// If a new bar has arrived for a given symbol and timeframe
   if(IsNewBar(m_symbol, m_timeframe)) {
// If the number of open positions is less than the allowed number
      if(m_ordersTotal < m_maxCountOfOrders) {
         // Get an open signal
         int signal = SignalForOpen();

         if(signal == 1) {          // If there is a buy signal, then 
            OpenBuy();              // open a BUY position
         } else if(signal == -1) {  // If there is a sell signal, then
            OpenSell();             // open a SELL_STOP position
         }
      }
   }
}

A verificação da presença de sinal de abertura foi isolada em um método separado, SignalForOpen(). Nesse método obtemos o array de cotações das velas anteriores e verificamos em sequência se todas elas são direcionadas para baixo ou para cima:

//+------------------------------------------------------------------+
//| Signal for opening pending orders                                |
//+------------------------------------------------------------------+
int CSimpleCandlesStrategy::SignalForOpen() {
// By default, there is no signal
   int signal = 0;

   MqlRates rates[];
// Copy the quote values (candles) to the destination array
   int res = CopyRates(m_symbol, m_timeframe, 1, m_signalSeqLen, rates);

// If the required number of candles has been copied
   if(res == m_signalSeqLen) {
      signal = 1; // buy signal

      // Loop through all the candles
      for(int i = 0; i < m_signalSeqLen; i++) {
         // If at least one upward candle occurs, cancel the signal
         if(rates[i].open < rates[i].close ) {
            signal = 0;
            break;
         }
      }

      if(signal == 0) {
         signal = -1; // otherwise, sell signal

         // Loop through all the candles
         for(int i = 0; i < m_signalSeqLen; i++) {
            // If at least one downward candle occurs, cancel the signal
            if(rates[i].open > rates[i].close ) {
               signal = 0;
               break;
            }
         }
      }

   }

   return signal;
}

Pela abertura das posições são responsáveis os métodos criados OpenBuy() e OpenSell(). Como eles são muito semelhantes, apresentaremos o código de apenas um deles. Os pontos-chave nesse método são a chamada do método de atualização dos níveis de StopLoss e TakeProfit, que atualiza os valores dos dois campos correspondentes da classe m_sl e m_tp, e também a chamada do método de abertura da primeira posição virtual ainda não utilizada do array m_orders.

//+------------------------------------------------------------------+
//| Open BUY order                                                   |
//+------------------------------------------------------------------+
void CSimpleCandlesStrategy::OpenBuy() {
// Retrieve the necessary symbol and price data
   double point = m_symbolInfo.Point();
   int digits = m_symbolInfo.Digits();

// Opening price
   double price = m_symbolInfo.Ask();

// Update SL and TP levels by calculating ATR
   UpdateLevels();

// StopLoss and TakeProfit levels
   double sl = NormalizeDouble(price - m_sl * point, digits);
   double tp = NormalizeDouble(price + m_tp * point, digits);

   bool res = false;
   for(int i = 0; i < m_maxCountOfOrders; i++) {   // Iterate through all virtual positions
      if(!m_orders[i].IsOpen()) {                  // If we find one that is not open, then open it
         // Open a virtual SELL position
         res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot,
                                0,
                                NormalizeDouble(sl, digits),
                                NormalizeDouble(tp, digits));

         break; // and exit
      }
   }

   if(!res) {
      PrintFormat(__FUNCTION__" | ERROR opening BUY virtual order", 0);
   }
}

O método de atualização dos níveis primeiro verifica se algum valor diferente de zero foi definido para o período de cálculo do ATR. Se sim, a função de cálculo do ATR é chamada. O resultado é armazenado na variável channelWidth. Quando o valor do período é igual a zero, a variável channelWidth recebe o valor um. Nesse caso, os valores dos parâmetros de entrada m_stopLevel e m_takeLevel são interpretados como pontos e transferidos diretamente para as variáveis m_sl e m_tp, sem alterações. Caso contrário, eles são interpretados como frações do valor do ATR e multiplicados pelo valor calculado do ATR:

//+------------------------------------------------------------------+
//| Update SL and TP levels based on calculated ATR                  |
//+------------------------------------------------------------------+
void CSimpleCandlesStrategy::UpdateLevels() {
// Calculate ATR
   double channelWidth = (m_periodATR > 0 ? ChannelWidth() : 1);

// Update SL and TP levels
   m_sl = m_stopLevel * channelWidth;
   m_tp = m_takeLevel * channelWidth;
}

O último método necessário para nossa nova estratégia de trading é o método de cálculo do ATR. Como já foi dito, ele pode ser implementado de várias formas, incluindo o uso de soluções prontas. Para simplificar, usaremos uma das implementações possíveis que tínhamos à disposição:

//+------------------------------------------------------------------+
//| Calculate the ATR value (non-standard implementation)            |
//+------------------------------------------------------------------+
double CSimpleCandlesStrategy::ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1) {
   int n = m_periodATR; // Number of bars for calculation
   MqlRates rates[];    // Array for quotes

   // Copy quotes from the daily (default) timeframe
   int res = CopyRates(m_symbol, p_tf, 1, n, rates);

   // If the required amount has been copied
   if(res == n) {
      double tr[];         // Array for price ranges
      ArrayResize(tr, n);  // Change its size
   
      double s = 0;        // Sum for calculating the average
      FOREACH(rates, {
         tr[i] = rates[i].high - rates[i].low; // Remember the bar size
      });
      
      ArraySort(tr); // Sort the sizes

      // Sum the inner two quarters of the bar sizes
      for(int i = n / 4; i < n * 3 / 4; i++) {
         s += tr[i];
      }
      
      // Return the average size in points
      return 2 * s / n / m_symbolInfo.Point();
   }

   return 0.0;
}

Salvamos as alterações realizadas no arquivo Strategies/SimpleCandlesStrategy.mqh, localizado na pasta de trabalho do projeto.


Conexão da estratégia

Agora que a estratégia está praticamente pronta, precisamos conectá-la ao arquivo do EA. Começamos pelo EA da primeira etapa. Lembrando que seu código agora está dividido em dois arquivos:

  • MQL5/Experts/Article.17277/Stage1.mq5 — arquivo do projeto atual para estudo da estratégia SimpleCandles;
  • MQL5/Include/antekov/Advisor/Experts/Stage1.mqh — arquivo da biblioteca, comum a todos os projetos.

No arquivo do projeto atual, é necessário realizar as seguintes ações:

  1. Definir a constante __NAME__, atribuindo a ela um valor único, diferente dos nomes usados em outros projetos.
  2. Incluir o arquivo com a classe da estratégia de trading desenvolvida.
  3. Incluir a parte comum do EA da primeira etapa a partir da biblioteca Advisor.
  4. Listar os parâmetros de entrada da estratégia de trading.
  5. Criar a função com nome GetStrategyParams(), que converte os valores dos parâmetros de entrada em uma string de inicialização do objeto da estratégia.
No código, isso pode ter a seguinte aparência:

// 1. Define a constant with the EA name
#define  __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME)

// 2. Connect the required strategy
#include "Strategies/SimpleCandlesStrategy.mqh";

// 3. Connect the general part of the first stage EA from the Advisor library
#include <antekov/Advisor/Experts/Stage1.mqh>

//+------------------------------------------------------------------+
//| 4. Strategy inputs                                               |
//+------------------------------------------------------------------+
sinput string     symbol_              = "GBPUSD";
sinput ENUM_TIMEFRAMES period_         = PERIOD_H1;

input group "===  Opening signal parameters"
input int         signalSeqLen_        = 5;     // Number of unidirectional candles
input int         periodATR_           = 30;    // ATR period

input group "===  Pending order parameters"
input double      stopLevel_           = 3750;  // Stop Loss (in points)
input double      takeLevel_           = 50;    // Take Profit (in points)

input group "===  Money management parameters"
input int         maxCountOfOrders_    = 3;     // Maximum number of simultaneously open orders


//+------------------------------------------------------------------+
//| 5. Strategy initialization string generation function            |
//|    from the inputs                                               |
//+------------------------------------------------------------------+
string GetStrategyParams() {
   return StringFormat(
             "class CSimpleCandlesStrategy(\"%s\",%d,%d,%d,%.3f,%.3f,%d)",
             symbol_, period_,
             signalSeqLen_, periodATR_, stopLevel_, takeLevel_, maxCountOfOrders_
          );
}
//+------------------------------------------------------------------+

No entanto, mesmo que compilarmos o arquivo do EA da primeira etapa (a compilação ocorre sem erros), ao executá-lo obteremos o seguinte erro na função OnInit(), que leva à parada do EA:

2018.01.01 00:00:00   CVirtualFactory::Create | ERROR: Constructor not found for:
2018.01.01 00:00:00   class CSimpleCandlesStrategy("GBPUSD",16385,5,30,2.95,3.92,3)

A causa está no fato de que, para criar objetos de todas as classes herdadas de CFactorable, é usada a função separada CVirtualFactory::Create() do arquivo Virtual/VirtualFactory.mqh. Ela é chamada nos macros NEW(C) e CREATE(C, O, P), declarados em Base/Factorable.mqh.

Nessa função, a partir da string de inicialização é lido o nome da classe do objeto e armazenado na variável className. Ao mesmo tempo, a parte lida é removida da string de inicialização. Depois disso, ocorre uma simples verificação sequencial de todos os possíveis nomes de classes (herdeiras de CFactorable), até encontrar uma correspondência com o nome recém-lido. Nesse caso, é criado um novo objeto da classe necessária, e o ponteiro para ele é retornado pela variável object como resultado da função de criação:

// Create an object from the initialization string
   static CFactorable* Create(string p_params) {
      // Read the object class name
      string className = CFactorable::ReadClassName(p_params);
      
      // Pointer to the object being created
      CFactorable* object = NULL;

      // Call the corresponding constructor  depending on the class name
      if(className == "CVirtualAdvisor") {
         object = new CVirtualAdvisor(p_params);
      } else if(className == "CVirtualRiskManager") {
         object = new CVirtualRiskManager(p_params);
      } else if(className == "CVirtualStrategyGroup") {
         object = new CVirtualStrategyGroup(p_params);
      } else if(className == "CSimpleVolumesStrategy") {
         object = new CSimpleVolumesStrategy(p_params);
      } else if(className == "CHistoryStrategy") {
         object = new CHistoryStrategy(p_params);
      } 
            
      // If the object is not created or is created in the invalid state, report an error
      if(!object) {
         ...
      }

      return object;
   }

Quando todo o nosso código ficava em uma única pasta, nós simplesmente adicionávamos aqui novos ramos ao operador condicional para cada nova classe-herdeira de CFactorable. Foi assim, por exemplo, que surgiu a parte responsável pela criação dos objetos da nossa primeira estratégia-modelo SimpleVolumes:

} else if(className == "CSimpleVolumesStrategy") {
   object = new CSimpleVolumesStrategy(p_params);
}

Seguindo essa mesma abordagem, deveríamos adicionar aqui um bloco semelhante também para a nossa nova estratégia-modelo SimpleCandles:

} else if(className == "CSimpleCandlesStrategy") {
   object = new CSimpleCandlesStrategy(p_params);
}

Mas agora isso já quebra o princípio da separação entre código da biblioteca e código do projeto. A parte da biblioteca não deve ter a obrigação de conhecer quais novas estratégias serão criadas durante seu uso. Inclusive, mesmo a criação da CSimpleVolumesStrategy dessa forma já não parece correta.

Vamos tentar pensar em um jeito de garantir, ao mesmo tempo, a criação de todos os objetos necessários e a separação clara entre código da biblioteca e do projeto.


Aprimorando o CFactorable

É preciso admitir que essa tarefa não é simples. Foi necessário refletir bastante sobre a solução e experimentar várias opções de implementação até chegar a uma que será mantida, pelo menos por enquanto. Se a linguagem MQL5 tivesse a capacidade de executar código a partir de uma string em um programa já compilado, tudo seria resolvido facilmente. No entanto, por motivos de segurança, não existe aqui uma função semelhante à eval() de outras linguagens. Portanto, foi necessário encontrar alternativas usando os recursos disponíveis.

Em resumo, cada herdeiro de CFactorable deve possuir uma função estática responsável por criar o objeto dessa classe. Ou seja, uma espécie de construtor estático. Dessa maneira, o construtor comum pode ser definido como não público e a criação de objetos seria feita exclusivamente pelo construtor estático. Em seguida, precisaríamos vincular os nomes das classes em formato de string a essas funções, de modo que, ao receber o nome da classe a partir da string de inicialização, soubéssemos qual função construtor deveria ser chamada.

Para essa solução, podemos recorrer aos ponteiros de função. Esse é um tipo especial de variável que permite armazenar nela o endereço de uma função e, posteriormente, chamar o código da função usando esse ponteiro. Podemos perceber que todos os construtores estáticos dos objetos de diferentes classes-herdeiras de CFactorable podem ser declarados com a mesma assinatura:

static CFactorable* Create(string p_params)

Portanto, podemos criar um array estático no qual serão armazenados os ponteiros para funções desse tipo, correspondentes a todas as classes-herdeiras. As classes que fazem parte da biblioteca Advisor (CVirtualAdvisor, CVirtualStrategyGroup, CVirtualRiskManager) seriam adicionadas a esse array dentro do código da própria biblioteca. Já as classes das estratégias de trading seriam adicionadas a esse array a partir do código localizado na pasta de trabalho do projeto. Dessa forma, alcançaríamos a separação desejada entre as partes do código.

Em seguida surge a questão: como exatamente faremos isso? Em qual classe declarar esse array estático e como garantir o seu preenchimento? Como manter o vínculo entre o nome da classe e o elemento correspondente nesse array?

A ideia mais adequada, a princípio, parecia ser criar esse array estático dentro da classe CFactorable. Para o vínculo, poderíamos ter ainda outro array estático de strings, contendo os nomes das classes. Se a operação de preenchimento adicionasse simultaneamente o nome da classe em um array e o ponteiro para o construtor estático de objetos dessa classe no outro, obteríamos uma associação por índice entre os elementos desses dois arrays. Ou seja, ao encontrar no primeiro array o índice do elemento com o nome de classe desejado, poderíamos usar esse índice para acessar no segundo array o ponteiro para a função-construtor e então chamá-la, passando a string de inicialização.

Mas como preencher esses arrays? Não parecia uma boa ideia criar funções que precisassem ser obrigatoriamente chamadas a partir do OnInit(). Embora, como acabou ficando claro, essa abordagem funcionasse, no final chegamos a outra solução.

A ideia principal era termos a possibilidade de executar determinado código não a partir do OnInit(), mas diretamente nos arquivos de definição das classes herdeiras de CFactorable. Contudo, se colocássemos o código simplesmente fora da definição da classe, ele não seria executado. Já se declararmos uma variável global, fora da definição da classe, e ela for um objeto de determinada classe, nesse ponto será chamado o construtor dessa variável!

Portanto, criaremos uma classe separada, chamada CFactorableCreator, especificamente para essa finalidade. Seus objetos armazenarão o nome da classe e o ponteiro para o construtor estático dos objetos da classe correspondente. Além disso, essa classe terá um array estático de ponteiros para objetos... dela mesma. E o construtor CFactorableCreator cuidará para que cada objeto criado seja automaticamente adicionado a esse array:

// Preliminary class definition
class CFactorable;

// Type declaration - pointer to the function for creating objects of the CFactorable class
typedef CFactorable* (*TCreateFunc)(string);

//+------------------------------------------------------------------+
//| Class of creators that bind names and static                     |
//| constructors of CFactorable descendant classes                   |
//+------------------------------------------------------------------+
class CFactorableCreator {
public:
   string            m_className;   // Class name
   TCreateFunc       m_creator;     // Static constructor for the class

   // Creator constructor
                     CFactorableCreator(string p_className, TCreateFunc p_creator);

   // Static array of all created creator objects
   static CFactorableCreator* creators[];
};

// Static array of all created creator objects
CFactorableCreator* CFactorableCreator::creators[];

//+------------------------------------------------------------------+
//| Creator constructor                                              |
//+------------------------------------------------------------------+
CFactorableCreator::CFactorableCreator(string p_className, TCreateFunc p_creator) :
   m_className(p_className),
   m_creator(p_creator) {
// Add the current creator object to the static array
   APPEND(creators, &this);
}
//+------------------------------------------------------------------+

Vejamos como é possível organizar o preenchimento do array CFactorableCreator::creators usando como exemplo a classe CVirtualAdvisor. Vamos mover o construtor CVirtualAdvisor para a seção protected, adicionar a função de construtor estático Create() e, após a definição da classe, criar um objeto global da classe CFactorableCreator com o nome CVirtualAdvisorCreator. É nesse ponto que, ao chamar o construtor CFactorableCreator, acontecerá o preenchimento do array CFactorableCreator::creators.

//+------------------------------------------------------------------+
//| Class of the EA handling virtual positions (orders)              |
//+------------------------------------------------------------------+
class CVirtualAdvisor : public CAdvisor {

protected:
   //...
                     CVirtualAdvisor(string p_param);    // Private constructor
public:
                     static CFactorable* Create(string p_params) { return new CVirtualAdvisor(p_params) };
                    //...
};

CFactorableCreator CVirtualAdvisorCreator("CVirtualAdvisor", CVirtualAdvisor::Create);

Exatamente as mesmas três modificações precisarão ser feitas em todas as classes herdeiras de CFactorable. Para simplificar um pouco, declararemos dois macros auxiliares no arquivo da classe CFactorable:

// Declare a static constructor inside the class
#define STATIC_CONSTRUCTOR(C) static CFactorable* Create(string p) { return new C(p); }

// Add a static constructor for the new CFactorable descendant class
// to a special array by creating a global object of the CFactorableCreator class 
#define REGISTER_FACTORABLE_CLASS(C) CFactorableCreator C##Creator(#C, C::Create);

Eles apenas repetem o padrão de código que já implementamos para a classe CVirtualAdvisor. Assim, poderemos realizar as alterações da seguinte forma:

//+------------------------------------------------------------------+
//| Class of the EA handling virtual positions (orders)              |
//+------------------------------------------------------------------+
class CVirtualAdvisor : public CAdvisor {
protected:
   // ...
                     CVirtualAdvisor(string p_param);    // Constructor
public:
                     STATIC_CONSTRUCTOR(CVirtualAdvisor);
                    // ...
};

REGISTER_FACTORABLE_CLASS(CVirtualAdvisor);

Essas mudanças devem ser aplicadas aos arquivos das três classes da biblioteca Advisor (CVirtualAdvisor, CVirtualStrategyGroup, CVirtualRiskManager), mas isso precisa ser feito apenas uma vez. Agora que essas alterações já estão na biblioteca, podemos esquecer delas. 

Já nos arquivos das classes de estratégias de trading, localizados na pasta de trabalho do projeto, tais adições são obrigatórias para cada nova classe. Vamos adicioná-las à nossa nova estratégia e, depois disso, o código de definição da classe ficará assim:

//+------------------------------------------------------------------+
//| Trading strategy using unidirectional candlesticks               |
//+------------------------------------------------------------------+
class CSimpleCandlesStrategy : public CVirtualStrategy {
protected:
   string            m_symbol;            // Symbol (trading instrument)
   ENUM_TIMEFRAMES   m_timeframe;         // Chart period (timeframe)

   //---  Open signal parameters
   int               m_signalSeqLen;      // Number of unidirectional candles
   int               m_periodATR;         // ATR period

   //---  Position parameters
   double            m_stopLevel;         // Stop Loss (in points or % ATR)
   double            m_takeLevel;         // Take Profit (in points or % ATR)

   //---  Money management parameters
   int               m_maxCountOfOrders;  // Max number of simultaneously open positions

   CSymbolInfo       *m_symbolInfo;       // Object for getting information about the symbol properties

   double            m_tp;                // Stop Loss in points
   double            m_sl;                // Take Profit in points

   //--- Methods
   int               SignalForOpen();     // Signal to open a position
   void              OpenBuy();           // Open a BUY position
   void              OpenSell();          // Open a SELL position

   double            ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1); // Calculate the ATR value
   void              UpdateLevels();      // Update SL and TP levels

   // Private constructor
                     CSimpleCandlesStrategy(string p_params);

public:
   // Static constructor
                     STATIC_CONSTRUCTOR(CSimpleCandlesStrategy);

   virtual string    operator~() override;   // Convert object to string
   virtual void      Tick() override;        // OnTick event handler
};

// Register the CFactorable descendant class
REGISTER_FACTORABLE_CLASS(CSimpleCandlesStrategy);

Mais uma vez, é importante destacar que essas partes destacadas devem obrigatoriamente estar presentes em qualquer nova classe de estratégia de trading.

Resta apenas aplicar o array preenchido de criadores de objetos na função geral de criação de objetos a partir da string de inicialização CVirtualFactory::Create(). Aqui também faremos algumas mudanças. Como ficou claro, agora não há necessidade de manter essa função em uma classe separada. Antes, isso era feito porque formalmente a classe CFactorable não era obrigada a conhecer os nomes de todos os seus herdeiros. Mas, após as alterações já realizadas, não precisamos mais conhecer nominalmente todos os descendentes, podemos criar qualquer um deles chamando os construtores estáticos por meio dos elementos do array unificado CFactorableCreator::creators. Portanto, moveremos o código dessa função para um novo método estático da classe CFactorable::Create():

//+------------------------------------------------------------------+
//| Base class of objects created from a string                      |
//+------------------------------------------------------------------+
class CFactorable {
 // ...

public:
   // ...

   // Create an object from the initialization string
   static CFactorable* Create(string p_params);
};


//+------------------------------------------------------------------+
//| Create an object from the initialization string                  |
//+------------------------------------------------------------------+
CFactorable* CFactorable::Create(string p_params) {
// Pointer to the object being created
   CFactorable* object = NULL;

// Read the object class name
   string className = CFactorable::ReadClassName(p_params);

// Find and call the corresponding constructor depending on the class name
   int i;
   SEARCH(CFactorableCreator::creators, className == CFactorableCreator::creators[i].m_className, i);
   if(i != -1) {
      object = CFactorableCreator::creators[i].m_creator(p_params);
   }

// If the object is not created or is created in the invalid state, report an error
   if(!object) {
      PrintFormat(__FUNCTION__" | ERROR: Constructor not found for:\n%s",
                  p_params);
   } else if(!object.IsValid()) {
      PrintFormat(__FUNCTION__
                  " | ERROR: Created object is invalid for:\n%s",
                  p_params);
      delete object; // Remove the invalid object
      object = NULL;
   }

   return object;
}

Como se pode ver, primeiro obtemos o nome da classe a partir da string de inicialização e, em seguida, buscamos o índice do elemento no array de criadores cujo nome de classe corresponda ao necessário. O índice encontrado é armazenado na variável i. Se o índice for válido, o construtor estático do objeto da classe correspondente será chamado por meio do ponteiro para a função associado. Nesse código já não há nenhuma menção direta aos nomes das classes-herdeiras de CFactorable. O arquivo com a classe CVirtualFactory tornou-se desnecessário e será removido da biblioteca.


Verificação do EA da primeira etapa

Compilamos o EA da primeira etapa e iniciamos a otimização, por enquanto manualmente. O intervalo de otimização escolhido foi de 2018 até 2023, inclusive, no símbolo GBPUSD e timeframe H4. A otimização iniciou com sucesso e, após algum tempo, pudemos analisar os resultados obtidos:

Fig. 1. Configurações de otimização e visualização dos resultados de otimização para o EA Stage1.mq5

Vejamos alguns passes individuais que pareceram mais ou menos razoáveis.

Fig. 2. Resultados do passe com parâmetros: class CSimpleCandlesStrategy("GBPUSD",16388,4,23,2.380,4.950,19)

Nos resultados mostrados na fig. 2, a abertura ocorreu após quatro velas consecutivas na mesma direção, e a relação entre os níveis de StopLoss e TakeProfit foi de aproximadamente 1:2. 

Fig. 3. Resultados do passe com parâmetros: class CSimpleCandlesStrategy("GBPUSD",16388,7,9,0.090,3.840,1)

Na fig. 3, são exibidos os resultados de um passe em que a abertura ocorreu após sete velas consecutivas na mesma direção. Nesse caso, foi usado um StopLoss muito curto e um TakeProfit bastante longo. Isso fica evidente no gráfico, onde a maioria das operações foi encerrada com pequenas perdas, e apenas cerca de dez operações, ao longo de seis anos, foram fechadas com lucro, mas um lucro expressivo.

Portanto, apesar de essa estratégia de trading ser muito simples, ainda é possível trabalhar com ela para obter resultados mais interessantes, após combinar várias instâncias em um EA final.


Considerações finais

Ainda não concluímos totalmente o processo de conexão de uma nova estratégia ao sistema de otimização automática, mas demos passos importantes que nos permitirão seguir adiante. Em primeiro lugar, já temos uma nova estratégia de trading implementada como uma classe separada, herdeira de CVirtualStrategy. Em segundo lugar, conseguimos conectá-la ao EA da primeira etapa e verificar que é possível iniciar o processo de otimização desse EA.

A otimização de uma instância única da estratégia de trading, realizada na primeira etapa, começa quando ainda não há resultados de nenhum passe no banco de dados de otimização. Já para a segunda e terceira etapas, é necessário que existam no banco de dados os resultados dos passes da primeira etapa. Portanto, ainda não podemos conectar e testar a estratégia nos EAs da segunda e terceira etapas. Antes, será preciso criar um projeto no banco de dados de otimização e executá-lo para acumular os resultados da primeira etapa. Na próxima parte, continuaremos esse trabalho analisando a modificação do EA de criação de projetos.

Obrigado pela atenção e até a próxima!


Aviso importante

Todos os resultados apresentados neste artigo e em todos os anteriores da série baseiam-se apenas em dados de testes históricos e não representam garantia de obtenção de qualquer lucro no futuro. O trabalho realizado neste projeto tem caráter exclusivamente de pesquisa. Todos os resultados publicados podem ser usados por qualquer pessoa por sua própria conta e risco.


Conteúdo do arquivo compactado

#
 Nome
Versão  Descrição  Últimas alterações
  MQL5/Experts/Article.17277   Pasta de trabalho do projeto  
1 CreateProject.mq5 1.01
EA-script de criação de projeto com etapas, trabalhos e tarefas de otimização.
Parte 23
2 Optimization.mq5
1.00 EA para otimização automática de projetos  Parte 23
3 SimpleCandles.mq5
1.00 EA final para execução paralela de vários grupos de estratégias-modelo. Os parâmetros serão obtidos da biblioteca incorporada de grupos.
Parte 24
4 Stage1.mq5 1.22  EA de otimização de uma instância única da estratégia de trading (Etapa 1)
Parte 24
5 Stage2.mq5
1.00 EA de otimização de um grupo de instâncias de estratégias de trading (Etapa 2)
Parte 23
Stage3.mq5
1.00 EA que salva o grupo normalizado de estratégias formado no banco de dados do expert com o nome especificado.
Parte 23
  MQL5/Experts/Article.17277/Strategies   Pasta de estratégias do projeto  
7 SimpleCandlesStrategy.mqh 1.01   Parte 24
  MQL5/Include/antekov/Advisor/Base
  Classes básicas das quais outras classes do projeto herdam    
8 Advisor.mqh 1.04 Classe base do expert Parte 10
9 Factorable.mqh
1.05
Classe base de objetos criados a partir de string
Parte 24
10 FactorableCreator.mqh
1.00   Parte 24
11 Interface.mqh 1.01
Classe base para visualização de diferentes objetos
Parte 4
12 Receiver.mqh
1.04  Classe base para conversão de volumes abertos em posições de mercado
Parte 12
13 Strategy.mqh
1.04
Classe base da estratégia de trading
Parte 10
  MQL5/Include/antekov/Advisor/Database
  Arquivos para trabalhar com todos os tipos de bancos de dados usados pelos EAs do projeto
 
14 Database.mqh 1.10 Classe para trabalhar com banco de dados Parte 22
15 db.adv.schema.sql 1.00
Esquema do banco de dados do EA final Parte 22
16 db.cut.schema.sql
1.00 Esquema do banco de dados reduzido de otimização
Parte 22
17 db.opt.schema.sql
1.05  Esquema do banco de dados de otimização
Parte 22
18 Storage.mqh   1.01
Classe para trabalhar com armazenamento Key-Value para o EA final no banco de dados do expert
Parte 23
  MQL5/Include/antekov/Advisor/Experts
  Arquivos com as partes comuns usadas pelos EAs de diferentes tipos
 
19 Expert.mqh  1.22 Arquivo da biblioteca para o EA final. Parâmetros dos grupos podem ser obtidos do banco de dados do expert
Parte 23
20 Optimization.mqh  1.04 Arquivo da biblioteca para o EA que gerencia a execução de tarefas de otimização
Parte 23
21 Stage1.mqh
1.19 Arquivo da biblioteca para o EA de otimização de uma instância única da estratégia de trading (Etapa 1)
Parte 23
22 Stage2.mqh 1.04 Arquivo da biblioteca para o EA de otimização de um grupo de instâncias de estratégias de trading (Etapa 2)   Parte 23
23 Stage3.mqh
1.04 Arquivo da biblioteca para o EA que salva o grupo normalizado de estratégias formado no banco de dados do expert com o nome especificado. Parte 23
  MQL5/Include/antekov/Advisor/Optimization
  Classes responsáveis pelo funcionamento da otimização automática
 
24 Optimizer.mqh
1.03  Classe para o gerenciador de otimização automática de projetos
Parte 22
25 OptimizerTask.mqh
1.03
Classe para tarefa de otimização
Parte 22
  MQL5/Include/antekov/Advisor/Strategies    Exemplos de estratégias de trading usadas para demonstração do funcionamento do projeto
 
26 HistoryStrategy.mqh 
1.00 Classe da estratégia de trading para reprodução do histórico de operações
Parte 16
27 SimpleVolumesStrategy.mqh
1.11
Classe da estratégia de trading com uso de volumes de ticks
Parte 22
  MQL5/Include/antekov/Advisor/Utils
  Utilitários auxiliares, macros para redução de código  
28 ExpertHistory.mqh 1.00 Classe para exportar o histórico de operações em arquivo Parte 16
29 Macros.mqh 1.05 Macros úteis para operações com arrays Parte 22
30 NewBarEvent.mqh 1.00  Classe para detecção de nova barra em um símbolo específico  Parte 8
31 SymbolsMonitor.mqh  1.00 Classe para obtenção de informações sobre instrumentos de trading (símbolos) Parte 21
  MQL5/Include/antekov/Advisor/Virtual
  Classes para criação de diferentes objetos, unificados pelo uso do sistema de ordens e posições virtuais
 
32 Money.mqh 1.01  Classe base de gestão de capital
Parte 12
33 TesterHandler.mqh  1.07 Classe para processamento de eventos de otimização  Parte 23
34 VirtualAdvisor.mqh  1.10  Classe de expert que trabalha com posições (ordens) virtuais Parte 24
35 VirtualChartOrder.mqh  1.01  Classe de posição virtual gráfica Parte 18
36 VirtualHistoryAdvisor.mqh 1.00  Classe de expert para reprodução do histórico de operações  Parte 16
37 VirtualInterface.mqh  1.00  Classe de interface gráfica do EA  Parte 4
38 VirtualOrder.mqh 1.09  Classe de ordens e posições virtuais  Parte 22
39 VirtualReceiver.mqh 1.04 Classe para conversão de volumes abertos em posições de mercado (receptor) Parte 23
40 VirtualRiskManager.mqh  1.05 Classe de gerenciamento de risco (risk-manager) Parte 24
41 VirtualStrategy.mqh 1.09  Classe de estratégia de trading com posições virtuais Parte 23
42 VirtualStrategyGroup.mqh  1.03  Classe de grupo de estratégias de trading ou grupos de estratégias Parte 24
43 VirtualSymbolReceiver.mqh  1.00 Classe de receptor simbólico  Parte 3

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/17277

Arquivos anexados |
MQL5.zip (102.06 KB)
Solicitação no Connexus (Parte 6): Criando uma Requisição e Resposta HTTP Solicitação no Connexus (Parte 6): Criando uma Requisição e Resposta HTTP
Neste sexto artigo da série da biblioteca Connexus, focamos em uma requisição HTTP completa, cobrindo cada componente que compõe uma requisição. Criamos uma classe que representa a requisição como um todo, o que nos ajudou a reunir as classes criadas anteriormente.
Otimização com Jogo do Caos — Chaos Game Optimization (CGO) Otimização com Jogo do Caos — Chaos Game Optimization (CGO)
Apresentamos o novo algoritmo meta-heurístico Chaos Game Optimization (CGO), que demonstra capacidade única de manter alta eficiência em tarefas de grande dimensionalidade. Ao contrário da maioria dos algoritmos de otimização, o CGO não apenas não perde desempenho, como também às vezes melhora sua performance quando a complexidade do problema aumenta, o que constitui sua principal característica.
Redes neurais em trading: Integração da teoria do caos na previsão de séries temporais (Attraos) Redes neurais em trading: Integração da teoria do caos na previsão de séries temporais (Attraos)
O Attraos é um framework que integra a teoria do caos à previsão de séries temporais de longo prazo, tratando-as como projeções de sistemas dinâmicos caóticos multidimensionais. Por meio da invariância do atrator, o modelo aplica a reconstrução do espaço de fases e a memória dinâmica com múltiplas resoluções para preservar estruturas históricas.
Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 1): Projetor de Gráficos Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 1): Projetor de Gráficos
Este projeto tem como objetivo aproveitar o algoritmo MQL5 para desenvolver um conjunto abrangente de ferramentas de análise para o MetaTrader 5. Essas ferramentas — que vão desde scripts e indicadores até modelos de IA e expert advisors — irão automatizar o processo de análise de mercado. Em alguns momentos, esse desenvolvimento gerará ferramentas capazes de realizar análises avançadas sem intervenção humana e prever resultados em plataformas apropriadas. Nenhuma oportunidade será perdida. Junte-se a mim enquanto exploramos o processo de construção de um conjunto robusto de ferramentas personalizadas de análise de mercado. Começaremos desenvolvendo um programa simples em MQL5 que chamei de Projetor de Gráficos.