Русский
preview
The Blue Monkey (BM) Algorithm

The Blue Monkey (BM) Algorithm

MetaTrader 5Trading |
364 0
Andrey Dik
Andrey Dik

Contents

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


Introduction

To find the most suitable optimization algorithm for discrete trading robot problems, we will examine another method and its capabilities. Deep in the heart of the African jungle live creatures whose lives — filled with complex interactions and a well-structured hierarchy — served as the inspiration for one of the optimization methods. We are talking about blue monkeys, or Cercopithecus mitis — small, agile inhabitants of the tree canopy whose seemingly chaotic behavior is in fact governed by deep biological laws and inspired the development of an algorithm named after them: Blue Monkey.

The algorithm was developed by M. Mahmood and B. Al-Khateeb and was published in 2019 in Periodicals of Engineering and Natural Sciences (PEN) 7(3):1054–1066, DOI:10.21533/pen.v7i3.621.

This complex yet harmonious ecosystem is reflected in the mathematical abstraction of the Blue Monkey algorithm:

  • Groups as Teams. The population of agents is divided into independent teams, each of which has its own leader, much like a dominant male.
  • The Leader —The Embodiment of Perfection. The best individual in each group, with the highest “fitness,” embodies the dominant male — a role model.
  • The Offspring — The New Generation. Young individuals, the “offspring,” represent a new stage of development; they are learning and ready to take the place of their elders, bringing fresh ideas and solutions.
  • Migration — System Renewal. The migration process works by allowing the best “offspring” to replace the less fit “adults,” ensuring continuous renewal and improved efficiency.
  • Social Learning —The Path to the Leader. The movement of the “offspring” toward the group leader, governed by mathematical formulas, mimics the process of social learning, in which the offspring learn from the experienced.


Algorithm Implementation

Imagine that you are leading a research expedition in the jungle in search of a lost city. You have 50 experienced researchers (adult monkeys) and 15 young individuals (offspring — 30% of the adult population).

Work organization:

  • We divide the experienced researchers into 5 teams of 10 people each.
  • Each team explores its own section of the jungle independently.
  • 5 teams = 5 different search zones: Team A will head north, Team B will head south, Team C will search in the east, Team D will search in the west, and Team E will stay in the center.

If the lost city is in the north, Team A will find it first, and the other teams will gradually catch up. The offspring work together as a training group. The main mechanism: 5 teams = 5 different search zones. “Follow the successful one.” A leader is identified in each team — the one who has found the best direction; the others adjust their route toward the leader, but do not go straight to them! They keep 70% of their original direction and shift 30% toward the leader. We do not abandon our route right away (there might be something there, too), but we gradually shift toward promising areas.

Training the young: The offspring do the same thing, but everyone follows the single best intern. We identify the worst performer on each team and compare them to the best offspring; if the young individual is better, it is retained, and the weaker performer is removed from the team. Offspring bring new ideas — perhaps an experienced researcher has gotten stuck in a dead end, while a young researcher has stumbled upon a gold mine.

blue_monkey

Figure 1. BM algorithm workflow

The diagram shows the operation of the BM algorithm, with the logic divided into two independent branches for adult individuals and offspring, along with the main formulas. Let's move on to writing the pseudocode for the BM algorithm.

Initialization:

  1. Create a population of adult monkeys (size N)
    • Each monkey is assigned a random position in the search space
    • Initialize the velocity to zero
    • Set the initial weight to a value between 4 and 6
  2. Create a population of offspring (30% of the adult population size)
    • Each offspring individual is assigned a random position
    • Initialize the velocity to zero
    • Set the initial weight to a value between 4 and 6
  3. Divide the adults into groups
    • Divide the population into T groups evenly
    • A monkey with index i is assigned to group (i modulo T)
  4. Compute the initial fitness
    • Assess the quality of each adult's position
    • Assess the quality of each offspring individual's position

Main loop (until the stopping criterion is met):

  1. Update weights based on fitness
    • Find the minimum and maximum fitness among adult individuals
    • Normalize weights: weight = 4 + 2 × (fitness - min)/(max - min)
    • Repeat for the offspring
  2. Replace the worst with the best (starting with the second iteration)
    • For each group of adult individuals:
      • Find the monkey with the worst fitness in the group
      • Select the next best unused offspring
      • If the offspring's fitness is better than the adult individual's fitness:
        • Replace the adult with the offspring
        • Create a new offspring to replace the one that was promoted
  3. Update the positions of adult monkeys
    • For each monkey in the group:
      • Find the group leader (the monkey with the best fitness)
      • Update velocity: new velocity = 0.7 × old velocity + (leader's weight - own weight) × random value × (leader's position - own position)
      • Update position: new position = old position + velocity × random value
      • Constrain the position to the search space boundaries
  4. Update the positions of offspring
    • Find the best offspring overall
    • For each other offspring:
      • Update velocity: new velocity = 0.7 × old velocity + (best weight - personal weight) × random × (best position - personal position)
      • Update position: new position = old position + velocity × random
      • Constrain the position to the bounds
  5. Calculate the new fitness
    • Evaluate the quality of the new positions for all the monkeys
    • Evaluate the quality of the new positions of all the offspring
  6. Update the global best solution
    • If a solution better than the current record is found:
      • Save it as the new record
    • Increment the iteration counter
  7. Check the stopping criterion
    • If the maximum number of iterations has been reached OR
    • If the required accuracy has been achieved:
      • Break out of the loop
    • Otherwise: return to step 5

Termination:

  1. Return the result
    • Return the best position found
    • Return the fitness value at this position

Now let's turn the pseudocode into an implementation. We will implement the C_AO_BM class. It is derived from the C_AO class, which serves as the base class. The C_AO_BM class inherits all public and protected fields from the C_AO class.

Public fields:

The C_AO_BM constructor initializes the algorithm's basic properties and sets its initial parameters: popSize (population size), numGroups (number of groups), childrenRatio (offspring ratio). Configures the "params" array, which is used to store the algorithm's configurable parameters. For each parameter, its name and current value are specified. Sets the minimum (minW) and maximum (maxW) values for the weights used in the algorithm.
  • SetParams() — the method for setting the algorithm's parameters from the "params" array. It reads the "val" values from "params" and converts them to the appropriate data types (int or double), assigning them to the class members.
  • Init() — the initialization method prepares the algorithm for execution using the input ranges (rangeMinP, rangeMaxP, rangeStepP) and the number of epochs (epochsP).
  • Moving() — implements the main logic for moving or updating individuals in the population according to the algorithm.
  • Revision() — used to evaluate and update the current state of the population, including selection and best-solution tracking.
Public member variables:
  • numGroups — the number of groups into which the population is divided.
  • childrenRatio — the offspring ratio, i.e., the share of the population that will consist of "offspring" (new individuals).
Private fields:
  • numChildren — the number of "offspring" in the population, calculated based on popSize and childrenRatio.
  • minW, maxW — the minimum and maximum values for the weights.
  • Arrays for adult monkeys:
    • monkeyRate — stores the "velocity" for each individual in the population. The size of the array depends on popSize and the number of dimensions (coordinates) in the search space.
    • monkeyWeight — stores the "weights" (W) for each adult individual.
    • groupId — an array that specifies which group each adult individual belongs to.
  • The offspring arrays store:
    • childPos — the positions of the "offspring" in the search space.
    • childRate — the "velocity" for "offspring".
    • childWeight — "weights" for "offspring".
    • childFitness — the fitness value for "offspring".
  • Private methods:
    • SwapWorstWithBest() — implements step 8 of the algorithm, which involves replacing the worst individual in the group with the best one.
    • UpdatePositions() — implements steps 9–10 of the algorithm, which are responsible for updating the positions of the individuals.
    • UpdateWeights() — implements step 2 of the algorithm and performs a general update of the weights.
    • FindGroupBest() — finds the best individual in the specified group.
    • FindGroupWorst() — finds the worst individual in the specified group.

The algorithm divides the population into groups, uses the concepts of "velocity" (Rate) and "weight" (weight) to move individuals, and includes mechanisms for creating "offspring" and replacing the worst individuals with the best ones within groups. Private members are intended for internal management of the algorithm's state, while public members provide an interface for configuring and running the optimization.

//————————————————————————————————————————————————————————————————————
class C_AO_BM : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_BM () { }
  C_AO_BM ()
  {
    ao_name = "BM";
    ao_desc = "Blue Monkey Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/19757";

    popSize        = 50;    // population size
    numGroups      = 5;     // number of groups T
    childrenRatio  = 0.3;   // offspring ratio in the population

    ArrayResize (params, 3);
    params [0].name = "popSize";       params [0].val = popSize;
    params [1].name = "numGroups";     params [1].val = numGroups;
    params [2].name = "childrenRatio"; params [2].val = childrenRatio;

    minW           = 4.0;
    maxW           = 6.0;
  }

  void SetParams ()
  {
    popSize       = (int)params [0].val;
    numGroups     = (int)params [1].val;
    childrenRatio = params      [2].val;
  }

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  int    numGroups;      // number of groups T
  double childrenRatio;  // offspring ratio

  private: //---------------------------------------------------------
  int    numChildren;         // number of offspring
  double minW;
  double maxW;

  // Arrays for adult monkeys
  double monkeyRate   [];     // Rate - velocity of change [popSize * coords]
  double monkeyWeight [];     // W - the monkeys' weights
  int    groupId      [];     // group membership

  // Arrays for offspring
  double childPos     [];      // offspring positions [numChildren * coords]
  double childRate    [];      // Rate for offspring
  double childWeight  [];      // W for offspring
  double childFitness [];      // offspring fitness

  void SwapWorstWithBest ();   // Step 8
  void UpdatePositions   ();   // Steps 9–10
  void UpdateWeights     ();   // Step 2 and update
  int  FindGroupBest     (int groupId);
  int  FindGroupWorst    (int groupId);
};
//————————————————————————————————————————————————————————————————————

The Init function performs the following actions:

Standard initialization. First, the base initialization function StandardInit is called with the range parameters (rangeMinP, rangeMaxP, rangeStepP) passed to it. If this base initialization fails, the Init function also terminates, returning 'false'.

Parameter adjustment:

  • The number of groups (numGroups) is checked.
  • The number of "offspring" (numChildren) is calculated based on the population size and the offspring ratio (childrenRatio).
Resizing arrays:
  • The arrays used to store data for "adult" individuals are resized. This includes monkeyRate (where the velocity is stored for each dimension),"monkeyWeight (the weight for each individual), and groupId (the individual's group membership).
  • The arrays for the "offspring" are resized, including their positions (childPos), velocities (childRate), weights (childWeight), and fitness values (childFitness).

Initializing velocities. The monkeyRate and childRate arrays (which are used to determine the direction of movement) are initialized with zero values.

Weight initialization: The weights (monkeyWeight and childWeight) for "adult" individuals and "offspring" are initialized with random values in the range from minW to maxW; the actual weights will be recalculated after the first fitness calculation.

Assignment to groups:

  • Adult individuals are distributed among groups (groupId) using the modulo operation. In other words, the first individual is assigned to group 0, the second to group 1, ..., the numGroups-th individual to group numGroups - 1, the (numGroups + 1)-th individual back to group 0, and so on.
  • All offspring will initially be in the same team.

Initializing offspring fitness: The childFitness array is initialized to the minimum possible value. This is done to ensure that any actual calculated fitness value is greater than this initial value.

The function returns 'true' if the initialization was successful. Thus, the Init function performs all the preparatory work: it sets the required sizes of the data structures, sets the initial values for velocity and weights, assigns individuals to groups, and prepares them for the first calculation cycle.

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

  //------------------------------------------------------------------
  // Parameter adjustment
  if (numGroups < 1) numGroups = 1;
  if (numGroups > popSize) numGroups = popSize;

  numChildren = (int)(popSize * childrenRatio);
  if (numChildren < 1) numChildren = 1;

  // Initializing arrays for adult individuals
  ArrayResize (monkeyRate, popSize * coords);
  ArrayResize (monkeyWeight, popSize);
  ArrayResize (groupId, popSize);

  // Initializing arrays for offspring
  ArrayResize (childPos, numChildren * coords);
  ArrayResize (childRate, numChildren * coords);
  ArrayResize (childWeight, numChildren);
  ArrayResize (childFitness, numChildren);

  // Step 2: Initialize velocity (Rate) and weight W
  // Rate ∈ [0, 1], W ∈ [4, 6]
  ArrayInitialize (monkeyRate, 0.0);
  ArrayInitialize (childRate, 0.0);

  // The weights will be updated after the first fitness calculation
  for (int i = 0; i < popSize; i++)
  {
    monkeyWeight [i] = u.RNDfromCI (minW, maxW);
  }

  for (int i = 0; i < numChildren; i++)
  {
    childWeight [i] = u.RNDfromCI (minW, maxW);
  }

  // Step 3: Distribution into groups
  // Adult individuals are divided into T groups
  for (int i = 0; i < popSize; i++)
  {
    groupId [i] = i % numGroups;
  }
  // "while all offspring are in one team" — all offspring are initially in one team

  ArrayInitialize (childFitness, -DBL_MAX);

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

The Moving method performs the following actions: first, the "revision" flag is checked, and if "revision" is 'false' (first run):

Initializing the adult population (Step 1). For each adult individual "i" from 0 to popSize - 1 and for each dimension "j" from 0 to coords - 1, the position (a[i].c[j]) is initialized with a random number within the specified range (rangeMin[j], rangeMax[j]). The position value is then adjusted using the u.SeInDiSp function, which ensures that the value remains within the specified bounds and accounts for the step size (rangeStep[j]).

Initializing the offspring population (Step 1). For each offspring "i" from 0 to numChildren - 1 and for each dimension "j" from 0 to coords - 1, the position (childPos[i * coords + j]) is initialized with a random number within the specified range (rangeMin, rangeMax). The position value is also adjusted by the u.SeInDiSp function.

The "revision" flag is set to 'true' so that a different part of the logic is executed on subsequent calls to the Moving method. The method completes execution. If "revision" is 'true' (on subsequent runs), the positions are updated by calling the UpdatePositions() method. This method is responsible for moving individuals according to the logic of the Blue Monkey algorithm.

Thus, the Moving method serves two main purposes:

  • When called for the first time, it fully initializes the positions of all adult individuals and offspring within the specified ranges.
  • In subsequent calls, it delegates the main work of moving individuals to the UpdatePositions() method, which implements the core movement logic of the algorithm.
//————————————————————————————————————————————————————————————————————
void C_AO_BM::Moving ()
{
  if (!revision)
  {
    // Step 1: Initialization of the adult population
    for (int i = 0; i < popSize; i++)
    {
      for (int j = 0; j < coords; j++)
      {
        a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
        a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
      }
    }

    // Step 1: Initialization of the offspring population
    for (int i = 0; i < numChildren; i++)
    {
      for (int j = 0; j < coords; j++)
      {
        childPos [i * coords + j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
        childPos [i * coords + j] = u.SeInDiSp (childPos [i * coords + j], rangeMin [j], rangeMax [j], rangeStep [j]);
      }
    }

    revision = true;
    return;
  }

  //------------------------------------------------------------------
  UpdatePositions ();
}
//————————————————————————————————————————————————————————————————————

The Revision method performs the following actions:

Weight update. First, the UpdateWeights() method is called. This means that the weights of each individual are recalculated based on its current fitness.
Swapping the worst with the best. Next, the SwapWorstWithBest() method is called. This method replaces the worst adult individuals in groups with the best available offspring when the replacement improves fitness.

    Updating the best individual.For adult individuals, the method iterates over all adult individuals "i" (from 0 to popSize - 1), and if the current fitness of an adult individual (a[i].f) is greater than that individual's personal best fitness (a[i].fB), the individual's personal best fitness is updated. The individual's personal best position (a[i].cB) is copied from the individual's current position (a[i].c).

    Similarly, if the current fitness of an adult individual (a[i].f) is greater than the global best fitness in the entire population (fB), then the global best fitness (fB) is updated. The position of the global best solution (cB) is copied from the individual's current position (a[i].c).

    For offspring. The method iterates over all offspring "i" (from 0 to numChildren - 1). If an offspring's fitness (childFitness[i]) is greater than the global best fitness (fB), then the global best fitness (fB) is updated. The position of the global best solution (cB) is updated by copying the coordinates of the offspring (childPos).

      In general, the Revision method is designed to improve the quality of solutions by tracking and storing the best solution found both for each individual (personal best) and for the entire population as a whole (global best), taking into account both adult individuals and offspring.

      //————————————————————————————————————————————————————————————————————
      void C_AO_BM::Revision ()
      {
        // Update weights based on current fitness
        UpdateWeights ();
      
        // Step 8: Swapping
        SwapWorstWithBest ();
      
        // Step 12: Update Current Best
        for (int i = 0; i < popSize; i++)
        {
          if (a [i].f > a [i].fB)
          {
            a [i].fB = a [i].f;
            ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
          }
      
          if (a [i].f > fB)
          {
            fB = a [i].f;
            ArrayCopy (cB, a [i].c, 0, 0, coords);
          }
        }
      
        // Checking offspring for the global best solution
        for (int i = 0; i < numChildren; i++)
        {
          if (childFitness [i] > fB)
          {
            fB = childFitness [i];
            for (int j = 0; j < coords; j++)
            {
              cB [j] = childPos [i * coords + j];
            }
          }
        }
      }
      //————————————————————————————————————————————————————————————————————

      The UpdatePositions method of the C_AO_BM class implements the core logic governing the movement of individuals in the population, following specific mathematical equations. The method is divided into two parts: updating the positions of adult individuals (the so-called "blue monkeys") and updating the positions of offspring.

      Updating the positions of adult individuals (blue monkeys):

      The method iterates over each adult individual "i" (from 0 to popSize - 1). Finding the group leader: for each current individual, its group (groupId[i]) is determined, and the index of the best individual in that group is found (bestIdx = FindGroupBest). If the current individual is itself the group leader, or if no leader is found, we move on to the next individual. Update according to Equations 1 and 2 (for each dimension).

      Velocity Update (Equation 1):

      • The weights of the group leader Wleader and the current individual Wi are determined.
      • The current positions of the leader Xbest and the current individual Xi in this dimension are retrieved.
      • A random number rand1 in the range from 0 to 1 is generated.
      • A new velocity monkeyRate is calculated for the current individual. It is a combination of the previous velocity (with a coefficient of 0.9) and the effect of the weight difference between the leader and the current individual, multiplied by a random number and the difference between the leader's and the current individual's positions.

      Position Update (Equation 2):

      Another random number, "rand2", is generated in the range from 0 to 1. An individual's new position is calculated by adding the product of the new velocity monkeyRate and the random number rand2 to its current position. The resulting new position is clamped to the specified ranges (u.SeInDiSp) so that it remains within the valid search space.

        Updating offspring positions:

        1. Searching for the best offspring: First, the method iterates through all offspring "i" from 0 to numChildren - 1 to find the one with the best fitness (bestChildFitness) and, accordingly, its index (bestChildIdx).
        2. Update using Equations 3 and 4 (if the best offspring is found):
          • If the best offspring has been found (bestChildIdx >= 0), the method iterates through all other offspring "i" from 0 to numChildren - 1, excluding the best one.
          • Updating the offspring's velocity (Equation 3):
            • As with adult individuals, the weights for the best offspring, Wchleader, and the current offspring, Wchi, are determined.
            • The current positions of the best offspring Xchbest and the current offspring Xchi in this dimension are retrieved.
            • A random number rand1 between 0 and 1 is generated.
            • A new velocity (childRate) is calculated for the current offspring using a formula similar to Equation 1.
          • Updating the offspring's position (Equation 4):
            • A random number rand2 between 0 and 1 is generated.
            • The offspring's new position is calculated by adding the product of its new velocity (childRate) and the random number rand2 to its current position.
          • Position constraint: The resulting new position of the offspring is also clamped to the specified ranges.

        Thus, the UpdatePositions method implements:

        • The behavioral model of "blue monkeys": adult individuals move guided by their group leader, with their velocity depending on the differences in weights and positions.
        • Offspring behavior model: the offspring move in a similar way, guided by the best offspring.
        • Keeping individuals within the feasible search space using the SeInDiSp function.
        //————————————————————————————————————————————————————————————————————
        void C_AO_BM::UpdatePositions ()
        {
          // Step 9: Update the positions of the blue monkeys using Equations 1 and 2
          for (int i = 0; i < popSize; i++)
          {
            int bestIdx = FindGroupBest (groupId [i]);
            if (bestIdx < 0 || bestIdx == i) continue;
        
            for (int j = 0; j < coords; j++)
            {
              // Equation (1): Velocity (Rate) update
              double Wleader = monkeyWeight [bestIdx];
              double Wi      = monkeyWeight [i];
              double Xbest   = a [bestIdx].c [j];
              double Xi      = a [i].c [j];
              double rand1   = u.RNDfromCI (0, 1);
        
              // Rate_{i,t} = (0.7 * Rate) + (W_leader - W_i) * rand * (X_best - X_i)
              monkeyRate [i * coords + j] = 0.9 * monkeyRate [i * coords + j] +
                                            (Wleader - Wi) * rand1 * (Xbest - Xi);
        
              // Equation (2): Position update
              // X_{i,t} = X_i + Rate_{i,t} * rand
              double rand2 = u.RNDfromCI (0, 1);
              a [i].c [j] = a [i].c [j] + monkeyRate [i * coords + j] * rand2;
        
              // Constraining positions within the range
              a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
            }
          }
        
          // Step 10: Update the positions of the offspring using Equations 3 and 4
          // Find the best offspring
          int bestChildIdx = -1;
          double bestChildFitness = -DBL_MAX;
          for (int i = 0; i < numChildren; i++)
          {
            if (childFitness [i] > bestChildFitness)
            {
              bestChildFitness = childFitness [i];
              bestChildIdx = i;
            }
          }
        
          if (bestChildIdx >= 0)
          {
            for (int i = 0; i < numChildren; i++)
            {
              if (i == bestChildIdx) continue;
        
              for (int j = 0; j < coords; j++)
              {
                // Equation (3): Offspring velocity (Rate) update
                double Wchleader = childWeight [bestChildIdx];
                double Wchi      = childWeight [i];
                double Xchbest   = childPos [bestChildIdx * coords + j];
                double Xchi      = childPos [i * coords + j];
                double rand1     = u.RNDfromCI (0, 1);
        
                childRate [i * coords + j] = 0.9 * childRate [i * coords + j] +
                                             (Wchleader - Wchi) * rand1 * (Xchbest - Xchi);
        
                // Equation (4): Offspring position update
                double rand2 = u.RNDfromCI (0, 1);
                childPos [i * coords + j] = childPos [i * coords + j] + childRate [i * coords + j] * rand2;
        
                childPos [i * coords + j] = u.SeInDiSp (childPos [i * coords + j], rangeMin [j], rangeMax [j], rangeStep [j]);
              }
            }
          }
        }
        //————————————————————————————————————————————————————————————————————

        The SwapWorstWithBest method performs the following steps:

        Preparing offspring. An array named "childIndices" is created to store the indices of all offspring. This array is filled with indices ranging from 0 to the total number of offspring. The indices of the offspring are then sorted in descending order of their fitness values (childFitness). This is done using a simple bubble sort algorithm. As a result, childIndices [0] will contain the index of the best offspring, "childIndices [1]" will contain the index of the second-best offspring, and so on. A boolean array named childUsed is created to track whether each offspring has been used as a replacement.

        Iteration over groups of adult individuals. The method iterates through each group of adult individuals (from g = 0 to numGroups - 1). For each group, if all the offspring have already been used, further processing stops. Within each group, the worst adult individual is found (worstAdultIdx). The best currently available offspring is selected (using the sorted "childIndices" array and the "childIndex" counter). It checks whether the fitness of the selected offspring (childFitness[currentChildIdx]) is better than the fitness of the worst adult individual in the group (a[worstAdultIdx].f).

        • Replacement (if better):
          • If the offspring is better:
            • The position and velocity of the worst adult individual are replaced with the position and velocity of the selected offspring.
            • The fitness of the adult individual is updated to the offspring's fitness.
            • The weight of the adult individual is updated to the offspring's weight.
            • The offspring is marked as used (childUsed[currentChildIdx] = true), and the "childIndex" counter is incremented to move on to the next best offspring.
        • Stopping replacement (if worse):
          • If even the best available offspring is no better than the worst adult individual in the current group, further replacement attempts in this and subsequent groups are stopped. This is because the offspring are sorted by fitness, and if the current best available offspring does not outperform the worst individual, then subsequent offspring will not provide any benefit either.
          Creating new offspring. After all possible replacements have been made, the method iterates through all the offspring. If the offspring was used as a replacement (childUsed[i] is true), its position is completely regenerated within the specified ranges (using u.RNDfromCI and u.SeInDiSp), its velocity is reset to 0, and its fitness value is reset to the minimum possible value (indicating that it has not yet been evaluated or updated). It is assigned a new random weight within the initial bounds (minW, maxW).

            Thus, the SwapWorstWithBest method serves the following purpose: it actively uses the best offspring to replace the worst adult individuals within their groups, if this leads to an improvement, thereby transferring the best young solutions to the more "mature" layers of the population. The offspring that were used for replacement are then "regenerated" with new random parameters.

            //————————————————————————————————————————————————————————————————————
            void C_AO_BM::SwapWorstWithBest ()
            {
              // Step 8: Replacing the individual with the worst fitness in each group with the individual with the best fitness from the offspring group
            
              // Create an array of offspring indices and sort them by fitness
              int childIndices [];
              ArrayResize (childIndices, numChildren);
              for (int i = 0; i < numChildren; i++)
              {
                childIndices [i] = i;
              }
            
              // Sort the offspring indices in descending order of fitness
              for (int i = 0; i < numChildren - 1; i++)
              {
                for (int j = 0; j < numChildren - i - 1; j++)
                {
                  if (childFitness [childIndices [j]] < childFitness [childIndices [j + 1]])
                  {
                    int temp = childIndices [j];
                    childIndices [j] = childIndices [j + 1];
                    childIndices [j + 1] = temp;
                  }
                }
              }
            
              // To track the offspring that have been used
              bool childUsed [];
              ArrayResize (childUsed, numChildren);
              ArrayInitialize (childUsed, false);
            
              // For each group, try to replace the worst individual
              int childIndex = 0;
            
              for (int g = 0; g < numGroups; g++)
              {
                if (childIndex >= numChildren) break;
            
                // Find the worst individual in the group
                int worstAdultIdx = FindGroupWorst (g);
                if (worstAdultIdx < 0) continue;
            
                // Use the best available offspring
                int currentChildIdx = childIndices [childIndex];
            
                // Check whether the offspring is better
                if (childFitness [currentChildIdx] > a [worstAdultIdx].f)
                {
                  // Perform the replacement
                  for (int j = 0; j < coords; j++)
                  {
                    a [worstAdultIdx].c [j] = childPos [currentChildIdx * coords + j];
                    monkeyRate [worstAdultIdx * coords + j] = childRate [currentChildIdx * coords + j];
                  }
                  a [worstAdultIdx].f = childFitness [currentChildIdx];
                  monkeyWeight [worstAdultIdx] = childWeight [currentChildIdx];
            
                  childUsed [currentChildIdx] = true;
                  childIndex++;
                }
                else
                {
                  // If the best offspring is no better than the worst adult,
                  // there is no point in checking further (they are sorted)
                  break;
                }
              }
            
              // Generate new offspring to replace the ones that have been used
              for (int i = 0; i < numChildren; i++)
              {
                if (childUsed [i])
                {
                  for (int j = 0; j < coords; j++)
                  {
                    childPos [i * coords + j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
                    childPos [i * coords + j] = u.SeInDiSp (childPos [i * coords + j],
                                                            rangeMin [j], rangeMax [j], rangeStep [j]);
                    childRate [i * coords + j] = 0.0;
                  }
                  childFitness [i] = -DBL_MAX;
                  childWeight [i] = u.RNDfromCI (minW, maxW); // New weight according to the initial conditions
                }
              }
            }
            //————————————————————————————————————————————————————————————————————

            The UpdateWeights function is responsible for adjusting the weights for two groups of entities: "monkeys" (parent individuals) and "offspring". Weight adjustment is based on normalization and scaling according to the fitness of each entity.

            Determining the fitness range. First, the function finds the minimum (minFit) and maximum (maxFit) fitness values among all the "monkeys" in the population.

            Handling the case of identical fitness. If the difference between the maximum and minimum fitness is very small (less than 1e-10, which effectively means that all fitness values are approximately the same), then all "monkeys" are assigned a fixed weight of 5.0 (the midpoint of the desired range).

            Normalization and scaling of the "monkey" weights. If the fitness range is sufficient (greater than 1e-10), the following occurs for each "monkey":

            • Its fitness (a[i].f) is normalized to the range [0, 1]. This is done by subtracting the minimum fitness value (minFit) and dividing by the difference between the maximum and minimum fitness values (maxFit - minFit).
            • The resulting normalized value is then scaled to obtain a final weight in the range [4, 6]. This is achieved by multiplying the normalized value by 2.0 and adding the minimum weight (minW). Thus, individuals with the lowest fitness receive a weight close to 4.0, while those with the highest fitness receive a weight close to 6.0.

              Processing the weights of the "offspring". After processing the "monkeys", the process is repeated for the "offspring". First, the minimum (minChildFit) and maximum (maxChildFit) fitness values are determined among all "offspring." In doing so, "offspring" whose fitness has not been initialized are ignored (it is assumed that uninitialized fitness has a very low value, close to -DBL_MAX). As with the "monkeys":

              • if the difference between the maximum and minimum fitness of the "offspring" is small, all initialized "offspring" are assigned a weight of 5.0;
              • if the fitness range of the "offspring" is sufficient, their fitness is normalized to the range [0, 1] and then scaled to the range [4, 6], as with the "monkeys."

                In general, the UpdateWeights function converts the fitness values of individuals (both "monkeys" and "offspring") into weights ranging from 4 to 6. This is done to emphasize or reduce the influence of individuals with varying fitness on the subsequent stages of the algorithm. Individuals with higher fitness are assigned a higher weight, which may indicate their greater importance.

                //————————————————————————————————————————————————————————————————————
                void C_AO_BM::UpdateWeights ()
                {
                  double minFit = DBL_MAX, maxFit = -DBL_MAX;
                
                  for (int i = 0; i < popSize; i++)
                  {
                    if (a [i].f > maxFit) maxFit = a [i].f;
                    if (a [i].f < minFit) minFit = a [i].f;
                  }
                
                  if (maxFit - minFit > 1e-10)
                  {
                    for (int i = 0; i < popSize; i++)
                    {
                      // Normalize to [0, 1] and scale to [4, 6]
                      double normalized = (a [i].f - minFit) / (maxFit - minFit);
                      monkeyWeight [i] = minW + normalized * 2.0; // Obtain a value in [4, 6]
                    }
                  }
                  else
                  {
                    for (int i = 0; i < popSize; i++)
                    {
                      monkeyWeight [i] = 5.0; // Average value
                    }
                  }
                
                  // Updating offspring weights
                  double minChildFit = DBL_MAX, maxChildFit = -DBL_MAX;
                
                  for (int i = 0; i < numChildren; i++)
                  {
                    if (childFitness [i] > -DBL_MAX + 1) // Initialization check
                    {
                      if (childFitness [i] > maxChildFit) maxChildFit = childFitness [i];
                      if (childFitness [i] < minChildFit) minChildFit = childFitness [i];
                    }
                  }
                
                  if (maxChildFit - minChildFit > 1e-10)
                  {
                    for (int i = 0; i < numChildren; i++)
                    {
                      if (childFitness [i] > -DBL_MAX + 1)
                      {
                        double normalized = (childFitness [i] - minChildFit) /
                                            (maxChildFit - minChildFit);
                        childWeight [i] = minW + normalized * 2.0; // In the range [4, 6]
                      }
                    }
                  }
                  else
                  {
                    for (int i = 0; i < numChildren; i++)
                    {
                      if (childFitness [i] > -DBL_MAX + 1)
                      {
                        childWeight [i] = 5.0;
                      }
                    }
                  }
                }
                //————————————————————————————————————————————————————————————————————

                The FindGroupBest function is designed to find the best individual within a given group. The function iterates over each individual in the overall population using a loop (from 0 to popSize - 1).

                  Checking group membership and comparing fitness. For each individual, the function checks whether it belongs to the target group; if so, its fitness (a[i].f) is checked. If the current fitness (a[i].f) is greater than the current maximum fitness value (bestFitness), then:
                  • bestFitness is updated to the fitness value of the current individual.
                  • bestIdx is updated to the index of the current individual "i".

                  After completing a full pass through the population, the function returns the "bestIdx" value. This will be the index of the individual with the highest fitness in the specified group. If the group is empty, or if it contains no individuals with a fitness value greater than the initial minimum value, the function returns -1.

                  Thus, the FindGroupBest function serves as an auxiliary tool for identifying the group leader or the best solution (individual) within a specific subgroup of the population.

                  //————————————————————————————————————————————————————————————————————
                  int C_AO_BM::FindGroupBest (int grpId)
                  {
                    int bestIdx = -1;
                    double bestFitness = -DBL_MAX;
                  
                    for (int i = 0; i < popSize; i++)
                    {
                      if (groupId [i] == grpId && a [i].f > bestFitness)
                      {
                        bestFitness = a [i].f;
                        bestIdx = i;
                      }
                    }
                  
                    return bestIdx;
                  }
                  //————————————————————————————————————————————————————————————————————

                  The FindGroupWorst function iterates through each individual, starting at index 0 and ending with the last individual in the population (popSize - 1).

                  Checking group membership and comparing fitness. For each individual in the loop, the function first checks whether it belongs to the target group specified by the "grpId" parameter. If an individual belongs to the group of interest, its fitness value (a[i].f) is compared with the current worst value (worstFitness). If the fitness of the current individual is lower than the current worst fitness (worstFitness), this means we have found a new, even worse individual. In that case:

                  • worstFitness is updated to the fitness value of this new, worse individual;
                  • worstIdx is updated to store the index of this new, worse individual "i".
                  Once the loop has finished (that is, once all individuals in the population have been checked), the function will return the value "worstIdx". This will be the index of the worst individual in the specified group. If the group is empty, the initial value of -1 will be returned. In other words, the FindGroupWorst function finds and returns the index of the individual with the lowest fitness value within a given group identifier.

                  //————————————————————————————————————————————————————————————————————
                  int C_AO_BM::FindGroupWorst (int grpId)
                  {
                    int worstIdx = -1;
                    double worstFitness = DBL_MAX;
                  
                    for (int i = 0; i < popSize; i++)
                    {
                      if (groupId [i] == grpId && a [i].f < worstFitness)
                      {
                        worstFitness = a [i].f;
                        worstIdx = i;
                      }
                    }
                  
                    return worstIdx;
                  }
                  //————————————————————————————————————————————————————————————————————


                  Test results

                  Let's take a look at the results; unfortunately, this is not enough to make it onto the leaderboard.

                  BM|Blue Monkey Algorithm|50.0|3.0|0.7|
                  =============================
                  5 Hilly's; Func runs: 10000; result: 0.6730156841791012
                  25 Hilly's; Func runs: 10000; result: 0.3811383925125479
                  500 Hilly's; Func runs: 10000; result: 0.26883473381494066
                  =============================
                  5 Forest's; Func runs: 10000; result: 0.6116553050534701
                  25 Forest's; Func runs: 10000; result: 0.3051920014986885
                  500 Forest's; Func runs: 10000; result: 0.18691769556662757
                  =============================
                  5 Megacity's; Func runs: 10000; result: 0.4384615384615385
                  25 Megacity's; Func runs: 10000; result: 0.19353846153846152
                  500 Megacity's; Func runs: 10000; result: 0.1037384615384623
                  =============================
                  Total score: 3.16249 (35.14%)

                  The visualization clearly shows the division into groups — areas where individuals are concentrated.

                  Hilly

                  BM on the Hilly test function

                  Forest

                  BM on the Forest test function

                  Megacity

                  BM on the Megacity test function

                  The BM algorithm is included in the leaderboard for informational purposes.

                  AO Description Hilly Hilly
                  Final
                  Forest Forest
                  Final
                  Megacity (discrete) Megacity
                  Final
                  Final
                  Result
                  % of
                  MAX
                  10 p (5 F) 50 p (25 F) 1,000 p (500 F) 10 p (5 F) 50 p (25 F) 1,000 p (500 F) 10 p (5 F) 50 p (25 F) 1,000 p (500 F)
                  DOAdingom dingo_optimization_algorithm_M 0.47968 0.45367 0.46369 1.39704 0.94145 0.87909 0.91454 2.73508 0.78615 0.86061 0.84805 2.49481 6.627 73.63
                  ANS across neighbourhood search 0.94948 0.84776 0.43857 2.23581 1.00000 0.92334 0.39988 2.32323 0.70923 0.63477 0.23091 1.57491 6.134 68.15
                  CLA code lock algorithm (joo) 0.95345 0.87107 0.37590 2.20042 0.98942 0.91709 0.31642 2.22294 0.79692 0.69385 0.19303 1.68380 6.107 67.86
                  AMOm animal migration optimization M 0.90358 0.84317 0.46284 2.20959 0.99001 0.92436 0.46598 2.38034 0.56769 0.59132 0.23773 1.39675 5.987 66.52
                  (P+O)ES (P+O) evolution strategies 0.92256 0.88101 0.40021 2.20379 0.97750 0.87490 0.31945 2.17185 0.67385 0.62985 0.18634 1.49003 5.866 65.17
                  CTA comet tail algorithm (joo) 0.95346 0.86319 0.27770 2.09435 0.99794 0.85740 0.33949 2.19484 0.88769 0.56431 0.10512 1.55712 5.846 64.96
                  TETA time evolution travel algorithm (joo) 0.91362 0.82349 0.31990 2.05701 0.97096 0.89532 0.29324 2.15952 0.73462 0.68569 0.16021 1.58052 5.797 64.41
                  SDSm stochastic diffusion search M 0.93066 0.85445 0.39476 2.17988 0.99983 0.89244 0.19619 2.08846 0.72333 0.61100 0.10670 1.44103 5.709 63.44
                  BOAm billiards optimization algorithm M 0.95757 0.82599 0.25235 2.03590 1.00000 0.90036 0.30502 2.20538 0.73538 0.52523 0.09563 1.35625 5.598 62.19
                  AAm archery algorithm M 0.91744 0.70876 0.42160 2.04780 0.92527 0.75802 0.35328 2.03657 0.67385 0.55200 0.23738 1.46323 5.548 61.64
                  ESG evolution of social groups (joo) 0.99906 0.79654 0.35056 2.14616 1.00000 0.82863 0.13102 1.95965 0.82333 0.55300 0.04725 1.42358 5.529 61.44
                  SIA simulated isotropic annealing (joo) 0.95784 0.84264 0.41465 2.21513 0.98239 0.79586 0.20507 1.98332 0.68667 0.49300 0.09053 1.27020 5.469 60.76
                  EOm extremal_optimization_M 0.76166 0.77242 0.31747 1.85155 0.99999 0.76751 0.23527 2.00277 0.74769 0.53969 0.14249 1.42987 5.284 58.71
                  BBO biogeography-based optimization 0.94912 0.69456 0.35031 1.99399 0.93820 0.67365 0.25682 1.86867 0.74615 0.48277 0.17369 1.40261 5.265 58.50
                  ACS artificial cooperative search 0.75547 0.74744 0.30407 1.80698 1.00000 0.88861 0.22413 2.11274 0.69077 0.48185 0.13322 1.30583 5.226 58.06
                  DA dialectical algorithm 0.86183 0.70033 0.33724 1.89940 0.98163 0.72772 0.28718 1.99653 0.70308 0.45292 0.16367 1.31967 5.216 57.95
                  BHAm black hole algorithm M 0.75236 0.76675 0.34583 1.86493 0.93593 0.80152 0.27177 2.00923 0.65077 0.51646 0.15472 1.32195 5.196 57.73
                  ASO anarchy society optimization 0.84872 0.74646 0.31465 1.90983 0.96148 0.79150 0.23803 1.99101 0.57077 0.54062 0.16614 1.27752 5.178 57.54
                  RFO royal flush optimization (joo) 0.83361 0.73742 0.34629 1.91733 0.89424 0.73824 0.24098 1.87346 0.63154 0.50292 0.16421 1.29867 5.089 56.55
                  AOSm atomic orbital search M 0.80232 0.70449 0.31021 1.81702 0.85660 0.69451 0.21996 1.77107 0.74615 0.52862 0.14358 1.41835 5.006 55.63
                  TSEA turtle shell evolution algorithm (joo) 0.96798 0.64480 0.29672 1.90949 0.99449 0.61981 0.22708 1.84139 0.69077 0.42646 0.13598 1.25322 5.004 55.60
                  BSA backtracking_search_algorithm 0.97309 0.54534 0.29098 1.80941 0.99999 0.58543 0.21747 1.80289 0.84769 0.36953 0.12978 1.34700 4.959 55.10
                  DE differential evolution 0.95044 0.61674 0.30308 1.87026 0.95317 0.78896 0.16652 1.90865 0.78667 0.36033 0.02953 1.17653 4.955 55.06
                  SRA successful restaurateur algorithm (joo) 0.96883 0.63455 0.29217 1.89555 0.94637 0.55506 0.19124 1.69267 0.74923 0.44031 0.12526 1.31480 4.903 54.48
                  CRO chemical reaction optimization 0.94629 0.66112 0.29853 1.90593 0.87906 0.58422 0.21146 1.67473 0.75846 0.42646 0.12686 1.31178 4.892 54.36
                  BIO blood inheritance optimization (joo) 0.81568 0.65336 0.30877 1.77781 0.89937 0.65319 0.21760 1.77016 0.67846 0.47631 0.13902 1.29378 4.842 53.80
                  DOA dream_optimization_algorithm 0.85556 0.70085 0.37280 1.92921 0.73421 0.48905 0.24147 1.46473 0.77231 0.47354 0.18561 1.43146 4.825 53.62
                  BSA bird swarm algorithm 0.89306 0.64900 0.26250 1.80455 0.92420 0.71121 0.24939 1.88479 0.69385 0.32615 0.10012 1.12012 4.809 53.44
                  DEA dolphin_echolocation_algorithm 0.75995 0.67572 0.34171 1.77738 0.89582 0.64223 0.23941 1.77746 0.61538 0.44031 0.15115 1.20684 4.762 52.91
                  HS harmony search 0.86509 0.68782 0.32527 1.87818 0.99999 0.68002 0.09590 1.77592 0.62000 0.42267 0.05458 1.09725 4.751 52.79
                  SSG saplings sowing and growing 0.77839 0.64925 0.39543 1.82308 0.85973 0.62467 0.17429 1.65869 0.64667 0.44133 0.10598 1.19398 4.676 51.95
                  BCOm bacterial chemotaxis optimization M 0.75953 0.62268 0.31483 1.69704 0.89378 0.61339 0.22542 1.73259 0.65385 0.42092 0.14435 1.21912 4.649 51.65
                  ABO african buffalo optimization 0.83337 0.62247 0.29964 1.75548 0.92170 0.58618 0.19723 1.70511 0.61000 0.43154 0.13225 1.17378 4.634 51.49
                  (PO)ES (PO) evolution strategies 0.79025 0.62647 0.42935 1.84606 0.87616 0.60943 0.19591 1.68151 0.59000 0.37933 0.11322 1.08255 4.610 51.22
                  FBA fractal-based algorithm 0.79000 0.65134 0.28965 1.73099 0.87158 0.56823 0.18877 1.62858 0.61077 0.46062 0.12398 1.19537 4.555 50.61
                  TSm tabu search M 0.87795 0.61431 0.29104 1.78330 0.92885 0.51844 0.19054 1.63783 0.61077 0.38215 0.12157 1.11449 4.536 50.40
                  BSO brain storm optimization 0.93736 0.57616 0.29688 1.81041 0.93131 0.55866 0.23537 1.72534 0.55231 0.29077 0.11914 0.96222 4.498 49.98
                  WOAm wale optimization algorithm M 0.84521 0.56298 0.26263 1.67081 0.93100 0.52278 0.16365 1.61743 0.66308 0.41138 0.11357 1.18803 4.476 49.74
                  AEFA artificial electric field algorithm 0.87700 0.61753 0.25235 1.74688 0.92729 0.72698 0.18064 1.83490 0.66615 0.11631 0.09508 0.87754 4.459 49.55
                  AEO artificial ecosystem-based optimization algorithm 0.91380 0.46713 0.26470 1.64563 0.90223 0.43705 0.21400 1.55327 0.66154 0.30800 0.28563 1.25517 4.454 49.49
                  CAm camel algorithm M 0.78684 0.56042 0.35133 1.69859 0.82772 0.56041 0.24336 1.63149 0.64846 0.33092 0.13418 1.11356 4.444 49.37
                  ACOm ant colony optimization M 0.88190 0.66127 0.30377 1.84693 0.85873 0.58680 0.15051 1.59604 0.59667 0.37333 0.02472 0.99472 4.438 49.31
                  CMAES covariance_matrix_adaptation_evolution_strategy 0.76258 0.72089 0.00000 1.48347 0.82056 0.79616 0.00000 1.61672 0.75846 0.49077 0.00000 1.24923 4.349 48.33
                  DA_duelist duelist_algorithm 0.92782 0.53778 0.27792 1.74352 0.86957 0.47536 0.18193 1.52686 0.62153 0.33569 0.11715 1.07437 4.345 48.28
                  BFO-GA bacterial foraging optimization - GA 0.89150 0.55111 0.31529 1.75790 0.96982 0.39612 0.06305 1.42899 0.72667 0.27500 0.03525 1.03692 4.224 46.93
                  BM blue_monkey _algorithm 0.67301 0.38113 0.26883 1.32297 0.61165 0.30519 0.18691 1.10375 0.43846 0.19354 0.10373 0.73573 3.162 35.14
                  RW random walk 0.48754 0.32159 0.25781 1.06694 0.37554 0.21944 0.15877 0.75375 0.27969 0.14917 0.09847 0.52734 2.348 26.09


                  Conclusions

                  The algorithm proved workable on the test functions. However, it did not produce results competitive enough to be included in the leaderboard. The results indicate the need for further optimization and refinement of key mechanisms. The group structure enables parallel exploration of the search space, and the generational replacement mechanism introduces an element of renewal into the population; however, the convergence rate toward the optimal solution is lower than expected.

                  The Blue Monkey algorithm offers an interesting approach to solving optimization problems based on the simulation of natural processes. Despite current performance limitations, the concept has potential for further development. The current version can be recommended for educational purposes and as a basis for further research in the field of metaheuristic optimization algorithms.

                  tab

                  Figure 2. Color-coded ranking of algorithms based on the corresponding tests

                  chart

                  Figure 3. Histogram of algorithm test results (on a scale from 0 to 100; the higher the score, the better, where 100 is the maximum possible theoretical result; the archive contains a script for calculating the leaderboard)

                  Pros and cons of the BM algorithm:

                  Pros:

                  1. A small number of external parameters.

                  Cons:

                  1. Low efficiency on optimization tasks, especially on discrete functions.

                  An archive with the current versions of the algorithm source code is attached to the article. The author of this article is not responsible for the absolute accuracy of the descriptions of the canonical algorithms; changes have been made to many of them to improve search capabilities. The conclusions and assessments presented in the articles are based on the results of the experiments conducted.


                  Programs used in the article

                  # Name Type Description
                  1 #C_AO.mqh
                  Include file
                  Parent class of population-based optimization algorithms
                  2 #C_AO_enum.mqh
                  Include file
                  Enumeration of population-based optimization algorithms
                  3 TestFunctions.mqh
                  Include file
                  Test function library
                  4
                  TestStandFunctions.mqh
                  Include file
                  Test bench function library
                  5
                  Utilities.mqh
                  Include file
                  Utility function library
                  6
                  CalculationTestResults.mqh
                  Include file
                  Script for calculating results for the comparison table
                  7
                  Testing AOs.mq5
                  Script A unified test bench for all population-based optimization algorithms
                  8
                  Simple use of population optimization algorithms.mq5
                  Script
                  A simple example of using population-based optimization algorithms without visualization
                  9
                  Test_AO_BM.mq5
                  Script Test bench for BM


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

                  Attached files |
                  BM.zip (279.93 KB)
                  Developing a Terminal Manager (Part 1): Problem Statement Developing a Terminal Manager (Part 1): Problem Statement
                  How can we conveniently monitor multiple terminals running Expert Advisors, especially when they are on different computers? Let's try to create a web interface for managing the launch of MetaTrader 5 trading terminals and viewing detailed information about the operation of each instance.
                  Building Your Personal Expert Advisor (Part 1): From Fragile Script to Working EA Building Your Personal Expert Advisor (Part 1): From Fragile Script to Working EA
                  This article focuses on EA architecture rather than signal design. Starting with a flawed Moving Average crossover EA, we add new‑bar detection to prevent duplicate entries, Magic Number and position awareness, ATR‑based risk levels, and data and trade result validation, along with basic safeguards. You obtain a practical base to build and test advanced systems.
                  Developing a Terminal Manager (Part 2): Running Multiple Terminal Instances Developing a Terminal Manager (Part 2): Running Multiple Terminal Instances
                  Let's move on to using multiple terminal instances on the server by setting up a simple control panel for starting and stopping them. Now it is time to expand the functionality and move on to the next stages — implementing more complex features, such as managing multiple terminal instances, state persistence, integration with the MetaTrader 5 API, and a web interface with comprehensive information about the terminals.
                  Automated Trade Statement Exporter to Excel-Compatible XLSX in MQL5 Automated Trade Statement Exporter to Excel-Compatible XLSX in MQL5
                  An MQL5 script reconstructs closed trades from deal history using a two-pass SL/TP lookup and exports them to an Excel-compatible XLSX file without third-party libraries. Four cooperating classes handle trade data, history reconstruction, SpreadsheetML XML generation, and ZIP assembly via .NET's ZipFile class through a direct ShellExecuteW call with marker-file polling. The output opens in Excel and Google Sheets with correct numeric types, formatted date columns, and a bold header row.