Русский Português
preview
Optimización de Battle Royale — Battle Royale Optimizer (BRO)

Optimización de Battle Royale — Battle Royale Optimizer (BRO)

MetaTrader 5Probador |
41 1
Andrey Dik
Andrey Dik

Contenido

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


Introducción

En la optimización metaheurística, donde los algoritmos suelen inspirarse en procesos naturales, fenómenos físicos y mecanismos evolutivos, ha surgido una fuente de inspiración fundamentalmente nueva: los juegos de computadora. Battle Royale Optimizer (BRO), desarrollado por Taymaz Rahkar Farshi, es un innovador algoritmo de optimización basado en la mecánica de populares juegos "Battle Royale" como PlayerUnknown's Battlegrounds (PUBG).

El algoritmo BRO descubre una nueva categoría de métodos de optimización: los métodos basados en juegos ("game-based"), que complementan el trío de enfoques bien establecido de algoritmos de optimización, incluidos los algoritmos evolutivos, los algoritmos de inteligencia de enjambre y los algoritmos basados en fenómenos físicos, pertenecientes al amplio grupo de algoritmos de optimización basados en poblaciones. A diferencia de los algoritmos de inteligencia de enjambre, en los que los agentes cooperan para lograr un objetivo común, en el BRO los individuos compiten entre sí en un intento de sobrevivir y ocupar la mejor posición en el espacio de búsqueda.

Una característica clave del BRO es su mecanismo único para competir y "dañar" soluciones. Cada solución se compara con la de su vecino más próximo y el perdedor sufre un "daño", mientras que el ganador comienza de cero. Las soluciones que han acumulado demasiado daño se eliminan de la población y son sustituidas por nuevas soluciones aleatorias, del mismo modo que los jugadores de PUBG son eliminados de una partida tras recibir daño crítico. Y esto ofrece un mecanismo para explorar el espacio de búsqueda.


Implementación del algoritmo

El algoritmo Battle Royale Optimiser (BRO) imagina figuradamente un mundo virtual en el que varios jugadores se lanzan a un campo de batalla y solo uno tiene que seguir con vida, y esta es la esencia del prototipo de juego. Vamos a trasladar ahora este concepto a la resolución de problemas de optimización.

Al principio del algoritmo, crearemos una población de soluciones distribuidas aleatoriamente por el espacio de búsqueda. Cada solución será una especie de "jugador" que tendrá una posición determinada y la calidad (aptitud) de esa posición. A continuación comenzará el ciclo principal de la competición, en el que cada solución se comparará con su vecina más próxima, más o menos como los jugadores de una batalla que se enfrentan entre sí.

Cuando dos soluciones "se encuentran", se compararán en términos de calidad. La mejor solución se declarará vencedora y recibirá cero daños, mientras que la peor será la perdedora y recibirá un daño. Este contador de daños es una característica clave del algoritmo. La solución perdedora no solo sufre daños, sino que intenta mejorar su posición aproximándose a la solución más conocida de la población. Este movimiento imita el impulso de sobrevivir buscando un lugar más seguro y rentable.

Si una solución acumula demasiados daños (supera un umbral determinado), quedará "eliminada del juego", es decir, se retirará de la población y será sustituida por una nueva solución aleatoria. Esto resulta similar a la expulsión definitiva de un jugador en battle royale y la aparición de uno nuevo en la siguiente partida. Este mecanismo garantiza la renovación constante de la población y favorece la diversidad de soluciones.

Periódicamente, el algoritmo reduce el espacio de búsqueda, de forma análoga a la reducción del área de juego en el battle royale, lo que obliga a los jugadores a acercarse unos a otros. Los límites de la búsqueda se estrechan en torno a la mejor solución encontrada, lo cual hace que la población se concentre en las zonas más prometedoras.

Con este enfoque, el algoritmo BRO se equilibra entre la exploración de nuevas áreas y el uso de las buenas soluciones ya encontradas. Las soluciones perdedoras tienden a ser mejores, manteniendo una tendencia hacia la mejora, mientras que las completamente perdedoras son sustituidas por otras nuevas, ofreciendo una nueva perspectiva del espacio de búsqueda. Al mismo tiempo, el estrechamiento periódico de los límites refuerza la búsqueda localizada en torno a soluciones prometedoras.

bro-algorithm

Figura 1. Ilustración del funcionamiento del algoritmo BRO.

Esta ilustración muestra los principales componentes del algoritmo de Battle Royale Optimizer (BRO). El espacio de búsqueda se representa como un área 2D con contornos que simbolizan la función de optimización (las áreas más brillantes representan las mejores soluciones). La mejor solución global está marcada con una estrella roja en el centro del "montículo" más alto. Las soluciones ganadoras están marcadas con puntos verdes: son las soluciones de daño cero (ganadoras en comparación con sus vecinas). Las soluciones perdedoras son puntos amarillos (con 1 daño) y naranjas (con 2 daños). Las nuevas soluciones aleatorias son puntos azules que aparecen cuando una solución acumula demasiados daños. Las soluciones perdedoras se desplazan hacia la mejor solución (mostrada por flechas punteadas). El estrechamiento del espacio de búsqueda se representa mediante un recuadro naranja discontinuo que se centra alrededor de la mejor solución.

Los pasos clave del algoritmo son: inicialización, comparación con los vecinos, avance hacia una solución mejor y reducción del espacio de búsqueda.

    Las soluciones del algoritmo BRO compiten entre sí, y las perdedoras reciben "daño". Las soluciones con demasiados daños son sustituidas por nuevas soluciones aleatorias. Ahora que entendemos cómo funciona el algoritmo, podemos escribir el pseudocódigo.

    Inicialización:

    1. Creamos una población de soluciones con un tamaño popSize
    2. Para cada solución ponemos el contador de daños a 0
    3. Establecemos el umbral máximo de daño maxDamage
    4. Determinamos el número de épocas epochs
    5. Calculamos el valor delta inicial para el estrechamiento periódico del espacio de búsqueda

    Algoritmo básico:

    1. Establecimiento de una población inicial:
      • Para cada decisión de la población:
        • Generamos coordenadas aleatorias en un espacio de búsqueda dado
    2. Para cada época se cumple:
      • Actualizamos la mejor solución global si se encuentra una mejor
      • Se realizan "batallas" entre decisiones:
        • Para cada decisión de la población:
          • Encontramos el vecino más próximo (solución de distancia mínima)
          • Comparamos la calidad de la solución actual con la de su vecino:
            • Si la solución actual es mejor:
              • Reiniciamos el contador de daños de la solución actual
              • Aumentamos el contador de daño del vecino
              • El perdedor (vecino) se desplaza hacia la mejor solución
            • De lo contrario:
              • Aumentamos el contador de daños de la solución actual
              • Reiniciamos el contador de daños del vecino
              • El perdedor (solución actual) se desplaza hacia la mejor solución
      • Procesamiento de soluciones gravemente dañadas:
        • Para cada decisión de la población:
          • Si el contador de daño ≥ maxDamage :
            • Reiniciamos el contador de daños
            • Sustituimos la solución por una nueva solución aleatoria
      • Reducción periódica del espacio de búsqueda:
        • Si el número de época actual es divisible por delta :
          • Calculamos las desviaciones típicas de las coordenadas en toda la población
          • Reducimos el espacio de búsqueda en torno a la mejor solución
          • Actualizamos el valor delta
    3. Retorno de la mejor solución encontrada

    En el algoritmo intervienen las siguientes fórmulas:

    • Cálculo del valor delta inicial para acotar el espacio de búsqueda: delta = ⌊epochs / log₁₀(epochs)⌋
    • Cálculo de la distancia euclidiana entre soluciones: distance = √(∑(a[idx1][c] - a[idx2][c])²)
    • Desplazamiento de la solución perdedora hacia la mejor solución global: a[i][c] = a[i][c] + r × (cB[c] - a[i][c])
    • Cálculo del valor medio de cada coordenada: mean[c] = (∑a[i][c]) / popSize
    • Cálculo de la desviación estándar de cada coordenada: sdValues[c] = √(∑(a[i][c] - mean[c])² / popSize)
    • Fórmulas para reducir el espacio de búsqueda: newMin[c] = cB[c] - sdValues[c] newMax[c] = cB[c] + sdValues[c]
    • Actualización del parámetro delta tras la contracción espacial: delta = delta + ⌊delta / 2⌉

    Para acotar periódicamente el espacio de búsqueda, el autor propone la siguiente fórmula: Δ (delta) = maxEpochs / log₁₀(maxEpochs), cuya gráfica se muestra a continuación:

    func

    Figura 2. Gráfico de la función de dependencia del parámetro delta respecto al número de épocas

    La gráfica de la función delta = epochs/log₁₀(epochs) es importante en el algoritmo BRO, ya que determina tras cuántas iteraciones se reducirá el espacio de búsqueda. Como podemos ver en el gráfico, el valor delta aumenta con el número de épocas, pero no tan rápido como las propias épocas, debido a la división por el logaritmo. Esto crea una dependencia no lineal que ofrece las siguientes ventajas: en las primeras fases de la optimización (cuando el número de épocas es pequeño), el estrechamiento se produce con relativa frecuencia, lo que ayuda al algoritmo a centrarse más rápidamente en las zonas prometedoras, mientras que en las últimas fases (cuando el número de épocas es grande), el estrechamiento se produce con menos frecuencia, lo que permite una exploración más exhaustiva de las zonas prometedoras ya encontradas.

    En nuestros experimentos, hemos convertido la fórmula de dependencia del parámetro delta; cambia dos veces con el logaritmo, y el algoritmo funciona mejor.

    // Вычисление начального delta для сужения пространства поиска
      delta = (int)MathFloor(epochs / MathLog(MathLog(epochs)));

    Vamos a pasar ahora a la generación del código; para ello, escribiremos una clase personalizada "C_AO_BRO", que heredará de la clase básica "C_AO", es decir, heredará todos los miembros públicos y protegidos de la clase "C_AO" y podrá sobreescribir su comportamiento. Esta clase consistirá en la aplicación de un algoritmo de optimización basado en el concepto "Battle Royale".

    1. Miembros públicos de la clase:

    • popSize — establece el tamaño de la población.
    • maxDamage — establece el umbral máximo de daño, cuántos "derrotas" puede sufrir la solución antes de ser eliminada.
    • SetParams () — el método actualiza los valores "popSize" y "maxDamage" basándose en los valores almacenados en el array "params", lo que permite cambiar los parámetros del algoritmo durante la ejecución.
    • Init () — método de inicialización del algoritmo. Admite los siguientes parámetros:
      • rangeMinP [] — valores mínimos del rango de búsqueda para cada variable.
      • rangeMaxP [] — valores máximos del rango de búsqueda.
      • rangeStepP [] — paso de búsqueda para cada variable.
      • epochsP — número de épocas (iteraciones) del algoritmo. El valor por defecto es 0.
    • Moving () — el método implementa la lógica básica de desplazamiento o actualización de soluciones en el espacio de búsqueda.
    • Revision () — el método implementa la lógica de revisión de las decisiones actuales, aquí se evalúa el "daño" recibido por cada decisión.
    • maxDamage — miembro público que almacena el umbral máximo de daño.

    2. Campos privados de la clase:

    • delta — intervalo para reducir el espacio de búsqueda. Permite adaptar el tamaño del paso de búsqueda durante la optimización.
    • daños [] — el array almacena el número de "daños" de cada solución de la población. 
    • epoch — época actual (número de iteración) del algoritmo.
    • epoch — número máximo de épocas (iteraciones) del algoritmo.

    3. Métodos auxiliares:

    • FindNearestNeighbor () — encuentra el vecino más cercano para una solución en un índice dado utilizado para la interacción entre soluciones.
    • CalculateDistance () — calcula la distancia entre dos soluciones identificadas por sus índices.
    • CalculateStandardDeviations () — calcula las desviaciones estándar de los valores de solución de la población utilizados para estimar la diversidad de la población y adaptar los parámetros de búsqueda.
    • ShrinkSearchSpace () — método que reduce el espacio de búsqueda. Se trata de una técnica estándar para la convergencia de un algoritmo a una solución óptima.

    Introducción general:

    C_AO_BRO es la clase para el algoritmo de optimización Battle Royale y la idea básica del algoritmo, en resumen, es la siguiente:

    1. Inicialización: creamos una población de soluciones aleatorias en un espacio de búsqueda definido.
    2. Evaluación: cada solución se evalúa con una función de aptitud.
    3. Battle Royale: las soluciones "luchan" entre sí (se comparan según los valores de la función objetivo).
    4. Daños: algunas soluciones reciben "daños" según los resultados de las "batallas".
    5. Expulsión: las soluciones que han recibido una cantidad de "daño" superior a "maxDamage" se eliminan de la población.
    6. Reproducción/sustitución: las soluciones eliminadas se sustituyen por nuevas soluciones aleatorias.
    7. Reducción del espacio de búsqueda: el espacio de búsqueda puede reducirse para centrarse en las áreas más prometedoras.
    8. Repetición: las etapas 2-7 se repiten durante un número determinado de épocas.
    //——————————————————————————————————————————————————————————————————————————————
    class C_AO_BRO : public C_AO
    {
      public: //--------------------------------------------------------------------
      ~C_AO_BRO () { }
      C_AO_BRO ()
      {
        ao_name = "BRO";
        ao_desc = "Battle Royale Optimizer";
        ao_link = "https://www.mql5.com/es/articles/17688";
    
        popSize   = 100;    // размер популяции
        maxDamage = 3;      // максимальный порог повреждений
    
        ArrayResize (params, 2);
    
        params [0].name = "popSize";   params [0].val = popSize;
        params [1].name = "maxDamage"; params [1].val = maxDamage;
      }
    
      void SetParams ()
      {
        popSize   = (int)params [0].val;
        maxDamage = (int)params [1].val;
      }
    
      bool Init (const double &rangeMinP [],  // минимальный диапазон поиска
                 const double &rangeMaxP [],  // максимальный диапазон поиска
                 const double &rangeStepP [], // шаг поиска
                 const int     epochsP = 0);  // количество эпох
    
      void Moving ();
      void Revision ();
    
      //----------------------------------------------------------------------------
      int maxDamage;    // максимальный порог повреждений
    
      private: //-------------------------------------------------------------------
      int    delta;      // интервал для сужения пространства поиска
      int    damages []; // количество повреждений для каждого решения
      int    epoch;      // текущая эпоха
      int    epochs;     // максимальное количество эпох
    
      // Вспомогательные методы
      int    FindNearestNeighbor (int index);
      double CalculateDistance (int idx1, int idx2);
      void   CalculateStandardDeviations (double &sdValues []);
      void   ShrinkSearchSpace ();
    };
    //——————————————————————————————————————————————————————————————————————————————

    El método "Init ()" inicializa el algoritmo BRO, llama a "StandardInit ()" para una inicialización estándar usando los rangos y pasos de búsqueda transmitidos. Si "StandardInit" retorna "false", el método "Init ()" también devolverá "false", señalando un error de inicialización. Inicializa el array "damages" asignando memoria para cada solución de la población "popSize" y estableciendo el número inicial de "daños" de cada solución en 0. Establece el número total de épocas "epochs" y restablece la época actual "epoch" a 0.

    El valor "delta" se calcula según el número total de épocas, de modo que el espacio de búsqueda se reduce gradualmente. Si "delta" resulta ser menor o igual que 0, se pone a 1. En general, este método prepara el algoritmo para su funcionamiento inicializando sus parámetros básicos y sus estructuras de datos.

    //——————————————————————————————————————————————————————————————————————————————
    bool C_AO_BRO::Init (const double &rangeMinP  [],  // минимальный диапазон поиска
                         const double &rangeMaxP  [],  // максимальный диапазон поиска
                         const double &rangeStepP [],  // шаг поиска
                         const int     epochsP = 0)    // количество эпох
    {
      if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
    
      //----------------------------------------------------------------------------
      // Инициализация счетчиков повреждений для каждого решения
      ArrayResize (damages, popSize);
      ArrayInitialize (damages, 0);
    
      // Установка эпох
      epochs = epochsP;
      epoch  = 0;
    
      // Вычисление начального delta для сужения пространства поиска
      delta = (int)MathFloor (epochs / MathLog10 (epochs));
      if (delta <= 0) delta = 1;
    
      return true;
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Moving ()" implementa la lógica de inicialización de una población de soluciones, con cada coordenada de cada solución generada aleatoriamente entre los rangos mínimo y máximo dados "rangeMin" y "rangeMax" y muestreada en un determinado paso "rangeStep". Este método garantiza que la población se inicialice solo una vez. 

    /——————————————————————————————————————————————————————————————————————————————
    void C_AO_BRO::Moving ()
    {
      if (!revision)
      {
        // Инициализация популяции случайными решениями
        for (int i = 0; i < popSize; i++)
        {
          for (int c = 0; c < coords; c++)
          {
            double coordinate = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            a [i].c [c] = u.SeInDiSp (coordinate, rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
    
        revision = true;
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "Revision ()" supone un paso clave en el algoritmo de optimización BRO. Cada iteración del método actualiza la mejor solución: si alguna solución de la población actual es mejor que la mejor solución global actual, entonces se actualizará la mejor solución global.

    El método compara las soluciones con sus vecinas: para cada solución de la población, se encuentra la vecina más próxima. A continuación, se comparan los valores de sus funciones. La mejor solución de la pareja es "recompensada" reiniciando el contador de daños, mientras que el contador de daños de la peor solución se aumenta. La peor solución del par se desplaza hacia la mejor solución global.

    Luego se sustituyen las soluciones "dañadas": si una solución ha acumulado suficiente "daño" (ha alcanzado el valor "maxDamage"), se sustituirá por una nueva generada aleatoriamente. Periódicamente, según la variable "delta", se reduce la zona de búsqueda. Este proceso se repite en varias iteraciones del algoritmo. Usando la comparación con los vecinos, las soluciones se desplazan a zonas más favorables para la búsqueda.

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_BRO::Revision ()
    {
      epoch++;
    
      // Обновление глобального наилучшего решения
      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);
        }
      }
    
      // Сравнение каждого решения с его ближайшим соседом и обновление счетчиков повреждений
      for (int i = 0; i < popSize; i++)
      {
        int neighbor = FindNearestNeighbor (i);
    
        if (neighbor != -1)
        {
          if (a [i].f >= a [neighbor].f)
          {
            // Решение i побеждает
            damages [i] = 0;
            damages [neighbor]++;
    
            // Проигравший (сосед) движется к наилучшему решению
            for (int c = 0; c < coords; c++)
            {
              double r = u.RNDfromCI (0, 1);
              a [neighbor].c [c] = a [neighbor].c [c] + r * (cB [c] - a [neighbor].c [c]);
              a [neighbor].c [c] = u.SeInDiSp (a [neighbor].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
            }
          }
          else
          {
            // Решение i проигрывает
            damages [i]++;
            damages [neighbor] = 0;
    
            // Проигравший (i) движется к наилучшему решению
            for (int c = 0; c < coords; c++)
            {
              double r = u.RNDfromCI (0, 1);
              a [i].c [c] = a [i].c [c] + r * (cB [c] - a [i].c [c]);
              a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
            }
          }
        }
      }
    
      // Проверка, достигло ли какое-либо решение максимального повреждения, и его замена
      for (int i = 0; i < popSize; i++)
      {
        if (damages [i] >= maxDamage)
        {
          // Сброс счетчика повреждений
          damages [i] = 0;
    
          // Генерация нового случайного решения
          for (int c = 0; c < coords; c++)
          {
            double coordinate = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            a [i].c [c] = u.SeInDiSp (coordinate, rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
      }
    
      // Периодическое сужение пространства поиска
      if (epochs > 0 && epoch % delta == 0)
      {
        ShrinkSearchSpace ();
        // Обновление delta
        delta = delta + (int)MathRound (delta / 2);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "FindNearestNeighbor ()" encuentra el índice del vecino más próximo para una solución con índice "index" en la población. Prueba todas las soluciones, calcula la distancia a cada una de ellas (salvo el propio "index" de la solución) y devuelve el índice de la solución con la distancia mínima. Si no se ha podido encontrar ningún vecino más cercano (por ejemplo, solo hay una solución en la población), retornará -1. En pocas palabras, el método encuentra el vecino más próximo para una solución dada.

    //——————————————————————————————————————————————————————————————————————————————
    int C_AO_BRO::FindNearestNeighbor (int index)
    {
      double minDistance = DBL_MAX;
      int nearestIndex = -1;
    
      for (int i = 0; i < popSize; i++)
      {
        if (i == index) continue;
    
        double distance = CalculateDistance (index, i);
        if (distance < minDistance)
        {
          minDistance = distance;
          nearestIndex = i;
        }
      }
    
      return nearestIndex;
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "CalculateDistance ()" calcula la distancia euclidiana entre dos soluciones de una población dada según sus índices "idx1" e "idx2". Comienza inicializando la variable "distanceSum" con cero. Esta variable acumulará la suma de los cuadrados de las diferencias de las coordenadas. El ciclo for itera todas las coordenadas de la solución. En cada iteración del ciclo, se calcula la diferencia entre las coordenadas correspondientes de las soluciones "idx1" e "idx2". El cuadrado de esta diferencia se añade a la "distanceSum".

    Una vez finalizado el ciclo, el método retornará la raíz cuadrada de "distanceSum", es decir, la distancia euclidiana entre las dos soluciones. Como resultado, el método retorna un valor numérico que refleja la "distancia" entre dos soluciones en el espacio de búsqueda. Cuanto mayor sea este valor, más alejadas se encontrarán las soluciones entre sí.

    //——————————————————————————————————————————————————————————————————————————————
    double C_AO_BRO::CalculateDistance (int idx1, int idx2)
    {
      double distanceSum = 0.0;
    
      for (int c = 0; c < coords; c++)
      {
        double diff = a [idx1].c [c] - a [idx2].c [c];
        distanceSum += diff * diff;
      }
    
      return MathSqrt (distanceSum);
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "CalculateStandardDeviations ()" calcula la desviación estándar para cada coordenada de la solución en la población y almacena los resultados en el array "sdValues". Redimensiona el array de entrada "sdValues" para que pueda almacenar la desviación estándar de cada una de las coordenadas "coords". Luego el ciclo prueba cada coordenada de la solución y se calcula la desviación estándar. El método pone a cero la suma de cuadrados de las desviaciones de la coordenada actual y después también pone a cero su valor medio. El ciclo suma los valores de la coordenada actual "c" para todas las soluciones de la población. A continuación, calcula el valor medio de esta coordenada. 

    En el cálculo de la suma de los cuadrados de las desviaciones: el ciclo prueba todas las soluciones de la población y calcula la suma de cuadrados de las desviaciones del valor medio para la coordenada actual. Calcula la diferencia entre el valor de la coordenada "c" para la solución "i" y su valor medio. Luego suma el cuadrado de la diferencia respecto a la suma de los cuadrados de las desviaciones. Y calcula la desviación estándar como la raíz cuadrada de la suma de los cuadrados de las varianzas dividida por el tamaño de la población. El resultado se almacena en el elemento correspondiente del array "sdValues".

    Como resultado, el método calcula una medida de la dispersión de los valores para cada coordenada de la solución en la población y la almacena en el array "sdValues" transmitido, mientras que la desviación estándar muestra cuánto varían los valores de las coordenadas alrededor del valor medio.

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_BRO::CalculateStandardDeviations (double &sdValues [])
    {
      ArrayResize (sdValues, coords);
    
      for (int c = 0; c < coords; c++)
      {
        double sum = 0.0;
        double mean = 0.0;
    
        // Вычисление среднего
        for (int i = 0; i < popSize; i++) mean += a [i].c [c];
    
        mean /= popSize;
    
        // Вычисление суммы квадратов отклонений
        for (int i = 0; i < popSize; i++)
        {
          double diff = a [i].c [c] - mean;
          sum += diff * diff;
        }
    
        sdValues [c] = MathSqrt (sum / popSize);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    El método "ShrinkSearchSpace ()" reduce el espacio de búsqueda según las desviaciones estándar de las coordenadas y la ubicación de la mejor solución encontrada. En cierto modo, centra la búsqueda en un área más prometedora en la que ya existe una buena solución.

    Primero viene el cálculo de las desviaciones estándar; esto se hace llamando al método "CalculateStandardDeviations ()", que calcula las desviaciones estándar para cada coordenada de la solución en la población y las almacena en el array "sdValues", o en otras palabras, se calcula cuánto difieren los valores de cada coordenada en la población. Cálculo de nuevos límites: los nuevos límites se centran alrededor de la mejor solución encontrada y su anchura viene definida por la desviación estándar. Si la desviación estándar es pequeña, la búsqueda se estrechará en torno a la mejor solución. Si la desviación estándar es elevada, la búsqueda seguirá siendo más amplia. Comprobación de admisibilidad: la búsqueda no irá más allá del espacio de soluciones inicial admisible.

    //——————————————————————————————————————————————————————————————————————————————
    void C_AO_BRO::ShrinkSearchSpace ()
    {
      double sdValues [];
      CalculateStandardDeviations (sdValues);
    
      for (int c = 0; c < coords; c++)
      {
        // Новые границы центрированы вокруг наилучшего решения с шириной стандартного отклонения
        double newMin = cB [c] - sdValues [c];
        double newMax = cB [c] + sdValues [c];
    
        // Убедитесь, что новые границы находятся в пределах исходных ограничений
        if (newMin < rangeMin [c]) newMin = rangeMin [c];
        if (newMax > rangeMax [c]) newMax = rangeMax [c];
    
        // Обновление границ
        rangeMin [c] = newMin;
        rangeMax [c] = newMax;
      }
    }
    //——————————————————————————————————————————————————————————————————————————————


    Resultados de las pruebas

    Tras ejecutar las pruebas, podemos ver que el algoritmo funciona razonablemente bien en las funciones Hilly y Forest, sin embargo, el rendimiento de convergencia es mucho más débil en la Megacity discreta.

    BRO|Battle Royale Optimizer|50.0|3.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.7494493002235458
    25 Hilly's; Func runs: 10000; result: 0.4983307394255448
    500 Hilly's; Func runs: 10000; result: 0.27994639979348446
    =============================
    5 Forest's; Func runs: 10000; result: 0.6962444245506945
    25 Forest's; Func runs: 10000; result: 0.3845619185097379
    500 Forest's; Func runs: 10000; result: 0.20427058729050862
    =============================
    5 Megacity's; Func runs: 10000; result: 0.3815384615384616
    25 Megacity's; Func runs: 10000; result: 0.21107692307692308
    500 Megacity's; Func runs: 10000; result: 0.10607692307692404
    =============================
    All score: 3.51150 (39.02%)

    En la visualización se aprecia la dispersión de los valores resultantes y una menor capacidad de búsqueda en la última función Megacity discreta.

    Hilly

    BRO en la función de prueba Hilly

    Forest

    BRO en la función de prueba Forest

    Megacity

    BRO en la función de prueba Megacity

    Según los resultados, el algoritmo BRO cierra la tabla de clasificación de los algoritmos de optimización basados en la población.

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


    Conclusiones

    El algoritmo BRO muestra un enfoque interesante de la optimización metaheurística abriendo el camino a métodos de "juego", utilizando una metáfora de "Battle Royale" en la que las soluciones compiten entre sí. Los puntos fuertes del algoritmo son su simplicidad conceptual (el algoritmo es intuitivo y relativamente fácil de aplicar), el estrechamiento automático del espacio de búsqueda basado en las características estadísticas de la población y el uso del concepto de vecinos más próximos para las competiciones locales. El algoritmo BRO es un método de optimización muy prometedor cuyo potencial está aún por explotar.

    Tab

    Figura 3. Clasificación por colores de los algoritmos según las pruebas respectivas

    chart

    Figura 4. Histograma de los resultados de las pruebas de algoritmos (en una escala de 0 a 100, cuanto más mejor, donde 100 es el máximo resultado teórico posible, el script para calcular la tabla de puntuación está en el archivo)

    Ventajas e inconvenientes del algoritmo BRO:

    Ventajas:

    1. Supone una idea interesante.
    2. Implementación sencilla.
    3. Desarrollo prometedor.

    Desventajas:

    1. Resultados débiles sobre funciones discretas.

    Adjuntamos al artículo un archivo con las versiones actuales de los códigos de los algoritmos. El autor de este artículo no se responsabiliza de la exactitud absoluta de la descripción de los algoritmos canónicos, muchos de ellos han sido modificados para mejorar las capacidades de búsqueda. Las conclusiones y juicios presentados en los artículos se basan en los resultados de los experimentos realizados.


    Programas usados en el artículo

    #NombreTipoDescripción
    1#C_AO.mqh
    Archivo de inclusión
    Clase padre de algoritmos de optimización basados en la población
    2#C_AO_enum.mqh
    Archivo de inclusión
    Enumeración de los algoritmos de optimización basados en la población
    3TestFunctions.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 en una tabla comparativa
    7
    Testing AOs.mq5
    ScriptBanco de pruebas único para todos los algoritmos de optimización basados en la población
    8
    Simple use of population optimization algorithms.mq5
    Script
    Ejemplo sencillo de utilización de algoritmos de optimización basados en la población sin visualización
    9
    Test_AO_BRO.mq5
    ScriptBanco de pruebas para BRO

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

    Archivos adjuntos |
    BRO.ZIP (423.38 KB)
    Juan Guillermo Marulanda Mesa
    Juan Guillermo Marulanda Mesa | 23 ene 2026 en 15:11
    Se ve muy interesante, lo voy a probar para mirar las soluciones mas óptimas a un par de combinaciones de factores que vengo midiendo. 
    Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
    En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
    Aprendizaje automático en la negociación de tendencias unidireccionales tomando el oro como ejemplo Aprendizaje automático en la negociación de tendencias unidireccionales tomando el oro como ejemplo
    En este artículo analizaremos un enfoque interesante: la negociación solo en la dirección seleccionada (compra o venta). Para ello, utilizaremos técnicas de inferencia causal y aprendizaje automático.
    Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
    En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
    Técnicas avanzadas de gestión y optimización de la memoria en MQL5 Técnicas avanzadas de gestión y optimización de la memoria en MQL5
    Descubra técnicas prácticas para optimizar el uso de la memoria en los sistemas de trading MQL5. Aprenda a crear asesores expertos e indicadores eficientes, estables y de rápido rendimiento. Exploraremos cómo funciona realmente la memoria en MQL5, las trampas comunes que ralentizan sus sistemas o provocan fallos y, lo más importante, cómo solucionarlos.