Русский
preview
Algoritmo baseado em fractais - Fractal-Based Algorithm (FBA)

Algoritmo baseado em fractais - Fractal-Based Algorithm (FBA)

MetaTrader 5Negociação |
362 0
Andrey Dik
Andrey Dik

Conteúdo
  1. Introdução
  2. Implementação do algoritmo
  3. Resultados dos testes


Introdução

Os algoritmos metaheurísticos se consolidaram como uma ferramenta poderosa para resolver tarefas complexas de otimização, graças à sua capacidade de encontrar soluções aceitáveis em um tempo razoável. Nas últimas décadas, foram desenvolvidos inúmeros métodos metaheurísticos, inspirados em diferentes processos naturais e sociais, como algoritmos genéticos, otimização por enxame de partículas, evolução diferencial, otimização por colônia de formigas e muitos outros, que já analisamos em artigos anteriores.

Neste artigo, vamos analisar um novo algoritmo metaheurístico para resolver tarefas de otimização contínua, o Fractal-Based Algorithm (FBA), de Marjan Kaedi, de 2017. Essa abordagem é baseada nas propriedades geométricas dos fractais e utiliza o conceito de auto-semelhança para a diversificação adaptativa do espaço. No núcleo do algoritmo está uma heurística inovadora que avalia a perspectiva de diferentes áreas de busca com base na densidade de soluções de qualidade localizadas nelas.

Um aspecto central do método proposto é a divisão iterativa do espaço de busca em subespaços, com a identificação das zonas mais promissoras, que depois são submetidas a uma análise mais detalhada e intensiva. Durante o funcionamento do algoritmo, formam-se estruturas fractais auto-semelhantes, direcionadas para a solução ótima, garantindo um equilíbrio entre diversificação global e refinamento local das soluções encontradas. No artigo, analisaremos em detalhe os fundamentos teóricos do algoritmo, os detalhes de sua implementação, a configuração dos parâmetros-chave e apresentaremos os resultados de uma análise comparativa com outros métodos populares de otimização em funções de teste padrão.


Implementação do algoritmo

Imagine que você está procurando um tesouro em um campo enorme. Como você agiria? Provavelmente não começaria a cavar cada centímetro do solo, pois isso levaria tempo demais. É exatamente esse tipo de problema que o algoritmo fractal (FBA) resolve, ele ajuda a encontrar o "tesouro", que é a solução ótima, em um espaço gigantesco de possibilidades, sem verificar cada ponto.

Como é possível representar o funcionamento desse algoritmo? Primeiro, dividimos todo o território de busca em quadrados iguais, como um tabuleiro de xadrez. Em seguida, lançamos "buscadores" (pontos aleatórios) por todo o campo para obter uma primeira noção do terreno. Cada buscador reporta a adaptabilidade da área que encontrou. O algoritmo seleciona os mais bem-sucedidos, isto é, 60% dos melhores pontos, e observa em quais quadrados eles se concentram. Esses quadrados são marcados como "zonas promissoras", correspondentes a 30% dos melhores quadrados.

Agora, a atenção se concentra exatamente nas áreas promissoras. Cada uma dessas áreas é dividida em quadrados menores, criando-se uma estrutura fractal. A maioria dos novos buscadores de tesouro é direcionada justamente para essas áreas promissoras e, para não perder o tesouro fora das zonas já exploradas, o algoritmo faz com que alguns buscadores, cerca de 5%, ajam de forma um pouco caótica, desviando-se de suas posições em direções aleatórias e explorando locais inesperados.

O processo se repete continuamente. A cada passo, o algoritmo determina com mais precisão onde o tesouro pode estar e direciona mais buscadores para lá. Gradualmente, forma-se uma estrutura hierárquica de quadrados de diferentes tamanhos, lembrando um fractal, daí o nome do algoritmo.

fba-space-partitioning

Figura 1. Representação visual do funcionamento do algoritmo FBA

A ideia principal do FBA, mostrada na figura acima, consiste na divisão gradual do espaço de busca em áreas cada vez menores de forma fractal, concentrando os recursos computacionais nas regiões mais promissoras. Isso cria uma estrutura auto-semelhante que explora o espaço de soluções. Passamos à escrita do pseudocódigo do algoritmo FBA.

Inicialização:
  • Dividir todo o espaço de busca em subespaços iguais
  • Gerar a população inicial de forma uniforme em todo o espaço
  • Avaliar a adaptabilidade de cada ponto
Processo de iteração:
  • Determinação dos pontos promissores: selecionar P1% dos pontos com melhor adaptabilidade.
  • Cálculo dos postos dos subespaços: contar quantos pontos promissores caem em cada subespaço.
  • Seleção dos subespaços promissores: selecionar P2% dos subespaços com os postos promissores mais altos.
  • Divisão dos subespaços promissores: divisão adicional das áreas promissoras em subespaços menores.
  • Geração da nova população: criação de novos pontos com maior densidade nas regiões promissoras.
  • Aplicação da mutação: adicionar ruído gaussiano a P3% dos pontos, mecanismo de diversificação.
  • Integração das populações: união da população atual com a nova população, preservando os melhores pontos.
Critério de parada:
  • Continue até que o critério de parada seja alcançado, como número máximo de iterações, limiar de adaptabilidade etc.
  • Retorne a melhor solução encontrada

Como já compreendemos o conceito do algoritmo, podemos passar à escrita do código. A classe "C_AO_FBA" representa uma implementação especializada do algoritmo de otimização baseado em princípios fractais e deriva da classe base "C_AO".

A classe possui métodos públicos e parâmetros responsáveis pela configuração e pelas ações principais do algoritmo. O construtor define os parâmetros iniciais, como o tamanho da população, as porcentagens de pontos e subespaços promissores, a fração de pontos para alterações aleatórias e o número de intervalos para a divisão de cada dimensão. O método "SetParams" permite atualizar dinamicamente os parâmetros a partir de fontes externas. O método "Init" e as funções subsequentes "Moving" e "Revision" controlam os estágios e as iterações do algoritmo.

Na classe também é declarada uma representação estrutural interna "S_Subspace", que é utilizada para descrever um subespaço de busca. Cada subespaço é caracterizado pelos limites mínimos e máximos em cada dimensão, pelo grau de sua adaptabilidade, pelo nível na hierarquia e pela ligação com o subespaço pai.

Os principais métodos dentro da classe incluem funcionalidades para:

  • Verificar se um ponto se encontra dentro de um determinado subespaço.
  • Criar a anotação inicial do espaço e sua divisão posterior.
  • Identificar pontos promissores, calcular seus postos e selecionar os melhores subespaços para divisões adicionais.
  • Gerar uma nova população por meio de união, mutações e ordenação segundo o critério de eficiência ou adaptabilidade.

Dessa forma, a classe "C_AO_FBA" implementa um algoritmo adaptativo, hierárquico e baseado em fractais para a busca de soluções ótimas em tarefas complexas e multidimensionais, utilizando a divisão dinâmica do espaço, a avaliação de áreas promissoras e operadores heurísticos para aumentar a eficiência da busca.

//——————————————————————————————————————————————————————————————————————————————
class C_AO_FBA : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_FBA () { }
  C_AO_FBA ()
  {
    ao_name = "FBA";
    ao_desc = "Fractal-Based Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/17458";

    popSize = 50;  // размер популяции
    P1      = 60;  // процент перспективных точек
    P2      = 30;  // процент перспективных подпространств
    P3      = 0.8; // процент точек для случайной модификации
    m_value = 10;  // число интервалов для разделения каждого измерения

    ArrayResize (params, 5);

    params [0].name = "popSize"; params [0].val = popSize;
    params [1].name = "P1";      params [1].val = P1;
    params [2].name = "P2";      params [2].val = P2;
    params [3].name = "P3";      params [3].val = P3;
    params [4].name = "m_value"; params [4].val = m_value;
  }

  void SetParams ()
  {
    popSize = (int)params [0].val;
    P1      = (int)params [1].val;
    P2      = (int)params [2].val;
    P3      =      params [3].val;
    m_value = (int)params [4].val;
  }

  bool Init (const double &rangeMinP  [],  // минимальные значения
             const double &rangeMaxP  [],  // максимальные значения
             const double &rangeStepP [],  // шаг изменения
             const int     epochsP = 0);   // количество эпох

  void Moving   ();
  void Revision ();

  //----------------------------------------------------------------------------
  int    P1;           // процент перспективных точек
  int    P2;           // процент перспективных подпространств
  double P3;           // доля точек для случайной модификации
  int    m_value;      // число интервалов для разделения каждого измерения

  private: //-------------------------------------------------------------------

  // Структура для представления подпространства
  struct S_Subspace
  {
      double min [];        // минимальные границы подпространства
      double max [];        // максимальные границы подпространства
      double promisingRank; // ранг перспективности (нормализованное значение)
      bool   isPromising;   // флаг перспективности
      int    parentIndex;   // индекс родительского подпространства (-1 для корневых)
      int    level;         // уровень в иерархии (0 для исходного пространства)

      void Init (int coords)
      {
        ArrayResize (min, coords);
        ArrayResize (max, coords);
        promisingRank = 0.0;
        isPromising   = false;
        parentIndex   = -1;
        level         = 0;
      }
  };

  S_Subspace subspaces     []; // массив подпространств

  // Вспомогательные методы
  bool   IsPointInSubspace              (const double &point [], const S_Subspace &subspace);
  void   CreateInitialSpacePartitioning ();
  void   DivideSubspace                 (int subspaceIndex);
  void   IdentifyPromisingPoints        (int &promisingIndices []);
  void   CalculateSubspaceRanks         (const int &promisingIndices []);
  void   SelectPromisingSubspaces       ();
  void   DividePromisingSubspaces       ();
  void   GenerateNewPopulation          ();
  void   MutatePoints                   ();
  void   SortByFitness                  (double &values [], int &indices [], int size, bool ascending = false);
  void   QuickSort                      (double &values [], int &indices [], int low, int high, bool ascending);
  int    Partition                      (double &values [], int &indices [], int low, int high, bool ascending);
};
//——————————————————————————————————————————————————————————————————————————————

O método de inicialização "Init" é destinado à definição das condições iniciais de funcionamento do algoritmo. Ele recebe arrays com os limites mínimos e máximos das variáveis e os passos de variação para cada parâmetro, bem como o número de épocas. Dentro do método, inicialmente é chamada a inicialização base e, em seguida, é criada a divisão inicial do espaço de busca, isto é, forma-se a estrutura inicial de subespaços com base nos intervalos e passos definidos. Se todas as operações forem bem-sucedidas, o método retorna "true", caso contrário, retorna "false".

O método principal de movimento "Moving" executa o ciclo de busca pela solução por meio de iterações sequenciais. A primeira iteração corresponde à inicialização da população inicial de pontos de forma uniforme em todo o espaço investigado, em que cada valor da variável é escolhido aleatoriamente dentro do intervalo e ajustado ao passo definido.

Em seguida, no processo principal de trabalho, ocorrem vários passos. Primeiro, são identificados os pontos mais promissores, isto é, uma pequena parte dos melhores segundo o valor da função de eficiência. Depois, é realizado o cálculo dos postos desses pontos em relação à sua adaptabilidade para cada subespaço. Com base nesses postos, são selecionados os subespaços mais promissores para posterior divisão em áreas menores. Após isso, essas áreas promissoras são subdivididas, criando localizações de busca mais precisas. Em seguida, forma-se uma nova população de pontos levando em conta seus postos de adaptabilidade, o que permite concentrar os esforços nas regiões mais promissoras. Ao final, é realizada a modificação aleatória dos pontos, isto é, a mutação, para aumentar a diversificação e evitar o aprisionamento em soluções locais.

//——————————————————————————————————————————————————————————————————————————————
bool C_AO_FBA::Init (const double &rangeMinP  [],  // минимальные значения
                     const double &rangeMaxP  [],  // максимальные значения
                     const double &rangeStepP [],  // шаг изменения
                     const int     epochsP = 0)    // количество эпох
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  // Создаем начальное разделение пространства поиска
  CreateInitialSpacePartitioning ();

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

//+----------------------------------------------------------------------------+
//| Основной метод оптимизации                                                 |
//+----------------------------------------------------------------------------+
void C_AO_FBA::Moving ()
{
  // Первая итерация - инициализация начальной популяции
  if (!revision)
  {
    // Инициализация начальной популяции равномерно по всему пространству
    for (int i = 0; i < popSize; i++)
    {
      for (int c = 0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }

    revision = true;
    return;
  }

  // Основной процесс оптимизации

  // 1. Выявление перспективных точек (P1% точек с лучшими значениями функции)
  int promisingIndices [];
  IdentifyPromisingPoints (promisingIndices);

  // 2. Расчет рангов перспективности для каждого подпространства
  CalculateSubspaceRanks (promisingIndices);

  // 3. Выбор P2% самых перспективных подпространств
  SelectPromisingSubspaces ();

  // 4. Разделение перспективных подпространств на более мелкие
  DividePromisingSubspaces ();

  // 5. Генерация новых точек с учетом рангов перспективности
  GenerateNewPopulation ();

  // 6. Случайная модификация (мутация)
  MutatePoints ();
}
//——————————————————————————————————————————————————————————————————————————————

O método "Revision" é responsável por atualizar a melhor solução encontrada no processo de execução do algoritmo. Ele percorre todos os elementos da população atual e compara o valor da função objetivo de cada elemento com o melhor valor atual. Se algum elemento apresentar um resultado melhor, a variável que armazena o melhor resultado é atualizada e o array de variáveis associado a ele, isto é, as coordenadas, é copiado para fixar essa solução ótima. Dessa forma, o método garante o acompanhamento contínuo e a preservação do melhor resultado encontrado até o momento.

//+----------------------------------------------------------------------------+
//| Обновление лучшего решения                                                 |
//+----------------------------------------------------------------------------+
void C_AO_FBA::Revision ()
{
  // Поиск лучшего решения
  for (int i = 0; i < popSize; i++)
  {
    // Обновление лучшего решения
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy (cB, a [i].c, 0, 0, WHOLE_ARRAY);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método de criação da divisão inicial do espaço é destinado à formação da estrutura de subespaços que será utilizada para localizar a busca por soluções ótimas. Ele determina o número total de subespaços com base no grau de divisão definido e na dimensionalidade. No caso de dimensionalidade muito elevada, esse número é limitado por um máximo previamente estabelecido, a fim de evitar custos excessivos de recursos.

Em seguida, ocorre a inicialização do array de subespaços, a cada um dos quais são atribuídos os parâmetros iniciais de nível e de limites para cada coordenada. Dependendo da dimensionalidade do espaço, é escolhido o método de divisão mais adequado:

  • Para um espaço unidimensional, ele é dividido em intervalos uniformes, e cada subespaço criado ocupa um determinado trecho do intervalo.
  • Para um espaço bidimensional, a divisão ocorre ao longo de dois eixos, formando uma grade de regiões retangulares.
  • No caso de dimensões mais altas, é utilizada uma abordagem iterativa, em que, de forma análoga a um sistema de contadores, são geradas combinações de índices para cada coordenada e, para cada região criada, são definidos limites com base nos intervalos correspondentes.

O cálculo dos limites ocorre por meio da divisão dos intervalos de cada dimensão em partes iguais, e para cada subespaço são estabelecidos os limites mínimos e máximos de acordo com os índices atuais. A iteração continua até que todos os subespaços necessários sejam criados ou até que seja atingido o limite de sua quantidade. Como resultado, forma-se uma estrutura que representa a divisão inicial do espaço de busca, pronta para posterior refinamento e para a busca de soluções.

//+----------------------------------------------------------------------------+
//| Создание начального разделения пространства                                |
//+----------------------------------------------------------------------------+
void C_AO_FBA::CreateInitialSpacePartitioning ()
{
  // Создаем начальное разделение пространства
  int totalSubspaces = (int)MathPow (m_value, coords);

  // При очень большой размерности ограничиваем количество подпространств
  if (totalSubspaces > 10000) totalSubspaces = 10000;

  ArrayResize (subspaces, totalSubspaces);

  // Инициализируем все подпространства
  for (int i = 0; i < totalSubspaces; i++)
  {
    subspaces [i].Init (coords);
    subspaces [i].level = 0; // Начальный уровень
  }

  // Разделяем начальное пространство на равные подпространства
  int index = 0;

  // В зависимости от размерности пространства выбираем метод разделения
  if (coords == 1)
  {
    // Одномерный случай
    double intervalSize = (rangeMax [0] - rangeMin [0]) / m_value;

    for (int i = 0; i < m_value && index < totalSubspaces; i++)
    {
      subspaces [index].min [0] = rangeMin [0] + i * intervalSize;
      subspaces [index].max [0] = rangeMin [0] + (i + 1) * intervalSize;
      index++;
    }
  }
  else
    if (coords == 2)
    {
      // Двумерный случай
      double intervalSize0 = (rangeMax [0] - rangeMin [0]) / m_value;
      double intervalSize1 = (rangeMax [1] - rangeMin [1]) / m_value;

      for (int i = 0; i < m_value && index < totalSubspaces; i++)
      {
        for (int j = 0; j < m_value && index < totalSubspaces; j++)
        {
          subspaces [index].min [0] = rangeMin [0] + i * intervalSize0;
          subspaces [index].max [0] = rangeMin [0] + (i + 1) * intervalSize0;
          subspaces [index].min [1] = rangeMin [1] + j * intervalSize1;
          subspaces [index].max [1] = rangeMin [1] + (j + 1) * intervalSize1;
          index++;
        }
      }
    }
    else
    {
      // Многомерный случай - используем итеративный подход
      int indices [];
      ArrayResize (indices, coords);
      for (int i = 0; i < coords; i++) indices [i] = 0;

      while (index < totalSubspaces)
      {
        // Вычисляем границы текущего подпространства
        for (int c = 0; c < coords; c++)
        {
          double intervalSize = (rangeMax [c] - rangeMin [c]) / m_value;
          subspaces [index].min [c] = rangeMin [c] + indices [c] * intervalSize;
          subspaces [index].max [c] = rangeMin [c] + (indices [c] + 1) * intervalSize;
        }

        // Переходим к следующему подпространству
        int c = coords - 1;
        while (c >= 0)
        {
          indices [c]++;
          if (indices [c] < m_value) break;
          indices [c] = 0;
          c--;
        }

        // Если завершили полный цикл, выходим
        if (c < 0) break;

        index++;
      }
    }
}
//——————————————————————————————————————————————————————————————————————————————

O método seguinte é destinado a determinar se um determinado ponto pertence a um subespaço específico. Ele verifica sequencialmente cada coordenada do ponto, comparando seu valor com os limites do subespaço correspondente. Se ao menos para uma coordenada o ponto ultrapassar os limites permitidos, isto é, for menor que o mínimo ou maior ou igual ao máximo, o método retorna "false", o que significa que o ponto não está contido nesse subespaço. Se todas as coordenadas satisfizerem as condições, o método confirma a pertença do ponto ao subespaço, retornando true.

//+----------------------------------------------------------------------------+
//| Определение принадлежности точки подпространству                           |
//+----------------------------------------------------------------------------+
bool C_AO_FBA::IsPointInSubspace (const double &point [], const S_Subspace &subspace)
{
  // Проверяем, находится ли точка в указанном подпространстве
  for (int c = 0; c < coords; c++)
  {
    if (point [c] < subspace.min [c] || point [c] >= subspace.max [c])
    {
      return false;
    }
  }

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

O método de determinação de pontos promissores é destinado a destacar as soluções mais "promising" da população atual. Ele começa com a criação de arrays temporários para armazenar os valores da função de adaptabilidade de cada elemento e seus respectivos índices. Em seguida, esses arrays são preenchidos com os valores e índices correspondentes dos elementos da população.

Depois disso, ocorre a ordenação dos elementos pelo valor da função de adaptabilidade em ordem decrescente. Após a ordenação, é selecionada uma determinada porcentagem das melhores soluções, definida como P1%, e a partir delas é formada uma lista de índices que representam os pontos promissores. A quantidade de pontos selecionados é garantida, no mínimo um, e não excede o tamanho total da população. Como resultado, a função retorna um array de índices das soluções promissoras.

//+----------------------------------------------------------------------------+
//| Выявление перспективных точек                                              |
//+----------------------------------------------------------------------------+
void C_AO_FBA::IdentifyPromisingPoints (int &promisingIndices [])
{
  // Выбираем P1% точек с лучшими значениями функции

  // Создаем массивы для сортировки
  double values  [];
  int    indices [];

  ArrayResize (values,  popSize);
  ArrayResize (indices, popSize);

  // Заполняем массивы
  for (int i = 0; i < popSize; i++)
  {
    values  [i] = a [i].f;
    indices [i] = i;
  }

  // Сортируем по убыванию (для задачи максимизации)
  SortByFitness (values, indices, popSize);

  // Выбираем P1% лучших точек
  int numPromisingPoints = (int)MathRound (popSize * P1 / 100.0);
  numPromisingPoints = MathMax (1, MathMin (numPromisingPoints, popSize));

  ArrayResize (promisingIndices, numPromisingPoints);

  for (int i = 0; i < numPromisingPoints; i++)
  {
    promisingIndices [i] = indices [i];
  }
}
//——————————————————————————————————————————————————————————————————————————————

Em seguida, o método é destinado à avaliação e ao ranqueamento dos subespaços de acordo com sua adaptabilidade. Ele começa com a reinicialização dos valores atuais de classificação para todos os subespaços. Depois disso, é contabilizado quantos pontos promissores caem em cada subespaço, comparando as coordenadas de cada ponto promissor com os limites de cada subespaço e incrementando o contador correspondente quando há coincidência.

É importante que cada ponto seja considerado apenas em um único subespaço. Após a contagem dos valores quantitativos, os postos de cada subespaço são normalizados pela divisão pelo número total de pontos promissores, o que fornece uma avaliação relativa da adaptabilidade de cada subespaço, em que o valor varia entre 0 e 1 e corresponde à proporção de pontos promissores dentro de cada subespaço em relação à população total de pontos promissores. Como resultado, obtém-se um ranking.

//+----------------------------------------------------------------------------+
//| Расчет рангов перспективности подпространств                               |
//+----------------------------------------------------------------------------+
void C_AO_FBA::CalculateSubspaceRanks (const int &promisingIndices [])
{
  // Сбрасываем ранги подпространств
  for (int i = 0; i < ArraySize (subspaces); i++)
  {
    subspaces [i].promisingRank = 0.0;
  }

  // Подсчитываем перспективные точки в каждом подпространстве
  for (int i = 0; i < ArraySize (promisingIndices); i++)
  {
    int pointIndex = promisingIndices [i];

    for (int j = 0; j < ArraySize (subspaces); j++)
    {
      if (IsPointInSubspace (a [pointIndex].c, subspaces [j]))
      {
        subspaces [j].promisingRank++;
        break; // Точка может находиться только в одном подпространстве
      }
    }
  }

  // Нормализуем ранги перспективности согласно статье
  // PromisingRank = Number of promising points in s / Total promising points
  int totalPromisingPoints = ArraySize (promisingIndices);
  if (totalPromisingPoints > 0)
  {
    for (int i = 0; i < ArraySize (subspaces); i++)
    {
      subspaces [i].promisingRank = subspaces [i].promisingRank / totalPromisingPoints;
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "SelectPromisingSubspaces" determina quais subespaços devem ser considerados promissores com base em seus postos. São criados arrays temporários "ranks" e "indices" para armazenar os rankings dos subespaços e seus respectivos índices. Os valores dos rankings e os índices dos subespaços são copiados para os arrays temporários. Para cada subespaço, o flag "isPromising" é definido como "false". Isso é necessário para começar do zero e não considerar os resultados de iterações anteriores. O array "ranks" é ordenado em ordem decrescente e, em paralelo, o array "indices" é reorganizado de forma a preservar a correspondência entre os índices e os rankings.

Dessa forma, após a ordenação, o array "indices" contém os índices dos subespaços classificados em ordem decrescente de seus rankings. É calculada a quantidade de subespaços que serão considerados promissores com base no parâmetro "P2", que representa a porcentagem dos melhores subespaços. O valor obtido é limitado ao intervalo entre 1 e o número total de subespaços. Em seguida, percorrem-se os primeiros "numPromisingSubspaces" elementos do array "indices". Para cada índice nesse array, o flag "isPromising" é definido como "true" para o subespaço correspondente. Isso significa que o subespaço em questão é considerado promissor.

Também é realizada uma verificação do índice para garantir que ele esteja dentro do intervalo permitido, evitando erros ao acessar o array "subspaces". Como resultado da execução do método, o flag "isPromising" fica definido como "true" para P2% dos subespaços com os postos mais altos.

//+----------------------------------------------------------------------------+
//| Выбор перспективных подпространств                                         |
//+----------------------------------------------------------------------------+
void C_AO_FBA::SelectPromisingSubspaces ()
{
  // Выбираем P2% подпространств с наивысшими рангами как перспективные

  // Создаем массивы для сортировки
  double ranks [];
  int indices [];

  int numSubspaces = ArraySize (subspaces);
  ArrayResize (ranks, numSubspaces);
  ArrayResize (indices, numSubspaces);

  // Заполняем массивы
  for (int i = 0; i < numSubspaces; i++)
  {
    ranks [i] = subspaces [i].promisingRank;
    indices [i] = i;
    // Сбрасываем флаг перспективности
    subspaces [i].isPromising = false;
  }

  // Сортируем по убыванию рангов
  SortByFitness (ranks, indices, numSubspaces);

  // Выбираем P2% самых перспективных подпространств
  int numPromisingSubspaces = (int)MathRound (numSubspaces * P2 / 100.0);
  numPromisingSubspaces = MathMax (1, MathMin (numPromisingSubspaces, numSubspaces));

  // Отмечаем перспективные подпространства
  for (int i = 0; i < numPromisingSubspaces && i < ArraySize (indices); i++)
  {
    // Защита от выхода за пределы массива
    if (indices [i] >= 0 && indices [i] < ArraySize (subspaces))
    {
      subspaces [indices [i]].isPromising = true;
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "DividePromisingSubspaces" é destinado à divisão dos subespaços promissores em partes menores. Inicialmente, ele identifica todos os subespaços marcados como promissores por meio da verificação do flag "isPromising". Os índices desses subespaços são coletados em um array temporário "promisingIndices". Em seguida, para cada subespaço cujo índice está presente no array "promisingIndices", é chamada a função "DivideSubspace", passando o índice desse subespaço.

Dessa forma, o método percorre todos os subespaços promissores identificados e aplica sequencialmente a divisão a cada um deles por meio da função "DivideSubspace".

//+----------------------------------------------------------------------------+
//| Разделение перспективных подпространств                                    |
//+----------------------------------------------------------------------------+
void C_AO_FBA::DividePromisingSubspaces ()
{
  // Собираем индексы перспективных подпространств
  int promisingIndices [];
  int numPromising = 0;

  for (int i = 0; i < ArraySize (subspaces); i++)
  {
    if (subspaces [i].isPromising)
    {
      numPromising++;
      ArrayResize (promisingIndices, numPromising);
      promisingIndices [numPromising - 1] = i;
    }
  }

  // Делим каждое перспективное подпространство
  for (int i = 0; i < numPromising; i++)
  {
    DivideSubspace (promisingIndices [i]);
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "DivideSubspace" é destinado à divisão de um subespaço específico em partes menores. Inicialmente, o subespaço pai é selecionado com base no índice fornecido. É verificado se o número total de subespaços não ultrapassa o limite estabelecido de 10 000. Em seguida, é determinado o número total de novos subespaços que serão gerados como resultado da divisão de cada dimensão em "m_value" partes, o que corresponde à elevação de "m_value" à potência igual ao número de dimensões.

O array que armazena todos os subespaços é expandido pelo número de novos subespaços a serem criados. Para cada novo subespaço, são definidos os parâmetros iniciais, como o nível, o índice do subespaço pai e os limites de cada coordenada, que são calculados com base nos limites do subespaço pai e na divisão em partes iguais.

Para cada dimensão, é determinado o tamanho do intervalo, e os limites do subespaço atual são estabelecidos de acordo com os índices correntes. Após a criação de cada novo subespaço, ocorre o incremento dos índices das dimensões para passar à próxima divisão no sistema de multi-índices. Quando o índice de uma dimensão atinge "m_value", ele é reiniciado para zero, e o índice da próxima dimensão é incrementado, realizando-se a enumeração de todas as combinações.

O processo continua até que todos os novos subespaços sejam completamente criados ou até que o limite seja atingido. No caso de enumeração completa de todas as combinações por meio do sistema de contadores, a finalização ocorre de forma natural. Como resultado da execução do método, o subespaço pai é dividido em diversos subespaços menores, cada um cobrindo a respectiva parte do intervalo original em todas as dimensões.

//+----------------------------------------------------------------------------+
//| Разделение конкретного подпространства                                     |
//+----------------------------------------------------------------------------+
void C_AO_FBA::DivideSubspace (int subspaceIndex)
{
  // Делим указанное подпространство на m_value^coords подпространств

  S_Subspace parent = subspaces [subspaceIndex];

  // Ограничение на максимальное количество подпространств
  if (ArraySize (subspaces) > 10000) return;

  // Для каждого измерения делим на m_value частей
  int totalNewSubspaces = (int)MathPow (m_value, coords);
  int currentSize = ArraySize (subspaces);
  ArrayResize (subspaces, currentSize + totalNewSubspaces);

  // Создаем новые подпространства
  int newIndex = currentSize;
  int indices [];
  ArrayResize (indices, coords);
  for (int i = 0; i < coords; i++) indices [i] = 0;

  for (int idx = 0; idx < totalNewSubspaces && newIndex < ArraySize (subspaces); idx++)
  {
    subspaces [newIndex].Init (coords);
    subspaces [newIndex].level = parent.level + 1;
    subspaces [newIndex].parentIndex = subspaceIndex;

    // Вычисляем границы текущего подпространства
    for (int c = 0; c < coords; c++)
    {
      double intervalSize = (parent.max [c] - parent.min [c]) / m_value;
      subspaces [newIndex].min [c] = parent.min [c] + indices [c] * intervalSize;
      subspaces [newIndex].max [c] = parent.min [c] + (indices [c] + 1) * intervalSize;
    }

    // Переходим к следующему подпространству
    int c = coords - 1;
    while (c >= 0)
    {
      indices [c]++;
      if (indices [c] < m_value) break;
      indices [c] = 0;
      c--;
    }

    // Если завершили полный цикл, выходим
    if (c < 0) break;

    newIndex++;
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "GenerateNewPopulation" cria uma nova população de pontos, distribuindo-os pelos subespaços de acordo com sua "adaptabilidade", determinada pelo parâmetro "promisingRank".

Primeiramente, é calculada a soma total de "promisingRank" de todos os subespaços. Esse valor será utilizado para determinar a quantidade proporcional de pontos que deve ser gerada em cada subespaço. Se a soma dos postos se mostrar próxima de zero, isto é, menor que 0.0001, o que pode ocorrer quando todos os subespaços apresentam posto zero, então todos os subespaços recebem um posto positivo mínimo, igual a 1.0, para garantir uma distribuição uniforme dos pontos. Em seguida, para cada subespaço, é calculado o número de pontos que devem ser gerados nele, de forma proporcional ao seu "promisingRank" em relação à soma total dos postos.

É utilizada a fórmula (subspaces[i].promisingRank / totalRank) * popSize, em que popSize é o tamanho desejado da população. O resultado é arredondado para o inteiro mais próximo. A quantidade de pontos é limitada superiormente para não ultrapassar "popSize". Dentro de cada subespaço, é gerado o número calculado de pontos e, para cada ponto, são geradas "coords" coordenadas, utilizando uma distribuição uniforme dentro dos limites do subespaço. Para cada dimensão, o valor da coordenada é limitado pelos valores mínimo e máximo definidos e ajustado à grade com passo "rangeStep[c]".

Se, após a distribuição dos pontos pelos subespaços, o número total de pontos gerados for menor que "popSize", os pontos restantes são gerados de forma uniforme em todo o espaço de busca, utilizando os limites globais do espaço "rangeMin[c]" e "rangeMax[c]", respeitando as restrições e ajustando à grade com passo "rangeStep[c]". Isso garante que a população sempre tenha o tamanho "popSize".

//+----------------------------------------------------------------------------+
//| Генерация новой популяции                                                  |
//+----------------------------------------------------------------------------+
void C_AO_FBA::GenerateNewPopulation ()
{
  // Вычисляем сумму рангов всех подпространств
  double totalRank = 0.0;
  for (int i = 0; i < ArraySize (subspaces); i++)
  {
    totalRank += subspaces [i].promisingRank;
  }

  // Если все ранги равны 0, установим равномерное распределение
  if (totalRank <= 0.0001) // Проверка на приближенное равенство к нулю
  {
    for (int i = 0; i < ArraySize (subspaces); i++)
    {
      subspaces [i].promisingRank = 1.0;
    }
    totalRank = ArraySize (subspaces);
  }

  int points = 0;

  for (int i = 0; i < ArraySize (subspaces) && points < popSize; i++)
  {
    // Вычисляем количество точек для этого подпространства согласно формуле
    int pointsToGenerate = (int)MathRound ((subspaces [i].promisingRank / totalRank) * popSize);

    // Ограничение, чтобы не выйти за пределы
    pointsToGenerate = MathMin (pointsToGenerate, popSize - points);

    // Генерируем точки в этом подпространстве
    for (int j = 0; j < pointsToGenerate; j++)
    {
      // Создаем новую точку равномерно в пределах подпространства
      for (int c = 0; c < coords; c++)
      {
        a [points].c [c] = u.RNDfromCI (subspaces [i].min [c], subspaces [i].max [c]);
        a [points].c [c] = u.SeInDiSp (a [points].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }

      points++;
      if (points >= popSize) break;
    }
  }

  // Если не все точки были сгенерированы, заполняем оставшиеся равномерно по всему пространству
  while (points < popSize)
  {
    for (int c = 0; c < coords; c++)
    {
      a [points].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
      a [points].c [c] = u.SeInDiSp (a [points].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
    points++;
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "MutatePoints" da classe "C_AO_FBA" é destinado à realização da mutação dos pontos na população e faz parte do procedimento do algoritmo para aumentar a variabilidade das soluções e evitar o aprisionamento em armadilhas locais.

Na parte inicial do método, estava prevista a ideia de adicionar ruído gaussiano às coordenadas de pontos selecionados aleatoriamente, conforme o algoritmo FBA original. No entanto, com esse tipo de mutação o algoritmo apresenta um desempenho muito fraco e praticamente não se diferencia dos resultados de um algoritmo aleatório, razão pela qual esse bloco foi comentado, pois foi encontrada uma implementação mais eficaz.

A principal parte implementada do método consiste em percorrer toda a população. Para cada agente, isto é, cada ponto, e para cada coordenada, é verificada uma condição probabilística. Se ela for satisfeita, com base em uma probabilidade de mutação previamente definida, o valor da coordenada é alterado por meio de uma função baseada em uma distribuição de potência, cuja intensidade permite controlar o grau de variação. Em seguida, o valor é refinado e limitado por uma função que garante que ele permaneça dentro dos intervalos estabelecidos e leve em consideração os passos de discretização.

Como resultado, o método assegura mutações aleatórias de pontos individuais da população, promovendo a diversidade das soluções e melhorando a capacidade de busca pelo ótimo global.

//+----------------------------------------------------------------------------+
//| Мутация точек в популяции                                                  |
//+----------------------------------------------------------------------------+
void C_AO_FBA::MutatePoints ()
{
  // Добавляем гауссовский шум к P3% случайно выбранных точек из новой популяции
  /*
  int numToMutate = (int)MathRound (popSize * P3 / 100.0);
  numToMutate = MathMax (1, MathMin (numToMutate, popSize));

  for (int i = 0; i < numToMutate; i++)
  {
    int index = u.RNDminusOne (popSize);

    // Добавляем шум к каждой координате
    for (int c = 0; c < coords; c++)
    {
      // Стандартное отклонение 10% от диапазона
      //double stdDev = (rangeMax [c] - rangeMin [c]) * 0.1;

      // Гауссовский шум с использованием метода Бокса-Мюллера
      //double noise = NormalRandom (0.0, stdDev);

      // Добавляем шум и ограничиваем значение
      a [index].c [c] += noise;
      a [index].c [c] = u.SeInDiSp (a [index].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
  */

  for (int p = 0; p < popSize; p++)
  {
    for (int c = 0; c < coords; c++)
    {
      if (u.RNDprobab () < P3)
      {
        a [p].c [c] = u.PowerDistribution (cB [c], rangeMin [c], rangeMax [c], 20);
        a [p].c [c] = u.SeInDiSp (a [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "SortByFitness" é destinado à ordenação de dois arrays, um contendo os valores da função de adaptabilidade e o outro contendo os índices correspondentes dos elementos. Ele recebe como parâmetros o array de valores, o array de índices, o tamanho dos arrays e um flag opcional que indica a ordem de ordenação.

Se o tamanho do array for maior que um, o método chama a função interna "QuickSort", que executa a ordenação rápida dos elementos. Nesse processo, ambos os arrays são ordenados simultaneamente, de modo que a correspondência entre valores e índices seja preservada. Como resultado, após a execução do método, os elementos ficam ordenados de acordo com o valor da função de adaptabilidade, conforme a ordem selecionada.

//+----------------------------------------------------------------------------+
//| Сортировка по значению фитнес-функции                                      |
//+----------------------------------------------------------------------------+
void C_AO_FBA::SortByFitness (double &values [], int &indices [], int size, bool ascending = false)
{
  if (size > 1) QuickSort (values, indices, 0, size - 1, ascending);
}
//——————————————————————————————————————————————————————————————————————————————

O método "QuickSort" implementa o algoritmo de ordenação rápida para dois arrays relacionados, "values", que é o array de valores, e "indices", que é o array de índices. Ele divide e ordena recursivamente os arrays de forma a manter a correspondência entre cada valor e seu índice original. Os parâmetros incluem os arrays a serem ordenados, os limites da região de ordenação, low e high, e o flag "ascending", que define a ordem da ordenação.

//+----------------------------------------------------------------------------+
//| Алгоритм быстрой сортировки                                                |
//+----------------------------------------------------------------------------+
void C_AO_FBA::QuickSort (double &values [], int &indices [], int low, int high, bool ascending)
{
  if (low < high)
  {
    int pi = Partition (values, indices, low, high, ascending);

    QuickSort (values, indices, low, pi - 1, ascending);
    QuickSort (values, indices, pi + 1, high, ascending);
  }
}
//——————————————————————————————————————————————————————————————————————————————

O método "Partition" é a parte central do algoritmo de ordenação rápida. Sua tarefa é selecionar um elemento pivô e redistribuir os elementos do array "indices" de modo que todos os elementos "menores" que o pivô, ou "maiores", dependendo da ordem de ordenação, fiquem à esquerda, enquanto os "maiores", ou "menores", fiquem à direita. Os conceitos de "menor" e "maior" são definidos em relação aos valores no array "values", para os quais apontam os índices contidos em "indices".

    //+----------------------------------------------------------------------------+
    //| Функция разделения для QuickSort                                           |
    //+----------------------------------------------------------------------------+
    int C_AO_FBA::Partition (double &values [], int &indices [], int low, int high, bool ascending)
    {
      double pivot = values [indices [high]];
      int i = low - 1;
    
      for (int j = low; j < high; j++)
      {
        bool condition = ascending ? (values [indices [j]] < pivot) : (values [indices [j]] > pivot);
        if (condition)
        {
          i++;
          // Обмен значениями
          int temp = indices [i];
          indices [i] = indices [j];
          indices [j] = temp;
        }
      }
    
      // Обмен значениями
      int temp = indices [i + 1];
      indices [i + 1] = indices [high];
      indices [high] = temp;
    
      return i + 1;
    }
    //——————————————————————————————————————————————————————————————————————————————


    Resultados dos testes

    O resultado apresentado pelo algoritmo é bom.

    FBA|Fractal-Based Algorithm|50.0|60.0|30.0|0.8|10.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.7900044352090774
    25 Hilly's; Func runs: 10000; result: 0.6513385092404853
    500 Hilly's; Func runs: 10000; result: 0.2896548031738138
    =============================
    5 Forest's; Func runs: 10000; result: 0.8715768614282637
    25 Forest's; Func runs: 10000; result: 0.5682316842631675
    500 Forest's; Func runs: 10000; result: 0.18876552479611478
    =============================
    5 Megacity's; Func runs: 10000; result: 0.6107692307692306
    25 Megacity's; Func runs: 10000; result: 0.46061538461538465
    500 Megacity's; Func runs: 10000; result: 0.12398461538461655
    =============================
    All score: 4.55494 (50.61%)

    Na visualização, é possível observar como o algoritmo divide o espaço de busca em subespaços cada vez menores. Também se percebe uma alta variabilidade dos resultados ao trabalhar com funções de baixa dimensionalidade.

    Hilly

    FBA na função de teste Hilly

    Forest

    FBA na função de teste Forest

    Megacity

    FBA na função de teste Megacity

    De acordo com os resultados dos testes, o algoritmo FBA ocupa a 29ª posição em nossa tabela de classificação.

    AO Description Hilly Hilly
    Final
    Forest Forest
    Final
    Megacity (discrete) Megacity
    Final
    Final
    Result
    % of
    MAX
    10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)
    1ANSacross neighbourhood search0,949480,847760,438572,235811,000000,923340,399882,323230,709230,634770,230911,574916,13468,15
    2CLAcode lock algorithm (joo)0,953450,871070,375902,200420,989420,917090,316422,222940,796920,693850,193031,683806,10767,86
    3AMOmanimal migration ptimization M0,903580,843170,462842,209590,990010,924360,465982,380340,567690,591320,237731,396755,98766,52
    4(P+O)ES(P+O) evolution strategies0,922560,881010,400212,203790,977500,874900,319452,171850,673850,629850,186341,490035,86665,17
    5CTAcomet tail algorithm (joo)0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
    6TETAtime evolution travel algorithm (joo)0,913620,823490,319902,057010,970960,895320,293242,159520,734620,685690,160211,580525,79764,41
    7SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
    8BOAmbilliards optimization algorithm M0,957570,825990,252352,035901,000000,900360,305022,205380,735380,525230,095631,356255,59862,19
    9AAmarchery algorithm M0,917440,708760,421602,047800,925270,758020,353282,036570,673850,552000,237381,463235,54861,64
    10ESGevolution of social groups (joo)0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
    11SIAsimulated isotropic annealing (joo)0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
    12ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
    13DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
    14BHAmblack hole algorithm M0,752360,766750,345831,864930,935930,801520,271772,009230,650770,516460,154721,321955,19657,73
    15ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
    16RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
    17AOSmatomic orbital search M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
    18TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
    19DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
    20SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
    21CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
    22BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
    23BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
    24HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
    25SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
    26BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
    27ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
    28(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
    29FBAfractal-based Algorithm0,790000,651340,289651,730990,871580,568230,188771,628580,610770,460620,123981,195374,55550,61
    30TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
    31BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
    32WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
    33AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
    34AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
    35ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
    36BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
    37SOAsimple optimization algorithm0,915200,469760,270891,655850,896750,374010,169841,440600,695380,280310,108521,084224,18146,45
    38ABHAartificial bee hive algorithm0,841310,542270,263041,646630,878580,477790,171811,528180,509230,338770,103970,951974,12745,85
    39ACMOatmospheric cloud model optimization0,903210,485460,304031,692700,802680,378570,191781,373030,623080,244000,107950,975034,04144,90
    40ADAMmadaptive moment estimation M0,886350,447660,266131,600140,844970,384930,168891,398800,661540,270460,105941,037944,03744,85
    41CGOchaos game optimization0,572560,371580,320181,264320,611760,619310,621611,852670,375380,219230,190280,784903,90243,35
    42ATAmartificial tribe algorithm M0,717710,553040,252351,523100,824910,559040,204731,588670,440000,186150,094110,720263,83242,58
    43CROmcoral reefs optimization M0,785120,460320,259581,505020,866880,352970,162671,382520,632310,267380,107341,007033,89543,27
    44CFOcentral force optimization0,609610,549580,278311,437500,634180,468330,225411,327920,572310,234770,095860,902943,66840,76
    45ASHAartificial showering algorithm0,896860,404330,256171,557370,803600,355260,191601,350460,476920,181230,097740,755893,66440,71
    RWneuroboids optimization algorithm 2(joo)0,487540,321590,257811,066940,375540,219440,158770,753750,279690,149170,098470,527342,34826,09


    Considerações finais

    A versão do algoritmo FBA com mutação modificada, com resultado de 50.61%, demonstra uma melhoria de duas vezes na eficiência em comparação com os resultados da versão original.

    FBA|Fractal-Based Algorithm|50.0|60.0|30.0|5.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.4780639253626462
    25 Hilly's; Func runs: 10000; result: 0.3222029113692554
    500 Hilly's; Func runs: 10000; result: 0.25720991988937014
    =============================
    5 Forest's; Func runs: 10000; result: 0.36567166223346415
    25 Forest's; Func runs: 10000; result: 0.22043205527307377
    500 Forest's; Func runs: 10000; result: 0.15869146061036057
    =============================
    5 Megacity's; Func runs: 10000; result: 0.2861538461538461
    25 Megacity's; Func runs: 10000; result: 0.15015384615384622
    500 Megacity's; Func runs: 10000; result: 0.09838461538461622
    =============================
    All score: 2.33696 (25.97%)

    Graças a mudanças fundamentais no mecanismo de mutação, a nova abordagem garante um equilíbrio mais razoável entre a diversificação global do espaço de busca e a intensificação local das soluções promissoras encontradas.

    A principal conquista reside no fato de que o algoritmo agora utiliza o conhecimento acumulado sobre o relevo do espaço de busca para direcionar o processo de mutação, em vez de realizar alterações completamente aleatórias. Isso está mais alinhado com processos naturais de otimização, nos quais aleatoriedade e determinismo coexistem de forma equilibrada. Essa abordagem permite que o algoritmo convirja mais rapidamente para o ótimo global, especialmente em espaços multidimensionais complexos com numerosos extremos locais, o que explica a melhoria significativa de desempenho.

    Tab

    Figura 2. Gradiente de cores dos algoritmos de acordo com os testes correspondentes

    chart

    Figura 3. Histograma dos resultados de teste dos algoritmos (em uma escala de 0 a 100, quanto maior, melhor, onde 100 — o resultado teórico máximo possível, no arquivo há um script para o cálculo da tabela de classificação)

    Prós e contras do algoritmo FBA:

    Prós:

    1. Resultados estáveis em funções de média e alta dimensionalidade.

    Contras:

    1. Grande dispersão dos resultados em funções de baixa dimensionalidade.
    2. Ideia base do algoritmo interessante, porém relativamente "fraca".

    Está anexado ao artigo um arquivo com as versões atualizadas dos códigos dos algoritmos. O autor do artigo não se responsabiliza pela absoluta precisão na descrição dos algoritmos canônicos, pois muitos deles sofreram modificações para melhorar as capacidades de busca. As conclusões e julgamentos apresentados nos artigos baseiam-se nos resultados dos experimentos realizados.


    Programas utilizados no artigo

    #NomeTipoDescrição
    1#C_AO.mqh
    Arquivo incluído
    Classe pai dos algoritmos populacionais de otimização
    2#C_AO_enum.mqh
    Arquivo incluído
    Enumeração dos algoritmos populacionais de otimização
    3TestFunctions.mqh
    Arquivo incluído
    Biblioteca de funções de teste
    4
    TestStandFunctions.mqh
    Arquivo incluído
    Biblioteca de funções do banco de testes
    5
    Utilities.mqh
    Arquivo incluído
    Biblioteca de funções auxiliares
    6
    CalculationTestResults.mqh
    Arquivo incluído
    Script para cálculo dos resultados na tabela comparativa
    7
    Testing AOs.mq5
    ScriptBanco de testes unificado para todos os algoritmos populacionais de otimização
    8
    Simple use of population optimization algorithms.mq5
    Script
    Exemplo simples de uso de algoritmos populacionais de otimização sem visualização
    9
    Test_FBA.mq5
    ScriptBanco de testes para o FBA

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

    Arquivos anexados |
    FBA.ZIP (274.96 KB)
    Modelos ocultos de Markov em sistemas de trading com aprendizado de máquina Modelos ocultos de Markov em sistemas de trading com aprendizado de máquina
    Os modelos ocultos de Markov (HMM) representam uma classe poderosa de modelos probabilísticos, destinados à análise de dados sequenciais, nos quais os eventos observáveis dependem de alguma sequência de estados não observáveis (ocultos), que formam um processo de Markov. As principais suposições dos HMM incluem a propriedade de Markov para os estados ocultos, o que significa que a probabilidade de transição para o próximo estado depende apenas do estado atual, e a independência das observações, desde que o estado oculto atual seja conhecido.
    Redefinindo os Indicadores MQL5 e MetaTrader 5 Redefinindo os Indicadores MQL5 e MetaTrader 5
    Uma abordagem inovadora para coletar informações de indicadores em MQL5 permite uma análise de dados mais flexível e simplificada, ao possibilitar que os desenvolvedores passem entradas personalizadas para os indicadores para cálculos imediatos. Essa abordagem é particularmente útil para o trading algorítmico, pois fornece maior controle sobre as informações processadas pelos indicadores, indo além das restrições tradicionais.
    Construindo Expert Advisors Auto-Otimizáveis em MQL5 (Parte 4): Dimensionamento Dinâmico de Posição Construindo Expert Advisors Auto-Otimizáveis em MQL5 (Parte 4): Dimensionamento Dinâmico de Posição
    Empregar com sucesso o trading algorítmico exige aprendizado contínuo e interdisciplinar. No entanto, a gama infinita de possibilidades pode consumir anos de esforço sem gerar resultados tangíveis. Para lidar com isso, propomos uma estrutura que introduz complexidade de forma gradual, permitindo que os traders refinem suas estratégias de maneira iterativa, em vez de dedicar tempo indefinido a resultados incertos.
    Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 9): Fluxo Externo Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 9): Fluxo Externo
    Este artigo explora uma nova dimensão de análise utilizando bibliotecas externas especificamente projetadas para análises avançadas. Essas bibliotecas, como o pandas, fornecem ferramentas poderosas para processar e interpretar dados complexos, permitindo que os traders obtenham percepções mais profundas sobre a dinâmica do mercado. Ao integrar essas tecnologias, podemos reduzir a lacuna entre dados brutos e estratégias acionáveis. Junte-se a nós enquanto estabelecemos as bases dessa abordagem inovadora e desbloqueamos o potencial de combinar tecnologia com expertise em trading.