Русский
preview
Algoritmo de Otimização por Sonhos: Dream Optimization Algorithm (DOA)

Algoritmo de Otimização por Sonhos: Dream Optimization Algorithm (DOA)

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

Sumário

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


Introdução

Durante um amplo estudo sobre métodos de otimização eficientes, uma abordagem nova e pouco comum, inspirada em um fenômeno controverso e pouco estudado, chamou minha atenção: o mecanismo dos sonhos.

Em março de 2025, Y. Lang e Y. Gao apresentaram à comunidade científica um algoritmo meta-heurístico inovador de otimização, o Dream Optimization Algorithm (DOA), publicado na revista Computer Methods in Applied Mechanics and Engineering (Volume 436). Esse algoritmo, inspirado nas características únicas dos sonhos humanos, abre novas perspectivas para a resolução de problemas complexos de otimização, incluindo o ajuste de parâmetros de sistemas de trading.

O DOA imita três aspectos fundamentais do processo do sono: preservação parcial da memória, esquecimento seletivo com posterior recomposição das informações e troca de "sonhos" entre os agentes da população. No contexto do trading algorítmico, esses mecanismos permitem equilibrar a exploração de novas regiões do espaço de parâmetros e a intensificação em torno das soluções ótimas encontradas, algo essencial na otimização de estratégias de trading em condições de não estacionariedade dos mercados financeiros.

Neste artigo, analisaremos em detalhes a base matemática do algoritmo, faremos uma implementação em MQL5 e conduziremos uma análise comparativa com outros métodos populacionais de otimização.


Implementação do algoritmo

Quando dormimos, nosso cérebro faz três coisas importantes: memoriza as informações relevantes do dia, esquece detalhes desnecessários e combina diferentes lembranças em novas ideias.

O DOA usa esses mesmos princípios para buscar soluções ótimas. Após a inicialização, toda a população é dividida em vários grupos. Cada grupo recebe seu próprio "estilo de memória", do primeiro, com a melhor memória, até aquele com maior tendência ao esquecimento; cada um altera uma quantidade diferente de dimensões "k" conforme o número do grupo e a dimensionalidade total do problema "D".

Durante determinado período de execução, o algoritmo permanece na fase de exploração. Primeiro, cada grupo aplica a estratégia de memória, retornando todos os seus agentes à melhor posição encontrada pelo grupo. Em seguida, com certa probabilidade, é aplicada a estratégia de esquecimento e complementação. Nesse caso, "k" dimensões escolhidas aleatoriamente são modificadas por meio de uma modulação cossenoidal dada pela fórmula cos((i+T/10)π/T), que permite passos maiores no início, diminuindo-os gradualmente ao longo do tempo. Com a probabilidade restante, é acionada a estratégia de troca de sonhos, que copia "k" dimensões de um agente aleatório da população.

Na etapa final da execução, o algoritmo passa para a fase de intensificação, na qual todos os agentes são reposicionados na melhor solução global e executam um ajuste fino com passos mínimos, por meio da função cossenoidal cos(iπ/T), que tende a zero no fim da otimização. Isso cria um equilíbrio entre a exploração do espaço de busca por meio do mecanismo de memória de grupo e uma otimização final breve, porém precisa.

DOA

Figura 1. Ilustração do funcionamento do algoritmo DOA

A imagem acima apresenta o diagrama estrutural do algoritmo DOA. Na parte superior, o elemento central é uma nuvem de pensamentos com a inscrição "Dream-Inspired Search". Dela partem três linhas que levam às três principais estratégias do algoritmo: o bloco azul-claro "Remember Best" reflete a estratégia de memória; no centro, o bloco rosa "Forget Explore" simboliza a estratégia de esquecimento e exploração; à direita, o bloco verde "Share Dreams" representa a estratégia de troca de experiência entre os agentes.

Abaixo dessas três estratégias, aparece a seção "Two Phases", que mostra a distribuição temporal da execução na forma de uma barra horizontal de progresso, em que a parte verde ocupa 99% e é identificada como "Explore", enquanto a faixa vermelha à direita ocupa o 1% restante, destacando visualmente a desproporção extrema entre as fases de exploração e intensificação.

Na parte inferior da ilustração, aparece a fórmula matemática central do algoritmo, "Position += Random × Cosine_Wave(iteration)", ressaltando a natureza técnica e a importância da modulação cossenoidal no funcionamento do algoritmo DOA. Depois de analisar em detalhes a estratégia de otimização, passamos à escrita do pseudocódigo.

Inicialização
INÍCIO do algoritmo DOA

DEFINIR parâmetros:
  Tamanho da população = 60 agentes
  Número de grupos = 6
  Proporção de exploração = 99% do tempo total
  Probabilidade de esquecimento = 30%

CRIAR 60 agentes com posições aleatórias no espaço de busca
DIVIDIR os agentes em 6 grupos iguais (10 em cada)
INICIALIZAR a melhor solução de cada grupo como a pior possível
Laço principal de otimização
PARA cada iteração de 1 até o máximo:

  SE iteração <= 99% do número total de iterações:
    EXECUTAR a fase de exploração
  CASO CONTRÁRIO:
    EXECUTAR a fase de intensificação
Fase de exploração (Exploration)
PARA cada grupo m de 1 a 6:
  
  ENCONTRAR o melhor agente no grupo m
  ATUALIZAR a melhor solução do grupo
  
  CALCULAR o número de dimensões a alterar:
    k_mínimo = teto(D / 8 / m)
    k_máximo = teto(D / 3 / m)
    k = número_aleatório entre k_mínimo e k_máximo
    
    Observação: O Grupo 1 altera mais dimensões (melhor memória),
                O Grupo 6 altera menos dimensões (maior esquecimento)
  
  PARA cada agente j no grupo m:
    
    PASSO 1 - Estratégia de memória:
      COPIAR a posição do melhor agente do grupo para o agente atual
      (todos os agentes do grupo "lembram" a melhor solução encontrada)
    
    PASSO 2 - Escolher k dimensões aleatórias para modificação
    
    PASSO 3 - Estratégia de esquecimento ou troca:
      
      SE número_aleatório < 0.3 (30% de probabilidade):
        // Estratégia de esquecimento e complementação
        PARA cada uma das k dimensões escolhidas:
          novo_valor = atual + aleatório × onda_cossenoidal
          
          onde onda_cossenoidal = (cos((iteração + T/10) × π / T) + 1) / 2
          
          Observação: o cosseno permite passos maiores no início
                      e passos menores no fim da exploração
      
      CASO CONTRÁRIO (70% de probabilidade):
        // Estratégia de troca de sonhos
        PARA cada uma das k dimensões escolhidas:
          COPIAR o valor de um agente aleatório da população
          (o agente "sonha" com a solução de outro agente)
    
    VERIFICAR e corrigir os limites para todas as dimensões
Fase de intensificação (Exploitation)
PARA cada agente j dentre os 60 agentes:
  
  PASSO 1 - Reposicionamento no melhor global:
    COPIAR a melhor solução global para o agente atual
    (todos os agentes se concentram na "crista" encontrada)
  
  PASSO 2 - Ajuste fino:
    CALCULAR o número de dimensões a alterar:
      k = número_aleatório entre 2 e máximo(2, teto(D/3))
    
    ESCOLHER k dimensões aleatórias
    
    PARA cada uma das k dimensões escolhidas:
      novo_valor = atual + aleatório × onda_cossenoidal
      
      onde onda_cossenoidal = (cos(iteração × π / T) + 1) / 2
      
      Observação: no fim do algoritmo, o cosseno é quase = 0,
                  o que resulta em passos muito pequenos
  
  VERIFICAR e corrigir os limites
Atualização dos resultados
APÓS cada alteração de posição:
  
  CALCULAR o valor da função objetivo para cada agente
  
  ATUALIZAR a melhor solução global:
    SE for encontrada uma solução melhor que a melhor solução global atual:
      SALVAR essa solução como a nova melhor global
  
  Na fase de exploração, também:
    ATUALIZAR as melhores soluções de cada grupo

FIM da iteração

Passemos à escrita do código do algoritmo. A classe implementará o algoritmo de otimização DOA herdando da classe base "C_AO" (interface para diferentes algoritmos de otimização). O construtor e o destrutor são padrão, sem ações adicionais no destrutor. O construtor define os principais parâmetros do algoritmo, seus valores e os vincula ao array de parâmetros "params", o que permite gerenciar facilmente os parâmetros externamente.

Parâmetros principais:
    • popSize: tamanho da população, número de soluções possíveis em cada iteração;
    • numGroups: número de grupos em que a população é dividida para troca paralela de informações;
    • explorationRate: proporção de iterações alocadas à fase de exploração, na qual o algoritmo busca novas regiões do espaço;
    • forgettingProb: probabilidade de aplicar a estratégia de "esquecimento", permitindo evitar que o algoritmo fique preso em mínimos locais.
    Métodos:
      • SetParams (): define os parâmetros da classe a partir do array "params";
      • Init (): inicializa o algoritmo, definindo os intervalos de busca e o número de épocas;
      • Moving (): executa um passo de otimização;
      • Revision (): revisa e atualiza o estado atual da solução.
      Variáveis internas:
        • currentIteration, totalIterations, explorationIters: contadores de iterações e das janelas temporais do algoritmo;
        • groupBest []: array que armazena as melhores soluções de cada grupo, auxiliando a troca de informações e a evolução das soluções.
        Métodos internos:
          • ExplorationPhase (): controla o modo exploratório da busca, ampliando o alcance da busca;
          • ExploitationPhase (): usa as boas soluções já encontradas para melhorá-las;
          • UpdateGroupBest (): atualiza a melhor solução em um grupo específico;
          • GetGroupStartIndex (), GetGroupEndIndex (): determinam os intervalos de índices das soluções dentro de cada grupo.

          Essa classe implementa o algoritmo DOA, que divide a população em um número fixo de grupos. Ao longo da execução, o algoritmo distribui as iterações entre a fase de exploração, voltada à busca de novas soluções, e a fase de intensificação, voltada ao aprimoramento das melhores soluções já encontradas. A estratégia de "esquecimento" permite que o algoritmo evite ficar preso em máximos locais. A ideia geral é garantir um equilíbrio entre a exploração de novas regiões e o refinamento das soluções já encontradas, contribuindo para uma otimização global eficiente.

          //————————————————————————————————————————————————————————————————————
          class C_AO_DOA_dream : public C_AO
          {
            public: //----------------------------------------------------------
            ~C_AO_DOA_dream () { }
            C_AO_DOA_dream ()
            {
              ao_name = "DOA";
              ao_desc = "Dream Optimization Algorithm";
              ao_link = "https://www.mql5.com/ru/articles/19177";
          
              popSize              = 60;    // размер популяции
              numGroups            = 6;     // количество групп (фиксировано в оригинале)
              explorationRate      = 0.99;  // доля итераций для фазы исследования (9/10 в оригинале)
              forgettingProb       = 0.3;   // вероятность применения основной стратегии забывания
          
              ArrayResize (params, 4);
          
              params [0].name = "popSize";           params [0].val = popSize;
              params [1].name = "numGroups";         params [1].val = numGroups;
              params [2].name = "explorationRate";   params [2].val = explorationRate;
              params [3].name = "forgettingProb";    params [3].val = forgettingProb;
            }
          
            void SetParams ()
            {
              popSize          = (int)params [0].val;
              numGroups        = (int)params [1].val;
              explorationRate  = params      [2].val;
              forgettingProb   = params      [3].val;
            }
          
            bool Init (const double &rangeMinP  [],
                       const double &rangeMaxP  [],
                       const double &rangeStepP [],
                       const int     epochsP = 0);
          
            void Moving   ();
            void Revision ();
          
            //------------------------------------------------------------------
            int    numGroups;         // количество групп
            double explorationRate;   // доля итераций для фазы исследования
            double forgettingProb;    // вероятность применения основной стратегии
          
            private: //---------------------------------------------------------
            int    currentIteration;  // текущая итерация
            int    totalIterations;   // общее количество итераций
            int    explorationIters;  // количество итераций исследования
          
            S_AO_Agent groupBest [];  // лучшие решения в каждой группе
          
            void   ExplorationPhase      ();
            void   ExploitationPhase     ();
            void   UpdateGroupBest       (int groupNum);
            int    GetGroupStartIndex    (int groupNum);
            int    GetGroupEndIndex      (int groupNum);
          };
          //————————————————————————————————————————————————————————————————————

          O método de inicialização da classe "C_AO_DOA_dream" prepara a execução do algoritmo de otimização. Ele define os parâmetros iniciais e configura os estados internos necessários para as iterações seguintes.

          Primeiro, o método geral de inicialização é chamado para verificar e definir os intervalos de busca e seus respectivos passos, garantindo que os parâmetros estejam configurados corretamente. Se essa etapa falhar, a inicialização é interrompida.

          Em seguida, o método configura os contadores: redefine a iteração atual para "0", define o número total de iterações a partir dos parâmetros recebidos, e o número de iterações alocadas à exploração é calculado como parte do total, levando em conta a proporção especificada em "explorationRate".

          Depois disso, o array "groupBest", que armazena as melhores soluções dos grupos, é inicializado com tamanho igual ao número de grupos. Para cada grupo, o método "Init" cria uma solução inicial e define o valor da função de qualidade dessa solução como o menor número possível, para permitir comparações e atualizações corretas nas etapas seguintes.

          Ao final da execução desse método, o algoritmo fica pronto para iniciar a otimização com os parâmetros, contadores e soluções iniciais dos grupos devidamente configurados.

          //————————————————————————————————————————————————————————————————————
          //--- Инициализация
          bool C_AO_DOA_dream::Init (const double &rangeMinP  [],
                                     const double &rangeMaxP  [],
                                     const double &rangeStepP [],
                                     const int     epochsP = 0)
          {
            if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
          
            //------------------------------------------------------------------
            currentIteration = 0;
            totalIterations  = epochsP;
            explorationIters = (int)(totalIterations * explorationRate);
          
            ArrayResize (groupBest, numGroups);
            for (int i = 0; i < numGroups; i++)
            {
              groupBest [i].Init (coords);
              groupBest [i].f = -DBL_MAX; // Инициализация худшим значением
            }
          
            return true;
          }
          //————————————————————————————————————————————————————————————————————
          

          O método "Moving" executa o principal passo de cada iteração do algoritmo DOA. Ele implementa a lógica de avanço do algoritmo de uma iteração para a próxima. Primeiro, o contador da iteração atual, "currentIteration", é incrementado para acompanhar o progresso do algoritmo.

          Inicialização inicial, apenas na primeira execução. O sinalizador "revision" é verificado; se estiver como "false", ou seja, se o algoritmo estiver sendo executado pela primeira vez, a população é inicializada. Nesse caso, para cada agente da população e para cada coordenada do agente:

          • gera-se um valor aleatório da coordenada dentro do intervalo especificado, "rangeMin" e "rangeMax", usando a função "u.RNDfromCI ()";
          • aplica-se uma correção a esse valor com base no passo, "rangeStep", por meio da função "u.SeInDiSp ()", que ajusta o valor ao valor permitido mais próximo, isto é, a um múltiplo do passo;
          • após a inicialização, o flag "revision" é definido como "true", para evitar nova inicialização nas iterações seguintes;
          • nesse ponto, o método é encerrado.
          Definição da fase do algoritmo (após a inicialização):
          • verifica-se se a iteração atual está na fase de exploração (currentIteration <= explorationIters);
          • se a iteração estiver na fase de exploração, chama-se o método "ExplorationPhase ()";
          • caso contrário, se a iteração estiver na fase de intensificação, chama-se o método "ExploitationPhase ()".

            Assim, o método "Moving" coordena a execução da otimização: primeiro inicializa a população e, em seguida, alterna entre as fases de exploração e intensificação com base no contador de iterações. A inicialização da população ocorre apenas uma vez; depois disso, o algoritmo entra no laço que determina qual fase deve ser executada na iteração atual.

            //————————————————————————————————————————————————————————————————————
            //--- Основной шаг алгоритма
            void C_AO_DOA_dream::Moving ()
            {
              currentIteration++;
            
              // Начальная инициализация популяции
              if (!revision)
              {
                for (int i = 0; i < popSize; i++)
                {
                  for (int j = 0; j < coords; j++)
                  {
                    a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
                    a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
                  }
                }
            
                revision = true;
                return;
              }
            
              //------------------------------------------------------------------
              // Определяем фазу алгоритма
              if (currentIteration <= explorationIters)
              {
                ExplorationPhase ();
              }
              else
              {
                ExploitationPhase ();
              }
            }
            //————————————————————————————————————————————————————————————————————

            O método "ExplorationPhase" implementa a fase de exploração no algoritmo DOA. Nessa fase, as soluções dos grupos de agentes são atualizadas e diversificadas com o objetivo de buscar possíveis novas regiões do espaço de busca. Para cada grupo de agentes, a melhor solução do grupo é atualizada para refletir o melhor resultado encontrado até o momento. Em seguida, determina-se o número de dimensões, ou coordenadas, para a etapa de esquecimento, com base no número do grupo atual e no total de coordenadas. Para cada grupo, são calculados os índices do primeiro e do último agente que pertencem a ele.

            Depois, para cada agente dentro do grupo, a melhor solução do grupo é copiada para a solução atual do agente pela estratégia "Memory", fornecendo uma boa solução já conhecida à qual o agente pode retornar. Cria-se uma lista de dimensões, ou coordenadas, que serão submetidas à operação de "esquecimento" e recomposição. O array de dimensões é embaralhado para escolher aleatoriamente quais delas serão alteradas.

            Para cada agente do grupo, define-se a estratégia de atualização das dimensões escolhidas: com a probabilidade definida pelo parâmetro "forgettingProb", aplica-se a estratégia de "esquecimento" com modulação cossenoidal.

            Para as dimensões escolhidas, é gerado um valor aleatório dentro do intervalo. A modulação por cosseno depende da iteração atual e do número total de iterações, permitindo controlar a intensidade das alterações ao longo do tempo. Após a atualização, o valor da dimensão é ajustado para o intervalo válido, considerando os passos. Caso o cenário de "esquecimento" não seja escolhido, aplica-se a estratégia "dream sharing". Os valores das dimensões são copiados de outro agente aleatório, ou seja, ocorre uma troca de informações entre os agentes.

            Como resultado, essa fase favorece a exploração do espaço de busca, aumentando a diversidade das soluções e ajudando a evitar máximos locais por meio de alterações aleatórias e da troca de informações entre os agentes.

            //————————————————————————————————————————————————————————————————————
            //--- Фаза исследования (Exploration phase)
            void C_AO_DOA_dream::ExplorationPhase ()
            {
              // Обрабатываем каждую группу
              for (int m = 0; m < numGroups; m++)
              {
                // Обновляем лучшее решение в группе
                UpdateGroupBest (m);
            
                // Вычисляем количество измерений для забывания
                int kMin = (int)MathCeil ((double)coords / 8.0 / (m + 1));
                int kMax = (int)MathCeil ((double)coords / 3.0 / (m + 1));
                int k    = u.RNDintInRange (kMin, kMax);
            
                // Обрабатываем агентов в группе
                int startIdx = GetGroupStartIndex (m);
                int endIdx   = GetGroupEndIndex   (m);
            
                for (int j = startIdx; j <= endIdx; j++)
                {
                  // Memory strategy - сброс к лучшему решению группы
                  ArrayCopy (a [j].c, groupBest [m].c, 0, 0, WHOLE_ARRAY);
            
                  // Выбираем случайные измерения для забывания
                  int dims [];
                  ArrayResize (dims, coords);
                  for (int i = 0; i < coords; i++) dims [i] = i;
            
                  // Перемешиваем массив измерений
                  for (int i = coords - 1; i > 0; i--)
                  {
                    int idx    = u.RNDintInRange (0, i);
                    int temp   = dims [i];
                    dims [i]   = dims [idx];
                    dims [idx] = temp;
                  }
            
                  // Стратегия забывания и восполнения
                  if (u.RNDprobab () < forgettingProb)
                  {
                    // Основная стратегия с косинусоидальной модуляцией
                    for (int h = 0; h < k; h++)
                    {
                      int    dim              = dims [h];
                      double range            = rangeMax [dim] - rangeMin [dim];
                      double randomValue      = u.RNDprobab () * range + rangeMin [dim];
                      double cosineModulation = (MathCos ((1.0 * currentIteration + totalIterations / 10.0) * M_PI / totalIterations) + 1.0) / 2.0;
            
                      a [j].c [dim] = a [j].c [dim] + randomValue * cosineModulation;
                      a [j].c [dim] = u.SeInDiSp (a [j].c [dim], rangeMin [dim], rangeMax [dim], rangeStep [dim]);
                    }
                  }
                  else
                  {
                    // Dream sharing - копирование из случайного агента
                    for (int h = 0; h < k; h++)
                    {
                      int dim   = dims [h];
                      int donor = u.RNDintInRange (0, popSize - 1);
                      a [j].c [dim] = a [donor].c [dim];
                    }
                  }
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            O método "ExploitationPhase" implementa a fase de intensificação no algoritmo de otimização. Sua principal tarefa é direcionar os agentes para a melhor solução encontrada até o momento, a fim de melhorar os resultados atuais.

            Para cada agente da população, a solução do agente é redefinida para a melhor solução global encontrada até o momento, permitindo concentrar a busca nas regiões mais promissoras do espaço de busca. Em seguida, determina-se o número de dimensões, ou coordenadas, que serão modificadas. Em geral, escolhem-se pelo menos duas dimensões e, no máximo, um valor determinado em função do número de coordenadas. Cria-se uma lista com todas as dimensões, ou coordenadas, que depois é embaralhada para escolher quais delas serão alteradas. Para cada dimensão escolhida:

            • calcula-se o intervalo de alteração dessa dimensão;
            • gera-se um valor aleatório dentro desse intervalo;
            • esse valor é modulado por meio de uma função cossenoidal, que depende do número da iteração atual e do número total de iterações, permitindo controlar a intensidade das alterações ao longo do tempo;
            • como resultado, o valor da dimensão muda com base no valor gerado e na modulação cossenoidal;
            • depois disso, o valor é ajustado ao intervalo permitido, considerando o passo de discretização, para que a solução permaneça válida.

            O objetivo desse método é intensificar as boas soluções já alcançadas usando modificações aleatórias e controladas, o que ajuda a avançar para uma região mais promissora do espaço de busca e obter soluções de maior qualidade.

            //————————————————————————————————————————————————————————————————————
            //--- Фаза эксплуатации (Exploitation phase)
            void C_AO_DOA_dream::ExploitationPhase ()
            {
              // В фазе эксплуатации все агенты движутся к глобальному лучшему
              for (int j = 0; j < popSize; j++)
              {
                // Сброс к глобальному лучшему решению
                ArrayCopy (a [j].c, cB, 0, 0, WHOLE_ARRAY);
            
                // Вычисляем количество измерений для модификации
                int km = MathMax (2, (int)MathCeil ((double)coords / 3.0));
                int k = u.RNDintInRange (2, km);
            
                // Выбираем случайные измерения
                int dims [];
                ArrayResize (dims, coords);
                for (int i = 0; i < coords; i++) dims [i] = i;
            
                // Перемешиваем массив измерений
                for (int i = coords - 1; i > 0; i--)
                {
                  int idx = u.RNDintInRange (0, i);
                  int temp = dims [i];
                  dims [i] = dims [idx];
                  dims [idx] = temp;
                }
            
                // Применяем стратегию забывания и дополнения
                for (int h = 0; h < k; h++)
                {
                  int dim = dims [h];
                  double range = rangeMax [dim] - rangeMin [dim];
            
                  double randomValue = u.RNDprobab () * range + rangeMin [dim];
                  double cosineModulation = (MathCos (currentIteration * M_PI / totalIterations) + 1.0) / 2.0;
            
                  a [j].c [dim] = a [j].c [dim] + randomValue * cosineModulation;
                  a [j].c [dim] = u.SeInDiSp (a [j].c [dim], rangeMin [dim], rangeMax [dim], rangeStep [dim]);
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            O método "UpdateGroupBest" tem como objetivo determinar a melhor solução dentro de um grupo específico de agentes. Suas principais operações são as seguintes:

            1. obter os índices das posições inicial e final dos agentes pertencentes a esse grupo;
            2. percorrer todos os agentes do grupo indicado;
            3. para cada agente, comparar o valor da função de avaliação, isto é, a métrica de qualidade da solução, com o melhor valor atual armazenado para o grupo;
            4. se um agente tiver uma solução com melhor valor da função de fitness, o registro da melhor solução do grupo é atualizado e passa a armazenar essa solução mais eficiente.

            Assim, o método mantém atualizado o registro da melhor solução dentro de cada grupo, o que é importante para as etapas seguintes do algoritmo, como as estratégias de busca e de atualização de soluções.

            //————————————————————————————————————————————————————————————————————
            //--- Обновить лучшее решение в группе
            void C_AO_DOA_dream::UpdateGroupBest (int groupNum)
            {
              int startIdx = GetGroupStartIndex (groupNum);
              int endIdx   = GetGroupEndIndex (groupNum);
            
              for (int i = startIdx; i <= endIdx; i++)
              {
                if (a [i].f > groupBest [groupNum].f)
                {
                  groupBest [groupNum].f = a [i].f;
                  ArrayCopy (groupBest [groupNum].c, a [i].c, 0, 0, WHOLE_ARRAY);
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            O método "GetGroupStartIndex" calcula o índice inicial dos elementos pertencentes ao grupo indicado no array de soluções ou agentes. Ele se baseia em cálculos que pressupõem uma distribuição uniforme dos grupos em todo o array. A ideia principal é determinar a posição do primeiro agente, ou elemento, em um grupo específico, com base no número do grupo, no número total de grupos e no tamanho total da população.

            Para isso, multiplica-se o número do grupo pelo tamanho total da população e divide-se o resultado pelo número de grupos, obtendo o índice do primeiro elemento dentro do grupo especificado. Essa abordagem permite dividir os dados uniformemente em grupos e facilita as operações seguintes relacionadas ao agrupamento das soluções.

            //————————————————————————————————————————————————————————————————————
            //--- Получить начальный индекс группы
            int C_AO_DOA_dream::GetGroupStartIndex (int groupNum)
            {
              return (int)((double)groupNum * popSize / numGroups);
            }
            //————————————————————————————————————————————————————————————————————

            O método "GetGroupEndIndex" calcula o índice final dos elementos pertencentes ao grupo indicado. Ele determina o último elemento do grupo com base no número do grupo, no tamanho total da população e no número de grupos. O cálculo é feito multiplicando o número do grupo, acrescido de uma unidade, pelo tamanho total da população e dividindo o resultado pelo número de grupos. Em seguida, o valor obtido é reduzido em uma unidade para determinar o índice do último elemento do grupo.

            Uma verificação adicional é necessária para evitar acesso fora dos limites do array: se o índice calculado exceder o tamanho da população, ele é corrigido para o valor do último índice válido. Essa abordagem permite determinar corretamente as fronteiras de índice de cada grupo de agentes na população.

            //————————————————————————————————————————————————————————————————————
            //--- Получить конечный индекс группы
            int C_AO_DOA_dream::GetGroupEndIndex (int groupNum)
            {
              int endIdx = (int)((double)(groupNum + 1) * popSize / numGroups) - 1;
              if (endIdx >= popSize) endIdx = popSize - 1;
              return endIdx;
            }
            //————————————————————————————————————————————————————————————————————

            O método "Revision" tem como objetivo atualizar o registro da melhor solução encontrada durante a execução do algoritmo. Primeiro, ele percorre todas as soluções da população atual. Dentro do laço, o método verifica, para cada solução, o valor da função objetivo e, se esse valor for maior que o melhor valor atual, "fB" recebe o valor da função objetivo da solução atual, e essa solução passa a ser a melhor solução encontrada.

            Depois disso, o método verifica se a iteração atual está na fase de "exploração", isto é, se currentIteration <= explorationIters. Em caso afirmativo, além de atualizar a melhor solução global, o método também considera as melhores soluções encontradas dentro de cada grupo. Para isso, o laço percorre todos os grupos e compara, em cada um, a função objetivo da melhor solução do grupo com o melhor valor atual.

            Se a solução dentro do grupo for melhor, "fB" é atualizado, e a solução armazenada em "groupBest" é copiada para o buffer global "cB". Assim, o método "Revision" acompanha e atualiza continuamente o registro da melhor solução encontrada, dependendo da fase atual do algoritmo (exploração ou intensificação), preservando a melhor solução encontrada até o momento.

            //————————————————————————————————————————————————————————————————————
            //--- Обновление лучшего и худшего решений
            void C_AO_DOA_dream::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);
                }
              }
            
              // Обновляем лучшие решения групп в фазе исследования
              if (currentIteration <= explorationIters)
              {
                for (int m = 0; m < numGroups; m++)
                {
                  if (groupBest [m].f > fB)
                  {
                    fB = groupBest [m].f;
                    ArrayCopy (cB, groupBest [m].c, 0, 0, WHOLE_ARRAY);
                  }
                }
              }
            }
            //————————————————————————————————————————————————————————————————————


            Resultados dos testes

            Agora, após implementar o algoritmo DOA, podemos passar diretamente aos testes nas funções de referência. Como vemos, o algoritmo DOA obteve 53,62% e será incluído na nossa tabela de classificação.

            DOA|Dream Optimization Algorithm|60.0|6.0|0.99|0.3|
            =============================
            5 Hilly's; Func runs: 10000; result: 0.8555594031110225
            25 Hilly's; Func runs: 10000; result: 0.7008493263471764 
            500 Hilly's; Func runs: 10000; result: 0.37279821121874124
            =============================
            5 Forest's; Func runs: 10000; result: 0.7342194493052585
            25 Forest's; Func runs: 10000; result: 0.48905397049976357
            500 Forest's; Func runs: 10000; result: 0.24146681094197792
            =============================
            5 Megacity's; Func runs: 10000; result: 0.7723076923076921
            25 Megacity's; Func runs: 10000; result: 0.4735384615384616
            500 Megacity's; Func runs: 10000; result: 0.18561538461538593
            =============================
            All score: 4.82541 (53.62%)

            Na visualização do funcionamento do algoritmo DOA em baixas dimensionalidades, representada pelas linhas verdes, observa-se uma dispersão dos resultados, especialmente nas funções "Forest" e "Megacity". 

            Hilly

            DOA na função de teste Hilly

            Forest

            DOA na função de teste Forest

            Megacity

            DOA na função de teste Megacity

            Com base nos resultados dos testes, o algoritmo DOA ocupa a 26ª posição na classificação geral dos algoritmos populacionais de otimizaçã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
            12EOmextremal_optimization_M0,761660,772420,317471,851550,999990,767510,235272,002770,747690,539690,142491,429875,28458,71
            13BBObiogeography based optimization0,949120,694560,350311,993990,938200,673650,256821,868670,746150,482770,173691,402615,26558,50
            14ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
            15DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
            16BHAmblack hole algorithm M0,752360,766750,345831,864930,935930,801520,271772,009230,650770,516460,154721,321955,19657,73
            17ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
            18RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
            19AOSmatomic orbital search M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
            20TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
            21BSAbacktracking_search_algorithm0,973090,545340,290981,809410,999990,585430,217471,802890,847690,369530,129781,347004,95955,10
            22DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
            23SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
            24CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
            25BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
            26DOAdream_optimization_algorithm0,855560,700850,372801,929210,734210,489050,241471,464730,772310,473540,185611,431464,82553,62
            27BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
            28DEAdolphin_echolocation_algorithm0,759950,675720,341711,777380,895820,642230,239411,777460,615380,440310,151151,206844,76252,91
            29HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
            30SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
            31BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
            32ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
            33(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
            34FBAfractal-based Algorithm0,790000,651340,289651,730990,871580,568230,188771,628580,610770,460620,123981,195374,55550,61
            35TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
            36BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
            37WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
            38AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
            39AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
            40CAmcamel algorithm M0,786840,560420,351331,698590,827720,560410,243361,631490,648460,330920,134181,113564,44449,37
            41ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
            42CMAEScovariance_matrix_adaptation_evolution_strategy0,762580,720890,000001,483470,820560,796160,000001,616720,758460,490770,000001,249234,34948,33
            43DA_duelistduelist_algorithm0,927820,537780,277921,743520,869570,475360,181931,526860,621530,335690,117151,074374,34548,28
            44BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
            45SOAsimple optimization algorithm0,915200,469760,270891,655850,896750,374010,169841,440600,695380,280310,108521,084224,18146,45
            RWrandom walk0,487540,321590,257811,066940,375540,219440,158770,753750,279690,149170,098470,527342,34826,09


            Conclusões

            O Dream Optimization Algorithm mostra resultados satisfatórios, ao obter 53,62% dos 100% possíveis nos testes realizados, sendo incluído na nossa tabela de classificação.

            A análise dos resultados revela um padrão característico de muitos algoritmos meta-heurísticos: o desempenho cai significativamente à medida que a dimensionalidade do problema aumenta. Em baixas dimensionalidades, o DOA apresenta resultados na faixa de 73% a 86%; ao passar para problemas de alta dimensionalidade, a eficiência cai para 18% a 37% com um número finito de iterações.

            De modo geral, o algoritmo se mostra um algoritmo mediano em comparação com os demais. Os interessados podem experimentar os ajustes do algoritmo; talvez ainda exista potencial para obter resultados melhores. 

            tab

            Figura 2. Graduação de cores dos algoritmos pelos testes correspondentes

            chart

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

            Pontos fortes e fracos do algoritmo DOA:

            Pontos fortes:

            1. Implementação simples.
            2. Rápido.

            Pontos fracos:

            1. Dispersão dos valores em funções de baixa dimensionalidade.

            O artigo inclui um arquivo anexado com as versões atuais dos códigos dos algoritmos. O autor do artigo não se responsabiliza pela precisão absoluta na descrição dos algoritmos canônicos; muitos deles foram modificados para melhorar sua capacidade de busca. As conclusões e avaliações apresentadas no artigo baseiam-se nos resultados dos experimentos realizados.


            Programas usados no artigo

            #NomeTipoDescrição
            1#C_AO.mqh
            Arquivo include
            Classe base dos algoritmos populacionais de otimização
            2#C_AO_enum.mqh
            Arquivo include
            Enumeração dos algoritmos populacionais de otimização
            3TestFunctions.mqh
            Arquivo include
            Biblioteca de funções de teste
            4
            TestStandFunctions.mqh
            Arquivo include
            Biblioteca de funções da bancada de testes
            5
            Utilities.mqh
            Arquivo include
            Biblioteca de funções auxiliares
            6
            CalculationTestResults.mqh
            Arquivo include
            Script para calcular os resultados para a tabela comparativa
            7
            Testing AOs.mq5
            ScriptBancada de testes unificada 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_AO_DOA.mq5
            ScriptBancada de testes para o DOA

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

            Arquivos anexados |
            DOArdream9.ZIP (333.48 KB)
            Treinamento de um U-Transformer não linear nos resíduos de um modelo autorregressivo linear Treinamento de um U-Transformer não linear nos resíduos de um modelo autorregressivo linear
            O artigo apresenta um sistema híbrido inovador para previsão de taxas de câmbio, que combina um modelo autorregressivo linear com a arquitetura U-Transformer para análise dos resíduos. O sistema alterna automaticamente entre as fontes de sinais conforme a qualidade de cada uma e inclui uma lógica de negociação completa, com estratégias de averaging/pyramiding. A principal vantagem da abordagem está no fato de a rede neural ser treinada nos resíduos do modelo linear, o que simplifica a tarefa e reduz o risco de sobreajuste. A implementação foi feita integralmente em MQL5 e está pronta para uso em negociação real, com adaptação automática às mudanças nas condições de mercado.
            Redes neurais em trading: Modelo de consultas temporais (Final) Redes neurais em trading: Modelo de consultas temporais (Final)
            Apresentamos a etapa final de implementação e teste do framework TQNet, na qual a teoria se encontra com a prática real de trading. Percorreremos o caminho do treinamento histórico até o teste de estresse em dados recentes de mercado, avaliando a robustez e a precisão do modelo. Os resultados finais não são apenas números frios, mas também uma demonstração clara do valor prático da abordagem proposta.
            Redes neurais em trading: treinamento de metaparâmetros com base na heterogeneidade (HimNet) Redes neurais em trading: treinamento de metaparâmetros com base na heterogeneidade (HimNet)
            Propomos conhecer o framework HimNet, que combina a flexibilidade da adaptação espaço-temporal com alta eficiência computacional, permitindo obter previsões precisas e estáveis em séries temporais financeiras. O artigo mostra em detalhes como seus principais componentes interagem entre si, transformando algoritmos complexos em uma arquitetura gerenciável.
            Redes neurais em trading: Modelo de consultas temporais (TQNet) Redes neurais em trading: Modelo de consultas temporais (TQNet)
            O TQNet é um framework que abre novas possibilidades para modelar e prever séries temporais financeiras, ao combinar modularidade, flexibilidade e alto desempenho. Neste artigo, exploramos a possibilidade de implementar mecanismos complexos para lidar com correlações globais, incluindo métodos avançados de inicialização de parâmetros.