English Русский Português
preview
Algoritmo del Bisonte: Bison Algorithm (BIA)

Algoritmo del Bisonte: Bison Algorithm (BIA)

MetaTrader 5 — Trading |
23 0
Andrey Dik
Andrey Dik

Contenido

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


Introducción

Con el fin de encontrar un método de optimización más eficaz, este artículo presenta el Algoritmo del Bisonte: Bison Algorithm (BIA), un algoritmo de optimización basada en poblaciones e inspirado en los enjambres, diseñado para resolver problemas de optimización continua con una única función objetivo. Las características clave del BIA son dos principios fundamentales inspirados en el comportamiento del bisonte: la capacidad de moverse de forma dinámica y una estrategia defensiva. El algoritmo fue desarrollado por investigadores checos en 2017 y publicado en la serie Lecture Notes in Computer Science (Springer) en 2019.

Los bisontes son animales increíbles, dotados de una gran resistencia y fuerza. Son capaces de alcanzar una gran velocidad y mantenerla durante media hora. Cuando se ven rodeados por depredadores, suelen formar un círculo, con los individuos más fuertes en el perímetro, mientras que el resto de la manada tiende a desplazarse hacia el interior para ponerse a salvo.

Estos dos patrones de comportamiento se modelaron en el algoritmo del bisonte y se aplicaron como técnica de optimización. El primer aspecto, «velocidad y resistencia», permite a los agentes del algoritmo explorar ampliamente el espacio de búsqueda, descubriendo zonas nuevas y potencialmente óptimas. El segundo aspecto, el «círculo defensivo», simula un mecanismo de búsqueda local y estabilización, en el que los agentes se agrupan en torno a las soluciones más prometedoras, protegiéndolas de un rechazo prematuro y perfeccionando sus posiciones. Ahora veamos cómo funciona esto realmente en el algoritmo.


Implementación del algoritmo

Estás observando una manada de bisontes en la pradera. Tienen que encontrar el mejor pastizal, con hierba abundante y agua limpia. ¡Pero la pradera es enorme! ¿Cómo lo hacen? Dividamos la manada en dos grupos.

La manada principal (el 80 % de los bisontes) está formada por animales cautelosos. Se mantienen juntos y se desplazan lentamente en grupo. «¡La unión hace la fuerza!» es su lema.

Los exploradores (el 20 % de los bisontes) son animales jóvenes y valientes. Corren hacia delante en una sola dirección, explorando nuevos territorios. «¿Qué habrá allí, detrás de la colina?», se preguntan.

Se forman dos grupos: 40 bisontes de la manada principal deambulan por una zona conocida, y 10 corredores se reúnen cerca del bisonte más gordo (¡que sin duda sabe dónde está la zona de pastoreo más rica!) y se preparan para la carrera. Cuando un bisonte de la manada principal se desplaza a una nueva posición, primero recuerda dónde estaba. Da varios pasos (de 0 a 3,5 metros). Después prueba la hierba. Si la nueva hierba es peor, vuelve atrás. «Más vale pájaro en mano que ciento volando». Si el corredor-explorador encuentra un lugar mejor que el del bisonte que ocupa el peor puesto de la manada, entonces intercambian sus puestos. Los corredores cambian ligeramente de dirección cada día (un 10 % hacia la izquierda o hacia la derecha). Esto aumenta las posibilidades de descubrir algo nuevo. Cuando una manada se reúne en torno al mejor bisonte, no se limita a agruparse en torno al centro. El líder (el más gordo) tiene un «peso» de 100, el segundo, de 90, el tercero, de 80... y el décimo, de 10.

Ahora imagínate esto:

  • Pastizal = espacio de búsqueda de soluciones
  • Calidad de la hierba = valor de la función objetivo
  • Posición del bisonte = solución potencial
  • El mejor pastizal = la solución óptima

El siguiente diagrama muestra la fase de inicialización: la división de la población en un grupo del enjambre (80 %) y un grupo de corredores (20 %). El bucle principal de iteración consta de cuatro pasos: determinación del punto objetivo del enjambre (elección entre el mejor corredor y el centro de élite), desplazamiento del grupo del enjambre hacia el centro (cálculo de la media ponderada del centro de élite), desplazamiento de los corredores en la dirección de exploración, comprobación y actualización de los resultados.

algoritmo-bisonte

Figura 1. Flujo de trabajo del algoritmo BIA

Parámetros clave del algoritmo. Esquema de colores: verde: grupo del enjambre; naranja: grupo de corredores; morado: fase de comprobación y actualización; azul: movimiento del enjambre. Las flechas indican el flujo de ejecución y las direcciones de movimiento de los grupos.

Empecemos a escribir un pseudocódigo para el algoritmo BIA.

INICIALIZACIÓN:
1. Crear una población de N bisontes
2. Dividir en grupos:
- Grupo del enjambre (80 %): Distribuir los bisontes aleatoriamente por todo el espacio
- Grupo de corredores (20 %): Situar los bisontes alrededor del mejor bisonte del grupo del enjambre
3. Establecer una dirección de carrera aleatoria para los exploradores

CICLO PRINCIPAL (repetir hasta la convergencia):

SELECCIÓN DEL OBJETIVO DE MOVIMIENTO:
- SI el mejor individuo del grupo de corredores ha encontrado un lugar mejor que el del peor individuo de la manada:
Objetivo = posición del mejor individuo del grupo de corredores
- EN CASO CONTRARIO:
Objetivo = centro ponderado de los 10 mejores individuos de la manada

MOVIMIENTO DEL GRUPO DEL ENJAMBRE:
- Para cada bisonte de la manada:
1. Guardar la posición actual
2. Calcular la dirección hacia el objetivo
3. Dar un paso hacia el objetivo (longitud aleatoria entre 0 y 3,5)
4. Comprobar los límites del área de búsqueda

MOVIMIENTO DEL GRUPO EN CARRERA:
- Cambiar ligeramente la dirección de carrera (×0.9-1.1)
- Para cada individuo:
1. Desplazarse en la dirección de carrera
2. Comprobar los límites del área de búsqueda

EVALUACIÓN Y ACTUALIZACIÓN:
1. Comprobar el grupo del enjambre:
- SI la nueva posición es peor que la anterior:
Volver a la posición anterior

2. Intercambio entre grupos:
- SI el mejor individuo de un grupo de corredores es mejor que el peor de la manada:
Sustituir al peor de la manada por el mejor individuo

3. Ordenación:
- Ordenar el grupo del enjambre por calidad

4. Actualización:
- Guardar la mejor solución encontrada
- Guardar la peor solución

FIN DEL CICLO

CÁLCULO DEL CENTRO PONDERADO:
- Seleccionar las 10 mejores soluciones
- Asignar pesos: la 1.ª recibe un peso de 100, la 2.ª, 90, ..., la 10.ª, 10
- Centro = suma(posición × peso) / suma(pesos)

Ahora que entendemos mejor cómo funciona todo, podemos empezar a escribir el código del algoritmo. La clase implementa el algoritmo del bisonte (BIA) y especifica que hereda de la clase base C_AO.

Parámetros del algoritmo:
  • popSize: define el número total de «individuos» o «agentes» de la población que participan en el algoritmo.
  • swarmGroupRate: especifica la proporción (en porcentaje) del número total de individuos que formarán el «grupo del enjambre».
  • eliteGroupSize: define el número de los mejores individuos que forman el «grupo de élite» para calcular el centro.
  • overstep: establece el paso máximo que puede dar un grupo del enjambre al desplazarse.

El constructor establece los valores por defecto para todos los parámetros. El método SetParams permite actualizar los valores de los parámetros del algoritmo utilizando datos procedentes de una fuente externa (representada por la matriz «params»).

Funcionalidad:

  • Init: método para inicializar el algoritmo utilizando rangos de valores para los parámetros.
  • Moving: método encargado del desplazamiento o la actualización del estado de los individuos de una población.
  • Revision: método utilizado para revisar y ajustar el comportamiento de individuos o parámetros.
  • ComputeCenter: método privado para calcular el centro de un grupo de élite.
  • CheckIfRunnerBetter: método privado que comprueba si algún corredor es mejor que los demás.
Variables internas:
  • swarmGroupSize: se calcula a partir de popSize y swarmGroupRate, y determina el número real de individuos del grupo del enjambre.
  • runningGroupSize — tamaño del grupo de corredores (un subgrupo de la población total).
  • runDirection — matriz que almacena la dirección de movimiento de cada individuo.
  • center — matriz que almacena las coordenadas del centro del grupo de élite.

En general, la clase C_AO_BisonAlgorithm es un bloque que implementa una estrategia de búsqueda basada en los conceptos de población, enjambre y grupos de élite, y cuenta con parámetros configurables.

class C_AO_BisonAlgorithm : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_BisonAlgorithm () { }
  C_AO_BisonAlgorithm ()
  {
    ao_name = "BIA";
    ao_desc = "Bison Algorithm";
    ao_link = "https://www.mql5.com/en/articles/19444";

    popSize          = 50;    // population size
    swarmGroupRate   = 0.8;   // share of swarm group from population (80%)
    eliteGroupSize   = 10;    // size of the elite group for calculating the center (s parameter)
    overstep         = 3.5;   // maximum step of the swarm group

    ArrayResize (params, 4);

    params [0].name = "popSize";        params [0].val = popSize;
    params [1].name = "swarmGroupRate"; params [1].val = swarmGroupRate;
    params [2].name = "eliteGroupSize"; params [2].val = eliteGroupSize;
    params [3].name = "overstep";       params [3].val = overstep;
  }

  void SetParams ()
  {
    popSize        = (int)params [0].val;
    swarmGroupRate = params      [1].val;
    eliteGroupSize = (int)params [2].val;
    overstep       = params      [3].val;
  }

  bool Init (const double &rangeMinP  [],
             const double &rangeMaxP  [],
             const double &rangeStepP [],
             const int     epochsP = 0);

  void Moving   ();
  void Revision ();
  
  private:
  void ComputeCenter ();
  bool CheckIfRunnerBetter ();

  //------------------------------------------------------------------
  public:
  double swarmGroupRate;     // share of swarm group
  int    eliteGroupSize;     // size of the elite group (s parameter)
  double overstep;           // maximum step of the swarm group

  private: //---------------------------------------------------------
  int    swarmGroupSize;     // swarm group size
  int    runningGroupSize;   // running group size
  double runDirection [];    // running direction
  double center [];          // elite group center
};
//————————————————————————————————————————————————————————————————————

El método `Init` de la clase `C_AO_BisonAlgorithm` se encarga de la configuración inicial del algoritmo antes de que comience a ejecutarse. En primer lugar, el método intenta realizar la parte común y estandarizada de la inicialización, utilizando los rangos de valores de los parámetros y el número de épocas que recibe. Si esta parte estándar falla, se interrumpe todo el proceso de inicialización. A continuación, a partir del tamaño total de la población y del coeficiente dado, se calcula el número de individuos que formarán parte del «grupo del enjambre». Los individuos restantes forman un «grupo de corredores».

Para garantizar el correcto funcionamiento del algoritmo, se comprueban los tamaños de estos grupos. Se garantiza que el grupo del enjambre siempre contará con al menos un miembro y que no abarcará toda la población (dejando espacio para el grupo de corredores). Del mismo modo, se comprueba que el grupo de élite tenga al menos un individuo y que su tamaño no supere el del grupo del enjambre, del que es un subconjunto.

Se asigna memoria para las matrices auxiliares. Tanto la matriz utilizada para almacenar la dirección de movimiento de los individuos (para cada dimensión del espacio de búsqueda) como la matriz utilizada para almacenar las coordenadas del centro del grupo de élite se redimensionan para que se ajusten al número de dimensiones definidas en el sistema. Si todos los pasos anteriores se han completado correctamente, el método devuelve «true» y el algoritmo está listo para funcionar.

//————————————————————————————————————————————————————————————————————
//--- Initialization
bool C_AO_BisonAlgorithm::Init (const double &rangeMinP  [],
                                const double &rangeMaxP  [],
                                const double &rangeStepP [],
                                const int     epochsP = 0)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  // Calculate the sizes of groups
  swarmGroupSize   = (int)MathFloor (popSize * swarmGroupRate);
  runningGroupSize = popSize - swarmGroupSize;
  
  // Adjust group sizes
  if (swarmGroupSize < 1) swarmGroupSize = 1;
  if (swarmGroupSize >= popSize) swarmGroupSize = popSize - 1;
  if (eliteGroupSize < 1) eliteGroupSize = 1;
  if (eliteGroupSize > swarmGroupSize) eliteGroupSize = swarmGroupSize;
  
  // Initialize auxiliary arrays
  ArrayResize (runDirection, coords);
  ArrayResize (center, coords);

  return true;
}
//————————————————————————————————————————————————————————————————————

El método «Moving» de la clase «C_AO_BisonAlgorithm» constituye el núcleo del algoritmo y se encarga de una iteración o paso de la evolución de la población. Consta de dos fases principales: la inicialización de la población (si el algoritmo se ejecuta por primera vez) y los pasos posteriores de actualización de las posiciones de los individuos.

Fase 1: Inicialización (primera ejecución): Si el algoritmo se ejecuta por primera vez (el indicador «revision» está establecido en «false»), se lleva a cabo la siguiente secuencia de acciones:

  • Generación del grupo del enjambre. Los individuos incluidos en el grupo del enjambre se inicializan con valores aleatorios dentro de los rangos especificados para cada parámetro. Una vez generados, estos valores pueden ajustarse de nuevo para respetar el paso de discretización de los parámetros.
  • Búsqueda del mejor bisonte. Entre todos los individuos de un grupo del enjambre, hay uno que presenta la mejor aptitud (el valor más alto de la función objetivo).
  • Generación del grupo de corredores. Los individuos restantes de la población (el grupo de corredores) se inicializan en torno al mejor bisonte encontrado. Sus posiciones se generan aleatoriamente en un entorno de la mejor solución y, a continuación, se ajustan también de acuerdo con los rangos y los pasos de discretización de los parámetros.
  • Inicialización del vector de dirección del movimiento. Para cada dimensión del espacio de búsqueda, se genera un vector de dirección del movimiento aleatorio. La magnitud de este vector depende del rango global de valores de los parámetros, y su signo (positivo o negativo) también se elige al azar.
  • Establecimiento del indicador de inicialización. El indicador «revision» se establece en «true», la inicialización ha finalizado y, a partir de ahora, solo se llevarán a cabo pasos de actualización.

    Fase 2: Ciclo de iteración principal (pasos siguientes): Una vez realizada la inicialización, el método lleva a cabo los siguientes pasos en cada iteración:

    Determinación del punto objetivo del enjambre. El algoritmo comprueba si alguna solución del grupo de corredores es mejor que el «punto objetivo del enjambre» actual.
    • Si la solución del grupo de corredores resulta ser mejor, el punto objetivo del enjambre (centroide) se hace coincidir con esa solución.
    • De lo contrario, se calcula un nuevo centroide, que representa el centro ponderado de las mejores soluciones de la población.
    Movimiento del grupo del enjambre. Cada individuo del grupo del enjambre se mueve.
    • En primer lugar, se guardan las posiciones actuales y los valores de aptitud del individuo.
    • A continuación, se calcula la nueva posición desplazándose desde la posición actual hacia el punto objetivo del enjambre. La magnitud de este movimiento viene determinada por un factor aleatorio que regula cuánto se sobrepasa el punto objetivo.
    • A continuación, la nueva posición resultante se ajusta para mantenerse dentro de los rangos permitidos y tener en cuenta el paso de discretización del parámetro.

    Corrección del vector de dirección del movimiento. El vector de dirección que utiliza el grupo de corredores varía ligeramente de forma aleatoria. Esto aporta cierta variedad a los movimientos de los corredores.

    Movimiento del grupo de corredores. Cada individuo del grupo de corredores se desplaza utilizando el vector de dirección ajustado.

    • La nueva posición se calcula sumando el vector de dirección a la posición actual.
    • A continuación, esta nueva posición se ajusta para mantenerse dentro de los rangos aceptables y tener en cuenta el paso de discretización del parámetro.

      Así pues, el método «Moving» modela el comportamiento de un grupo de individuos: una parte del grupo (el enjambre) se desplaza hacia un objetivo común, respondiendo a las mejores soluciones, mientras que la otra parte (el grupo de corredores) sigue el vector de dirección ajustado, que también puede cambiar.

      /————————————————————————————————————————————————————————————————————
      //--- Main step of the algorithm
      void C_AO_BisonAlgorithm::Moving ()
      {
        // Starting initialization of the population
        if (!revision)
        {
          // Generate a swarm group randomly
          for (int i = 0; i < swarmGroupSize; 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]);
            }
          }
          
          // Find the best solution among the swarm group to initialize the runners
          int bestIdx = 0;
          double bestFit = a [0].f;
          for (int i = 1; i < swarmGroupSize; i++)
          {
            if (a [i].f > bestFit)
            {
              bestFit = a [i].f;
              bestIdx = i;
            }
          }
          
          // Generate a running group around the best bison
          double neighbourhood = (rangeMax [0] - rangeMin [0]) / 15.0;
          for (int i = swarmGroupSize; i < popSize; i++)
          {
            for (int j = 0; j < coords; j++)
            {
              a [i].c [j] = a [bestIdx].c [j] + u.RNDfromCI (-neighbourhood, neighbourhood);
              a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
            }
          }
          
          // Generate run direction = random((ub-lb)/45, (ub-lb)/15)
          for (int j = 0; j < coords; j++)
          {
            double range = rangeMax [j] - rangeMin [j];
            runDirection [j] = u.RNDfromCI (range / 45.0, range / 15.0);
            if (u.RNDprobab () < 0.5) runDirection [j] = -runDirection [j];
          }
          
          revision = true;
          return;
        }
      
        //------------------------------------------------------------------
        // Main iteration loop
        
        // 1. Define swarming target
        if (CheckIfRunnerBetter ())
        {
          // If runner is better than swarmer, center = runner
          // center is already set in CheckIfRunnerBetter
        }
        else
        {
          // Otherwise, calculate the center of the strongest solutions
          ComputeCenter ();
        }
        
        // 2. Swarm group movement
        for (int i = 0; i < swarmGroupSize; i++)
        {
          // Save old positions in cP and fitness in fP
          ArrayCopy (a [i].cP, a [i].c, 0, 0, coords);
          a [i].fP = a [i].f;
          
          // Calculate a new candidate for the position
          for (int c = 0; c < coords; c++)
          {
            double direction = center [c] - a [i].c [c];
            a [i].c [c] = a [i].c [c] + direction * u.RNDfromCI (0.0, overstep);
            a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
        
        // 3. Correction of the vector 'run direction'
        for (int j = 0; j < coords; j++)
        {
          runDirection [j] = runDirection [j] * u.RNDfromCI (0.9, 1.1);
        }
        
        // 4. Running group movement
        for (int i = swarmGroupSize; i < popSize; i++)
        {
          for (int c = 0; c < coords; c++)
          {
            a [i].c [c] = a [i].c [c] + runDirection [c];
            a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
          }
        }
      }
      //————————————————————————————————————————————————————————————————————
      
      

      El método «Revision» de la clase C_AO_BisonAlgorithm está destinado a actualizar el estado de la población tras completar un paso de movimiento y evaluar la aptitud de los individuos, así como a resumir los resultados de la iteración.

      Corrección del grupo del enjambre. Para cada individuo del grupo del enjambre, se comprueba si su nueva posición (calculada en el paso anterior) es mejor que su posición anterior (guardada). Si la nueva posición es peor, el individuo vuelve a su posición anterior, que era mejor. De este modo, los individuos del grupo del enjambre no empeoran sus resultados.

      Comparación entre el grupo de corredores y el grupo del enjambre:

      • El mejor individuo del grupo de corredores (con la mayor aptitud) y el peor individuo del grupo del enjambre (con la menor aptitud).
      • Si el mejor individuo del grupo de corredores resulta ser mejor que el peor individuo del grupo del enjambre, el peor individuo del grupo del enjambre se sustituye por el mejor individuo del grupo de corredores. Esto permite que las mejores soluciones del grupo de corredores pasen a formar parte del grupo del enjambre.
      Ordenación del grupo del enjambre:
      • Se crea una copia temporal de todos los individuos del grupo del enjambre.
      • Esta copia temporal se ordena por aptitud en orden descendente (de mejor a peor).
      • Tras la ordenación, los individuos de la copia temporal ordenada se vuelven a copiar en la población general. Esto garantiza que el grupo del enjambre contenga siempre las mejores soluciones encontradas hasta el momento.
      Actualización de las soluciones globales:
      • Mejor solución global. La aptitud del mejor individuo del grupo del enjambre (que ahora es la mejor solución tras la ordenación) se compara con la de la mejor solución global actual. Si el mejor individuo del grupo del enjambre es mejor, se actualiza la mejor solución global.
      • Peor solución global. A continuación, se examinan todos los individuos de la población (tanto los del grupo del enjambre como los del grupo de corredores) para encontrar al de menor aptitud. Si se encuentra un individuo con un valor de aptitud inferior al de la peor solución global actual, se actualiza dicha solución.

        Por lo tanto, el método Revision constituye la fase de procesamiento de resultados que selecciona las mejores soluciones, mantiene el orden y actualiza el grupo del enjambre, y realiza un seguimiento del progreso general del algoritmo, almacenando las mejores y peores soluciones encontradas.

        //————————————————————————————————————————————————————————————————————
        //--- Update and check results
        void C_AO_BisonAlgorithm::Revision ()
        {
          // 1. For a swarm group: update positions only if the new position is better
          for (int i = 0; i < swarmGroupSize; i++)
          {
            // If the new position is worse than the old one, return the old one
            if (a [i].f < a [i].fP)
            {
              ArrayCopy (a [i].c, a [i].cP, 0, 0, coords);
              a [i].f = a [i].fP;
            }
          }
          
          // 2. Let's check if runner has found a better solution than swarmer
          double bestRunnerFitness = -DBL_MAX;
          int bestRunnerIdx = -1;
          double worstSwarmerFitness = DBL_MAX;
          int worstSwarmerIdx = -1;
          
          for (int i = swarmGroupSize; i < popSize; i++)
          {
            if (a[i].f > bestRunnerFitness)
            {
              bestRunnerFitness = a[i].f;
              bestRunnerIdx = i;
            }
          }
          
          for (int i = 0; i < swarmGroupSize; i++)
          {
            if (a[i].f < worstSwarmerFitness)
            {
              worstSwarmerFitness = a[i].f;
              worstSwarmerIdx = i;
            }
          }
          
          // If the runner is better than the worst swarmer, copy the runner to the swarming group
          if (bestRunnerIdx >= 0 && worstSwarmerIdx >= 0 && bestRunnerFitness > worstSwarmerFitness)
          {
            ArrayCopy (a[worstSwarmerIdx].c, a[bestRunnerIdx].c, 0, 0, coords);
            a[worstSwarmerIdx].f = a[bestRunnerIdx].f;
          }
          
          // 3. Sort the swarm group
          // Create a temporary array to sort only the swarm group
          S_AO_Agent swarmTemp [];
          ArrayResize (swarmTemp, swarmGroupSize);
          
          // Copy the swarm group
          for (int i = 0; i < swarmGroupSize; i++)
          {
            swarmTemp[i].Init(coords);
            ArrayCopy (swarmTemp[i].c, a[i].c, 0, 0, coords);
            swarmTemp[i].f = a[i].f;
            ArrayCopy (swarmTemp[i].cP, a[i].cP, 0, 0, coords);
            swarmTemp[i].fP = a[i].fP;
          }
          
          // Sort
          S_AO_Agent tempSorted [];
          ArrayResize (tempSorted, swarmGroupSize);
          u.Sorting (swarmTemp, tempSorted, swarmGroupSize);
          
          // Copy the swarm group sorted back
          for (int i = 0; i < swarmGroupSize; i++)
          {
            ArrayCopy (a[i].c, swarmTemp[i].c, 0, 0, coords);
            a[i].f = swarmTemp[i].f;
            ArrayCopy (a[i].cP, swarmTemp[i].cP, 0, 0, coords);
            a[i].fP = swarmTemp[i].fP;
          }
          
          // 4. Update the global best and worst case solution
          if (a[0].f > fB)
          {
            fB = a[0].f;
            ArrayCopy (cB, a[0].c, 0, 0, WHOLE_ARRAY);
          }
          
          // Find the worst in the entire population
          for (int i = 0; i < popSize; i++)
          {
            if (a[i].f < fW)
            {
              fW = a[i].f;
              ArrayCopy (cW, a[i].c, 0, 0, WHOLE_ARRAY);
            }
          }
        }
        //————————————————————————————————————————————————————————————————————
        

        El método CheckIfRunnerBetter se utiliza para determinar si el grupo de corredores contiene una solución más prometedora que las presentes en el grupo del enjambre. Si se encuentra una solución de este tipo, se convierte en un nuevo punto objetivo del enjambre.

        Búsqueda del mejor «corredor». El método examina primero a todos los individuos que pertenecen al «grupo de corredores» (que no forman parte del enjambre). Identifica al individuo con la mejor aptitud (el valor más alto de la función objetivo) entre todos los corredores y almacena su índice y su valor de aptitud.

        Búsqueda del peor individuo del «enjambre». A continuación, el método analiza a los individuos que componen el «grupo del enjambre». Encuentra al individuo con la aptitud más baja (el valor más bajo de la función objetivo) del enjambre.

        Comparación y determinación del punto objetivo. A continuación, se lleva a cabo una comparación:

        • Si el mejor corredor tiene una aptitud superior a la del peor individuo del enjambre, esto significa que el grupo de corredores ha encontrado, efectivamente, una solución mejor.
        • En este caso, la posición (coordenadas) del mejor «corredor» se asigna a una variable que representa el «centro» o el «punto objetivo del enjambre». El método devuelve un valor que indica que el punto objetivo del enjambre se ha actualizado.

        Devolución del resultado:

        • Si el mejor «corredor» es mejor que el peor individuo del «enjambre», el método lo indica devolviendo un valor afirmativo.
        • De lo contrario (si ningún «corredor» ha superado al peor individuo del «enjambre»), el método lo indica devolviendo un valor negativo, y el centro del enjambre permanece igual.

        Este método permite, por tanto, cambios dinámicos en la dirección de la búsqueda: si surgen soluciones mejores en el grupo de corredores «exploratorio», estas sirven de guía para el grupo del enjambre «social».

        //————————————————————————————————————————————————————————————————————
        //--- Check whether a runner is better than a swarmer
        bool C_AO_BisonAlgorithm::CheckIfRunnerBetter ()
        {
          // Find the best runner
          double bestRunnerFitness = -DBL_MAX;
          int bestRunnerIdx = -1;
          
          for (int i = swarmGroupSize; i < popSize; i++)
          {
            if (a[i].f > bestRunnerFitness)
            {
              bestRunnerFitness = a[i].f;
              bestRunnerIdx = i;
            }
          }
          
          // Find the worst swarmer 
          double worstSwarmerFitness = DBL_MAX;
          
          for (int i = 0; i < swarmGroupSize; i++)
          {
            if (a[i].f < worstSwarmerFitness)
            {
              worstSwarmerFitness = a[i].f;
            }
          }
          
          // If the runner is better than the swarmer, set it as the center
          if (bestRunnerIdx >= 0 && bestRunnerFitness > worstSwarmerFitness)
          {
            ArrayCopy (center, a[bestRunnerIdx].c, 0, 0, coords);
            return true;
          }
          
          return false;
        }
        //————————————————————————————————————————————————————————————————————
        

        El método ComputeCenter calcula una nueva posición «objetivo» (centro) para el grupo del enjambre basándose en las mejores soluciones encontradas. Así es como funciona:

        • Restablecimiento de la posición objetivo. En primer lugar, todos los componentes de la futura posición objetivo (centro) se restablecen a cero. Esto los prepara para acumular nuevos valores.
        • Determinación del número de mejores soluciones. El método determina exactamente cuántas de las mejores soluciones se utilizarán para calcular el centro. Toma el valor mínimo de dos parámetros:

          • eliteGroupSize — número máximo de soluciones «de élite» que se pueden tener en cuenta;
          • swarmGroupSize — tamaño del grupo del enjambre actual;
          • Esto garantiza que utilicemos todas las soluciones de élite proporcionadas o, si hay menos soluciones de élite, tantas como haya en el grupo del enjambre.
        • Asignación de ponderaciones. Se crea un conjunto de ponderaciones para cada una de las mejores soluciones seleccionadas. Estas ponderaciones aumentan de forma lineal: la primera solución recibe una ponderación menor, la segunda, mayor, y así sucesivamente, hasta la última. Concretamente, las ponderaciones se establecen en 10, 20, 30... hasta s * 10, donde s es el número de mejores soluciones que se tienen en cuenta. Estas ponderaciones indican la importancia que tiene cada solución a la hora de calcular el centro.
        • Acumulación de la suma total de las ponderaciones. Se suman todas las ponderaciones calculadas. Esta cantidad total será necesaria para la normalización.
        • Suma ponderada de las posiciones. Ahora, para cada dimensión (coordenada) de la posición:
          • el valor de la coordenada correspondiente se toma de cada una de las s mejores soluciones;
          • este valor de la coordenada se multiplica por la ponderación de la mejor solución correspondiente. Es importante señalar que las ponderaciones se aplican en orden inverso: la mejor solución (la primera de la lista ordenada) recibe la mayor ponderación, la segunda recibe la siguiente mayor, y así sucesivamente.
          • los resultados de estas multiplicaciones para las s mejores soluciones se suman para una coordenada determinada.
        • Normalización. La suma resultante de las coordenadas ponderadas para cada dimensión se divide entre la suma total de todas las ponderaciones. Esto hace que el cálculo sea una «media ponderada», lo que garantiza que la posición final del centro se sitúe en una zona «intermedia» de las mejores soluciones encontradas, de modo que las soluciones de mayor calidad tengan una mayor influencia.

          Como resultado, el método ComputeCenter crea una nueva posición «objetivo», que es una especie de «media» de las mejores soluciones, pero con preferencia por aquellas de mayor calidad, lo que ayuda a orientar la búsqueda posterior del enjambre.

          //————————————————————————————————————————————————————————————————————
          //--- Calculate the center of the elite group
          void C_AO_BisonAlgorithm::ComputeCenter ()
          {
            // Reset the center
            for (int j = 0; j < coords; j++)
            {
              center [j] = 0.0;
            }
            
            // Calculate the weighted center from the s best solutions
            double weights [];
            int s = MathMin(eliteGroupSize, swarmGroupSize);
            ArrayResize (weights, s);
            double totalWeight = 0.0;
            
            // weight = (10, 20, 30, ..., 10*s)
            for (int i = 0; i < s; i++)
            {
              weights[i] = (i + 1) * 10.0;
              totalWeight += weights[i];
            }
            
            // center = sum(weight[i] * position[i]) / sum(weights)
            for (int j = 0; j < coords; j++)
            {
              for (int i = 0; i < s; i++)
              {
                center[j] += (weights[s - 1 - i] * a[i].c[j]) / totalWeight;
              }
            }
          }
          //————————————————————————————————————————————————————————————————————
          


          Resultados de las pruebas

          Como podemos ver, los resultados son de nivel medio; en la función discreta Megacity son ligeramente inferiores; el algoritmo tiene más dificultades para resolver problemas de optimización discreta.

          Bison|algoritmo del bisonte (BIA)|50,0|0,8|10|3,5|
          =============================
          5 Hilly; Ejecuciones de la función: 10 000; resultado: 0,761847128451914
          25 Hilly; Ejecuciones de la función: 10 000; resultado: 0,4002698460741261
          500 Hilly; Ejecuciones de la función: 10 000; resultado: 0,25201531651422443
          =============================
          5 Forest; Ejecuciones de la función: 10 000; resultado: 0,7620992740937584
          25 Forest; Ejecuciones de la función: 10 000; resultado: 0,45225292646780135
          500 Forest; Ejecuciones de la función: 10 000; resultado: 0,19295587559803248
          =============================
          5 Megacity; Ejecuciones de la función: 10 000; resultado: 0,48769230769230765
          25 Megacity; Ejecuciones de la función: 10 000; resultado: 0,19876923076923075
          500 Megacity; Ejecuciones de la función: 10 000; resultado: 0,10058461538461608
          =============================
          Puntuación total: 3,60849 (40,09 %)


          La visualización del rendimiento del algoritmo BIA muestra una dispersión bastante amplia de los resultados, no solo en el caso de las funciones de baja dimensión (líneas verdes), sino también en el de las funciones de dimensión media (líneas azules).

          Hilly

          BIA en la función de prueba Hilly

          Forest

          BIA en la función de prueba Forest

          Megacity

          BIA en la función de prueba Megacity

          El algoritmo BIA se presenta, a título informativo, en la tabla de clasificación de los métodos de optimización basada en poblaciones.

          #AODescripciónHillyHilly
          Final
          ForestForest
          Final
          Megacity (discreta)Megacity
          Final
          Resultado
          final
          % 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)
          1DOAdingomdingo_optimization_algorithm_M0,479680,453670,463691,397040,941450,879090,914542,735080,786150,860610,848052,494816,62773,63
          1ANSacross neighbourhood search0,949480,847760,438572,235811,000000,923340,399882,323230,709230,634770,230911,574916,13468,15
          2CLAcode lock algorithm (joo)0,953450,871070,375902,200420,989420,917090,316422,222940,796920,693850,193031,683806,10767,86
          3AMOmanimal migration ptimization M0,903580,843170,462842,209590,990010,924360,465982,380340,567690,591320,237731,396755,98766,52
          4(P+O)ES(P+O) evolution strategies0,922560,881010,400212,203790,977500,874900,319452,171850,673850,629850,186341,490035,86665,17
          5CTAcomet tail algorithm (joo)0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
          6TETAtime evolution travel algorithm (joo)0,913620,823490,319902,057010,970960,895320,293242,159520,734620,685690,160211,580525,79764,41
          7SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
          8BOAmbilliards optimization algorithm M0,957570,825990,252352,035901,000000,900360,305022,205380,735380,525230,095631,356255,59862,19
          9AAmarchery algorithm M0,917440,708760,421602,047800,925270,758020,353282,036570,673850,552000,237381,463235,54861,64
          10ESGevolution of social groups (joo)0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
          11SIAsimulated isotropic annealing (joo)0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
          12EOmextremal_optimization_M0,761660,772420,317471,851550,999990,767510,235272,002770,747690,539690,142491,429875,28458,71
          13BBObiogeography based optimization0,949120,694560,350311,993990,938200,673650,256821,868670,746150,482770,173691,402615,26558,50
          14ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
          15DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
          16BHAmblack hole algorithm M0,752360,766750,345831,864930,935930,801520,271772,009230,650770,516460,154721,321955,19657,73
          17ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
          18RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
          19AOSmatomic orbital search M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
          20TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
          21BSAbacktracking_search_algorithm0,973090,545340,290981,809410,999990,585430,217471,802890,847690,369530,129781,347004,95955,10
          22DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
          23SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
          24CROchemical reaction optimization0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
          25BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
          26DOAdream_optimization_algorithm0,855560,700850,372801,929210,734210,489050,241471,464730,772310,473540,185611,431464,82553,62
          27BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121.120124,80953,44
          28DEAdolphin_echolocation_algorithm0,759950,675720,341711,777380,895820,642230,239411,777460,615380,440310,151151,206844,76252,91
          29HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
          30SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
          31BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
          32ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
          33(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
          34FBAfractal-based Algorithm0,790000,651340,289651,730990,871580,568230,188771,628580,610770,460620,123981,195374,55550,61
          35TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
          36BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
          37WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
          38AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
          39AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
          40CAmcamel algorithm M0,786840,560420,351331,698590,827720,560410,243361,631490,648460,330920,134181,113564,44449,37
          41ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
          42CMAEScovariance_matrix_adaptation_evolution_strategy0,762580,720890,000001,483470,820560,796160,000001,616720,758460,490770,000001,249234,34948,33
          43DA_duelistduelist_algorithm0,927820,537780,277921,743520,869570,475360,181931,526860,621530,335690,117151,074374,34548,28
          44BFO-GAbacterial foraging optimization - ga0,891500,551110,315291,757900,969820,396120,063051,428990,726670,275000,035251,036924,22446,93
          BIAbison_algorithm0,761850,400270,252011,414130,762100,452250,192961,407310,487690,198770,100580,787043,60840,09
          RWrandom walk0,487540,321590,257811,066940,375540,219440,158770,753750,279690,149170,098470,527342,34826/09


          Resumen

          El algoritmo del bisonte (BIA) introduce un concepto interesante de separación entre exploración y explotación, pero en su implementación actual es inferior a las metaheurísticas modernas. El principal valor reside en la idea de una clara separación de funciones, que puede integrarse con éxito en enfoques híbridos.

          Los resultados de las pruebas muestran que el algoritmo tiene un rendimiento medio, no alcanza el nivel de las mejores metaheurísticas y presenta dificultades para resolver problemas de optimización discreta. Para entrar en la tabla de clasificación, es necesario introducir mejoras significativas en los mecanismos de movimiento y en la adaptación de los parámetros. El algoritmo puede utilizarse en problemas en los que la sencillez de implementación y la interpretabilidad son importantes, pero no se requiere la máxima precisión.

          Pestaña

          Figura 2. Comparación de algoritmos, codificada por colores, en las pruebas correspondientes

          gráfico

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

          Ventajas e inconvenientes del BIA:

          Ventajas:

          1. Implementación sencilla.
          2. Rápido.

          Inconvenientes:

          1. Alta variabilidad en funciones de prueba de baja y media dimensión.

          El artículo va acompañado de un archivo con las versiones actuales del código 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. Se han introducido cambios en muchos de ellos para mejorar su capacidad de búsqueda. Las conclusiones y valoraciones que se presentan en los artículos se basan en los resultados de los experimentos.


          Programas utilizados en el artículo

          #NombreTipoDescripción
          1#C_AO.mqh
          Archivo de inclusión
          Clase base de los algoritmos de optimización basada en poblaciones
          2#C_AO_enum.mqh
          Archivo de inclusión
          Enumeración de los algoritmos de optimización basada en poblaciones
          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 de la tabla comparativa
          7
          Testing AOs.mq5
          ScriptEl banco de pruebas unificado para todos los algoritmos de optimización basada en poblaciones
          8
          Simple use of population optimization algorithms.mq5
          Script
          Un ejemplo sencillo del uso de algoritmos de optimización basada en poblaciones sin visualización
          9
          Test_AO_BIA.mq5
          ScriptBanco de pruebas de BIA

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

          Archivos adjuntos |
          BIA.zip (284.26 KB)
          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.
          Cómo crear y optimizar un sistema de trading basado en ciclos (Detrended Price Oscillator - DPO) Cómo crear y optimizar un sistema de trading basado en ciclos (Detrended Price Oscillator - DPO)
          En este artículo se explica cómo diseñar y optimizar un sistema de trading usando el Detrended Price Oscillator (DPO) en MQL5. En él se describe la lógica fundamental del indicador, mostrando cómo identifica los ciclos a corto plazo al filtrar las tendencias a largo plazo. A través de una serie de ejemplos paso a paso y estrategias sencillas, los lectores aprenderán a programar el indicador, definir señales de entrada y salida y realizar pruebas con datos históricos. Por último, el artículo presenta métodos prácticos de optimización para mejorar el rendimiento y adaptar el sistema a las condiciones cambiantes del mercado.
          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.
          Explorador de la biblioteca estándar de MQL5 (Parte 1): Introducción con CTrade, CiMA y CiATR Explorador de la biblioteca estándar de MQL5 (Parte 1): Introducción con CTrade, CiMA y CiATR
          La biblioteca estándar de MQL5 desempeña un papel fundamental en el desarrollo de algoritmos de trading para MetaTrader 5. En esta serie de artículos, nuestro objetivo es dominar su uso para simplificar la creación de herramientas de trading eficientes para MetaTrader 5. Entre estas herramientas se incluyen asesores expertos personalizados, indicadores y otras utilidades. Hoy vamos a desarrollar un asesor experto de seguimiento de tendencias utilizando las clases CTrade, CiMA y CiATR. Este es un tema especialmente importante para todo el mundo, tanto si usted es principiante como si es un desarrollador con experiencia. Participe en esta discusión para descubrir más información.