Русский
preview
Bison Algorithm (BIA)

Bison Algorithm (BIA)

MetaTrader 5Trading |
583 0
Andrey Dik
Andrey Dik

Contents

  1. Introduction
  2. Implementation of the algorithm
  3. Test results


Introduction

In search of a better optimization method, this article introduces the Bison Algorithm (BIA), a swarm-inspired population-based optimization algorithm designed for solving continuous problems with a single objective function. The key features of BIA are two fundamental principles borrowed from the behavior of bison: the ability to move dynamically and a defensive strategy. The algorithm was developed by Czech researchers in 2017 and published in the Lecture Notes in Computer Science (Springer) in 2019. 

Bison are amazing animals with great endurance and strength. They are capable of developing high speed and maintaining it for half an hour. When surrounded by predators, they tend to form a circle, with the strongest individuals on the perimeter and the rest of the herd tending to move inside to take a safe position.

These two behavioral patterns were modeled in the Bison Algorithm and applied as an optimization technique. The first aspect, "speed and endurance," allows the algorithm's agents to explore the search space broadly, exploring new and potentially optimal areas. The second aspect, the "defensive circle," models a local search and stabilization mechanism, where agents gather around the most promising solutions, protecting them from premature rejection and refining their positions. Now let's see how this actually works in the algorithm.


Implementation of the algorithm

You are watching a herd of bison on the prairie. They need to find the best pasture with lush grass and clean water. But the prairie is huge! How do they do it? Let's divide the herd into two groups.

Main herd (80% of bison) consists of cautious animals. They stick together and move slowly in a group. "Security in numbers!" is their motto.

Explorers (20% of bison) are brave young animals. They run forward in one direction, exploring new territories. "What's there, behind the hill?" they think.

Two groups are formed: 40 bison from the main herd wander around a known area, and 10 runners gather near the fattest bison (it clearly knows where the richer grazing area is!) and prepare for the race. When a bison from the main herd moves to a new position, it first remembers where it stood. It takes several steps (from 0 to 3.5 meters). Then it tries the grass. If the new grass is worse, then it goes back. "A bird in the hand is worth two in the bush". If the runner-explorer finds a better place than the worst bison in the herd, then they swap places. Runners change direction slightly every day (10% to the left or right). This increases the chances of finding something new. When a herd gathers around the best bison, they do not simply cluster around the center. The leader (the fattest) has a "weight" of 100, the second - 90, the third - 80... the tenth - 10. 

Now imagine that:

  • Pasture = solution search space
  • Grass quality = objective function value
  • Bison position = potential solution
  • The best pasture = the optimal solution

The diagram below shows the initialization phase - the division of the population into a swarm group (80%) and a running group (20%). The main iteration loop consists of four steps: determining the swarm target (choosing between the best runner or the elite center), movement of the swarm group towards the center (weighted averaging of the elite center), movement of runners in the direction of research, checking and updating results.

bison-algorithm

Figure 1. BIA algorithm workflow

Algorithm key parameters. Color scheme: green - swarm group, orange - running group, purple - checking and updating phase, blue - swarm movement. The arrows show the flow of execution and the directions of movement of the groups.

Let's start writing a pseudocode for the BIA algorithm.

INITIALIZATION:
1. Create a population of N bison
2. Divide into groups:
   - Swarm group (80%): Place randomly throughout the space
   - Running group (20%): Place around the best bison of the swarm group
3. Set a random running direction for scouts

MAIN CYCLE (repeat until convergence):
  
  SELECTING THE MOVEMENT GOAL:
  - IF the best of the running group has found a better place than the worst in the herd:
      Goal = position of the best of the running group
  - OTHERWISE:
      Target = weighted center of top 10 in herd
  
  SWARM GROUP MOVEMENT:
  - For each bison in the herd:
    1. Save current position
    2. Calculate the direction to the target
    3. Take a step towards the target (random length from 0 to 3.5)
    4. Check search area boundaries
  
  MOVEMENT OF THE RUNNING GROUP:
  - Change running direction slightly (×0.9-1.1)
  - For each individual:
    1. move in the direction of running
    2. check the boundaries of the search area
  
  ASSESSMENT AND UPDATE:
  1. Check the swarm group:
     - IF the new position is worse than the old one:
         Return to the old position
  
  2. Exchange between groups:
     - IF the best individual in a running group is better than the worst in the herd:
         Replace the worst in the herd with the best individual
  
  3. Sorting:
     - Sort the swarm group by quality
  
  4. Update:
     - Remember the best solution found
     - Remember the worst solution

CYCLE END

CALCULATION OF THE WEIGHTED CENTER:
- Take the 10 best solutions
- Assign weights: 1st gets weight 100, 2nd - 90, ..., 10th - 10
- Center = sum(position × weight) / sum(weights)

Now we have a better understanding of how everything works, and we can start writing the algorithm code. The class implements the Bison Algorithm and specifies inheritance from the C_AO base class.

Algorithm parameters:
  • popSize — defines the total number of "individuals" or "agents" in the population participating in the algorithm.
  • swarmGroupRate — specifies the proportion (in percent) of the total number of individuals that will make up the "swarm group".
  • eliteGroupSize — defines the number of the very best individuals that form the "elite group" for calculating the center.
  • overstep — sets the maximum step that a swarm group can take when moving.

The constructor sets default values for all parameters. The SetParams method allows updating the values of the algorithm parameters using data from an external source (represented by the "params" array).

Functionality:

  • Init — method for initializing the algorithm using ranges of values for the parameters.
  • Moving — method responsible for the movement or updating the state of individuals in a population.
  • Revision — method used to revise and adjust the behavior of individuals or parameters.
  • ComputeCenter — private method for computing the center of an elite group.
  • CheckIfRunnerBetter — private method that checks whether any individual runner is better than others.
Internal variables:
  • swarmGroupSize — calculated based on popSize and swarmGroupRate, determines the actual number of individuals in the swarm group.
  • runningGroupSize — size of the running group (a subgroup in the overall population).
  • runDirection — array that stores the direction of movement for each individual.
  • center — array that stores the coordinates of the center of the elite group.

In general, the C_AO_BisonAlgorithm class is a block that implements a search strategy based on the concept of population, swarm and elite groups, and has configurable parameters.

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
};
//————————————————————————————————————————————————————————————————————

The Init method of the C_AO_BisonAlgorithm class is responsible for the initial configuration of the algorithm before it starts running. The method first attempts to perform a common, standardized initialization part, using the ranges of values for the parameters and the number of epochs passed to it. If this standard part fails, the entire initialization process is aborted. Next, based on the total population size and the given coefficient, the number of individuals that will belong to the "swarm group" is calculated. The remaining individuals form a "running group".

To ensure the correct operation of the algorithm, the sizes of these groups are checked. It is guaranteed that the swarm group will always have at least one member, and also that it will not occupy the entire population (leaving room for the running group). Similarly, it is verified that the elite group is not less than one and does not exceed the size of the swarm group, being its subset.

Memory is allocated for service arrays. The array used to store the direction of movement of individuals (for each dimension of the search space) and the array used to store the coordinates of the center of the elite group both arrays are resized to match the number of dimensions defined in the system. If all previous steps are completed successfully, the method returns "true" and the algorithm is ready to work.

//————————————————————————————————————————————————————————————————————
//--- 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;
}
//————————————————————————————————————————————————————————————————————

The Moving method of the C_AO_BisonAlgorithm class is the core of the algorithm, responsible for one iteration or step of population evolution. It consists of two main phases: the initial initialization of the population (if the algorithm is launched for the first time) and subsequent steps of updating the positions of individuals.

Phase 1: Initial initialization (first run): If the algorithm is run for the first time (the "revision" flag is set to 'false'), the following sequence of actions is performed:

  • Swarm group generation. Individuals included in the swarm group are initialized with random values within the ranges specified for each parameter. Once generated, these values can be further adjusted to comply with the parameter discretization step.
  • Searching for the best bison. Among all the individuals in a swarm group, there is one that has the best fitness (the highest value of the objective function).
  • Running group generation. The remaining individuals of the population (the running group) are initialized around the best bison found. Their positions are generated randomly in some neighborhood of the best solution, and then also adjusted in accordance with the ranges and steps of the parameters.
  • Initialization of the movement direction vector. For each dimension of the search space, a random movement direction vector is generated. The magnitude of this vector depends on the overall range of parameter values, and its sign (positive or negative) is also chosen randomly.
  • Set the initialization flag. The "revision" flag is set to 'true', the initialization is complete, and only update steps will be performed from now on.

    Phase 2: Main iteration cycle (next steps): Once the initialization has been performed, the method performs the following steps for each iteration:

    Determining the swarm target. The algorithm checks whether any solution from the running group is better than the current "swarming target".
    • If the solution from the running group turns out to be better, then this swarming goal (centroid) is set equal to this better solution.
    • Otherwise, a new centroid is calculated, which represents a weighted center of the best solutions in the population.
    Swarm group movement. Each individual in the swarm group moves.
    • First, the current positions and fitness values of the individual are saved.
    • The new position is then calculated by moving from the current position towards the target swarming point. The magnitude of this movement is determined by a random factor that regulates the "stepping" over the target.
    • The resulting new position is then adjusted to stay within the acceptable ranges and take into account the parameter step.

    Correction of the movement direction vector. The direction vector used by the running group varies slightly randomly. This adds some variety to the runners' movements.

    Running group movement. Each individual in the running group moves using adjusted direction vector.

    • The new position is calculated by adding the direction vector to the current position.
    • This new position is then adjusted to stay within the acceptable ranges and take into account the parameter step.

      Thus, the Moving method models the behavior of a group of individuals: part of the group (the swarm) moves toward a common goal, responding to the best solutions, and the other part (the running) follows the adjusted direction vector, which can also change.

      /————————————————————————————————————————————————————————————————————
      //--- 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]);
          }
        }
      }
      //————————————————————————————————————————————————————————————————————
      
      

      Revision method of the C_AO_BisonAlgorithm class is meant for updating the state of the population after completing one step of movement and assessing the fitness of individuals, as well as for summing up the results of the iteration.

      Swarm group correction. For each individual in the swarm group, it is checked whether its new position (calculated in the previous step) is better than its previous (saved) position. If the new position is worse, the individual returns to its previous, better position. In this way, individuals of the swarm group do not worsen their results.

      Comparing a running group with a swarm:

      • The best individual in the running group (with the highest fitness) and the worst individual in a swarm group (with the lowest fitness).
      • If the best individual from the running group turns out to be better than the worse the individual from the swarm group, the worse the individual from the swarm group is replaced with the best individual from the running group. This allows stronger solutions from the running group to enter the swarm group.
      Sorting the swarm group:
      • A temporary copy of all individuals in the swarm group is created.
      • This temporary copy is sorted in descending order of fitness (from best to worst).
      • After sorting, individuals from the sorted temporary copy are copied back to the general population. This ensures that the swarm group will always contain the best solutions found so far.
      Global solutions update:
      • Best global solution. The fitness of the best individual in the swarm group (which is now the best solution after sorting) is compared with the current best global solution. If the best individual of the swarm group is better, the global best solution is updated.
      • Worst global solution. All individuals in the population (both swarming and running) are then examined to find the weakest one. If an individual is found with a fitness value lower than the current worst global solution, the worst global solution is updated.

        Thus, the Revision method serves as the result-processing stage that selects the best solutions, maintains order and updates in the swarm group, and tracks the overall progress of the algorithm, storing the best and worst solutions found.

        //————————————————————————————————————————————————————————————————————
        //--- 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);
            }
          }
        }
        //————————————————————————————————————————————————————————————————————
        

        The CheckIfRunnerBetter method is used to determine whether the running group contains a more promising solution than those present in the swarm group. If such a solution is found, it becomes a new "target" for swarming.

        Search for the best "runner". The method first scans all individuals that belong to the "running group" (not part of the swarm). It finds the individual with the best fitness (the highest objective function value) among all runners and stores its index and fitness value.

        Search for the worst "swarm". The method then analyzes the individuals that make up the "swarm group". It finds the individual with the worst fitness (the lowest value of the objective function) among the swarm.

        Comparing and goal determination. After this, a comparison takes place:

        • If the best runner has a fitness higher than the worst swarm, this means that the running group has indeed found a better solution.
        • In this case, the position (coordinates) of the best "runner" is assigned to a variable denoting the "center" or "swarming target". The method returns a value indicating that the swarming target has been updated.

        Returning the result:

        • If the best "runner" is better than the worst individual in the "swarm", the method reports this by returning an affirmative value.
        • Otherwise (if no "runner" has surpassed the worst "swarm" member), the method reports this by returning a negative value, and the swarm center remains the same.

        This method thus allows for dynamic changes in the search direction: if stronger solutions emerge in the "exploratory" running group, they become a guide for the "social" swarm group.

        //————————————————————————————————————————————————————————————————————
        //--- 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;
        }
        //————————————————————————————————————————————————————————————————————
        

        The ComputeCenter method calculates a new "target" position (center) for the swarm group based on the best solutions found. Here is how it works:

        • Reset the target position. First, all components of the future target position (center) are reset to zero. This prepares them to accumulate new values.
        • Determine the number of best solutions. The method selects exactly how many best solutions will be used to calculate the center. It takes the minimum value of two parameters:

          • eliteGroupSize — maximum number of "elite" solutions that can be taken into account;
          • swarmGroupSize — size of the current swarm group;
          • This ensures that we use either all given elite solutions, or as many as there are in the swarm group if there are fewer elite solutions.
        • Assigning weights. A set of weights is created for each of the selected best solutions. These weights increase linearly: the first solution receives less weight, the second one receives more, and so on, until the last one. Specifically, the weights are set as 10, 20, 30... up to s * 10, where s is the number of best solutions taken into account. These weights indicate how significant each decision is when calculating the center.
        • Accumulation of the total sum of weights. All calculated weights are summed up. This total amount will be needed for normalization.
        • Weighted summation of positions. Now for each dimension (coordinate) of the position:
          • the value of the corresponding coordinate is taken from each of the s best solutions;
          • this coordinate value is multiplied by the weight of the corresponding best solution. It is important to note that the weights are applied in reverse order: the best solution (the first in the sorted list) gets the largest weight, the second gets the next largest, and so on.
          • the results of these multiplications for all s best solutions are summed for a given coordinate.
        • Normalization. The resulting sum of weighted coordinates for each dimension is divided by the total sum of all weights. This makes the calculation a "weighted average", ensuring that the final position of the center is in some "middle" region of the best solutions found, with higher quality solutions having a greater influence.

          As a result, the ComputeCenter method creates a new "target" position, which is a kind of "average" best solution, but with a preference for the highest quality ones, which helps guide the further search of the swarm.

          //————————————————————————————————————————————————————————————————————
          //--- 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;
              }
            }
          }
          //————————————————————————————————————————————————————————————————————
          


          Test results

          As we can see, the results are average; on the discrete Megacity function they are slightly lower; the algorithm has more difficulty coping with discrete problems.

          Bison|Bison Algorithm|50.0|0.8|10|3.5|
          =============================
          5 Hilly's; Func runs: 10000; result: 0.761847128451914
          25 Hilly's; Func runs: 10000; result: 0.4002698460741261
          500 Hilly's; Func runs: 10000; result: 0.25201531651422443
          =============================
          5 Forest's; Func runs: 10000; result: 0.7620992740937584
          25 Forest's; Func runs: 10000; result: 0.45225292646780135
          500 Forest's; Func runs: 10000; result: 0.19295587559803248
          =============================
          5 Megacity's; Func runs: 10000; result: 0.48769230769230765
          25 Megacity's; Func runs: 10000; result: 0.19876923076923075
          500 Megacity's; Func runs: 10000; result: 0.10058461538461608
          =============================
          All score: 3.60849 (40.09%)


          The visualization of the BIA algorithm performance shows a fairly large spread of results not only for low-dimensional functions (green lines), but also for medium-dimensional functions (blue lines).

          Hilly

          BIA on the Hilly test function

          Forest

          BIA on the Forest test function

          Megacity

          BIA on the Megacity test function

          The BIA algorithm is presented for informational purposes in the rating table of population optimization methods.

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


          Summary

          The Bison Algorithm introduces an interesting concept of the explorer/exploiter divide, but in its current implementation it is inferior to modern metaheuristics. The main value lies in the idea of a clear separation of roles, which can be successfully integrated into hybrid approaches. 

          Test results show that the algorithm performs averagely, failing to reach the level of top metaheuristics, and has difficulty solving discrete problems. To get into the rating table, significant improvements to the movement mechanisms and adaptation of parameters are required. The algorithm can be used in problems where simplicity of implementation and interpretability are important, but maximum accuracy is not required.

          Tab

          Figure 2. Color-coded comparison of algorithms across the corresponding tests

          chart

          Figure 3. Histogram of algorithm testing results (scale from 0 to 100, the higher the better, where 100 is the maximum possible theoretical result, in the archive there is a script for calculating the rating table)

          BIA pros and cons:

          Pros:

          1. Simple implementation.
          2. Fast.

          Cons:

          1. High variability on low- and medium-dimensional test functions.

          The article is accompanied by an archive with the current versions of the algorithm codes. The author of the article is not responsible for the absolute accuracy in the description of canonical algorithms. Changes have been made to many of them to improve search capabilities. The conclusions and judgments presented in the articles are based on the results of the experiments.


          Programs used in the article

          #NameTypeDescription
          1#C_AO.mqh
          Include
          Parent class of population optimization algorithms
          2#C_AO_enum.mqh
          Include
          Enumeration of population optimization algorithms
          3TestFunctions.mqh
          Include
          Library of test functions
          4
          TestStandFunctions.mqh
          Include
          Test stand function library
          5
          Utilities.mqh
          Include
          Library of auxiliary functions
          6
          CalculationTestResults.mqh
          Include
          Script for calculating results for the comparison table
          7
          Testing AOs.mq5
          ScriptThe unified test stand for all population optimization algorithms
          8
          Simple use of population optimization algorithms.mq5
          Script
          A simple example of using population optimization algorithms without visualization
          9
          Test_AO_BIA.mq5
          ScriptBIA test stand

          Translated from Russian by MetaQuotes Ltd.
          Original article: https://www.mql5.com/ru/articles/19444

          Attached files |
          BIA.zip (284.26 KB)
          Features of Custom Indicators Creation Features of Custom Indicators Creation
          Creation of Custom Indicators in the MetaTrader trading system has a number of features.
          Defining your Edge (Part 2): Using Divergence Mapping and a Temporal Fusion Transformer in a Trading Robot Defining your Edge (Part 2): Using Divergence Mapping and a Temporal Fusion Transformer in a Trading Robot
          In this article we make the case for merging Divergence Mapping with a Temporal Fusion Proxy in a Trading Robot. Rather than depending on lagging price confirmations, the Divergence Mapping's thesis is that acting like a structural sensor can help identify hidden momentum shifts from price action and indicator anomalies. To establish how these anomalies are interpreted over time we use a Temporal Fusion Transformer proxy. This network incorporates historical context to weigh developing trends such that merging it with Divergence Mapping should set us up to spot shifts in accumulation and distribution before price breakouts.
          Features of Experts Advisors Features of Experts Advisors
          Creation of expert advisors in the MetaTrader trading system has a number of features.
          Mapping the Shape of Price: The Mapper Lens and Cover in MQL5 Mapping the Shape of Price: The Mapper Lens and Cover in MQL5
          The article introduces the Mapper pipeline in MQL5 by implementing the two fundamental components: CTDAMapperFilter (lens) and CTDAMapperCover (overlapping intervals). It explains three lens options—eccentricity, density, and coordinate—plus cover parameters (resolution and gain), and demonstrates how a price point cloud is reduced to one value per point and interval memberships. Readers obtain ready inputs for subsequent clustering and graph construction.