English Русский Deutsch
preview
Classe base de algoritmos populacionais como alicerce para otimização eficiente

Classe base de algoritmos populacionais como alicerce para otimização eficiente

MetaTrader 5Testador | 26 julho 2024, 13:35
14 0
Andrey Dik
Andrey Dik

Conteúdo:

1. Introdução. Perspectivas e possibilidades ao utilizar uma classe base de algoritmos populacionais
2. Implementando a classe base para algoritmos populacionais
3. Código dos algoritmos derivados
4. Código para um banco de testes universal aplicável a todos os algoritmos
5. Incorporando funções de teste amplamente conhecidas
6. Construção de funções de teste 3D
7. Considerações finais


1. Introdução. Perspectivas e possibilidades ao utilizar uma classe base de algoritmos populacionais

No mundo da computação moderna e da inteligência artificial, uma classe base projetada para integrar algoritmos de otimização em soluções de software finitas é um elemento fundamental que abre horizontes infinitos de possibilidades técnicas para os desenvolvedores. Herdar dessa classe base no contexto dos algoritmos populacionais não só facilita e melhora desenvolvimento de novos métodos de otimização, mas também expande as perspectivas para a criação de algoritmos híbridos, capazes de se adaptar às mais diversas tarefas.

A união dos algoritmos de otimização dentro da classe base abre portas para desenvolver soluções inovadoras que combinam as melhores características de diferentes métodos. Os algoritmos híbridos, resultantes dessa abordagem, são capazes de superar eficazmente as limitações de métodos individuais e alcançar novos patamares na resolução de tarefas complexas de otimização.

Além disso, a classe base para algoritmos populacionais garante que os algoritmos desenvolvidos sejam simples de utilizar e testar em conjuntos de funções de teste padrão. Isso permite que pesquisadores e desenvolvedores avaliem rapidamente a eficácia dos novos métodos de otimização, comparando seu desempenho com soluções já estabelecidas.

Vamos pensar no mundo da otimização e busca de soluções como um incrível mundo culinário, onde cada método de otimização é como um ingrediente especial que dá seu toque único ao prato. A hibridização, nesse contexto, é como misturar habilmente diferentes ingredientes para criar novos pratos, mais saborosos e interessantes.

Existem muitos métodos de otimização disponíveis, como algoritmos genéticos, estratégias evolucionárias, algoritmos de formigas, otimização por enxame de partículas e muitos outros. Cada um desses métodos tem suas próprias vantagens e limitações.

É aí que a hibridização se torna importante! Você pode escolher o melhor de cada método e combiná-los de maneira única, como um chef experiente. Assim, os métodos de otimização híbridos podem aproveitar as vantagens de diferentes abordagens, compensando suas fraquezas e resultando em ferramentas mais poderosas e eficazes para encontrar soluções ótimas.

Pense na combinação de um algoritmo genético com uma busca local como a mistura ideal de pimenta picante e mel doce em um prato, dando-lhe um sabor profundo e rico. Assim como na culinária, combinar diferentes algoritmos populacionais permite criar métodos inovadores que encontram soluções ótimas de maneira rápida e precisa em diversas áreas, como engenharia, análise financeira ou inteligência artificial.

Assim, a hibridização na otimização não se resume apenas a misturar métodos; é a habilidade de criar novas abordagens que potencializam cada método e alcançam resultados notáveis. Em última análise, graças à hibridização, podemos desenvolver métodos de otimização mais eficazes, inovadores e poderosos, capazes de resolver problemas complexos e impulsionar descobertas em diversas áreas.

Além disso, uma classe base unificada permite aos usuários integrar elementos individuais de cada algoritmo em projetos, facilitando o design de novos métodos de otimização únicos e robustos.


2. Implementando a classe base para algoritmos populacionais

No contexto do artigo sobre a herança da classe base para algoritmos populacionais e a criação de métodos de otimização híbridos, podemos considerar alguns exemplos interessantes de combinações:

  • Algoritmo genético com busca local reforçada. Nessa combinação, o algoritmo genético busca a solução ótima em escala global, enquanto a busca local é usada para aprimorar a solução dentro de um entorno próximo. Isso permite combinar as vantagens da busca global e local, aumentando a precisão e a velocidade de convergência do algoritmo.
  • Estratégia evolucionária com algoritmo de formigas. Aqui, a estratégia evolucionária pode ser usada para alterar os parâmetros do modelo, enquanto o algoritmo de formigas busca o caminho ótimo no espaço de parâmetros. Essa combinação pode ser eficaz para otimizar tarefas complexas, onde é necessário encontrar a combinação ideal de parâmetros.
  • Otimização por enxame de partículas com programação genética. Nessa combinação, o enxame de partículas pode ser usado para explorar o espaço de soluções, enquanto a programação genética evolui as estruturas de programas que resolvem a tarefa de otimização. Isso permite explorar de forma eficaz tanto o espaço de parâmetros quanto as estruturas de soluções.
  • Busca por recozimento simulado com algoritmo genético. Aqui, o recozimento simulado pode ser usado para explorar o espaço de soluções levando em consideração o regime de temperatura, enquanto o algoritmo genético busca soluções ótimas no espaço definido. Essa combinação pode garantir uma exploração mais profunda do espaço de soluções e melhorar a convergência do algoritmo.

Podemos também considerar as seguintes direções como exemplo de uso das possibilidades unificadas em uma única classe base de algoritmos populacionais:

  • Método de abordagem metaheurística combinada. Nesse método, podemos combinar vários algoritmos metaheurísticos diferentes, como algoritmos genéticos, algoritmos de formigas, algoritmos de otimização por enxame de partículas, recozimento simulado, etc. Esses algoritmos podem operar em paralelo ou sequencialmente, trocando informações e combinando seus pontos fortes para uma busca mais eficaz da solução ótima.
  • Método híbrido com gestão adaptativa de estratégias de busca. Nessa abordagem, é possível utilizar mecanismos de gestão adaptativa para combinar dinamicamente diferentes estratégias de otimização dependendo das características do problema. Por exemplo, pode-se ajustar os pesos ou parâmetros de cada método com base na eficácia do seu desempenho na fase atual da otimização.
  • Método híbrido com redes neurais artificiais. Nessa abordagem, redes neurais artificiais podem ser usadas para ajustar dinamicamente os parâmetros e estratégias de otimização dos algoritmos populacionais. As redes neurais podem aprender em tempo real, adaptando-se às mudanças no espaço de busca e recomendando os parâmetros ideais para cada método de otimização.
  • Método de otimização conjunta e aprendizado por reforço. Nessa abordagem, é possível combinar algoritmos populacionais com métodos de aprendizado por reforço para criar um sistema híbrido capaz de explorar e otimizar eficazmente espaços de soluções complexas. Agentes de aprendizado por reforço podem aprender com os resultados dos algoritmos populacionais e vice-versa, criando uma interação entre diferentes métodos de otimização.


A variação na quantidade de parâmetros externos em cada algoritmo de otimização pode criar problemas na herança e aplicação unificada. Para resolver isso, optou-se por definir os parâmetros externos dos algoritmos como padrão no construtor, com base nos resultados de testes extensivos. Ainda assim, há a possibilidade de alterar esses parâmetros antes da inicialização dos algoritmos. Assim, o objeto de cada algoritmo representará uma solução final pronta para uso. Anteriormente, os algoritmos e seus parâmetros eram tratados como entidades separadas.

Vamos começar com a configuração dos parâmetros dos algoritmos. Cada parâmetro é descrito de forma conveniente por uma estrutura "S_AlgoParam", que contém o nome e o valor do parâmetro. Assim, um array contendo objetos dessa estrutura representa o conjunto de parâmetros externos dos algoritmos.

struct S_AlgoParam
{
    double val;
    string name;
};

Em cada algoritmo de otimização, há um agente de busca – a unidade elementar e participante indispensável da estratégia de busca, seja ele uma delicada criatura – um vaga-lume no algoritmo de vaga-lumes, ou uma abelha trabalhadora no algoritmo de abelhas, uma formiga laboriosa no algoritmo de formigas, etc. Eles são artistas únicos no labirinto da otimização, revelando a magnificência da arte de buscar e descobrir caminhos ótimos para o sucesso. Seus esforços e aspirações, como magia, transformam o caos dos dados em harmonia de soluções, iluminando o caminho para novos horizontes de otimização ideal.

Hmmm, sobre o que eu estava falando... ah sim, sobre os agentes de otimização. :)

Assim, cada agente representa uma solução específica para o problema de otimização e possui duas propriedades obrigatórias mínimas: coordenadas no espaço de busca (parâmetros a serem otimizados) e qualidade da solução (função de adaptabilidade ou fitness). Para possibilitar a expansão da funcionalidade e das capacidades e a implementação das propriedades específicas dos algoritmos, organizaremos o agente na forma da classe "C_AO_Agent", que poderá ser herdada posteriormente.

class C_AO_Agent
{
  public:
  ~C_AO_Agent () { }

  double c []; //coordinates
  double f;    //fitness
};

A maioria das operações lógicas nos algoritmos de otimização se repete e pode ser estruturada separadamente na forma de um conjunto de funções da classe "C_AO_Utilities", cujo objeto pode ser usado tanto nas classes dos algoritmos quanto nos agentes.

A classe "C_AO_Utilities" contém os seguintes métodos:

  • Scale: Método sobrecarregado que executa a escala do valor de entrada "In" do intervalo [InMIN, InMAX] para o intervalo [OutMIN, OutMAX]. Também é possível realizar a escala inversa, se o parâmetro "revers" estiver configurado como "true".
  • RNDfromCI: Gera um número real aleatório dentro do intervalo especificado [min, max].
  • RNDintInRange: Gera um número inteiro aleatório dentro do intervalo especificado [min, max].
  • RNDbool: Gera um valor lógico aleatório (true/false).
  • RNDprobab: Gera uma probabilidade aleatória (número real de 0 a 1).
  • SeInDiSp: Calcula o valor considerando um passo especificado "Step" dentro do intervalo [InMin, InMax].
  • Métodos "DecimalToGray", "IntegerToBinary", "GrayToDecimal", "BinaryToInteger", "GetMaxDecimalFromGray": Executam conversões entre números decimais, números binários e códigos Gray.
  • Métodos "GaussDistribution" e "PowerDistribution": Realizam cálculos para a distribuição normal e distribuição de potência, respectivamente.
  • Método "Sorting" (método template): Classifica o array "p" do tipo "T" em ordem decrescente.
  • Estrutura "S_Roulette": Contém os campos "start" e "end" para representar o intervalo.
  • Métodos "PreCalcRoulette" e "SpinRoulette": "PreCalcRoulette" calcula os intervalos para objetos do tipo "T" e os salva no array "roulette". "SpinRoulette" executa a operação de "girar" a roleta com base no tamanho da população "aPopSize".
//——————————————————————————————————————————————————————————————————————————————
class C_AO_Utilities
{
  public: //--------------------------------------------------------------------
  double Scale                 (double In, double InMIN, double InMAX, double OutMIN, double OutMAX);
  double Scale                 (double In, double InMIN, double InMAX, double OutMIN, double OutMAX,  bool revers);
  double RNDfromCI             (double min, double max);
  int    RNDintInRange         (int min, int max);
  bool   RNDbool               ();
  double RNDprobab             ();
  double SeInDiSp              (double In, double InMin, double InMax, double Step);
  void   DecimalToGray         (ulong decimalNumber, char &array []);
  void   IntegerToBinary       (ulong number, char &array []);
  ulong  GrayToDecimal         (const char &grayCode [], int startInd, int endInd);
  ulong  BinaryToInteger       (const char &binaryStr [], const int startInd, const int endInd);
  ulong  GetMaxDecimalFromGray (int digitsInGrayCode);
  double GaussDistribution     (const double In, const double outMin, const double outMax, const double sigma);
  double PowerDistribution     (const double In, const double outMin, const double outMax, const double p);

  //----------------------------------------------------------------------------
  template<typename T>
  void Sorting (T &p [], T &pTemp [], int size)
  {
    int    cnt = 1;
    int    t0  = 0;
    double t1  = 0.0;
    int    ind [];
    double val [];

    ArrayResize (ind, size);
    ArrayResize (val, size);

    for (int i = 0; i < size; i++)
    {
      ind [i] = i;
      val [i] = p [i].f;
    }

    while (cnt > 0)
    {
      cnt = 0;
      for (int i = 0; i < size - 1; i++)
      {
        if (val [i] < val [i + 1])
        {
          t0 = ind [i + 1];
          t1 = val [i + 1];
          ind [i + 1] = ind [i];
          val [i + 1] = val [i];
          ind [i] = t0;
          val [i] = t1;
          cnt++;
        }
      }
    }

    for (int u = 0; u < size; u++) pTemp [u] = p [ind [u]];
    for (int u = 0; u < size; u++) p [u] = pTemp [u];
  }

  //----------------------------------------------------------------------------
  struct S_Roulette
  {
      double start;
      double end;
  };
  S_Roulette roulette [];

  template<typename T>
  void PreCalcRoulette (T &agents [])
  {
    int aPopSize = ArraySize (agents);
    roulette [0].start = agents [0].f;
    roulette [0].end   = roulette [0].start + (agents [0].f - agents [aPopSize - 1].f);

    for (int s = 1; s < aPopSize; s++)
    {
      if (s != aPopSize - 1)
      {
        roulette [s].start = roulette [s - 1].end;
        roulette [s].end   = roulette [s].start + (agents [s].f - agents [aPopSize - 1].f);
      }
      else
      {
        roulette [s].start = roulette [s - 1].end;
        roulette [s].end   = roulette [s].start + (agents [s - 1].f - agents [s].f) * 0.1;
      }
    }
  }
  int  SpinRoulette (int aPopSize);
};
//——————————————————————————————————————————————————————————————————————————————

Como os algoritmos estocásticos de otimização são baseados na geração de números aleatórios, essa operação pode ser executada centenas ou até milhares de vezes para obter cada solução. Portanto, é sensato otimizar o processo de geração de números aleatórios dividindo as tarefas específicas. Considerando que o gerador padrão gera números inteiros, existe um potencial para acelerar esse processo.

Já nos deparamos anteriormente com o método "RNDfromCI", que gera um número real aleatório dentro do intervalo ["min", "max"]:

double C_AO_Utilities ::RNDfromCI (double min, double max)
{
  if (min == max) return min;
  if (min > max)
  {
    double temp = min;
    min = max;
    max = temp;
  }
  return min + ((max - min) * rand () / 32767.0);
}

Muitas vezes há a necessidade de gerar um número inteiro aleatório, por exemplo, para a seleção aleatória de um agente na população. Para isso, o método "RNDintInRange" será útil.

int C_AO_Utilities :: RNDintInRange (int min, int max)
{
  if (min == max) return min;
  if (min > max)
  {
    int temp = min;
    min = max;
    max = temp;
  }
  return min + rand () % (max - min + 1);
}

Obter um valor booleano aleatório com o método "RNDbool" pode ser feito muito rapidamente, em comparação com os dois métodos anteriores, por isso fazia sentido separar os números aleatórios em métodos distintos, dependendo da tarefa.

bool C_AO_Utilities :: RNDbool ()
{
  return rand () % 2 == 0;
}

E ainda temos o método "RNDprobab", que permite obter um número real aleatório no intervalo [0.0, 1.0]. Ele é ideal para executar a probabilidade de realização de certas operações, como a probabilidade de crossover no algoritmo genético. Tais operações também são realizadas com bastante frequência.

double C_AO_Utilities :: RNDprobab ()
{
  return (double)rand () / 32767;
}
Agora, consideremos a classe base "C_AO" dos algoritmos populacionais de otimização. Esta classe descreve os atributos obrigatórios de todos os algoritmos populacionais, tais como:

  • Métodos e propriedades da classe "C_AO":
- SetParams: método virtual para configurar os parâmetros do algoritmo.
- Init: método virtual para inicializar o algoritmo, passando o intervalo mínimo e máximo de busca, o passo e o número de épocas.
- Moving: método virtual para executar um passo do algoritmo.
- Revision: método virtual para realizar a revisão do algoritmo.
- GetName: método para obter o nome do algoritmo.
- GetDesc: método para obter a descrição do algoritmo.
- GetParams: método para obter os parâmetros do algoritmo em formato de string.
  • - Propriedades protegidas da classe "C_AO":
- ao_name: nome do algoritmo.
- ao_desc: descrição do algoritmo.
- rangeMin, rangeMax, rangeStep: arrays para armazenar o intervalo mínimo e máximo de busca, bem como o passo.
- coords: número de coordenadas.
- popSize: tamanho da população.
- revision: flag para revisão.
- u: objeto da classe "C_AO_Utilities" para executar funções auxiliares.
#include "#C_AO_Utilities.mqh"

//——————————————————————————————————————————————————————————————————————————————
class C_AO
{
  public: //--------------------------------------------------------------------
  C_AO () { }
  ~C_AO () { for (int i = 0; i < ArraySize (a); i++) delete a [i];}

  double      cB     []; //best coordinates
  double      fB;        //FF of the best coordinates
  C_AO_Agent *a      []; //agents
  S_AlgoParam params []; //algorithm parameters

  virtual void SetParams () { }
  virtual bool Init (const double &rangeMinP  [], //minimum search range
                     const double &rangeMaxP  [], //maximum search range
                     const double &rangeStepP [], //step search
                     const int     epochsP = 0)   //number of epochs
  { return false;}

  virtual void Moving   () { }
  virtual void Revision () { }

  string GetName   () { return ao_name;}
  string GetDesc   () { return ao_desc;}
  string GetParams ()
  {
    string str = "";
    for (int i = 0; i < ArraySize (params); i++)
    {
      str += (string)params [i].val + "|";
    }
    return str;
  }


  protected: //-----------------------------------------------------------------
  string ao_name;      //ao name;
  string ao_desc;      //ao description

  double rangeMin  []; //minimum search range
  double rangeMax  []; //maximum search range
  double rangeStep []; //step search

  int    coords;       //coordinates number
  int    popSize;      //population size
  bool   revision;

  C_AO_Utilities u;     //auxiliary functions

  bool StandardInit (const double &rangeMinP  [], //minimum search range
                     const double &rangeMaxP  [], //maximum search range
                     const double &rangeStepP []) //step search
  {
    MathSrand ((int)GetMicrosecondCount ()); //reset of the generator
    fB       = -DBL_MAX;
    revision = false;

    coords  = ArraySize (rangeMinP);
    if (coords == 0 || coords != ArraySize (rangeMaxP) || coords != ArraySize (rangeStepP)) return false;

    ArrayResize (rangeMin,  coords);
    ArrayResize (rangeMax,  coords);
    ArrayResize (rangeStep, coords);
    ArrayResize (cB,        coords);

    ArrayCopy (rangeMin,  rangeMinP,  0, 0, WHOLE_ARRAY);
    ArrayCopy (rangeMax,  rangeMaxP,  0, 0, WHOLE_ARRAY);
    ArrayCopy (rangeStep, rangeStepP, 0, 0, WHOLE_ARRAY);

    return true;
  }
};
//——————————————————————————————————————————————————————————————————————————————

Também neste mesmo arquivo, junto com a classe base, está a enumeração "E_AO", contendo identificadores de algoritmos de otimização e a função "SelectAO", que permite criar uma instância do algoritmo correspondente e obter seu ponteiro.

#include "AO_BGA_Binary_Genetic_Algorithm.mqh"
#include "AO_(P_O)ES_Evolution_Strategies.mqh"
#include "AO_DE_Differential_Evolution.mqh"
#include "AO_SDSm_Stochastic_Diffusion_Search.mqh"
#include "AO_ESG_Evolution_of_Social_Groups.mqh";

//——————————————————————————————————————————————————————————————————————————————
enum E_AO
{
  AO_BGA,
  AO_P_O_ES,
  AO_SDSm,
  AO_ESG,
  AO_DE,
  AO_NONE
};
C_AO *SelectAO (E_AO a)
{
  C_AO *ao;
  switch (a)
  {
    case  AO_BGA:
      ao = new C_AO_BGA (); return (GetPointer (ao));
    case  AO_P_O_ES:
      ao = new C_AO_P_O_ES (); return (GetPointer (ao));
    case  AO_SDSm:
      ao = new C_AO_SDSm (); return (GetPointer (ao));
    case  AO_ESG:
      ao = new C_AO_ESG (); return (GetPointer (ao));
    case  AO_DE:
      ao = new C_AO_DE (); return (GetPointer (ao));

    default:
      ao = NULL; return NULL;
  }
}
//——————————————————————————————————————————————————————————————————————————————


3. Código dos algoritmos derivados

Como exemplo de herança da classe base, consideremos o algoritmo de busca difusiva estocástica, SDSm. O agente "C_SDS_Agent" deste algoritmo será herdado do agente base "C_AO_Agent". Note que no método de inicialização do agente estão presentes as coordenadas "c" e a adaptabilidade "f", porém na classe "C_SDS_Agent" elas não são declaradas. Isso é perfeitamente lógico, pois esses atributos são obrigatórios para todos os agentes dos algoritmos de otimização e são herdados do algoritmo base, não sendo necessário declará-los novamente.

//——————————————————————————————————————————————————————————————————————————————
class C_SDS_Agent : public C_AO_Agent
{
  public: //--------------------------------------------------------------------
  ~C_SDS_Agent () { }

  int    raddr     []; //restaurant address
  int    raddrPrev []; //previous restaurant address
  double cPrev     []; //previous coordinates (dishes)
  double fPrev;        //previous fitness

  void Init (int coords)
  {
    ArrayResize (c,         coords);
    ArrayResize (cPrev,     coords);
    ArrayResize (raddr,     coords);
    ArrayResize (raddrPrev, coords);
    f        = -DBL_MAX;
    fPrev    = -DBL_MAX;
  }
};
//——————————————————————————————————————————————————————————————————————————————

A classe "C_AO_SDSm" do algoritmo SDSm é herdada da classe "C_AO". Ao declarar um objeto da classe no construtor, faremos a inicialização dos parâmetros externos do algoritmo, que posteriormente podem ser alterados pelo usuário, se desejado, e os parâmetros estarão disponíveis em formato de array, garantindo a compatibilidade com o banco de testes.

//——————————————————————————————————————————————————————————————————————————————
class C_AO_SDSm : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_SDSm () { }
  C_AO_SDSm ()
  {
    ao_name = "SDSm";
    ao_desc = "Stochastic Diffusion Search";

    popSize    = 100; //population size

    restNumb   = 100;  //restaurants number
    probabRest = 0.05; //probability restaurant choosing

    ArrayResize (params, 3);

    params [0].name = "popSize";    params [0].val  = popSize;

    params [1].name = "restNumb";   params [1].val  = restNumb;
    params [2].name = "probabRest"; params [2].val  = probabRest;
  }

  void SetParams ()
  {
    popSize    = (int)params [0].val;

    restNumb   = (int)params [1].val;
    probabRest = params      [2].val;
  }

  bool Init (const double &rangeMinP  [], //minimum search range
             const double &rangeMaxP  [], //maximum search range
             const double &rangeStepP [], //step search
             const int     epochsP = 0);  //number of epochs

  void Moving   ();
  void Revision ();

  //----------------------------------------------------------------------------
  int    restNumb;          //restaurants number
  double probabRest;        //probability restaurant choosing

  C_SDS_Agent *agent []; //candidates

  private: //-------------------------------------------------------------------
  struct S_Riverbed //русло реки
  {
      double coordOnSector []; //coordinate on the sector (количество ячеек: количество секторов на координате, значение ячеек: конкретная координата на секторе)
  };

  double restSpace [];      //restaurants space
  S_Riverbed    rb [];      //riverbed

  void Research  (const double  ko,
                  const int     raddr,
                  const double  restSpace,
                  const double  rangeMin,
                  const double  rangeStep,
                  const double  pitOld,
                  double       &pitNew);
};
//——————————————————————————————————————————————————————————————————————————————
Em seguida, deve-se considerar especialmente o método de inicialização "Init" da classe "C_AO_SDSm". Passo a passo, o método realiza o seguinte:

1. Primeiramente, é necessário chamar o método da classe base "StandardInit" e passar para ele "rangeMinP", "rangeMaxP", "rangeStepP". Se o método retornar "false", a função "Init" também retorna "false", indicando uma inicialização malsucedida do algoritmo.
2. Exclusão de agentes usando "delete". Isso é necessário ao utilizar o objeto do algoritmo várias vezes.
3. Em seguida, os arrays "agent" do algoritmo SDSm e "a" da classe base são redimensionados para "popSize" e os tipos são convertidos.
4. Depois, são realizadas ações semelhantes ao algoritmo descrito anteriormente no artigo sobre SDSm.
//——————————————————————————————————————————————————————————————————————————————
bool C_AO_SDSm::Init (const double &rangeMinP  [], //minimum search range
                      const double &rangeMaxP  [], //maximum search range
                      const double &rangeStepP [], //step search
                      const int     epochsP = 0)   //number of epochs
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  for (int i = 0; i < ArraySize (agent); i++) delete agent [i];

  ArrayResize (agent, popSize);
  ArrayResize (a,     popSize);

  for (int i = 0; i < popSize; i++)
  {
    a     [i] = new C_SDS_Agent ();
    agent [i] = (C_SDS_Agent *)a [i];

    agent [i].Init (coords);
  }

  ArrayResize (restSpace, coords);
  ArrayResize (rb,        coords);
  for (int i = 0; i < coords; i++)
  {
    ArrayResize     (rb [i].coordOnSector, restNumb);
    ArrayInitialize (rb [i].coordOnSector, -DBL_MAX);
  }

  for (int i = 0; i < coords; i++)
  {
    restSpace [i] = (rangeMax [i] - rangeMin [i]) / restNumb;
  }

  return true;
}
//——————————————————————————————————————————————————————————————————————————————


4. Código para um banco de testes universal aplicável a todos os algoritmos

Embora no momento não haja necessidade de "multiplicar" bancos de testes, ainda assim transferiremos todas as funções do banco de testes para a classe "C_TestStand". Isso permitirá encapsular convenientemente a funcionalidade do banco de testes. Como não houve alterações significativas nas funções do banco de testes, não vamos descrevê-las detalhadamente. Basta apenas observar como agora se parece esta "infraestrutura de teste":

#include <Canvas\Canvas.mqh>
#include <\Math\Functions.mqh>

//——————————————————————————————————————————————————————————————————————————————
class C_TestStand
{
  public: void Init (int width, int height)
  {
    W = width;  //750;
    H = height; //375;

    WscrFunc = H - 2;
    HscrFunc = H - 2;

    //creating a table ---------------------------------------------------------
    string canvasName = "AO_Test_Func_Canvas";

    if (!Canvas.CreateBitmapLabel (canvasName, 5, 30, W, H, COLOR_FORMAT_ARGB_RAW))
    {
      Print ("Error creating Canvas: ", GetLastError ());
      return;
    }

    ObjectSetInteger (0, canvasName, OBJPROP_HIDDEN, false);
    ObjectSetInteger (0, canvasName, OBJPROP_SELECTABLE, true);

    ArrayResize (FunctScrin, HscrFunc);

    for (int i = 0; i < HscrFunc; i++) ArrayResize (FunctScrin [i].clr, HscrFunc);

  }

  struct S_CLR
  {
    color clr [];
  };

  //----------------------------------------------------------------------------
  public: void CanvasErase ()
  {
    Canvas.Erase (XRGB (0, 0, 0));
    Canvas.FillRectangle (1,     1, H - 2, H - 2, COLOR2RGB (clrWhite));
    Canvas.FillRectangle (H + 1, 1, W - 2, H - 2, COLOR2RGB (clrWhite));
  }

  //----------------------------------------------------------------------------
  public: void MaxMinDr (C_Function & f)
  {
    //draw Max global-------------------------------------------------------------
    int x = (int)Scale(f.GetMaxFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 1, W/2 - 1, false);
    int y = (int)Scale(f.GetMaxFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 1, H   - 1, true);

    Canvas.Circle(x, y, 12, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 13, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 14, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 15, COLOR2RGB(clrBlack));

    //draw Min global-------------------------------------------------------------
    x = (int)Scale(f.GetMinFuncX(), f.GetMinRangeX(), f.GetMaxRangeX(), 0, W/2 - 1, false);
    y = (int)Scale(f.GetMinFuncY(), f.GetMinRangeY(), f.GetMaxRangeY(), 0, H - 1, true);

    Canvas.Circle(x, y, 12, COLOR2RGB(clrBlack));
    Canvas.Circle(x, y, 13, COLOR2RGB(clrBlack));
  }

  //----------------------------------------------------------------------------
  public: void PointDr (double &args [], C_Function & f, int shiftX, int shiftY, int count, bool main)
  {
    double x = 0.0;
    double y = 0.0;

    double xAve = 0.0;
    double yAve = 0.0;

    int width  = 0;
    int height = 0;

    color clrF = clrNONE;

    for(int i = 0; i < count; i++)
    {
      xAve += args [i * 2];
      yAve += args [i * 2 + 1];

      x = args [i * 2];
      y = args [i * 2 + 1];

      width  = (int)Scale(x, f.GetMinRangeX(), f.GetMaxRangeX(), 0, WscrFunc - 1, false);
      height = (int)Scale(y, f.GetMinRangeY(), f.GetMaxRangeY(), 0, HscrFunc - 1, true);

      clrF = DoubleToColor(i, 0, count - 1, 0, 270);
      Canvas.FillCircle(width + shiftX, height + shiftY, 1, COLOR2RGB(clrF));
    }

    xAve /=(double)count;
    yAve /=(double)count;

    width  = (int)Scale(xAve, f.GetMinRangeX(), f.GetMaxRangeX(), 0, WscrFunc - 1, false);
    height = (int)Scale(yAve, f.GetMinRangeY(), f.GetMaxRangeY(), 0, HscrFunc - 1, true);

    if(!main)
    {
      Canvas.FillCircle(width + shiftX, height + shiftY, 3, COLOR2RGB(clrBlack));
      Canvas.FillCircle(width + shiftX, height + shiftY, 2, COLOR2RGB(clrWhite));
    }
    else
    {
      Canvas.Circle (width + shiftX, height + shiftY, 5, COLOR2RGB (clrBlack));
      Canvas.Circle (width + shiftX, height + shiftY, 6, COLOR2RGB (clrBlack));
    }
  }

  //----------------------------------------------------------------------------
  public: void SendGraphToCanvas ()
  {
    for (int w = 0; w < HscrFunc; w++)
    {
      for (int h = 0; h < HscrFunc; h++)
      {
        Canvas.PixelSet (w + 1, h + 1, COLOR2RGB (FunctScrin [w].clr [h]));
      }
    }
  }

  //----------------------------------------------------------------------------
  public: void DrawFunctionGraph (C_Function & f)
  {
    double ar [2];
    double fV;

    for (int w = 0; w < HscrFunc; w++)
    {
      ar [0] = Scale (w, 0, H, f.GetMinRangeX (), f.GetMaxRangeX (), false);
      for (int h = 0; h < HscrFunc; h++)
      {
        ar [1] = Scale (h, 0, H, f.GetMinRangeY (), f.GetMaxRangeY (), true);
        fV = f.CalcFunc (ar, 1);
        FunctScrin [w].clr [h] = DoubleToColor (fV, f.GetMinFunValue (), f.GetMaxFunValue (), 0, 270);
      }
    }
  }

  //----------------------------------------------------------------------------
  public: void Update ()
  {
    Canvas.Update ();
  }

  //----------------------------------------------------------------------------
  //Scaling a number from a range to a specified range
  public: double Scale (double In, double InMIN, double InMAX, double OutMIN, double OutMAX, bool Revers = false)
  {
    if (OutMIN == OutMAX) return (OutMIN);
    if (InMIN == InMAX) return ((OutMIN + OutMAX) / 2.0);
    else
    {
      if (Revers)
      {
        if (In < InMIN) return (OutMAX);
        if (In > InMAX) return (OutMIN);
        return (((InMAX - In) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
      }
      else
      {
        if (In < InMIN) return (OutMIN);
        if (In > InMAX) return (OutMAX);
        return (((In - InMIN) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
      }
    }
  }

  //----------------------------------------------------------------------------
  private: color DoubleToColor (const double In,    //input value
                                const double inMin, //minimum of input values
                                const double inMax, //maximum of input values
                                const int    loH,   //lower bound of HSL range values
                                const int    upH)   //upper bound of HSL range values
  {
    int h = (int) Scale (In, inMin, inMax, loH, upH, true);
    return HSLtoRGB (h, 1.0, 0.5);
  }

  //----------------------------------------------------------------------------
  private: color HSLtoRGB (const int    h, //0   ... 360
                           const double s, //0.0 ... 1.0
                           const double l) //0.0 ... 1.0
  {
    int r;
    int g;
    int b;
    if (s == 0.0)
    {
      r = g = b = (unsigned char)(l * 255);
      return StringToColor ((string) r + "," + (string) g + "," + (string) b);
    }
    else
    {
      double v1, v2;
      double hue = (double) h / 360.0;
      v2 = (l < 0.5) ? (l * (1.0 + s)) : ((l + s) - (l * s));
      v1 = 2.0 * l - v2;
      r = (unsigned char)(255 * HueToRGB (v1, v2, hue + (1.0 / 3.0)));
      g = (unsigned char)(255 * HueToRGB (v1, v2, hue));
      b = (unsigned char)(255 * HueToRGB (v1, v2, hue - (1.0 / 3.0)));
      return StringToColor ((string) r + "," + (string) g + "," + (string) b);
    }
  }

  //----------------------------------------------------------------------------
  private: double HueToRGB (double v1, double v2, double vH)
  {
    if (vH < 0) vH += 1;
    if (vH > 1) vH -= 1;
    if ((6 * vH) < 1) return (v1 + (v2 - v1) * 6 * vH);
    if ((2 * vH) < 1) return v2;
    if ((3 * vH) < 2) return (v1 + (v2 - v1) * ((2.0f / 3) - vH) * 6);
    return v1;
  }

  //----------------------------------------------------------------------------
  public: int W; //monitor screen width
  public: int H; //monitor screen height

  private: int WscrFunc; //test function screen width
  private: int HscrFunc; //test function screen height

  public:  CCanvas Canvas;      //drawing table
  private: S_CLR FunctScrin []; //two-dimensional matrix of colors
};
//——————————————————————————————————————————————————————————————————————————————
Agora vamos dar uma olhada no próprio código do banco de testes. Ao estudar os parâmetros de entrada do banco de testes, fica claro que agora temos a possibilidade de escolher o algoritmo de otimização e as funções de teste nas configurações. Isso permite criar conjuntos únicos de funções de teste para verificar o algoritmo. Agora é possível realizar testes apenas em funções contínuas ou apenas em funções discretas. Além disso, agora é possível escolher qualquer combinação de funções de teste, mais adequada às necessidades do usuário.
#include "PAO\#C_TestStandFunctions.mqh"
#include "PAO\#C_AO.mqh"

//——————————————————————————————————————————————————————————————————————————————
input string AOparam            = "----------------"; //AO parameters-----------
input E_AO   AOexactly_P        = AO_NONE;

input string TestStand_1        = "----------------"; //Test stand--------------
input double ArgumentStep_P     = 0.0;   //Argument Step

input string TestStand_2        = "----------------"; //------------------------
input int    Test1FuncRuns_P    = 5;     //Test #1: Number of functions in the test
input int    Test2FuncRuns_P    = 25;    //Test #2: Number of functions in the test
input int    Test3FuncRuns_P    = 500;   //Test #3: Number of functions in the test

input string TestStand_3        = "----------------"; //------------------------
input EFunc  Function1          = Hilly;
input EFunc  Function2          = Forest;
input EFunc  Function3          = Megacity;

input string TestStand_4        = "----------------"; //------------------------
input int    NumbTestFuncRuns_P = 10000; //Number of test function runs
input int    NumberRepetTest_P  = 10;    //Test repets number

input string TestStand_5        = "----------------"; //------------------------
input bool   Video_P            = true;  //Show video
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  C_AO *AO = SelectAO (AOexactly_P);
  if (AO == NULL)
  {
    Print ("AO is not selected...");
    return;
  }

  Print (AO.GetName (), "|", AO.GetDesc (), "|", AO.GetParams ());

  //============================================================================
  C_TestStand ST; //stand
  ST.Init (750, 375);

  double allScore = 0.0;
  double allTests = 0.0;

  C_Function *F1 = SelectFunction (Function1);
  C_Function *F2 = SelectFunction (Function2);
  C_Function *F3 = SelectFunction (Function3);

  if (F1 != NULL)
  {
    Print ("=============================");
    ST.CanvasErase ();

    FuncTests (AO, ST, F1, Test1FuncRuns_P, clrLime,      allScore, allTests);
    FuncTests (AO, ST, F1, Test2FuncRuns_P, clrAqua,      allScore, allTests);
    FuncTests (AO, ST, F1, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    delete F1;
  }

  if (F2 != NULL)
  {
    Print ("=============================");
    ST.CanvasErase ();
    FuncTests (AO, ST, F2, Test1FuncRuns_P, clrLime,      allScore, allTests);
    FuncTests (AO, ST, F2, Test2FuncRuns_P, clrAqua,      allScore, allTests);
    FuncTests (AO, ST, F2, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    delete F2;
  }

  if (F3 != NULL)
  {
    Print ("=============================");
    ST.CanvasErase ();
    FuncTests (AO, ST, F3, Test1FuncRuns_P, clrLime,      allScore, allTests);
    FuncTests (AO, ST, F3, Test2FuncRuns_P, clrAqua,      allScore, allTests);
    FuncTests (AO, ST, F3, Test3FuncRuns_P, clrOrangeRed, allScore, allTests);
    delete F3;
  }

  Print ("=============================");
  if (allTests > 0.0) Print ("All score: ", DoubleToString (allScore, 5), " (", DoubleToString (allScore * 100 / allTests, 2), "%)");
  delete AO;
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void FuncTests (C_AO          &ao,
                C_TestStand   &st,
                C_Function    &f,
                const  int     funcCount,
                const  color   clrConv,
                double        &allScore,
                double        &allTests)
{
  if (funcCount <= 0) return;

  allTests++;

  if (Video_P)
  {
    st.DrawFunctionGraph (f);
    st.SendGraphToCanvas ();
    st.MaxMinDr          (f);
    st.Update            ();
  }

  int    xConv      = 0.0;
  int    yConv      = 0.0;
  double aveResult  = 0.0;
  int    params     = funcCount * 2;
  int    epochCount = NumbTestFuncRuns_P / (int)ao.params [0].val;

  //----------------------------------------------------------------------------
  double rangeMin  [], rangeMax  [], rangeStep [];
  ArrayResize (rangeMin,  params);
  ArrayResize (rangeMax,  params);
  ArrayResize (rangeStep, params);

  for (int i = 0; i < funcCount; i++)
  {
    rangeMax  [i * 2] = f.GetMaxRangeX ();
    rangeMin  [i * 2] = f.GetMinRangeX ();
    rangeStep [i * 2] = ArgumentStep_P;

    rangeMax  [i * 2 + 1] = f.GetMaxRangeY ();
    rangeMin  [i * 2 + 1] = f.GetMinRangeY ();
    rangeStep [i * 2 + 1] = ArgumentStep_P;
  }

  for (int test = 0; test < NumberRepetTest_P; test++)
  {
    //--------------------------------------------------------------------------
    if (!ao.Init (rangeMin, rangeMax, rangeStep)) break;

    // Optimization-------------------------------------------------------------
    for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
    {
      ao.Moving ();

      for (int set = 0; set < ArraySize (ao.a); set++)
      {
        ao.a [set].f = f.CalcFunc (ao.a [set].c, funcCount);
      }

      ao.Revision  ();

      if (Video_P)
      {
        //drawing a population--------------------------------------------------
        st.SendGraphToCanvas  ();

        for (int i = 0; i < ArraySize (ao.a); i++)
        {
          st.PointDr (ao.a [i].c, f, 1, 1, funcCount, false);
        }
        st.PointDr (ao.cB, f, 1, 1, funcCount, true);

        st.MaxMinDr (f);

        //drawing a convergence graph---------------------------------------------
        xConv = (int)st.Scale (epochCNT, 1, epochCount, st.H + 2, st.W - 3, false);
        yConv = (int)st.Scale (ao.fB, f.GetMinFunValue (), f.GetMaxFunValue (), 2, st.H - 2, true);
        st.Canvas.FillCircle (xConv, yConv, 1, COLOR2RGB (clrConv));

        st.Update ();
      }
    }

    aveResult += ao.fB;
  }

  aveResult /= (double)NumberRepetTest_P;

  double score = aveResult;

  Print (funcCount, " ", f.GetFuncName (), "'s; Func runs: ", NumbTestFuncRuns_P, "; result: ", aveResult);
  allScore += score;
}
//——————————————————————————————————————————————————————————————————————————————


5. Incorporando funções de teste amplamente conhecidas

Às vezes me perguntam por que eu não incluí no conjunto de funções de teste aquelas amplamente conhecidas e frequentemente usadas em pesquisas e desenvolvimento de algoritmos de otimização. A razão é que todas elas pertencem à categoria de "funções de teste simples" segundo minha classificação. Mais da metade de suas superfícies está concentrada perto do extremo global, o que as torna muito "previsíveis" para um teste adequado. Mas, apesar disso, as pessoas estão acostumadas a confiar nelas e tendem a testar seus algoritmos justamente nessas funções. Portanto, decidi incluir no conjunto as funções Ackley, Goldstein-Price e Schaffer No. 2. Isso ajudará a equilibrar a escolha das funções de teste para os usuários e a garantir testes mais abrangentes e confiáveis dos algoritmos de otimização, abrindo novos horizontes para os pesquisadores e promovendo uma compreensão mais profunda de sua eficácia.

Fórmula de Ackley:

f(x, y) = -(-20 * exp(-0.2 * sqrt(0.5 * (x^2 + y^2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20)

onde:
x, y são os parâmetros de entrada da função,
e é o número de Euler (aproximadamente 2.71828),
π é o número pi (aproximadamente 3.14159).

Código da função:

double Core (double x, double y)
{
  double res1 = -20.0 * MathExp (-0.2 * MathSqrt (0.5 * (x * x + y * y)));
  double res2 = -MathExp (0.5 * (MathCos (2.0 * M_PI * x) + MathCos (2.0 * M_PI * y)));
  double res3 = -(res1 + res2 + M_E + 20.0);

  return Scale (res3, -14.302667500265278, 0.0, 0.0, 1.0);
}

Fórmula de Goldstein-Price:

f(x, y) = -([1 + (x + y + 1)^2 * (19 - 14x + 3x^2 - 14y + 6xy + 3y^2)] * [30 + (2x - 3y)^2 * (18 - 32x + 12x^2 + 48y - 36xy + 27y^2)])

Código da função:

double Core (double x, double y)
{
  double part1 = 1 + MathPow ((x + y + 1), 2) * (19 - 14 * x + 3 * x * x - 14 * y + 6 * x * y + 3 * y * y);
  double part2 = 30 + MathPow ((2 * x - 3 * y), 2) * (18 - 32 * x + 12 * x * x + 48 * y - 36 * x * y + 27 * y * y);

  return Scale (-part1 * part2, -1015690.2717980597, -3.0, 0.0, 1.0);
}

Fórmula de Schaffer No. 2:

f(x, y) = -(0.5 + ((sin(x^2 - y2)2 - 0.5) / (1 + 0.001 * (x^2 + y2))2))

Código da função:

double Core (double x, double y)
{
  double numerator   = MathPow (MathSin (x * x - y * y), 2) - 0.5;
  double denominator = MathPow (1 + 0.001 * (x * x + y * y), 2);
    
  return Scale (-(0.5 + numerator / denominator), -0.9984331449753265, 0, 0, 1.0);
}

Nota: os valores das funções de teste foram ajustados para o intervalo [0.0, 1.0].

6. Construção de funções de teste 3D

Às vezes, para uma compreensão mais profunda das funções de teste e seu relevo, é necessário não apenas abstrair-se dos números e fórmulas, mas vê-los visualmente. Portanto, decidi aproveitar a oportunidade de construir cenas 3D usando DirectX na plataforma MetaTrader 5. Fui inspirado pelo arquivo "...\MQL5\Experts\Examples\Math 3D Morpher\Math 3D Morpher.mq5" dos desenvolvedores, que faz parte da distribuição padrão e visualiza funções que desenvolvi anteriormente (e que planejo adicionar à lista de funções para o banco de testes). Assim, decidi criar um visualizador para as funções de teste.

Para isso, tive que expandir o arquivo "Functions.mqh", onde está armazenada a classe de funções de teste usadas na testagem dos algoritmos de otimização. As funções adicionais que adicionei permitem não apenas estudar visualmente as mudanças nas funções ao modificá-las, se necessário, mas também investigar mais profundamente suas propriedades e características.

Esse processo não só torna minha pesquisa mais envolvente e visual, como também me ajuda a entender melhor o comportamento das funções de teste. Em última análise, a visualização em 3D me permite não apenas ver números na tela, mas interagir diretamente com a forma e estrutura das funções, o que é importante ao modificá-las e analisar suas propriedades.

Código das funções adicionais para construir o modelo para a cena 3D:

//——————————————————————————————————————————————————————————————————————————————
//GenerateFunctionDataFixedSize
bool GenerateFunctionDataFixedSize (int x_size, int y_size, double &data [], double x_min, double x_max, double y_min, double y_max, C_Function &function)
{
  if (x_size < 2 || y_size < 2)
  {
    PrintFormat ("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size);
    return (false);
  }

  double dx = (x_max - x_min) / (x_size - 1);
  double dy = (y_max - y_min) / (y_size - 1);

  ArrayResize (data, x_size * y_size);

  //---
  for (int j = 0; j < y_size; j++)
  {
    for (int i = 0; i < x_size; i++)
    {
      double x = x_min + i * dx;
      double y = y_min + j * dy;

      data [j * x_size + i] = function.Core (x, y);
    }
  }
  return (true);
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
//GenerateDataFixedSize
bool GenerateDataFixedSize (int x_size, int y_size, C_Function &function, double &data [])
{
  if (x_size < 2 || y_size < 2)
  {
    PrintFormat ("Error in data sizes: x_size=%d,y_size=%d", x_size, y_size);
    return (false);
  }

  return GenerateFunctionDataFixedSize (x_size, y_size, data,
                                        function.GetMinRangeX (),
                                        function.GetMaxRangeX (),
                                        function.GetMinRangeY (),
                                        function.GetMaxRangeY (),
                                        function);
}
//——————————————————————————————————————————————————————————————————————————————

Adicionalmente, adicionei variantes discretas das funções que já conhecemos: SkinDiscrete e HillyDiscrete.

Hilly Discrete

Função HillyDiscrete.

Skin Discrete

Função SkinDiscrete.

Shaffer

Função Shaffer #2.

7. Considerações finais

Continuaremos utilizando as funções de teste "Hilly", "Forest" e "Megacity", que já conquistaram sua reputação como verdadeiros desafios para algoritmos de otimização, ajudando a criar uma tabela de classificação confiável. No entanto, não devemos nos limitar apenas a essas funções, pois, afinal, a lista de funções de teste foi ampliada com sucesso! Por isso, vocês têm a liberdade de escolha: experimentem, explorem, descubram novos horizontes, pois essa é a verdadeira essência da pesquisa científica.

Depois de nossa pesquisa "culinária" sobre métodos de otimização híbridos através da herança da classe base, vemos que a criação de tal ponto de partida abre portas para possibilidades infinitas de pesquisa. A união de diferentes algoritmos populacionais em uma única classe nos permite não apenas melhorar a precisão e velocidade na busca de soluções ótimas, mas também criar uma ferramenta universal para pesquisas.

A criação de um banco de testes universal que reúne diversas funções de teste — discretas, suaves, agudas, não diferenciáveis — abre novas possibilidades para testar e comparar diferentes métodos de otimização. Essa abordagem permite que os pesquisadores não apenas usem conjuntos padrão de funções, mas também criem seus próprios bancos de testes, adaptados a tarefas e requisitos específicos.

Assim, a união em uma única classe de algoritmos populacionais e a criação de um banco de testes universal nos abre um caminho fascinante para criar novas combinações "gastronômicas" de métodos de otimização, permitindo-nos descobrir novos sabores no mundo da busca de soluções ótimas. Vamos continuar esse experimento "culinário" e criar novas obras-primas no caminho para a perfeição e inovação na área de otimização!

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

Arquivos anexados |
AOs.zip (36.92 KB)
Desenvolvendo um EA multimoeda (Parte 4): Ordens virtuais pendentes e salvamento de estado Desenvolvendo um EA multimoeda (Parte 4): Ordens virtuais pendentes e salvamento de estado
Ao começar a desenvolver um EA multimoeda, já alcançamos alguns resultados e realizamos várias iterações de melhoria do código. No entanto, nosso EA não podia trabalhar com ordens pendentes e retomar o trabalho após reiniciar o terminal. Vamos adicionar essas funcionalidades.
Desenvolvendo um sistema de Replay (Parte 58): Voltando a trabalhar no serviço Desenvolvendo um sistema de Replay (Parte 58): Voltando a trabalhar no serviço
Depois de ter dado um tempo no desenvolvimento e aperfeiçoamento do serviço usado no replay / simulação. Iremos voltar a trabalhar nele. Mas como já não iremos mais usar alguns recursos, como as variáveis globais de terminal, se torna necessário uma completa reestruturação de algumas partes do mesmo. Mas não fiquem aflitos, tal processo será adequadamente explicado, para que todos consigam de fato acompanhar o desenrolar do desenvolvimento do serviço.
Trabalho com modelos ONNX nos formatos float16 e float8 Trabalho com modelos ONNX nos formatos float16 e float8
Os formatos de dados utilizados para representar modelos de aprendizado de máquina desempenham um papel fundamental em sua eficiência. Nos últimos anos, surgiram vários novos tipos de dados desenvolvidos especificamente para trabalhar com modelos de aprendizado profundo. Neste artigo, vamos focar em dois novos formatos de dados que se tornaram amplamente utilizados nos modelos modernos.
Redes neurais de maneira fácil (Parte 78): Detecção de objetos baseada em Transformador (DFFT) Redes neurais de maneira fácil (Parte 78): Detecção de objetos baseada em Transformador (DFFT)
Neste artigo, proponho olhar a questão da construção de uma estratégia de trading de outra perspectiva. Em vez de prever o movimento futuro dos preços, tentaremos construir um sistema de trading baseado na análise de dados históricos.