Русский
preview
Algoritmo de Otimização de Força Central (Central Force Optimization, CFO)

Algoritmo de Otimização de Força Central (Central Force Optimization, CFO)

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


Conteúdo

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


Introdução

A natureza, evoluindo por bilhões de anos, criou inúmeros mecanismos de otimização eficientes que inspiram pesquisadores a desenvolver novos algoritmos. Um desses fenômenos naturais inspiradores é a gravidade a força fundamental que governa o movimento dos corpos celestes. Já analisamos anteriormente algoritmos de natureza semelhante.

O algoritmo de otimização de força central (Central Force Optimization, CFO) representa uma tentativa de transferir os princípios da cinemática gravitacional para o campo da otimização numérica. Este algoritmo meta-heurístico, baseado em leis de movimento determinísticas, modela a interação de partículas virtuais em um espaço multidimensional de soluções, onde cada partícula busca regiões com melhores valores da função objetivo sob a ação de um análogo da força gravitacional.

A base do CFO é uma metáfora simples e intuitiva: imagine um conjunto de pontos de teste (probes) distribuídos no espaço de soluções possíveis. Cada probe possui uma massa proporcional à qualidade da solução que represent ao valor da função objetivo. Assim como corpos celestes massivos atraem os menos massivos, os probes com melhores soluções criam um campo gravitacional virtual que atrai os demais.

O movimento de cada probe obedece a leis análogas às leis de Newton: a aceleração é determinada pela força total de atração proveniente dos outros probes, e o deslocamento ocorre de acordo com as equações cinemáticas do movimento. Uma característica importante do algoritmo é sua natureza determinística diferentemente de muitos outros métodos meta-heurísticos, o CFO não utiliza variáveis aleatórias em seu ciclo principal de operação.


Implementação do algoritmo

A história do algoritmo CFO começou quando pesquisadores se perguntaram: e se aplicássemos as leis da física à busca por soluções ótimas? Imagine uma vasta paisagem montanhosa, onde a altura de cada ponto corresponde à qualidade da solução. Quanto mais alto o pico, melhor a solução. Nosso objetivo é encontrar o ponto mais alto, mas o problema é que não conseguimos ver toda a paisagem de uma só vez. Em vez disso, temos um grupo de exploradores, chamemo-los de probes, que podem se mover por essa paisagem e relatar a altura de sua posição atual.

No início, nossos probes estão distribuídos aleatoriamente pelo território. Alguns acabam em vales, outros em encostas de colinas, e alguns podem ter sorte e cair diretamente no topo de pequenas elevações. Cada probe possui seu próprio peso, proporcional à altura do ponto em que se encontra. Quanto mais alto o ponto, mais pesado o probe. É aqui que começa a parte mais interessante: nossos probes não se movem de forma aleatória, eles seguem as leis da gravidade. Imagine que os probes mais pesados (aqueles que encontraram pontos melhores) atraem os probes mais leves (aqueles que estão em posições piores). Essa atração, porém, funciona apenas em uma direção: boas soluções atraem as ruins, mas não o contrário.

A força de atração é calculada segundo regras semelhantes à lei da gravitação universal de Newton. Ela depende da diferença de peso entre os probes (diferença na qualidade das soluções) e da distância entre eles. Um probe com alto valor de função de fitness exerce forte atração sobre probes próximos com valores mais baixos, mas tem pouca influência sobre probes distantes. Sob a ação dessas forças, cada probe adquire aceleração e começa a se mover. Os probes pequenos e leves se dirigem em direção aos mais pesados, como se esferas rolassem pelas encostas das colinas rumo aos picos. A cada passo do algoritmo, os probes recalculam as forças de atração e ajustam seus movimentos. Caso um probe tente sair dos limites definidos da área de busca, entra em ação o mecanismo de reflexão: imagine uma parede na borda do território, contra a qual o probe rebate e retorna à área permitida.

Com o passar do tempo, os probes começam a se agrupar em torno das regiões mais altas do terreno. A maioria deles se concentra nas áreas mais promissoras e, a cada iteração, localiza com maior precisão as posições dos picos. No cenário ideal, se o algoritmo tiver tempo suficiente para operar, todos os probes acabam se reunindo em torno do máximo global, o ponto mais alto de todo o terreno.

A característica distintiva do CFO é que, em sua essência, ele é um algoritmo determinístico. Se for executado duas vezes com a mesma distribuição inicial de probes, o resultado será exatamente o mesmo. Essa propriedade o diferencia de muitos outros algoritmos meta-heurísticos que dependem de elementos aleatórios. 

cfo-algorithm

Figura 1. Esquema de funcionamento do algoritmo de otimização de força central

Na figura 1 é mostrado o princípio de funcionamento do algoritmo de otimização de força central (CFO) no espaço de busca. É apresentado o relevo da função objetivo, com um gradiente de cores do azul (valores baixos) ao amarelo (valores altos). Máximo global (ponto mais alto) e máximo local (pico mais baixo). Três probes (agentes de busca), identificados como Probe 1, Probe 2 e Probe 3. Forças de atração (seta vermelha) mostram como pontos mais altos atraem os probes. Isso é a ideia central do CFO, melhores soluções atraem as piores, mas não o contrário. Linhas pontilhadas mostram a trajetória dos probes rumo aos máximos. A fórmula matemática desse processo inclui:

Cálculo da força: para quaisquer dois probes i e j, se o valor da função (massa) de j for maior que o de i, então j exerce força sobre i de acordo com: F = g × [(m_j - m_i)^α / d^β] × direção, onde:
  • g — constante gravitacional
  • m_j e m_i — valores da função (massas) dos probes j e i
  • α — expoente de massa (geralmente 2)
  • d — distância entre os probes
  • β — expoente de distância (geralmente 2)
  • direção — vetor unitário apontando do probe i para o probe j
Cálculo da aceleração: a aceleração do probe i é calculada como a soma de todas as forças que atuam sobre ele.
Atualização de posição: a posição de cada probe é atualizada de acordo com: x_new = x_old + 0,5 × a, onde:
  • x_new — nova posição
  • x_old — posição atual
  • a aceleração
Tratamento de fronteiras: se um probe sair dos limites de busca, ele será trazido de volta.

    O algoritmo aplica iterativamente esses cálculos a todos os probes, movendo-os gradualmente em direção aos ótimos no espaço de busca. Com o tempo, os probes tendem a se agrupar em torno dos máximos globais e locais, sendo que a maior concentração geralmente é observada ao redor do ótimo global.

    A particularidade do CFOdecorre de sua natureza determinística e do mecanismo de atração unidirecional, que direciona a busca para as melhores soluções sem envolver aleatoriedade em sua forma básica.  Pseudocódigo do algoritmo CFO:

    1. Inicialização:
      • Definimos os limites do espaço de busca.
      • Definimos os parâmetros: número de probes, constante gravitacional, expoentes para massa e distância, fator de reposicionamento.
      • Criamos o número especificado de probes e os posicionamos aleatoriamente no espaço de busca.
      • Para cada probe, calculamos o valor inicial da função objetivo.
    2. Ciclo principal (repetimos por um número definido de épocas):
      • Para cada probe:
        • Zeramos o vetor de aceleração.
        • Consideramos a influência de outros probes:
          • Se outro probe tiver melhor valor de função objetivo (maior "massa"), ele cria uma força de atração.
          • Calculamos a distância entre os probes.
          • A força de atração é proporcional à diferença de "massas" elevada à potência alpha e inversamente proporcional à distância elevada à potência beta.
          • Direção da força: do probe atual para o mais "pesado".
          • Somamos as influências de todos os probes com melhores valores de função.
      • Após calcular todas as acelerações, atualizamos as posições dos probes:
        • Nova posição = posição antiga + metade da aceleração.
        • Se um probe ultrapassar os limites do espaço de busca, aplicamos reposicionamento:
          • Ao ultrapassar o limite inferior: refletimos o probe para dentro do espaço considerando o fator de reposicionamento.
          • Ao ultrapassar o limite superior: de forma análoga, mas do outro lado.
      • Recalculamos os valores da função objetivo para todos os probes em suas novas posições.
      • Atualizamos a informação sobre a melhor solução encontrada.
    3. Encerramento:
      • Devolvemos a melhor solução encontrada (coordenadas do probe com o valor máximo da função objetivo).

    Passemos à implementação do código do algoritmo, definindo a estrutura "S_CFO_Agent", que herda de "S_AO_Agent" e indica que "S_CFO_Agent" recebe todas as propriedades e métodos definidos em "S_AO_Agent". 

    Os campos da estrutura: a[] é um array dinâmico destinado a armazenar os valores de aceleração. O método "Init()" é usado para inicializar a estrutura, redimensionando o array "c" de acordo com o parâmetro "coords" recebido e redimensionando o array de aceleração "a" conforme "coords". Isso permite definir dinamicamente o tamanho do array com base no número de coordenadas. Todos os elementos do array de aceleração "a" são inicializados com o valor "0.0", zerando os valores antes de seu uso. O valor da variável "f" é definido como o menor valor possível do tipo "double" para inicializar a função de fitness, garantindo que qualquer valor calculado posteriormente seja maior que esse valor.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Структура для пробы CFO
    struct S_CFO_Agent : public S_AO_Agent
    {
        double a []; // вектор ускорения
    
        void Init (int coords)
        {
          ArrayResize (c, coords);   // координаты
          ArrayResize (a, coords);   // ускорение
          ArrayInitialize (a, 0.0);  // обнуляем ускорения
          f = -DBL_MAX;              // значение фитнес-функции
        }
    };
    //——————————————————————————————————————————————————————————————————————————————

    A classe "C_AO_CFO" herda da classe "C_AO" e define o algoritmo CFO. Construtor e destrutor. Neste caso, o destrutor não executa nenhuma ação específica. "C_AO_CFO()" é o construtor, responsável por inicializar os parâmetros do algoritmo. Ele define os valores de várias variáveis, tais como:

    • popSize, g, alpha, beta, initialFrep, finalFrep, noiseFactor são parâmetros relacionados ao algoritmo e às suas funções de otimização.
    • frep o fator de reposicionamento atual, inicializado com o valor de "initialFrep".
    • inicializa o array "params", que conterá os parâmetros do algoritmo, e em seguida copia seus valores para o array com os nomes correspondentes.

    Métodos da classe:

    • SetParams() define os parâmetros do algoritmo com base nos valores do array "params". Também define o fator de reposicionamento atual para "initialFrep".
    • Init() responsável pela inicialização do algoritmo, configurando os valores mínimos e máximos dos parâmetros, bem como os passos utilizados para suas variações. O parâmetro "epochsP" define o número de épocas em que o algoritmo será executado.
    • Moving() responsável pelo processo de movimentação dos probes (agentes) dentro do algoritmo.
    • Revision() pode ser utilizado para realizar uma revisão ou atualização do estado dos agentes.

    Campos da classe:

    • S_CFO_Agent probe[] array de probes (agentes) que serão utilizados no processo de otimização.
    • epochs, epochNow campos privados, representando respectivamente o número total de épocas e a época atual.

    Métodos privados adicionais:

    • InitialDistribution() utilizado para inicializar a distribuição dos agentes no espaço de busca.
    • UpdateRepFactor() responsável por atualizar o fator de reposicionamento conforme o estado atual do sistema.
    • CalculateAccelerations() usado para calcular as acelerações dos agentes com base em suas posições e interações.
    • UpdatePositions() utilizado para atualizar as posições dos agentes de acordo com suas acelerações.
    • CalculateDistanceSquared() método usado para calcular a distância entre dois pontos no espaço, servindo para estimar a interação entre os agentes.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Основной класс алгоритма CFO
    class C_AO_CFO : public C_AO
    {
      public: //--------------------------------------------------------------------
      ~C_AO_CFO () { }
      C_AO_CFO ()
      {
        ao_name = "CFO";
        ao_desc = "Central Force Optimization";
        ao_link = "https://www.mql5.com/ru/articles/17167";
    
        popSize     = 30;          // число проб
        g           = 1.0;         // гравитационная постоянная
        alpha       = 0.1;         // степень для массы
        beta        = 0.1;         // степень для расстояния
        initialFrep = 0.9;         // начальный фактор репозиционирования
        finalFrep   = 0.1;         // конечный фактор репозиционирования
        noiseFactor = 1.0;         // фактор случайного шума
    
        frep        = initialFrep; // текущий фактор репозиционирования
    
        ArrayResize (params, 7);
        params [0].name = "popSize";     params [0].val = popSize;
        params [1].name = "g";           params [1].val = g;
        params [2].name = "alpha";       params [2].val = alpha;
        params [3].name = "beta";        params [3].val = beta;
        params [4].name = "initialFrep"; params [4].val = initialFrep;
        params [5].name = "finalFrep";   params [5].val = finalFrep;
        params [6].name = "noiseFactor"; params [6].val = noiseFactor;
      }
    
      void SetParams ()
      {
        popSize     = (int)MathMax (1, params [0].val);
        g           = params [1].val;
        alpha       = params [2].val;
        beta        = params [3].val;
        initialFrep = params [4].val;
        finalFrep   = params [5].val;
        noiseFactor = params [6].val;
    
        frep        = initialFrep;
      }
    
      bool Init (const double &rangeMinP  [],  // минимальные значения
                 const double &rangeMaxP  [],  // максимальные значения
                 const double &rangeStepP [],  // шаг изменения
                 const int     epochsP = 0);   // количество эпох
    
      void Moving   ();
      void Revision ();
    
      //----------------------------------------------------------------------------
      double g;              // гравитационная постоянная
      double alpha;          // степень для массы
      double beta;           // степень для расстояния
      double initialFrep;    // начальный фактор репозиционирования
      double finalFrep;      // конечный фактор репозиционирования
      double noiseFactor;    // фактор случайного шума
    
      S_CFO_Agent probe [];  // массив проб
    
      private: //-------------------------------------------------------------------
      int    epochs;         // общее число эпох
      int    epochNow;       // текущая эпоха
      double frep;           // фактор репозиционирования
    
      void   InitialDistribution      ();
      void   UpdateRepFactor          ();
      void   CalculateAccelerations   ();
      void   UpdatePositions          ();
      double CalculateDistanceSquared (const double &x1 [], const double &x2 []);
    };
    //——————————————————————————————————————————————————————————————————————————————

    O método "Init()" da classe "C_AO_CFO" é responsável pela inicialização do algoritmo CFO, recebendo arrays com os valores mínimos e máximos dos parâmetros, o passo de variação desses valores e o número de épocas (por padrão, 0). Ele chama o método "StandardInit" para verificar a validade dos intervalos de valores fornecidos. Caso a verificação falhe, o método retorna "false". Define o número total de épocas e a época atual (0). Redimensiona o array de probes (agentes) para corresponder ao tamanho da população (popSize). Inicializa cada agente no array "probe", chamando o método "Init" para cada um deles e passando as coordenadas correspondentes. Se a inicialização for bem-sucedida, o método retorna "true". Assim, o método "Init" define os parâmetros e as condições iniciais necessárias para o funcionamento do algoritmo.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Инициализация
    bool C_AO_CFO::Init (const double &rangeMinP  [], // минимальные значения
                         const double &rangeMaxP  [], // максимальные значения
                         const double &rangeStepP [], // шаг изменения
                         const int     epochsP = 0)
    {
      if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
    
      //----------------------------------------------------------------------------
      epochs   = epochsP;
      epochNow = 0;
    
      // Инициализация проб
      ArrayResize (probe, popSize);
      for (int i = 0; i < popSize; i++) probe [i].Init (coords);
    
      return true;
    }
    //——————————————————————————————————————————————————————————————————————————————

    O método "Moving()" da classe "C_AO_CFO" é responsável pela etapa principal do algoritmo CFO. O método começa incrementando o contador da época atual, indicando o próximo passo da execução. Se o método for chamado pela primeira vez, quando "revision" é igual a "false", ele realiza a inicialização inicial e encerra em seguida. Isso é necessário para configurar os valores e estados iniciais. Em seguida, ocorre a cópia dos valores da função de fitness do array de agentes para um array temporário, garantindo que os dados permaneçam atualizados para os cálculos subsequentes.

    O método atualiza o parâmetro responsável pelo reposicionamento dos agentes no espaço de busca, algo essencial para a adaptabilidade do algoritmo, em seguida, o método calcula as acelerações dos agentes com base em seu estado atual e atualiza suas posições, garantindo o movimento dos agentes dentro do espaço de busca. Ao final, o método sincroniza as novas posições dos agentes com o array principal, registrando as mudanças e assegurando a consistência dos dados. O método "Moving()" realiza uma atualização sistemática do estado dos agentes, baseada em suas funções de fitness e posições atuais durante a execução do algoritmo.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Основной шаг алгоритма
    void C_AO_CFO::Moving ()
    {
      epochNow++;
    
      // Начальная инициализация
      if (!revision)
      {
        InitialDistribution ();
        revision = true;
        return;
      }
    
      //----------------------------------------------------------------------------
      // Копируем значения фитнес-функции из базового класса
      for (int p = 0; p < popSize; p++)
      {
        probe [p].f = a [p].f;
      }
    
      // Обновляем параметр репозиционирования
      UpdateRepFactor ();
    
      // Основной цикл CFO
      CalculateAccelerations ();
      UpdatePositions ();
    
      // Синхронизируем позиции с базовым классом
      for (int p = 0; p < popSize; p++)
      {
        ArrayCopy (a [p].c, probe [p].c);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    O método "InitialDistribution" da classe "C_AO_CFO" é responsável pela distribuição inicial dos probes (agentes) no espaço de busca. O método percorre cada agente da população "popSize". Para cada agente, os valores são inicializados, atribuindo o array "a" com zeros e configurando a função de fitness "f" para o menor valor possível. Para cada coordenada do agente, é realizado um sorteio aleatório de valores dentro dos limites especificados (rangeMin e rangeMax). Após gerar o valor aleatório, ele é processado pelo método "SeInDiSp", que ajusta o valor para o intervalo permitido e o passo "rangeStep". Por fim, as coordenadas do agente são copiadas do array temporário "c" para o array principal "a", registrando o estado inicial de cada agente.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Начальное распределение проб
    void C_AO_CFO::InitialDistribution ()
    {
      for (int p = 0; p < popSize; p++)
      {
        ArrayInitialize (probe [p].a, 0.0);
        probe [p].f = -DBL_MAX;
    
        // Случайное распределение
        for (int c = 0; c < coords; c++)
        {
          probe [p].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
          probe [p].c [c] = u.SeInDiSp (probe [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
    
        ArrayCopy (a [p].c, probe [p].c);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    O método "UpdateRepFactor" da classe "C_AO_CFO" é responsável por atualizar o fator de reposicionamento (ou fator de ajuste) durante a execução do algoritmo. O método começa verificando se o número de épocas "epochs" é maior que zero, caso positivo, calcula-se um novo valor para o fator de reposicionamento "frep" através de uma redução linear entre o valor inicial "initialFrep" e o valor final "finalFrep". Esse cálculo é baseado na época atual "epochNow" dentro do total de épocas definido. Se o número de épocas for igual a zero, o valor de "frep" permanece igual ao valor inicial "initialFrep". Isso garante estabilidade do fator quando o algoritmo ainda está na fase inicial. No final do método, o valor de "frep" é limitado ao intervalo entre 0 e 1 utilizando as funções "MathMax" e "MathMin". Essa limitação garante que o fator de reposicionamento não ultrapasse os limites definidos, o que é essencial para a estabilidade e a precisão do funcionamento do algoritmo.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Обновление фактора репозиционирования
    void C_AO_CFO::UpdateRepFactor ()
    {
      // Линейное уменьшение frep от начального к конечному значению
      if (epochs > 0) frep = initialFrep - (initialFrep - finalFrep) * epochNow / epochs;
      else frep = initialFrep;
    
      // Ограничение значения
      frep = MathMax (0.0, MathMin (1.0, frep));
    }
    //——————————————————————————————————————————————————————————————————————————————

    O método "CalculateAccelerations" da classe "C_AO_CFO" é projetado para calcular as acelerações de cada agente (ou probe) da população com base na influência de todos os outros agentes. A seguir, são descritos os principais passos e a lógica de funcionamento do método. Para cada agente da população (de 0 até popSize), os valores de aceleração "probe[p].a" são zerados. Isso é feito para garantir que o cálculo comece do zero e que as acelerações sejam acumuladas apenas com base nas interações com os outros agentes. Para cada agente, um laço interno percorre todos os demais agentes (de 0 até popSize). Se o índice do agente atual "p" for igual ao índice de outro agente "k", a iteração é ignorada com o comando "continue". Em seguida, é calculada a diferença entre os valores de fitness dos dois agentes (massDiff = probe[k].f - probe[p].f). Esse valor é usado para determinar o quanto um agente é "mais pesado" (ou melhor) em comparação ao outro. Se "massDiff" for maior que zero, isso significa que o agente com índice "k" possui um valor de fitness superior ao do agente com índice "p". Nesse caso, executam-se as seguintes operações:

    1. Cálculo da distância: é calculado o quadrado da distância entre as coordenadas atuais de dois agentes utilizando a função "CalculateDistanceSquared". Se essa distância for muito pequena (menor que o menor valor positivo possível), a iteração é ignorada.

    2. Formação da direção da força: se a distância for maior que "DBL_EPSILON", calcula-se a distância real e, para cada coordenada "c", é determinado o vetor de direção da força, apontando do agente atual para o agente com maior valor de fitness.

    3. Fórmula de aceleração: a aceleração do agente atual é atualizada de acordo com a fórmula: probe[p].a[c] += g * MathPow(massDiff, alpha) * direction / MathPow(distance, beta), onde são considerados a diferença de massa (valores de fitness), a distância entre os probes e os coeficientes definidos (g, alpha, beta), que influenciam a intensidade da interação.

    O método permite que cada agente leve em consideração a influência dos demais sobre sua aceleração, o que constitui um elemento essencial do processo de otimização. As acelerações calculadas dessa forma serão posteriormente utilizadas para atualizar as posições dos agentes no espaço de soluções.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Вычисление ускорений
    void C_AO_CFO::CalculateAccelerations ()
    {
      for (int p = 0; p < popSize; p++)
      {
        // Обнуляем ускорение для текущей пробы
        ArrayInitialize (probe [p].a, 0.0);
    
        // Суммируем влияние всех других проб
        for (int k = 0; k < popSize; k++)
        {
          if (k == p) continue;
    
          // Разница масс (фитнес-значений)
          double massDiff = probe [k].f - probe [p].f;
    
          // Проверяем условие единичной функции U(...)
          if (massDiff > 0) // Строгое условие для единичной функции
          {
            // Вычисляем расстояние между пробами
            double distSquared = CalculateDistanceSquared (probe [k].c, probe [p].c);
            if (distSquared < DBL_EPSILON) continue;
    
            double distance = MathSqrt (distSquared);
    
            for (int c = 0; c < coords; c++)
            {
              // Направление силы
              double direction = (probe [k].c [c] - probe [p].c [c]) / distance;
    
              // Формула ускорения
              probe [p].a [c] += g * MathPow (massDiff, alpha) * direction / MathPow (distance, beta);
            }
          }
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    O método "UpdatePositions" da classe "C_AO_CFO" tem a função de atualizar as posições dos agentes (ou probes) no espaço de soluções, levando em conta suas acelerações, fatores aleatórios e as restrições das fronteiras. Dentro desse método, são realizadas diversas etapas:

    Redução do coeficiente de ruído aleatório:
    • É analisado o valor atual do coeficiente de ruído "currentNoiseFactor", inicialmente definido como igual a "noiseFactor".
    • Se o número de épocas "epochs" for maior que zero, o coeficiente é reduzido proporcionalmente à época atual "epochNow". Isso significa que, conforme o número de épocas aumenta, o ruído diminui, permitindo que o algoritmo encontre soluções cada vez mais precisas à medida que se aproxima do ótimo.

    O método percorre todos os agentes da população (de 0 até popSize) e, para cada agente, percorre todas as suas coordenadas (de 0 até coords). A posição do agente é atualizada pela fórmula que utiliza a aceleração atual "probe[p].a[c]". Nesse caso, é usada uma fórmula simples em que a nova posição é igual à posição antiga somada à metade da aceleração atual. 

    Na nova posição é adicionado um pequeno desvio aleatório, que depende do valor atual do coeficiente de ruído, da constante gravitacional "g" e de um número aleatório dentro do intervalo de -1 a 1.  O algoritmo original é estritamente determinístico, mas decidi acrescentar esse elemento de aleatoriedade, sobre isso falarei mais adiante. Esse desvio introduz um componente estocástico que ajuda a evitar o travamento em mínimos locais. Caso a nova posição ultrapasse os limites definidos (valores mínimos e máximos armazenados em rangeMin e rangeMax), a posição é ajustada de modo que permaneça dentro do intervalo permitido. Se existir um passo de discretização definido, a posição do agente é ainda ajustada usando o método "SeInDiSp", que adapta a posição ao valor mais próximo múltiplo do passo. 

    //——————————————————————————————————————————————————————————————————————————————
    //--- Обновление позиций
    void C_AO_CFO::UpdatePositions ()
    {
      // Коэффициент случайного шума, уменьшается с ростом номера эпохи
      double currentNoiseFactor = noiseFactor;
      if (epochs > 0) currentNoiseFactor *= (1.0 - (double)epochNow / epochs);
    
      for (int p = 0; p < popSize; p++)
      {
        for (int c = 0; c < coords; c++)
        {
          // Обновление позиции по формуле 
          probe [p].c [c] += 0.5 * probe [p].a [c];
    
          // Добавление небольшого случайного смещения непосредственно к позиции
          probe [p].c [c] += currentNoiseFactor * g * u.RNDfromCI (-1.0, 1.0);
    
          // Репозиционирование при выходе за границы
          if (probe [p].c [c] < rangeMin [c]) probe [p].c [c] = rangeMin [c];
          if (probe [p].c [c] > rangeMax [c]) probe [p].c [c] = rangeMax [c];
    
          // Дискретизация если задан шаг
          probe [p].c [c] = u.SeInDiSp (probe [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    O método "CalculateDistanceSquared" da classe "C_AO_CFO" é responsável por calcular o quadrado da distância entre dois pontos em um espaço multidimensional. O método é utilizado por razões de eficiência, já que retornar o quadrado da distância elimina a necessidade de calcular a raiz quadrada, reduzindo significativamente o custo computacional. O método recebe dois parâmetros: x1 e x2. Esses parâmetros são arrays (const double &x1[] e const double &x2[]), que representam as coordenadas de dois pontos no espaço, cuja dimensionalidade é igual ao número de coordenadas "coords". No início do método é criada a variável "sum", inicializada com zero. Essa variável serve para acumular a soma dos quadrados das diferenças entre as coordenadas. O método percorre todas as coordenadas (de 0 até coords-1) e, para cada uma delas:

    • Calcula-se a diferença entre os elementos correspondentes dos arrays x1 e x2 (diff = x1[i] - x2[i]).
    • Calcula-se o quadrado dessa diferença (diff * diff).
    • O valor obtido é somado à variável "sum".
    Ao final do loop, o método retorna a soma dos quadrados das diferenças, ou seja, o valor de "sum", que corresponde ao quadrado da distância entre os dois pontos.
    //——————————————————————————————————————————————————————————————————————————————
    //--- Вычисление расстояния (возвращает квадрат расстояния для оптимизации)
    double C_AO_CFO::CalculateDistanceSquared (const double &x1 [], const double &x2 [])
    {
      double sum = 0.0;
      for (int i = 0; i < coords; i++)
      {
        double diff = x1 [i] - x2 [i];
        sum += diff * diff;
      }
      return sum;
    }
    //——————————————————————————————————————————————————————————————————————————————

    O método "Revision()" da classe "C_AO_CFO" é responsável por atualizar a melhor solução encontrada durante o processo de otimização. O método percorre todos os agentes (ou probes) da população "popSize". Para cada agente, verifica se o valor da sua função de adaptabilidade "a[p].f" é maior que o valor atual do melhor fitness "fB". Caso essa condição seja verdadeira, o valor de "fB" é atualizado para o fitness do agente, e as coordenadas "cB" do agente são copiadas, registrando a nova melhor posição encontrada. Dessa forma, o método "Revision" identifica e armazena a melhor solução entre todos os agentes, atualizando os parâmetros globais sempre que um agente apresenta um resultado superior.

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


    Resultados dos testes

    A versão original, estritamente determinística do algoritmo, em sua forma básica, apresentou os seguintes resultados: 

    CFO|Central Force Optimization|30.0|1.0|0.1|0.1|0.9|0.1|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.34508431921321436
    25 Hilly's; Func runs: 10000; result: 0.2826594689557952
    500 Hilly's; Func runs: 10000; result: 0.25174636412054047
    =============================
    5 Forest's; Func runs: 10000; result: 0.26234538930351947
    25 Forest's; Func runs: 10000; result: 0.1852230195779629
    500 Forest's; Func runs: 10000; result: 0.15353213276989314
    =============================
    5 Megacity's; Func runs: 10000; result: 0.24923076923076923
    25 Megacity's; Func runs: 10000; result: 0.1261538461538462
    500 Megacity's; Func runs: 10000; result: 0.09492307692307768
    =============================
    All score: 1.95090 (21.68%)

    Com esses resultados, infelizmente, o algoritmo não pôde ser incluído em nossa tabela de classificação. Melhorias eram necessárias. Por isso, como mencionei anteriormente, foi adicionado um elemento de aleatoriedade ao código. Sem esse componente, a natureza determinística do processo de busca não possuía diversidade suficiente nas soluções.

    // Добавление небольшого случайного смещения непосредственно к позиции
    probe [p].c [c] += currentNoiseFactor * g * u.RNDfromCI (-1.0, 1.0);

    Após a introdução de um leve elemento aleatório, um pequeno desvio estocástico adicionado ao movimento de cada probe, o algoritmo passou a explorar direções inesperadas. Realizamos então um novo conjunto de testes. Agora podemos observar resultados completamente diferentes, já dignos de serem registrados em nossa tabela de desempenho. 

    CFO|Central Force Optimization|30.0|1.0|0.1|0.1|0.9|0.1|1.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.6096110105488222
    25 Hilly's; Func runs: 10000; result: 0.5495761567207647
    500 Hilly's; Func runs: 10000; result: 0.27830861578120414
    =============================
    5 Forest's; Func runs: 10000; result: 0.6341793648294705
    25 Forest's; Func runs: 10000; result: 0.4683296629644541
    500 Forest's; Func runs: 10000; result: 0.22540930020804817
    =============================
    5 Megacity's; Func runs: 10000; result: 0.5723076923076923
    25 Megacity's; Func runs: 10000; result: 0.2347692307692307
    500 Megacity's; Func runs: 10000; result: 0.09586153846153929
    =============================
    All score: 3.66835 (40.76%)

    Podemos também observar a visualização do funcionamento do algoritmo. Nota-se que o método apresenta bom desempenho em funções de dimensão média, mas não é suficientemente eficaz em funções de baixa ou alta dimensionalidade. 

    Hilly

    CFO na função de teste Hilly

    Forest

    CFO na função de teste Forest

    Megacity 

    CFO na função de teste Megacity

    Com base nos testes realizados, o algoritmo entrou para o grupo dos melhores algoritmos populacionais de otimização, ocupando a 42ª posiçã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)
    1 ANS across neighbourhood search 0,94948 0,84776 0,43857 2,23581 1,00000 0,92334 0,39988 2,32323 0,70923 0,63477 0,23091 1,57491 6,134 68,15
    2 CLA code lock algorithm (joo) 0,95345 0,87107 0,37590 2,20042 0,98942 0,91709 0,31642 2,22294 0,79692 0,69385 0,19303 1,68380 6,107 67,86
    3 AMOm animal migration ptimization M 0,90358 0,84317 0,46284 2,20959 0,99001 0,92436 0,46598 2,38034 0,56769 0,59132 0,23773 1,39675 5,987 66,52
    4 (P+O)ES (P+O) evolution strategies 0,92256 0,88101 0,40021 2,20379 0,97750 0,87490 0,31945 2,17185 0,67385 0,62985 0,18634 1,49003 5,866 65,17
    5 CTA comet tail algorithm (joo) 0,95346 0,86319 0,27770 2,09435 0,99794 0,85740 0,33949 2,19484 0,88769 0,56431 0,10512 1,55712 5,846 64,96
    6 TETA time evolution travel algorithm (joo) 0,91362 0,82349 0,31990 2,05701 0,97096 0,89532 0,29324 2,15952 0,73462 0,68569 0,16021 1,58052 5,797 64,41
    7 SDSm stochastic diffusion search M 0,93066 0,85445 0,39476 2,17988 0,99983 0,89244 0,19619 2,08846 0,72333 0,61100 0,10670 1,44103 5,709 63,44
    8 BOAm billiards optimization algorithm M 0,95757 0,82599 0,25235 2,03590 1,00000 0,90036 0,30502 2,20538 0,73538 0,52523 0,09563 1,35625 5,598 62,19
    9 AAm archery algorithm M 0,91744 0,70876 0,42160 2,04780 0,92527 0,75802 0,35328 2,03657 0,67385 0,55200 0,23738 1,46323 5,548 61,64
    10 ESG evolution of social groups (joo) 0,99906 0,79654 0,35056 2,14616 1,00000 0,82863 0,13102 1,95965 0,82333 0,55300 0,04725 1,42358 5,529 61,44
    11 SIA simulated isotropic annealing (joo) 0,95784 0,84264 0,41465 2,21513 0,98239 0,79586 0,20507 1,98332 0,68667 0,49300 0,09053 1,27020 5,469 60,76
    12 ACS artificial cooperative search 0,75547 0,74744 0,30407 1,80698 1,00000 0,88861 0,22413 2,11274 0,69077 0,48185 0,13322 1,30583 5,226 58,06
    13 DA dialectical algorithm 0,86183 0,70033 0,33724 1,89940 0,98163 0,72772 0,28718 1,99653 0,70308 0,45292 0,16367 1,31967 5,216 57,95
    14 BHAm black hole algorithm M 0,75236 0,76675 0,34583 1,86493 0,93593 0,80152 0,27177 2,00923 0,65077 0,51646 0,15472 1,32195 5,196 57,73
    15 ASO anarchy society optimization 0,84872 0,74646 0,31465 1,90983 0,96148 0,79150 0,23803 1,99101 0,57077 0,54062 0,16614 1,27752 5,178 57,54
    16 RFO royal flush optimization (joo) 0,83361 0,73742 0,34629 1,91733 0,89424 0,73824 0,24098 1,87346 0,63154 0,50292 0,16421 1,29867 5,089 56,55
    17 AOSm atomic orbital search M 0,80232 0,70449 0,31021 1,81702 0,85660 0,69451 0,21996 1,77107 0,74615 0,52862 0,14358 1,41835 5,006 55,63
    18 TSEA turtle shell evolution algorithm (joo) 0,96798 0,64480 0,29672 1,90949 0,99449 0,61981 0,22708 1,84139 0,69077 0,42646 0,13598 1,25322 5,004 55,60
    19 DE differential evolution 0,95044 0,61674 0,30308 1,87026 0,95317 0,78896 0,16652 1,90865 0,78667 0,36033 0,02953 1,17653 4,955 55,06
    20 SRA successful restaurateur algorithm (joo) 0,96883 0,63455 0,29217 1,89555 0,94637 0,55506 0,19124 1,69267 0,74923 0,44031 0,12526 1,31480 4,903 54,48
    21 CRO chemical reaction optimisation 0,94629 0,66112 0,29853 1,90593 0,87906 0,58422 0,21146 1,67473 0,75846 0,42646 0,12686 1,31178 4,892 54,36
    22 BIO blood inheritance optimization (joo) 0,81568 0,65336 0,30877 1,77781 0,89937 0,65319 0,21760 1,77016 0,67846 0,47631 0,13902 1,29378 4,842 53,80
    23 BSA bird swarm algorithm 0,89306 0,64900 0,26250 1,80455 0,92420 0,71121 0,24939 1,88479 0,69385 0,32615 0,10012 1,12012 4,809 53,44
    24 HS harmony search 0,86509 0,68782 0,32527 1,87818 0,99999 0,68002 0,09590 1,77592 0,62000 0,42267 0,05458 1,09725 4,751 52,79
    25 SSG saplings sowing and growing 0,77839 0,64925 0,39543 1,82308 0,85973 0,62467 0,17429 1,65869 0,64667 0,44133 0,10598 1,19398 4,676 51,95
    26 BCOm bacterial chemotaxis optimization M 0,75953 0,62268 0,31483 1,69704 0,89378 0,61339 0,22542 1,73259 0,65385 0,42092 0,14435 1,21912 4,649 51,65
    27 ABO african buffalo optimization 0,83337 0,62247 0,29964 1,75548 0,92170 0,58618 0,19723 1,70511 0,61000 0,43154 0,13225 1,17378 4,634 51,49
    28 (PO)ES (PO) evolution strategies 0,79025 0,62647 0,42935 1,84606 0,87616 0,60943 0,19591 1,68151 0,59000 0,37933 0,11322 1,08255 4,610 51,22
    29 TSm tabu search M 0,87795 0,61431 0,29104 1,78330 0,92885 0,51844 0,19054 1,63783 0,61077 0,38215 0,12157 1,11449 4,536 50,40
    30 BSO brain storm optimization 0,93736 0,57616 0,29688 1,81041 0,93131 0,55866 0,23537 1,72534 0,55231 0,29077 0,11914 0,96222 4,498 49,98
    31 WOAm wale optimization algorithm M 0,84521 0,56298 0,26263 1,67081 0,93100 0,52278 0,16365 1,61743 0,66308 0,41138 0,11357 1,18803 4,476 49,74
    32 AEFA artificial electric field algorithm 0,87700 0,61753 0,25235 1,74688 0,92729 0,72698 0,18064 1,83490 0,66615 0,11631 0,09508 0,87754 4,459 49,55
    33 AEO artificial ecosystem-based optimization algorithm 0,91380 0,46713 0,26470 1,64563 0,90223 0,43705 0,21400 1,55327 0,66154 0,30800 0,28563 1,25517 4,454 49,49
    34 ACOm ant colony optimization M 0,88190 0,66127 0,30377 1,84693 0,85873 0,58680 0,15051 1,59604 0,59667 0,37333 0,02472 0,99472 4,438 49,31
    35 BFO-GA bacterial foraging optimization - ga 0,89150 0,55111 0,31529 1,75790 0,96982 0,39612 0,06305 1,42899 0,72667 0,27500 0,03525 1,03692 4,224 46,93
    36 SOA simple optimization algorithm 0,91520 0,46976 0,27089 1,65585 0,89675 0,37401 0,16984 1,44060 0,69538 0,28031 0,10852 1,08422 4,181 46,45
    37 ABHA artificial bee hive algorithm 0,84131 0,54227 0,26304 1,64663 0,87858 0,47779 0,17181 1,52818 0,50923 0,33877 0,10397 0,95197 4,127 45,85
    38 ACMO atmospheric cloud model optimization 0,90321 0,48546 0,30403 1,69270 0,80268 0,37857 0,19178 1,37303 0,62308 0,24400 0,10795 0,97503 4,041 44,90
    39 ADAMm adaptive moment estimation M 0,88635 0,44766 0,26613 1,60014 0,84497 0,38493 0,16889 1,39880 0,66154 0,27046 0,10594 1,03794 4,037 44,85
    40 CGO chaos game optimization 0,57256 0,37158 0,32018 1,26432 0,61176 0,61931 0,62161 1,85267 0,37538 0,21923 0,19028 0,78490 3,902 43,35
    41 ATAm artificial tribe algorithm M 0,71771 0,55304 0,25235 1,52310 0,82491 0,55904 0,20473 1,58867 0,44000 0,18615 0,09411 0,72026 3,832 42,58
    42 CFO central force optimization 0,60961 0,54958 0,27831 1,43750 0,63418 0,46833 0,22541 1,32792 0,57231 0,23477 0,09586 0,90294 3,668 40,76
    43 ASHA artificial showering algorithm 0,89686 0,40433 0,25617 1,55737 0,80360 0,35526 0,19160 1,35046 0,47692 0,18123 0,09774 0,75589 3,664 40,71
    44 ASBO adaptive social behavior optimization 0,76331 0,49253 0,32619 1,58202 0,79546 0,40035 0,26097 1,45677 0,26462 0,17169 0,18200 0,61831 3,657 40,63
    45 MEC mind evolutionary computation 0,69533 0,53376 0,32661 1,55569 0,72464 0,33036 0,07198 1,12698 0,52500 0,22000 0,04198 0,78698 3,470 38,55
    RW random walk 0,48754 0,32159 0,25781 1,06694 0,37554 0,21944 0,15877 0,75375 0,27969 0,14917 0,09847 0,52734 2,348 26,09


    Considerações finais

    O algoritmo CFO, baseado nos princípios da atração gravitacional entre objetos, representa uma tentativa interessante de aplicar as leis da física à resolução de problemas complexos de otimização. Após testes detalhados em nosso conjunto padrão de funções, o CFO demonstrou uma eficiência de aproximadamente 40% do máximo teórico possível, o que lhe garantiu o 42º lugar entre os 45 melhores algoritmos populacionais de otimização. Vale destacar que até mesmo esse resultado modesto só foi alcançado após a modificação do algoritmo original, com a introdução de um componente aleatório em sua estrutura inicialmente determinística.

    Apesar do desempenho relativamente baixo, o CFO possui diversas características atrativas. Em primeiro lugar, é um algoritmo com uma interpretação física extremamente clara, o que o torna intuitivo e fácil de compreender. A simplicidade de sua ideia central, as probes, representando possíveis soluções, sendo atraídas pelas melhores soluções assim como corpos materiais são atraídos por objetos mais massivos, confere transparência ao funcionamento do algoritmo e facilita sua implementação.

    Entretanto, os testes também revelaram limitações significativas do CFO, que exigem revisão ou aprimoramento. A excessiva dependência da distribuição inicial das probes é uma das principais fragilidades, já que, devido ao mecanismo determinístico de movimento, as posições iniciais dos probes acabam influenciando fortemente o resultado final da busca. 

    O mecanismo de atração unidirecional, no qual apenas as melhores soluções influenciam as piores, mas não o contrário, embora tenha uma justificativa lógica, pode limitar significativamente a capacidade do algoritmo de explorar o espaço de busca. A introdução de um mecanismo adaptativo, que permitisse certa influência também das piores soluções, especialmente nas fases iniciais da busca, poderia ampliar o potencial do algoritmo na identificação de regiões promissoras dentro do espaço de soluções.

    Figura 2. Gradação de cores dos algoritmos de acordo com os testes correspondentes

    chart

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

    Vantagens e desvantagens do algoritmo CFO:

    Vantagens:

    1. Desempenho satisfatório em funções de média dimensionalidade.

    Desvantagens:

    1. Desempenho insatisfatório em funções de baixa e alta dimensionalidade.
    2. Baixa capacidade de exploração em funções discretas.

    O artigo inclui um arquivo compactado com as versões mais recentes dos códigos dos algoritmos. O autor não se responsabiliza pela precisão absoluta na descrição das versões canônicas dos algoritmos, pois diversas modificações foram implementadas para melhorar suas capacidades de busca. As conclusões e considerações apresentadas neste material baseiam-se exclusivamente nos resultados dos experimentos realizados.


    Programas utilizados no artigo

    # Nome Tipo Descrição
    1 #C_AO.mqh
    Arquivo incluído
    Classe base dos algoritmos populacionais de
    otimização
    2 #C_AO_enum.mqh
    Arquivo incluído
    Enumeração dos algoritmos populacionais de otimização
    3 TestFunctions.mqh
    Arquivo incluído
    Biblioteca de funções de teste
    4 TestStandFunctions.mqh
    Arquivo incluído
    Biblioteca de funções da plataforma 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 e geração da tabela comparativa
    7 Testing AOs.mq5
    Script Plataforma unificada de testes para todos os algoritmos populacionais de otimização
    8 Simple use of population optimization algorithms.mq5
    Script
    Exemplo simples de uso dos algoritmos populacionais de otimização sem visualização
    9 Test_AO_CFO.mq5
    Script Plataforma de testes para o CFO

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

    Arquivos anexados |
    CFO.ZIP (350.99 KB)
    Reimaginando Estratégias Clássicas (Parte 12): Estratégia de Breakout EURUSD Reimaginando Estratégias Clássicas (Parte 12): Estratégia de Breakout EURUSD
    Junte-se a nós hoje enquanto nos desafiamos a construir uma estratégia de negociação de rompimento lucrativa em MQL5. Selecionamos o par EURUSD e tentamos negociar rompimentos de preço no período de uma hora. Nosso sistema teve dificuldade em distinguir entre falsos rompimentos e o início de tendências reais. Camadas de filtros foram adicionadas ao sistema para minimizar perdas e aumentar ganhos. No final, conseguimos tornar nosso sistema lucrativo e menos propenso a falsos rompimentos.
    Ondas triangulares e em forma de serra: ferramentas para o trader Ondas triangulares e em forma de serra: ferramentas para o trader
    Um dos métodos de análise técnica é a análise de ondas. Neste artigo, vamos examinar ondas de um tipo um pouco incomum, nomeadamente as triangulares e as em forma de serra. Com base nessas ondas, é possível construir vários indicadores técnicos que permitem analisar o movimento do preço no mercado.
    Desenvolvimento de estratégias de trading de tendência baseadas em aprendizado de máquina Desenvolvimento de estratégias de trading de tendência baseadas em aprendizado de máquina
    Neste artigo é proposto um método original para o desenvolvimento de estratégias de tendência. Você aprenderá como é possível fazer a anotação dos exemplos de treinamento e treinar classificadores com base neles. O resultado final são sistemas de trading prontos para uso, operando sob o controle do terminal MetaTrader 5.
    Do básico ao intermediário: Classes (II) Do básico ao intermediário: Classes (II)
    Este artigo foi pensado para ser o mais didático possível. Isto porque o tema que será abordado aqui, por si só já gera muita confusão na cabeça de muita gente. Então meu caro leitor, procure experimentar na prática o que estará sendo visto aqui em forma de texto. E qualquer dúvida, não deixe de comentar. Pois de fato entender destructores não é uma das tarefas mais simples.