English Русский Português
preview
Algoritmo de optimización basado en los sueños — Dream Optimization Algorithm (DOA)

Algoritmo de optimización basado en los sueños — Dream Optimization Algorithm (DOA)

MetaTrader 5Trading |
49 1
Andrey Dik
Andrey Dik

Índice

  1. Introducción
  2. Implementación del algoritmo
  3. Resultados de las pruebas


Introducción

En el marco de un amplio estudio de métodos óptimos de optimización, me llamó la atención un enfoque completamente nuevo e inusual, inspirado en un fenómeno aún controvertido y escasamente estudiado: el fenómeno de los sueños humanos.

En marzo de 2025, Y. Lang y Y. Gao presentaron a la comunidad científica un innovador algoritmo de optimización metaheurístico: Dream Optimization Algorithm (DOA), publicado en la revista Computer Methods in Applied Mechanics and Engineering (volumen 436). Este algoritmo, inspirado en las características únicas de los sueños humanos, abre nuevas perspectivas para resolver problemas complejos de optimización, incluido el ajuste de los parámetros de los sistemas de trading.

DOA imita tres aspectos clave del proceso del sueño: la conservación parcial de la memoria, el olvido selectivo con posterior reposición de información y el intercambio de «sueños» entre los agentes de la población. En el contexto del trading algorítmico, estos mecanismos permiten mantener un equilibrio entre la exploración de nuevas áreas del espacio paramétrico y la explotación de las soluciones óptimas encontradas, lo cual resulta crítico al optimizar estrategias de trading en condiciones de no estacionariedad de los mercados financieros.

En este artículo analizaremos en detalle los fundamentos matemáticos del algoritmo, lo implementaremos en el lenguaje MQL5 y realizaremos un análisis comparativo con otros métodos de optimización poblacional.


Implementación del algoritmo

Cuando dormimos, nuestro cerebro realiza tres funciones importantes, a saber: memoriza la información relevante del día, olvida los detalles innecesarios y combina distintos recuerdos para crear nuevas ideas.

DOA utiliza estos mismos principios para buscar soluciones óptimas. Tras la inicialización, toda la población se divide en varios grupos. Cada grupo recibe su propio «estilo de memoria»: desde el primer grupo, que tiene la mejor memoria, hasta el grupo más olvidadizo, donde cada grupo modifica un número diferente de dimensiones «k» en función del número de grupo y de la dimensionalidad total del problema «D».

Durante un determinado periodo de funcionamiento, el algoritmo se encuentra en la fase de exploración. En primer lugar, cada grupo aplica una estrategia de memoria y reposiciona a todos sus agentes en la mejor posición del grupo; a continuación, con una probabilidad determinada, se aplica una estrategia de olvido y reposición. En este caso, las «k» dimensiones seleccionadas al azar se modifican mediante una modulación cosenoidal según la fórmula cos((i+T/10)π/T), lo que garantiza pasos de mayor amplitud al principio y su reducción gradual. Con la probabilidad restante, se aplica la estrategia de intercambio de información («sueños»), que copia «k» dimensiones de un agente aleatorio de la población.

En la etapa final, el algoritmo pasa a la fase de explotación, en la que todos los agentes se restablecen a la mejor solución global y realizan un ajuste fino con pasos mínimos gracias a la función cosenoidal cos(iπ/T), que tiende a cero al final de la optimización; esto crea un equilibrio entre la exploración del espacio de búsqueda mediante el mecanismo de memoria grupal y una optimización final breve, pero precisa.

DOA

Figura 1. Ilustración del funcionamiento del algoritmo DOA

En la imagen anterior se muestra un diagrama estructural del algoritmo DOA. En la parte superior, el elemento central es una nube de pensamientos con la inscripción «Dream-Inspired Search». De él parten tres líneas que conducen a las tres estrategias principales del algoritmo: el bloque azul «Remember Best» refleja la estrategia de memoria; en el centro, el bloque rosa «Forget Explore» simboliza la estrategia de olvido y exploración; a la derecha, el bloque verde «Share Dreams» representa la estrategia de intercambio de información («sueños») entre los agentes.

Debajo de estas tres estrategias se encuentra la sección «Two Phases», que muestra la distribución temporal del funcionamiento del algoritmo en forma de una barra de progreso horizontal, en la que la parte verde ocupa el 99 % y se denomina «Explore», mientras que la franja roja de la derecha ocupa el 1 % restante, lo que resalta visualmente la desproporción extrema entre las fases de exploración y de explotación.

En la parte inferior de la ilustración se muestra la fórmula matemática clave del algoritmo «Position += Random × Cosine_Wave(iteration)», lo que pone de relieve la naturaleza técnica y la importancia de la modulación cosenoidal en el funcionamiento del algoritmo DOA. Tras analizar en detalle la estrategia de optimización del algoritmo, pasamos a escribir el pseudocódigo.

Inicialización
INICIO del algoritmo DOA

ESTABLECER los parámetros:
- Tamaño de la población = 60 agentes
- Número de grupos = 6
- Porcentaje de exploración = 99 % del tiempo total
- Probabilidad de olvido = 30 %

CREAR 60 agentes con posiciones aleatorias en el espacio de búsqueda
DIVIDIR los agentes en 6 grupos iguales (10 agentes en cada uno)
INICIALIZAR la mejor solución de cada grupo como la peor posible
Ciclo principal de optimización
PARA cada iteración, de 1 al máximo:

SI la iteración <= 99 % del número total de iteraciones:
EJECUTAR la fase de exploración
EN CASO CONTRARIO:
EJECUTAR la fase de explotación
Fase de exploración (Exploration)
PARA cada grupo m de 1 a 6:
  
ENCONTRAR el mejor agente del grupo m
ACTUALIZAR la mejor solución del grupo
  
CALCULAR el número de dimensiones que se van a modificar:
k_mínimo = techo(D / 8 / m)
k_máximo = techo(D / 3 / m)
k = número_aleatorio entre k_mínimo y k_máximo
    
Nota: El grupo 1 cambia más dimensiones (mejor memoria),
El grupo 6 cambia menos dimensiones (olvida más)
  
PARA cada agente j del grupo m:
    
PASO 1 - Estrategia de memoria:
COPIAR la posición del mejor agente del grupo en el agente actual
(todos los agentes del grupo «recuerdan» la mejor solución encontrada)
    
PASO 2 - Seleccionar k dimensiones aleatorias para modificar
    
PASO 3 - Estrategia de olvido o intercambio:
      
SI número_aleatorio < 0,3 (probabilidad del 30 %):
// Estrategia de olvido y reposición
PARA cada una de las k dimensiones seleccionadas:
nuevo_valor = valor_actual + valor_aleatorio × onda_cosenoidal
          
donde onda_cosenoidal = (cos((iteración + T/10) × π / T) + 1) / 2
          
Nota: el coseno proporciona pasos más grandes al principio,
pequeños pasos hacia el final de la exploración
      
DE LO CONTRARIO (70 % de probabilidad):
// Estrategia de intercambio de información (sueños)
PARA cada una de las k dimensiones seleccionadas:
COPIAR el valor de un agente aleatorio de la población
(un agente «ve en sueños» la solución de otro agente)
    
COMPROBAR y corregir los límites para todas las dimensiones
Fase de explotación (Exploitation)
PARA cada agente j de entre los 60 agentes:
  
PASO 1 - Restablecimiento a la mejor solución global:
COPIAR la mejor solución global en el agente actual
(todos los agentes se reúnen en la «cima» encontrada)
  
PASO 2 - Ajuste fino:
CALCULAR el número de dimensiones que se van a cambiar:
k = número_aleatorio entre 2 y máximo(2, techo(D/3))
    
SELECCIONAR k dimensiones aleatorias
    
PARA cada una de las k dimensiones seleccionadas:
nuevo_valor = valor_actual + valor_aleatorio × onda_cosenoidal
      
donde onda_cosenoidal = (cos(iteración × π / T) + 1) / 2
      
Nota: al final del algoritmo, el coseno es casi igual a 0,
lo que da lugar a pasos muy pequeños
  
COMPROBAR y corregir los límites
Actualización de los resultados
DESPUÉS de cada cambio de posiciones:
  
CALCULAR el valor de la función objetivo para cada agente
  
ACTUALIZAR la mejor solución global:
SI se ha encontrado una solución mejor que la mejor solución global actual:
GUARDARLO como la nueva mejor solución global
  
En la fase de exploración también:
ACTUALIZAR las mejores soluciones de cada grupo

FIN de la iteración

Empecemos a escribir ahora el código del algoritmo. La clase implementará el algoritmo de optimización DOA y heredará de la clase base «C_AO» (interfaz para diversos algoritmos de optimización). El constructor y el destructor son estándar, sin acciones adicionales en el destructor. En el constructor se definen los parámetros principales del algoritmo y sus valores, y además se vinculan con el array de parámetros «params», lo que permite gestionar fácilmente los parámetros desde el exterior.

Parámetros principales:
    • popSize: tamaño de la población, es decir, el número de soluciones posibles en cada iteración;
    • numGroups: número de grupos en los que se divide la población para el intercambio paralelo de información;
    • explorationRate: proporción de iteraciones destinadas a la fase de exploración, en la que el algoritmo busca nuevas regiones del espacio;
    • forgettingProb: probabilidad de aplicar la estrategia de «olvido», que permite evitar quedarse estancado en mínimos locales.
    Métodos:
      • SetParams () — establece los parámetros de la clase a partir del array «params»;
      • Init () — inicializa el algoritmo, estableciendo los rangos de búsqueda y el número de épocas;
      • Moving () — realiza un paso de optimización;
      • Revision () — se utiliza para revisar y actualizar el estado actual de la solución.
      Variables internas:
        • currentIteration, totalIterations, explorationIters — contadores de iteraciones y de intervalos temporales del algoritmo;
        • groupBest [] — array que almacena la mejor solución de cada grupo, lo que facilita el intercambio de información y la evolución de las soluciones.
        Métodos internos:
          • ExplorationPhase () — se encarga del modo de búsqueda exploratoria, ampliando los horizontes de la búsqueda;
          • ExploitationPhase () — fase de explotación de las buenas soluciones ya identificadas para mejorarlas;
          • UpdateGroupBest () — actualización de la mejor solución del grupo en un grupo concreto;
          • GetGroupStartIndex (), GetGroupEndIndex () — permiten determinar los intervalos de índices de las soluciones dentro de cada grupo.

          Esta clase implementa el algoritmo DOA, en el que la población se divide en un número fijo de grupos. A lo largo de las iteraciones, el algoritmo divide su tiempo entre la fase de exploración (búsqueda de nuevas soluciones) y la fase de explotación (mejora de las mejores soluciones ya encontradas). La estrategia de «olvido» permite al algoritmo evitar quedar atrapado en óptimos locales. La idea general es garantizar un equilibrio entre la exploración de nuevas áreas y la mejora minuciosa de las soluciones ya descubiertas, lo que contribuye a una optimización global eficaz.

          //————————————————————————————————————————————————————————————————————
          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;    // tamaño de la población
              numGroups            = 6;     // número de grupos (fijo en el original)
              explorationRate      = 0.99;  // proporción de iteraciones para la fase de exploración (0.99 en el origina)
              forgettingProb       = 0.3;   // probabilidad de aplicar la estrategia principal de olvido
          
              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;         // número de grupos
            double explorationRate;   // proporción de iteraciones para la fase de exploración
            double forgettingProb;    // probabilidad de aplicar la estrategia principal
          
            private: //---------------------------------------------------------
            int    currentIteration;  // iteración actual
            int    totalIterations;   // número total de iteraciones
            int    explorationIters;  // número de iteraciones de exploración
          
            S_AO_Agent groupBest [];  // mejores soluciones de cada grupo
          
            void   ExplorationPhase      ();
            void   ExploitationPhase     ();
            void   UpdateGroupBest       (int groupNum);
            int    GetGroupStartIndex    (int groupNum);
            int    GetGroupEndIndex      (int groupNum);
          };
          //————————————————————————————————————————————————————————————————————

          El método de inicialización de la clase «C_AO_DOA_dream» realiza las operaciones preparatorias para poner en marcha el algoritmo de optimización. Establece los parámetros iniciales y configura los estados internos necesarios para las iteraciones posteriores.

          En primer lugar, se invoca el método general de inicialización, que comprueba y establece los rangos de búsqueda y los pasos correspondientes, garantizando que los parámetros se hayan definido correctamente. Si esta comprobación o configuración no se realiza correctamente, la inicialización se interrumpe.

          A continuación, se establecen los contadores: la iteración actual se pone a «0», el número total de iteraciones se establece según los parámetros recibidos y el número de iteraciones destinadas a la exploración se calcula como una parte del total, teniendo en cuenta la proporción especificada (explorationRate).

          Luego se inicializa el array de las mejores soluciones de los grupos (groupBest), cuyo tamaño es igual al número de grupos. Para cada grupo se crea una solución inicial mediante el método «Init», y el valor de la función de calidad de dicha solución se establece en el mínimo valor posible, con el fin de garantizar una comparación y una actualización correctas más adelante.

          Como resultado, una vez ejecutado este método, el algoritmo está listo para iniciar el proceso de optimización con los parámetros, los contadores y las soluciones iniciales de los grupos ya establecidos.

          //————————————————————————————————————————————————————————————————————
          //--- Inicialización
          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; // Inicialización con el peor valor
            }
          
            return true;
          }
          //————————————————————————————————————————————————————————————————————
          

          El método «Moving» es el paso principal de la iteración del algoritmo DOA. Implementa la lógica que permite que el algoritmo avance de una iteración a otra. En primer lugar, se incrementa el contador de la iteración actual (currentIteration), que hace seguimiento del progreso del algoritmo.

          Inicialización inicial (solo en la primera ejecución). Se comprueba el indicador «revision»; si es «false» (es decir, si el algoritmo se ejecuta por primera vez), se realiza la inicialización inicial de la población. A continuación, para cada agente de la población y para cada coordenada del agente:

          • se genera un valor de coordenada aleatorio dentro del rango especificado (rangeMin y rangeMax) mediante la función «u.RNDfromCI ()»;
          • se aplica un ajuste a este valor teniendo en cuenta el paso (rangeStep) mediante la función «u.SeInDiSp ()», que ajusta el valor al valor admisible más cercano (múltiplo del paso);
          • tras la inicialización, el indicador «revision» se establece en «true» para evitar una nueva inicialización en las siguientes iteraciones;
          • En ese momento, el método finaliza su ejecución.
          Definición de la fase del algoritmo (tras la inicialización):
          • Se comprueba si la iteración actual se encuentra en la fase de exploración (currentIteration <= explorationIters);
          • si la iteración se encuentra en la fase de exploración, se invoca el método «ExplorationPhase()»;
          • En caso contrario (si la iteración se encuentra en la fase de explotación), se invoca el método «ExploitationPhase()».

            De este modo, el método «Moving» controla el proceso de optimización, realizando la inicialización inicial de la población y alternando después entre las fases de exploración y explotación en función del contador de iteraciones. La inicialización inicial se realiza una sola vez; a continuación, el algoritmo entra en un bucle que determina qué fase debe ejecutarse en la iteración actual.

            //————————————————————————————————————————————————————————————————————
            //--- Paso principal del algoritmo
            void C_AO_DOA_dream::Moving ()
            {
              currentIteration++;
            
              // Inicialización inicial de la población
              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;
              }
            
              //------------------------------------------------------------------
              // Determinamos la fase del algoritmo
              if (currentIteration <= explorationIters)
              {
                ExplorationPhase ();
              }
              else
              {
                ExploitationPhase ();
              }
            }
            //————————————————————————————————————————————————————————————————————

            El método «ExplorationPhase» implementa la fase de exploración en el algoritmo DOA. En esta fase se produce la actualización y diversificación de las soluciones de los grupos de agentes con el objetivo de explorar nuevas regiones potenciales del espacio de búsqueda. Para cada grupo de agentes, se actualiza la mejor solución del grupo, de modo que refleje el mejor resultado encontrado hasta el momento. A continuación, se determina el número de dimensiones (dimensionalidad) para el procedimiento de olvido, en función del número actual del grupo y del número total de coordenadas. Para cada grupo se calculan los índices inicial y final de los agentes que lo componen.

            A continuación, para cada agente del grupo, se copia la mejor solución de todo el grupo en la solución actual del agente (estrategia «Memory»), lo que proporciona una buena solución «antigua» a la que volver. Se crea una lista de dimensiones (coordenadas) que se someterán a la operación de «olvido» y reposición. El array de dimensiones se baraja para seleccionar al azar cuáles de ellas se modificarán.

            Para cada agente del grupo se determina una estrategia de actualización de las dimensiones seleccionadas y, con la probabilidad definida por el parámetro «forgettingProb», se aplica la estrategia de «olvido» con modulación cosenoidal.

            Para las dimensiones seleccionadas, se genera un desplazamiento aleatorio dentro del rango. La modulación cosenoidal depende de la iteración actual y del número total de iteraciones, lo que permite controlar el grado de variación a lo largo del tiempo. Tras la actualización, el valor de la dimensión se ajusta al rango válido teniendo en cuenta los pasos de discretización. O bien, si no se selecciona el escenario de «olvido», se aplica la estrategia «dream sharing». Los valores de las dimensiones se copian de otro agente elegido al azar, es decir, se produce un intercambio de información entre los agentes.

            Como resultado, esta fase contribuye a la exploración del espacio de búsqueda, aumentando la diversidad de soluciones y ayudando a evitar trampas locales gracias a los cambios aleatorios y al intercambio de información entre los agentes.

            //————————————————————————————————————————————————————————————————————
            //--- Fase de exploración (Exploration phase)
            void C_AO_DOA_dream::ExplorationPhase ()
            {
              // Procesamos cada grupo
              for (int m = 0; m < numGroups; m++)
              {
                // Actualizamos la mejor solución del grupo
                UpdateGroupBest (m);
            
                // Calculamos el número de dimensiones que se olvidarán
                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);
            
                // Procesamos los agentes del grupo
                int startIdx = GetGroupStartIndex (m);
                int endIdx   = GetGroupEndIndex   (m);
            
                for (int j = startIdx; j <= endIdx; j++)
                {
                  // Estrategia de memoria: restablecimiento a la mejor solución del grupo
                  ArrayCopy (a [j].c, groupBest [m].c, 0, 0, WHOLE_ARRAY);
            
                  // Elegimos dimensiones aleatorias para olvidar
                  int dims [];
                  ArrayResize (dims, coords);
                  for (int i = 0; i < coords; i++) dims [i] = i;
            
                  // Mezclamos el array de dimensiones
                  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;
                  }
            
                  // Estrategia de olvido y reposición
                  if (u.RNDprobab () < forgettingProb)
                  {
                    // Estrategia principal con modulación cosenoidal
                    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: copia desde un agente aleatorio
                    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];
                    }
                  }
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            El método «ExploitationPhase» implementa la fase de explotación en el algoritmo de optimización. Su objetivo principal es dirigir el proceso de búsqueda hacia la mejor solución encontrada, con el fin de mejorar los resultados actuales.

            Para cada agente de la población, la solución del agente se restablece a la mejor solución global encontrada hasta ese momento, lo que permite centrarse en las áreas más prometedoras del espacio de búsqueda. A continuación, se determina el número de dimensiones (dimensionalidad) que se van a modificar. Normalmente se elige un número no inferior a dos ni superior a un valor determinado, relacionado con el número de coordenadas. Se crea una lista con todas las dimensiones (coordenadas), que luego se baraja aleatoriamente para seleccionar al azar cuáles de ellas se modificarán. Para cada dimensión seleccionada:

            • se calcula el rango de variación de esta dimensión;
            • se genera un valor aleatorio dentro de ese intervalo;
            • este valor se modula mediante una función cosenoidal que depende del número de la iteración actual y del número total de iteraciones, lo que permite controlar el grado de impacto de los cambios a lo largo del tiempo;
            • Como resultado, el valor de la dimensión cambia en función del valor generado y de la modulación cosenoidal;
            • A continuación, el valor se ajusta al intervalo admisible, teniendo en cuenta el paso de discretización, para que la solución siga siendo válida.

            El objetivo de este método es reforzar las buenas soluciones ya obtenidas mediante modificaciones aleatorias y controladas, lo que ayuda a desplazarse hacia regiones más prometedoras del espacio de búsqueda y a obtener soluciones de mayor calidad.

            //————————————————————————————————————————————————————————————————————
            //--- Fase de explotación (Exploitation phase)
            void C_AO_DOA_dream::ExploitationPhase ()
            {
              // En la fase de explotación, todos los agentes se mueven hacia la mejor solución global
              for (int j = 0; j < popSize; j++)
              {
                // Restablecimiento a la mejor solución global
                ArrayCopy (a [j].c, cB, 0, 0, WHOLE_ARRAY);
            
                // Calculamos el número de dimensiones que se van a modificar
                int km = MathMax (2, (int)MathCeil ((double)coords / 3.0));
                int k = u.RNDintInRange (2, km);
            
                // Seleccionamos dimensiones aleatorias
                int dims [];
                ArrayResize (dims, coords);
                for (int i = 0; i < coords; i++) dims [i] = i;
            
                // Barajamos el array de dimensiones
                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;
                }
            
                // Aplicamos la estrategia de olvido y reposición
                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]);
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            El método «UpdateGroupBest» está diseñado para determinar la mejor solución del grupo para un grupo concreto de agentes. Sus principales acciones son las siguientes:

            1. Obtención de los índices de las posiciones inicial y final de los agentes que forman parte de dicho grupo;
            2. Recorrido por todos los agentes del grupo indicado;
            3. Para cada agente, se compara el valor de la función de evaluación (métrica de calidad de la solución) con el mejor valor actual registrado para el grupo;
            4. Si un agente encuentra una solución con un valor mejor de la función de aptitud, actualiza el registro de la mejor solución del grupo, sustituyéndola por la más eficaz.

            De este modo, el método garantiza que se mantenga información actualizada sobre la mejor solución dentro de cada grupo, lo cual es importante para las fases posteriores del algoritmo, como la estrategia de búsqueda y la actualización de soluciones.

            //————————————————————————————————————————————————————————————————————
            //--- Actualizar la mejor solución del grupo
            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);
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            El método «GetGroupStartIndex» sirve para calcular el índice inicial de los elementos que forman parte del grupo especificado en un array de soluciones o agentes. Se basa en cálculos que suponen una distribución uniforme de los grupos por todo el array. La idea principal es determinar la posición del primer agente (o elemento) en un grupo concreto, a partir del número de grupo, el número total de grupos y el tamaño total de la población.

            Para ello, se multiplica el número de grupo por el tamaño total de la población y se divide entre el número de grupos, lo que da como resultado el índice del primer elemento dentro de un grupo determinado. Este enfoque garantiza una división uniforme de los datos en grupos y resulta práctico para las operaciones posteriores relacionadas con la agrupación de soluciones.

            //————————————————————————————————————————————————————————————————————
            //--- Obtener el índice inicial del grupo
            int C_AO_DOA_dream::GetGroupStartIndex (int groupNum)
            {
              return (int)((double)groupNum * popSize / numGroups);
            }
            //————————————————————————————————————————————————————————————————————

            El método «GetGroupEndIndex» calcula el índice final de los elementos que forman parte del grupo especificado. Determina el último elemento del grupo basándose en el número de grupo, el tamaño total de la población y el número de grupos. El cálculo se realiza multiplicando el número de grupo, incrementado en uno, por el tamaño total de la población y dividiendo el resultado entre el número de grupos. A continuación, el valor obtenido se reduce en una unidad para determinar el índice del último elemento del grupo.

            Es necesario realizar una comprobación adicional para evitar salir de los límites del array: si el índice calculado supera el tamaño de la población, se ajusta al valor del último índice válido. Este enfoque garantiza una definición correcta de los límites de cada grupo de agentes en la población.

            //————————————————————————————————————————————————————————————————————
            //--- Obtener el índice final del grupo
            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;
            }
            //————————————————————————————————————————————————————————————————————

            El método «Revision» está diseñado para actualizar la información sobre la mejor solución encontrada durante el funcionamiento del algoritmo. En primer lugar, recorre todas las soluciones de la población actual. Dentro del bucle, para cada solución, se comprueba el valor de su función objetivo y, si dicho valor es mayor que el mejor valor actual, «fB» se actualiza con el valor de la función objetivo de la solución actual, y la solución actual pasa a ser la mejor solución.

            A continuación, el método comprueba si la iteración actual se encuentra en la fase de «exploración» (explorationIters); si es así, además de actualizar la mejor solución global, el método también tiene en cuenta las mejores soluciones encontradas dentro de cada grupo. Para ello, el bucle recorre todos los grupos y, para cada uno de ellos, se compara el valor de la función objetivo de la mejor solución del grupo con el mejor valor actual.

            Si la solución dentro del grupo es mejor, se actualiza «fB» y la solución de «groupBest» se copia en el búfer global «cB». De este modo, el método «Revision» supervisa y actualiza constantemente la información sobre la mejor solución encontrada en función de la fase actual del algoritmo (exploración o explotación), lo que garantiza que se conserve la mejor solución encontrada hasta ese momento.

            //————————————————————————————————————————————————————————————————————
            //--- Actualización de la mejor y la peor solución
            void C_AO_DOA_dream::Revision ()
            {
              // Actualizamos la mejor solución global
              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);
                }
              }
            
              // Actualizamos las mejores soluciones de los grupos en la fase de exploración
              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 de las pruebas

            Ahora que ya hemos implementado el algoritmo DOA, podemos pasar directamente a las pruebas con las funciones de prueba. Como se puede observar, el algoritmo DOA ha obtenido un 53,62 % y se incluirá en nuestra tabla de clasificación.

            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%)

            En la visualización del funcionamiento del algoritmo DOA en dimensionalidades reducidas (líneas verdes) se aprecia cierta dispersión en los resultados, especialmente en las funciones «Forest» y «Megacity».

            Hilly

            DOA en la función de prueba Hilly

            Forest

            DOA en la función de prueba Forest

            Megacity

            DOA en la función de prueba Megacity

            Según los resultados de las pruebas, el algoritmo DOA ocupa el 26.º lugar en la clasificación general de los algoritmos de optimización poblacional.

            N.º 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 EOm extremal_optimization_M 0,76166 0,77242 0,31747 1,85155 0,99999 0,76751 0,23527 2,00277 0,74769 0,53969 0,14249 1,42987 5,284 58,71
            13 BBO biogeography based optimization 0,94912 0,69456 0,35031 1,99399 0,93820 0,67365 0,25682 1,86867 0,74615 0,48277 0,17369 1,40261 5,265 58,50
            14 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
            15 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
            16 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
            17 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
            18 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
            19 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
            20 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
            21 BSA backtracking_search_algorithm 0,97309 0,54534 0,29098 1,80941 0,99999 0,58543 0,21747 1,80289 0,84769 0,36953 0,12978 1,34700 4,959 55,10
            22 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
            23 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
            24 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
            25 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
            26 DOA dream_optimization_algorithm 0,85556 0,70085 0,37280 1,92921 0,73421 0,48905 0,24147 1,46473 0,77231 0,47354 0,18561 1,43146 4,825 53,62
            27 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
            28 DEA dolphin_echolocation_algorithm 0,75995 0,67572 0,34171 1,77738 0,89582 0,64223 0,23941 1,77746 0,61538 0,44031 0,15115 1,20684 4,762 52,91
            29 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
            30 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
            31 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
            32 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
            33 (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
            34 FBA fractal-based Algorithm 0,79000 0,65134 0,28965 1,73099 0,87158 0,56823 0,18877 1,62858 0,61077 0,46062 0,12398 1,19537 4,555 50,61
            35 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
            36 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
            37 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
            38 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
            39 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
            40 CAm camel algorithm M 0,78684 0,56042 0,35133 1,69859 0,82772 0,56041 0,24336 1,63149 0,64846 0,33092 0,13418 1,11356 4,444 49,37
            41 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
            42 CMAES covariance_matrix_adaptation_evolution_strategy 0,76258 0,72089 0,00000 1,48347 0,82056 0,79616 0,00000 1,61672 0,75846 0,49077 0,00000 1,24923 4,349 48,33
            43 DA_duelist duelist_algorithm 0,92782 0,53778 0,27792 1,74352 0,86957 0,47536 0,18193 1,52686 0,62153 0,33569 0,11715 1,07437 4,345 48,28
            44 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
            45 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
            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


            Conclusiones

            El Dream Optimization Algorithm demuestra una eficiencia suficiente: obtuvo un 53,62 % del 100 % posible en las pruebas realizadas y se situó en nuestra tabla de clasificación.

            El análisis de los resultados pone de manifiesto una tendencia característica de muchos algoritmos metaheurísticos: una reducción significativa del rendimiento a medida que aumenta la dimensión del problema. En dimensionalidades reducidas, el DOA ofrece resultados en el rango del 73 % al 86 %; al pasar a problemas de alta dimensión, la eficiencia cae hasta el rango del 18 % al 37 % tras un número finito de iteraciones.

            En conjunto, el algoritmo se sitúa claramente un “término medio” entre los demás métodos de optimización. Quienes lo deseen pueden probar diferentes configuraciones del algoritmo; quizá aún haya margen para obtener mejores resultados.

            tab

            Figura 2. Gradación por colores de los algoritmos según las pruebas correspondientes

            gráfico

            Figura 3. Histograma de los resultados de las pruebas de los algoritmos (en una escala de 0 a 100; cuanto mayor sea el valor, mejor; donde 100 es el resultado teórico máximo posible; en el archivo comprimido se incluye el script para calcular la tabla de clasificación)

            Ventajas y desventajas del algoritmo DOA:

            Ventajas:

            1. Implementación sencilla.
            2. Es rápido.

            Desventajas:

            1. Dispersión de valores en funciones de baja dimensionalidad.

            Se adjunta al artículo un archivo comprimido con las versiones actualizadas de los códigos de los algoritmos. El autor del artículo no se hace responsable de la exactitud absoluta en la descripción de los algoritmos canónicos, ya que en muchos de ellos se han introducido modificaciones para mejorar las capacidades de búsqueda. Las conclusiones y consideraciones expuestas en los artículos se basan en los resultados de los experimentos realizados.


            Programas utilizados en el artículo

            # Nombre Tipo Descripción
            1 #C_AO.mqh
            Archivo de inclusión
            Clase base de los algoritmos de optimización poblacional
            2 #C_AO_enum.mqh
            Archivo de inclusión
            Enumeración de los algoritmos de optimización poblacional
            3 TestFunctions.mqh
            Archivo de inclusión
            Biblioteca de funciones de prueba
            4
            TestStandFunctions.mqh
            Archivo de inclusión
            Biblioteca de funciones del banco de pruebas
            5
            Utilities.mqh
            Archivo de inclusión
            Biblioteca de funciones auxiliares
            6
            CalculationTestResults.mqh
            Archivo de inclusión
            Script para calcular los resultados de la tabla comparativa
            7
            Testing AOs.mq5
            Script Banco de pruebas unificado para todos los algoritmos de optimización poblacional
            8
            Simple use of population optimization algorithms.mq5
            Script
            Ejemplo sencillo de uso de algoritmos de optimización poblacional sin visualización
            9
            Test_AO_DOA.mq5
            Script Banco de pruebas para DOA

            Traducción del ruso hecha por MetaQuotes Ltd.
            Artículo original: https://www.mql5.com/ru/articles/19177

            Archivos adjuntos |
            DOArdream9.ZIP (333.48 KB)
            Chacha Ian Maroa
            Chacha Ian Maroa | 2 jul 2026 en 20:03
            ¡Vaya! ¡Estoy deseando que salga el algoritmo giratorio de la Galaxia de Andrómeda!!
            Características del Wizard MQL5 que debe conocer (Parte 75): Uso del Awesome Oscillator y Envelopes Características del Wizard MQL5 que debe conocer (Parte 75): Uso del Awesome Oscillator y Envelopes
            El «Awesome Oscillator» de Bill Williams y el canal de envolventes son una combinación que podría utilizarse de forma complementaria dentro de un asesor experto de MQL5. Utilizamos el Awesome Oscillator por su capacidad para detectar tendencias, mientras que el canal de envolventes se incorpora para definir nuestros niveles de soporte y resistencia. Al explorar esta combinación de indicadores, utilizamos el asistente MQL5 para construir y probar cualquier potencial que estos dos puedan tener.
            Redes neuronales en el trading: modelo de consultas temporales (TQNet) Redes neuronales en el trading: modelo de consultas temporales (TQNet)
            El framework TQNet abre nuevas posibilidades en el modelado y la previsión de series temporales financieras, combinando modularidad, flexibilidad y un alto rendimiento. En el artículo se expone la posibilidad de implementar mecanismos complejos para trabajar con correlaciones globales, incluyendo métodos avanzados de inicialización de parámetros.
            Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles
            Este artículo describe el diseño de una estrategia de cruce de medias móviles dobles que utiliza señales de un marco temporal superior (D1) para orientar las entradas en un marco temporal inferior (M15), calculándose los niveles de stop-loss a partir de un marco temporal de riesgo intermedio (H4). Introduce constantes del sistema, enumeraciones personalizadas y la lógica para los modos de seguimiento de tendencias y de retorno a la media, al tiempo que hace hincapié en la modularidad y en la optimización futura mediante un algoritmo genético. Este enfoque permite establecer condiciones flexibles de entrada y salida, con el objetivo de reducir el retraso de la señal y mejorar la sincronización de las operaciones, alineando las entradas en los marcos temporales más cortos con las tendencias de los marcos temporales más largos.
            Algoritmo del duelista — Duelist Algorithm Algoritmo del duelista — Duelist Algorithm
            ¿Y si nuestras estrategias de trading pudieran aprender unas de otras, como auténticos luchadores? Duelist Algorithm es un nuevo método de optimización en el que los parámetros de los sistemas de trading se enfrentan literalmente en duelos por el derecho a ser considerados los mejores.