Algoritmo de optimización basado en los sueños — Dream Optimization Algorithm (DOA)
Índice
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.

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.
- 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.
- 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.
- 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.
- 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:
- Obtención de los índices de las posiciones inicial y final de los agentes que forman parte de dicho grupo;
- Recorrido por todos los agentes del grupo indicado;
- 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;
- 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.
=============================
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».

DOA en la función de prueba Hilly

DOA en la función de prueba Forest

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.

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

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:
- Implementación sencilla.
- Es rápido.
Desventajas:
- 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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Características del Wizard MQL5 que debe conocer (Parte 75): Uso del Awesome Oscillator y Envelopes
Redes neuronales en el trading: modelo de consultas temporales (TQNet)
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 9): Cruce de dos medias móviles
Algoritmo del duelista — Duelist Algorithm
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso